Skip to content
1 change: 1 addition & 0 deletions outputs.tf
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
output "all" {
value = local.secrets
description = "The final secrets pulled from various sources."
sensitive = true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You call this out as a breaking change in the PR title, but I'm not sure it is. Mind sharing why you're considering this a breaking change? It changes the output of this module, but I would just call that a feature enhancement, not a breaking change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am expecting that adding sensitive = true requires downstream child or root modules then also mark the output as sensitive. My practical concern is that users would need to make a code change on their end in order to get terraform plan/applies to run.

For example, https://developer.hashicorp.com/terraform/tutorials/configuration-language/sensitive-variables#reference-sensitive-variables
CleanShot 2025-08-05 at 09 38 34@2x

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this is interesting. I wasn't aware of this behaviour, but we have to treat this as a breaking change indeed.
Screenshot 2025-08-12 at 12 01 15

@westonplatter could you please rename a PR title to reflect this: feat(outputs)!: add sensitive true to outputs + add tests), so release-please correctly generates the release.
Also, we should ensure that this is described in the release notes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@westonplatter good thinking in terms of this breaking downstream outputs, you're obviously right that this is a breaking change 💯 👍

}
81 changes: 81 additions & 0 deletions tests/locals.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
mock_provider "sops" {
mock_data "sops_file" {
defaults = {
raw = "db_password: supersecret123\napi_key: test-api-key-456"
}
}
}

run "test_empty_secret_mapping" {
command = plan

variables {
secret_mapping = []
}

assert {
condition = length(local.secrets) == 0
error_message = "Empty input should result in empty final secrets"
}
}

run "test_multiple_files_and_secrets" {
command = plan

variables {
secret_mapping = [
{
name = "db_password"
type = "sops"
file = "database.yaml"
},
{
name = "api_key"
type = "sops"
file = "api.yaml"
},
{
name = "redis_password"
type = "sops"
file = "database.yaml"
}
]
}

assert {
condition = length(local.sops_secret_mapping) == 3
error_message = "Should have 3 sops secret mappings"
}

assert {
condition = (
length(local.sops_files) == 2
&& contains(local.sops_files, "database.yaml")
&& contains(local.sops_files, "api.yaml")
)
error_message = "Should have 2 unique sops files (database.yaml and api.yaml)"
}

assert {
condition = length(local.sops_yamls) == 2
error_message = "Should have 2 YAML files loaded in sops_yamls"
}

assert {
condition = length(local.sops_secrets) == 3
error_message = "Should have 3 secrets in sops_secrets map"
}

assert {
condition = alltrue([
for mapping in local.sops_secret_mapping :
contains(keys(local.sops_secrets), mapping.name)
])
error_message = "All secret mappings should result in corresponding secret names"
}

assert {
condition = length(distinct(keys(local.sops_secrets))) == length(keys(local.sops_secrets))
error_message = "All secret keys should be unique"
}
}
64 changes: 64 additions & 0 deletions tests/outputs.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
mock_provider "sops" {
mock_data "sops_file" {
defaults = {
raw = "db_password: supersecret123\napi_key: test-api-key-456\nredis_password: redis-secret-789"
}
}
}

run "test_output_structure_and_content" {
command = plan

variables {
secret_mapping = [
{
name = "db_password"
type = "sops"
file = "database.yaml"
},
{
name = "api_key"
type = "sops"
file = "api.yaml"
}
]
}

assert {
condition = output.all["db_password"] == "supersecret123"
error_message = "db_password should have the expected value from mocked SOPS data"
}

assert {
condition = output.all["api_key"] == "test-api-key-456"
error_message = "api_key should have the expected value from mocked SOPS data"
}

assert {
condition = !contains(keys(output.all), "redis_password")
error_message = "redis_password should not be in the output"
}

assert {
condition = length(output.all) == 2
error_message = "Output should contain exactly 2 secrets"
}
}

run "test_output_empty_secrets" {
command = plan

variables {
secret_mapping = []
}

assert {
condition = length(output.all) == 0
error_message = "Output should be empty when no secrets are configured"
}

assert {
condition = output.all == {}
error_message = "Output should be an empty map when no secrets are configured"
}
}