diff --git a/.trunk/configs/.hadolint.yaml b/.trunk/configs/.hadolint.yaml new file mode 100644 index 0000000..98bf0cd --- /dev/null +++ b/.trunk/configs/.hadolint.yaml @@ -0,0 +1,4 @@ +# Following source doesn't work in most setups +ignored: + - SC1090 + - SC1091 diff --git a/aqua.yaml b/aqua.yaml index 264e850..d06d23e 100644 --- a/aqua.yaml +++ b/aqua.yaml @@ -15,3 +15,5 @@ packages: tags: [terraform] - name: opentofu/opentofu@v1.9.1 tags: [tofu] + - name: trunk-io/launcher + version: 1.0.0 diff --git a/main.tf b/main.tf index e085872..2d81fcf 100644 --- a/main.tf +++ b/main.tf @@ -115,6 +115,46 @@ locals { _root_module_yaml_decoded = merge(local._multi_instance_root_module_yaml_decoded, local._single_instance_root_module_yaml_decoded) + ## Stack Name Template Resolution + # Determine the default template based on structure type + # MultiInstance: "${workspace}-${module_path}" (e.g., "dev-network") + # SingleInstance: "${module_name}" (e.g., "rds-cluster") + default_stack_name_template = local._multi_instance_structure ? "$${workspace}-$${module_path}" : "$${module_name}" + + # Use provided template or fall back to default + stack_name_template = coalesce(var.stack_name_template, local.default_stack_name_template) + + # Generate stack names using the template + # Iterates over all modules and their stack files, applying the template with available parameters + stack_names = { + for module, files in local._root_module_yaml_decoded : + module => { + for file, content in files : + file => ( + file == var.common_config_file ? null : + templatestring( + local.stack_name_template, + { + module_name = basename(module) + workspace = trimsuffix(file, ".yaml") + module_path = module + } + ) + ) if file != var.common_config_file + } + } + + # Clean up stack names to handle edge cases + # Removes double hyphens, leading/trailing hyphens, and applies trimspace + # This handles cases where template parameters may be empty (e.g., SingleInstance with ${workspace}) + cleaned_stack_names = { + for module, names in local.stack_names : + module => { + for file, name in names : + file => name != null ? trimspace(replace(replace(replace(name, "--", "-"), "/^-/", ""), "/-$/", "")) : null + } + } + ## Common Stack configurations # Retrieve common Stack configurations for each root module. # SingleInstance root_module_structure does not support common configs today. @@ -155,7 +195,7 @@ locals { # } _root_module_stack_configs = merge([for module, files in local._root_module_yaml_decoded : { for file, content in files : - local._multi_instance_structure ? "${module}-${trimsuffix(file, ".yaml")}" : module => + local.cleaned_stack_names[module][file] => merge( { # Use specified project_root, if not, build it using the root_modules_path and module name diff --git a/tests/main.tftest.hcl b/tests/main.tftest.hcl index e87d1f5..50009ca 100644 --- a/tests/main.tftest.hcl +++ b/tests/main.tftest.hcl @@ -1,6 +1,7 @@ variables { - root_modules_path = "./tests/fixtures/multi-instance" - common_config_file = "common.yaml" + root_modules_path = "./tests/fixtures/multi-instance" + stack_name_template = "$${module_path}-$${workspace}" + common_config_file = "common.yaml" github_enterprise = { namespace = "masterpointio" } diff --git a/tests/nested-directories.tftest.hcl b/tests/nested-directories.tftest.hcl index 35c16d8..7b88163 100644 --- a/tests/nested-directories.tftest.hcl +++ b/tests/nested-directories.tftest.hcl @@ -1,5 +1,6 @@ variables { - repository = "terraform-spacelift-automation" + repository = "terraform-spacelift-automation" + stack_name_template = "$${module_path}-$${workspace}" github_enterprise = { namespace = "masterpointio" } @@ -55,7 +56,7 @@ run "test_nested_directory_multi_instance_support" { } assert { - condition = spacelift_stack.default["parent/nested-prod"].branch == "main" + condition = spacelift_stack.default["parent/nested-prod"].branch == "main" error_message = "Nested directory prod stack should have main branch: ${spacelift_stack.default["parent/nested-prod"].branch}" } } @@ -185,4 +186,4 @@ run "test_deeply_nested_directory_support" { condition = contains(spacelift_stack.default["nested-multi-instance/parent/nested-dev"].labels, "folder:nested-multi-instance/parent/nested/dev") error_message = "Deeply nested folder label incorrect: ${jsonencode(spacelift_stack.default["nested-multi-instance/parent/nested-dev"].labels)}" } -} \ No newline at end of file +} diff --git a/tests/resource-id-resolver.tftest.hcl b/tests/resource-id-resolver.tftest.hcl index ff121d1..5c3c3ad 100644 --- a/tests/resource-id-resolver.tftest.hcl +++ b/tests/resource-id-resolver.tftest.hcl @@ -62,8 +62,9 @@ mock_provider "jsonschema" { } variables { - root_modules_path = "./tests/fixtures/multi-instance" - common_config_file = "common.yaml" + stack_name_template = "$${module_path}-$${workspace}" + root_modules_path = "./tests/fixtures/multi-instance" + common_config_file = "common.yaml" github_enterprise = { namespace = "masterpointio" } @@ -108,8 +109,8 @@ run "test_global_space_id_variable_is_used" { command = plan variables { - space_id = "global-space-id-from-variable" - root_modules_path = "./tests/fixtures/single-instance" + space_id = "global-space-id-from-variable" + root_modules_path = "./tests/fixtures/single-instance" root_module_structure = "SingleInstance" } @@ -124,7 +125,7 @@ run "test_default_space_id_is_used_when_no_values_provided" { command = plan variables { - root_modules_path = "./tests/fixtures/single-instance" + root_modules_path = "./tests/fixtures/single-instance" root_module_structure = "SingleInstance" } @@ -139,7 +140,7 @@ run "test_single_instance_space_id_from_stack_yaml" { command = plan variables { - root_modules_path = "./tests/fixtures/single-instance" + root_modules_path = "./tests/fixtures/single-instance" root_module_structure = "SingleInstance" } diff --git a/tests/single-instance.tftest.hcl b/tests/single-instance.tftest.hcl index e505639..db55d4c 100644 --- a/tests/single-instance.tftest.hcl +++ b/tests/single-instance.tftest.hcl @@ -1,6 +1,7 @@ variables { - root_modules_path = "./tests/fixtures/single-instance" + stack_name_template = "$${module_path}-$${workspace}" + root_modules_path = "./tests/fixtures/single-instance" github_enterprise = { namespace = "masterpointio" } diff --git a/tests/vcs_integrations.tftest.hcl b/tests/vcs_integrations.tftest.hcl index ced829f..855fed4 100644 --- a/tests/vcs_integrations.tftest.hcl +++ b/tests/vcs_integrations.tftest.hcl @@ -27,6 +27,7 @@ mock_provider "jsonschema" { } variables { + stack_name_template = "$${module_path}-$${workspace}" root_modules_path = "./tests/fixtures/multi-instance" common_config_file = "common.yaml" repository = "terraform-spacelift-automation" @@ -149,9 +150,9 @@ run "test_vcs_blocks_empty_when_null" { variables { github_enterprise = null - raw_git = null - gitlab = null - bitbucket_cloud = null + raw_git = null + gitlab = null + bitbucket_cloud = null bitbucket_datacenter = null } diff --git a/variables.tf b/variables.tf index 23fdbb1..5db57e3 100644 --- a/variables.tf +++ b/variables.tf @@ -418,6 +418,25 @@ variable "worker_pool_name" { default = null } +variable "stack_name_template" { + type = string + description = <<-EOT + Template for generating stack names. Supports the following parameters: + - $${module_name}: The root module directory name (e.g., 'network', 'delegated-tf') + - $${workspace}: The workspace/environment name from YAML filename (e.g., 'dev', 'prod') + - $${module_path}: The root module path for S3 backend workspace isolation (e.g., 'aws-iam/delegated-tf') + Examples: + - "$${workspace}-$${module_path}" (default for MultiInstance) + - "$${module_name}" (default for SingleInstance) + EOT + default = null + + validation { + condition = var.stack_name_template == null || can(regex("\\$\\{(module_name|workspace|module_path)\\}", var.stack_name_template)) + error_message = "stack_name_template must contain at least one of: $${module_name}, $${workspace}, or $${module_path}" + } +} + variable "spaces" { description = "A map of Spacelift Spaces to create" type = map(object({