Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SC-403] Add blurb about invalid CostCenterOther tags #21

Merged
merged 2 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
rev: v4.5.0
hooks:
# On Windows, git will convert all CRLF to LF, but only after all hooks are done executing.
# yamllint will fail before git has a chance to convert line endings, so line endings must be explicitly converted before yamllint
Expand All @@ -11,20 +11,20 @@ repos:
- id: trailing-whitespace
- id: check-ast
- repo: https://github.com/adrienverge/yamllint
rev: v1.27.1
rev: v1.35.1
hooks:
- id: yamllint
- repo: https://github.com/awslabs/cfn-python-lint
rev: v0.63.2
rev: v0.85.2
hooks:
- id: cfn-python-lint
files: template\.(json|yml|yaml)$
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.3.1
rev: v1.5.5
hooks:
- id: remove-tabs
- repo: https://github.com/executablebooks/mdformat
rev: 0.7.16 # Use the ref you want to point at
rev: 0.7.17 # Use the ref you want to point at
hooks:
- id: mdformat
# Optionally add plugins
Expand Down
128 changes: 64 additions & 64 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 35 additions & 8 deletions email_totals/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,12 +291,10 @@ def _build_attr_dict(attributes):
return output, account_names


def get_missing_other_tags(owner):
def _parse_ce_tag_results(result_data):
"""
Query cost explorer for resource usage by the given resource owner,
filtering for resources missing a required CostCenterOther tag,
and grouped by account id, then generate a dictionary mapping an
account ID to a list of resource IDs.
Parse data returned by cost explorer and generate a dictionary mapping
an account ID to a list of resource IDs.

Example:
```
Expand All @@ -308,9 +306,7 @@ def get_missing_other_tags(owner):

output = {}

missing_data = ce.get_ce_missing_tag_for_email(owner)

for result in missing_data['ResultsByTime']:
for result in result_data['ResultsByTime']:
for group in result['Groups']:
# Keys preserve the order defined in the GroupBy parameter from
# the call to get_cost_and_usage().
Expand All @@ -331,9 +327,33 @@ def get_missing_other_tags(owner):

# Add this resource to the account
output[account_id].append(resource)

return output


def get_invalid_other_tags(owner):
"""
Query cost explorer for resource usage by the given resource owner,
filtering for resources with an unexpected CostCenterOther tag (i.e.
CostCenter is not set to Other).
"""

invalid_data = ce.get_ce_invalid_tag_for_email(owner)
invalid_tags = _parse_ce_tag_results(invalid_data)
return invalid_tags


def get_missing_other_tags(owner):
"""
Query cost explorer for resource usage by the given resource owner,
filtering for resources missing a required CostCenterOther tag.
"""

missing_data = ce.get_ce_missing_tag_for_email(owner)
missing_tags = _parse_ce_tag_results(missing_data)
return missing_tags


def build_summary(target_period, compare_period, team_sage):
"""
Build a complex data structure representing the input needed for email
Expand All @@ -360,6 +380,9 @@ def build_summary(target_period, compare_period, team_sage):
The 'missing_other_tag' subkey will list owner resources that are missing a
required 'CostCenterOther' tag.

The 'invalid_other_tag' subkey will list owner resources tagged with a
'CostCenterOther' tag without 'CostCenter' set to 'Other'.

Since IT-2369 is blocked, the owner category does not include accounts
tagged with an account owner. As a workaround, we add an 'accounts' subkey
with account totals for owned accounts, and build an additional email
Expand Down Expand Up @@ -460,6 +483,10 @@ def build_summary(target_period, compare_period, team_sage):
if missing_tags:
filtered[recipient]['missing_other_tag'] = missing_tags

invalid_tags = get_invalid_other_tags(recipient)
if invalid_tags:
filtered[recipient]['invalid_other_tag'] = invalid_tags

LOG.debug(f"Final summary: {filtered}")

return {
Expand Down
49 changes: 49 additions & 0 deletions email_totals/ce.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,55 @@ def get_ce_account_costs(period):
return response


def get_ce_invalid_tag_for_email(email):
"""
Get cost category resource information for a given owner email and
grouped by account, filtered for resources where the CostCenterOther
is set and CostCenter is not 'Other / 000001'.
"""

response = ce_client.get_cost_and_usage_with_resources(
TimePeriod=yesterday,
Granularity='MONTHLY',
Metrics=[
'UnblendedCost',
],
Filter={
'And': [{
'CostCategories': {
'Key': 'Owner Email',
'Values': [email, ],
'MatchOptions': ['EQUALS', ],
}
}, {
'Not': {
'Tags': {
'Key': 'CostCenter',
'Values': ['Other / 000001', ],
'MatchOptions': ['EQUALS', ],
}
}
}, {
'Not': {
'Tags': {
'Key': 'CostCenterOther',
'MatchOptions': ['ABSENT', ],
}
}
}
]},
GroupBy=[{
'Type': 'DIMENSION',
'Key': 'LINKED_ACCOUNT',
}, {
'Type': 'DIMENSION',
'Key': 'RESOURCE_ID',
}],
)

return response


def get_ce_missing_tag_for_email(email):
"""
Get cost category resource information for a given owner email and
Expand Down
Loading
Loading