Skip to content
Merged
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
20 changes: 20 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
## v0.5.39

### Features

### Changes

- **Dependency Update**: Upgraded c7n-left to 0.3.39 (c7n 0.9.53, tfparse 0.6.22, python-hcl2 8.1.4) and bumped boto3/botocore to 1.43.103 to stay in sync with c7n's pinned versions

### Fixes

- **Terraform Scanning**: `each.value.<attr>` inside a `for_each` resource now resolves when the collection contains a module output reference. Previously the attribute stayed an unresolved reference marker, so policies matching on it could not see the real value ([tfparse#283](https://github.com/cloud-custodian/tfparse/issues/283))
- **Terraform Scanning**: References from resources to `data` source attributes are now tracked ([tfparse#277](https://github.com/cloud-custodian/tfparse/issues/277))

### Other

- **GitHub Actions**: Bumped GitHub Actions dependencies ([#85](https://github.com/stacklet/sinistral-cli/pull/85))
- **Dependency Update**: Refreshed transitive and development dependencies to current releases

---

## v0.5.38

### Features
Expand Down
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
[project]
name = "stacklet.client.sinistral"
version = "0.5.38"
version = "0.5.39"
description = "Sinistral CLI"
maintainers = [ { name = "Stacklet Engineering", email = "foss+sinistral-cli@stacklet.io" } ]
authors = [ { name = "Stacklet Engineering", email = "foss+sinistral-cli@stacklet.io" } ]
requires-python = ">=3.10.2,<4"
readme = "README.md"
license = "Apache-2.0"
dependencies = [
"c7n-left==0.3.38",
"c7n-left==0.3.39",
# We pin to the exact versions used by c7n for compatibility purposes
"botocore==1.43.78",
"boto3==1.43.78",
"botocore==1.43.103",
"boto3==1.43.103",
# Other dependencies
"click>=8.1.3,<9",
"codecov-cli>=11.1.0,<12",
Expand Down
31 changes: 31 additions & 0 deletions tests/terraform/foreach-module-output/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Regression fixture: the for_each collection holds a module output reference.
# The collection itself used to resolve, but each.value.<attr> inside the
# resource body stayed an unresolved reference marker, so policies matching
# on that attribute evaluated the marker instead of the real value.
provider "aws" {
region = "us-east-2"
}

module "tags" {
source = "./tags"
environment = "prod"
}

locals {
queues = {
# tags come from the module output and include Environment: compliant
tagged = {
tags = module.tags.out
}
# static tags without Environment: a genuine violation
untagged = {
tags = { Team = "platform" }
}
}
}

resource "aws_sqs_queue" "x" {
for_each = local.queues
name = "queue-${each.key}"
tags = each.value.tags
}
9 changes: 9 additions & 0 deletions tests/terraform/foreach-module-output/tags/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
variable "environment" {
type = string
}

output "out" {
value = {
Environment = var.environment
}
}
44 changes: 44 additions & 0 deletions tests/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,47 @@ def test_submit_run_nested_module_file_path():
# with the expanded resource instances in refs
assert resource["__tfmeta"]["path"] == "module.a"
assert resource["__tfmeta"]["refs"] == ['module.a.aws_sqs_queue.x["queue-1"]']


@patch.object(StackletContext, "_write_token", Mock())
def test_submit_run_foreach_module_output():
"""each.value.<attr> must resolve when the for_each collection contains a
module output reference.

The collection itself used to resolve, but each.value bindings inside the
resource body kept an unresolved reference marker. A policy checking that
attribute then saw the marker rather than the module's real value, so a
compliant resource was reported as a violation.
"""
path = str(pathlib.Path(__file__).parent.resolve()) + "/terraform/foreach-module-output"
runner = CliRunner()
with patch.object(
RestExecutor,
"get",
side_effect=[
get_mock_response(json=get_policies_for_project_response),
],
):
with patch.object(
RestExecutor,
"post",
return_value=get_mock_response(json=create_scan_response),
) as patched_post:
result = runner.invoke(cli, ["run", "--project", "foo", "-d", path])
# a policy violation exits non-zero, so SystemExit is expected here;
# anything else is a real error, and silent exceptions in tests are
# hard to debug, so raise for visibility
if result.exception is not None and not isinstance(result.exception, SystemExit):
raise result.exception

patched_post.assert_called_once()
payload = patched_post.mock_calls[0].args[2]
assert payload["status"] == "FAILED"
# only the instance with static, Environment-less tags violates; the
# module-sourced instance must resolve to its real tags and pass
assert len(payload["results"]) == 1
resource = payload["results"][0]["resource"]
# unresolved, the for_each never expanded and this was the bare
# resource address with an each.value.tags marker for tags
assert resource["__tfmeta"]["path"] == 'aws_sqs_queue.x["untagged"]'
assert resource["tags"] == {"Team": "platform"}
Loading
Loading