Skip to content

Commit

Permalink
Change project-groups to include a costshare for each project
Browse files Browse the repository at this point in the history
  • Loading branch information
manics committed Jun 13, 2024
1 parent 037b960 commit b760605
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 16 deletions.
18 changes: 15 additions & 3 deletions aws_project_costs/project-cost-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,25 @@
}
},
"project-groups": {
"description": "Groups of projects which use common infrastructure, a mapping of group-name to list of projects in the group",
"description": "Groups of projects which use common infrastructure, a mapping of group-name to projects in the group, and their proportion of shared costs",
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
"type": "object",
"properties": {
"name": {
"description": "Name of the project",
"type": "string",
"minLength": 1
},
"costshare": {
"description": "Proportion of shared costs to be allocated to project, relative to other project shares",
"type": "integer",
"minimum": 0
}
},
"required": ["name", "costshare"]
}
}
},
Expand Down
25 changes: 19 additions & 6 deletions aws_project_costs/project_costs.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,22 @@ def _get_account_cfg(config, accountname):
raise ValueError(f"Configuration for account {accountname} not found")


def _get_projects_in_groups(config, groupnames):
projects = set()
def _get_project_costshares_in_groups(config, groupnames):
"""
Get the combined set of all projects in groupnames, and their costshare
If a project is in multiple groups it must have the same costshare
"""
projects = dict()
for group in groupnames:
projects.update(config["project-groups"][group])
for p in config["project-groups"][group]:
name = p["name"]
costshare = p["costshare"]
if name in projects and costshare != projects[name]:
raise ValueError(
f"Project {name} appears in multiple project groups"
f"{groupnames} but has different costshares"
)
projects[name] = costshare
return projects


Expand Down Expand Up @@ -69,15 +81,16 @@ def _shared_account(
)
else:
# Either untagged, or a tag that should be considered shared
cost_per_project = item["COST"] / len(projects_in_group)
total_costshare = sum(projects_in_group.values())
cost_per_share = item["COST"] / total_costshare
for project_name in projects_in_group:
rows.append(
(
start.date().isoformat(),
project_name,
description,
costs_tag,
cost_per_project,
cost_per_share * projects_in_group[project_name],
)
)
return rows
Expand Down Expand Up @@ -108,7 +121,7 @@ def allocate_costs(*, accountname, config, start, df):
f"Project tag {project_tagname} not yet implemented"
)

projects_in_group = _get_projects_in_groups(
projects_in_group = _get_project_costshares_in_groups(
config, account_cfg["project-groups"]
)
rows = _shared_account(
Expand Down
5 changes: 5 additions & 0 deletions aws_project_costs/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def validate(projects, raise_on_error=False):

# Check accounts[].project-groups are valid
# Check accounts[].name are unique
# Check at least one project in project-groups has a non-zero costshare
acc_names = set()
for acc in projects["accounts"]:
name = acc["name"].lower()
Expand All @@ -29,6 +30,10 @@ def validate(projects, raise_on_error=False):
f"project-group {group} in account {acc['name']} does not exist!"
)

for group in acc.get("project-groups", []):
if set(p["costshare"] for p in group) == {0}:
errors.append(f"All projects in project-group {group} have costshare=0")

if raise_on_error and errors:
raise ValueError(errors)
return errors
Expand Down
21 changes: 14 additions & 7 deletions example/projects.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,23 @@ shared-tag-values:

project-groups:
tre:
- "Project A"
- "Project B"
- "SPECIAL-OPS"
- "Internal - development"
- "Internal - training"
- name: "Project A"
costshare: 1
- name: "Project B"
costshare: 1
- name: "SPECIAL-OPS"
costshare: 1
- name: "Internal - development"
costshare: 1
- name: "Internal - training"
costshare: 1
web:
- "SPECIAL-OPS"
- name: "SPECIAL-OPS"
costshare: 1
# TODO: Should every project be in at least one group?
other:
- "Complicated Project Name / 🐣 / 🐧"
- name: "Complicated Project Name / 🐣 / 🐧"
costshare: 1

accounts:
- name: "aws-auth"
Expand Down

0 comments on commit b760605

Please sign in to comment.