Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion documentation/docs/tirith-providers/infracost.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ tirith -policy-path policy.json -input-path infracost.json

The provider reads `projects[].breakdown.resources[]` and understands both the older per-resource keys (`totalMonthlyCost` / `totalHourlyCost`) and the newer ones (`monthlyCost` / `hourlyCost`). Resources whose cost field is missing or `null` contribute nothing to the sum.

Note: only the **first** project in the `projects` array is summed.
Every project in the `projects` array is summed.

## Operation types

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ tirith -policy-path policy.json -input-path infracost.json

The provider reads `projects[].breakdown.resources[]` and understands both the older per-resource keys (`totalMonthlyCost` / `totalHourlyCost`) and the newer ones (`monthlyCost` / `hourlyCost`). Resources whose cost field is missing or `null` contribute nothing to the sum.

Note: only the **first** project in the `projects` array is summed.
Every project in the `projects` array is summed.

## Operation types

Expand Down
2 changes: 1 addition & 1 deletion documentation/static/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2049,7 +2049,7 @@ tirith -policy-path policy.json -input-path infracost.json

The provider reads `projects[].breakdown.resources[]` and understands both the older per-resource keys (`totalMonthlyCost` / `totalHourlyCost`) and the newer ones (`monthlyCost` / `hourlyCost`). Resources whose cost field is missing or `null` contribute nothing to the sum.

Note: only the **first** project in the `projects` array is summed.
Every project in the `projects` array is summed.

## Operation types

Expand Down
8 changes: 4 additions & 4 deletions src/tirith/providers/infracost/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ def __get_all_costs(operation_type, input_data):
else:
pass
# raise KeyError(f'{costType} not found in one of the resource')
logger.debug(f"Total sum of {operation_type} of all resources : {total_sum}")
return total_sum, matched_count
else:
raise KeyError("breakdown/resources not found in one of the project")
logger.debug(f"Total sum of {operation_type} of all resources : {total_sum}")
return total_sum, matched_count
else:
raise KeyError("projects not found in input_data")

Expand Down Expand Up @@ -75,10 +75,10 @@ def __get_resources_costs(resource_type, operation_type, input_data):
else:
pass
# raise KeyError(f'{costType} not found in one of the resource')
logger.debug(f"Total sum of {operation_type} of specific resources : {total_sum}")
return total_sum, matched_count
else:
raise KeyError("breakdown/resources not found in one of the project")
logger.debug(f"Total sum of {operation_type} of specific resources : {total_sum}")
return total_sum, matched_count
else:
raise KeyError("projects not found in input_data")

Expand Down
61 changes: 61 additions & 0 deletions tests/providers/infracost/test_infracost_multiple_projects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""
Tests for infracost input with more than one project.

`infracost breakdown` writes one entry in `projects` per Terraform directory it finds. Only the
first one used to be summed, so a cost limit passed whenever the expensive project was not first.
"""

from pytest import mark

from tirith.core.core import start_policy_evaluation_from_dict
from tirith.providers.infracost import handler


INPUT = {
"currency": "USD",
"projects": [
{
"name": "networking",
"breakdown": {"resources": [{"name": "aws_nat_gateway.main", "monthlyCost": "10"}]},
},
{
"name": "data",
"breakdown": {"resources": [{"name": "aws_redshift_cluster.warehouse", "monthlyCost": "5000"}]},
},
],
}


def evaluate(provider_args, condition):
policy = {
"meta": {"version": "v1", "required_provider": "stackguardian/infracost"},
"evaluators": [{"id": "cost", "provider_args": provider_args, "condition": condition}],
"eval_expression": "cost",
}
return start_policy_evaluation_from_dict(policy, INPUT)


@mark.passing
def test_total_cost_sums_every_project():
result = handler.provide({"operation_type": "total_monthly_cost", "resource_type": ["*"]}, INPUT)

assert result[0]["value"] == 5010
assert result[0]["context"]["matched_resources"] == 2


@mark.passing
def test_resource_type_is_found_outside_the_first_project():
result = handler.provide({"operation_type": "total_monthly_cost", "resource_type": ["aws_redshift_cluster"]}, INPUT)

assert result[0]["value"] == 5000
assert result[0]["context"]["matched_resources"] == 1


@mark.passing
def test_cost_limit_fails_when_a_later_project_exceeds_it():
result = evaluate(
{"operation_type": "total_monthly_cost", "resource_type": ["*"]},
{"type": "LessThanEqualTo", "value": 1000},
)

assert result["final_result"] is False