Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ Check out an example configuration in the [examples/complete](./examples/complet

### What goes in a Stack config file? e.g. `stacks/dev.yaml`, `stacks/common.yaml`, `stack.yaml`, etc

Most settings that you would set on [the Spacelift Stack resource](https://search.opentofu.org/provider/spacelift-io/spacelift/latest/docs/resources/stack) are supported. Additionally, you can include certain Stack specific settings that will override this module's defaults like `default_tf_workspace_enabled`, `tfvars.enabled`, and similar. See the code for full details.
Most settings that you would set on [the Spacelift Stack resource](https://search.opentofu.org/provider/spacelift-io/spacelift/latest/docs/resources/stack) are supported. Additionally, you can include certain Stack specific settings that will override this module's defaults like `default_tf_workspace_enabled`, `tfvars.enabled`, `space_name`, and similar. See the code for full details.

### Why are variable values provided separately in `tfvars/` and not in the `yaml` file?

Expand Down Expand Up @@ -268,7 +268,8 @@ This is to support easy local and outside-spacelift operations. Keeping variable
| <a name="input_root_module_structure"></a> [root_module_structure](#input_root_module_structure) | The root module structure of the Stacks that you're reading in. See README for full details.<br/><br/>MultiInstance - You're using Workspaces or Dynamic Backend configuration to create multiple instances of the same root module code.<br/>SingleInstance - You're using copies of a root module and your directory structure to create multiple instances of the same Terraform code. | `string` | `"MultiInstance"` | no |
| <a name="input_root_modules_path"></a> [root_modules_path](#input_root_modules_path) | The path, relative to the root of the repository, where the root module can be found. | `string` | `"root-modules"` | no |
| <a name="input_runner_image"></a> [runner_image](#input_runner_image) | URL of the Docker image used to process Runs. Defaults to `null` which is Spacelift's standard (Alpine) runner image. | `string` | `null` | no |
| <a name="input_space_id"></a> [space_id](#input_space_id) | Place the created stacks in the specified space_id. | `string` | `"root"` | no |
| <a name="input_space_id"></a> [space_id](#input_space_id) | Place the created stacks in the specified space_id. Mutually exclusive with space_name. | `string` | `null` | no |
| <a name="input_space_name"></a> [space_name](#input_space_name) | Place the created stacks in the specified space_name. Mutually exclusive with space_id. | `string` | `null` | no |
| <a name="input_terraform_smart_sanitization"></a> [terraform_smart_sanitization](#input_terraform_smart_sanitization) | Indicates whether runs on this will use terraform's sensitive value system to sanitize<br/>the outputs of Terraform state and plans in spacelift instead of sanitizing all fields. | `bool` | `false` | no |
| <a name="input_terraform_version"></a> [terraform_version](#input_terraform_version) | Terraform version to use. | `string` | `"1.7.2"` | no |
| <a name="input_terraform_workflow_tool"></a> [terraform_workflow_tool](#input_terraform_workflow_tool) | Defines the tool that will be used to execute the workflow.<br/>This can be one of OPEN_TOFU, TERRAFORM_FOSS or CUSTOM. | `string` | `"OPEN_TOFU"` | no |
Expand Down
2 changes: 2 additions & 0 deletions data.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Look up all spaces in order to map space names to space IDs
data "spacelift_spaces" "all" {}
26 changes: 25 additions & 1 deletion main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,30 @@ locals {
try(local.stack_configs[stack].before_init, []),
))
}

# Allow usage of space_name along with space_id.
# A space_id is long and hard to look at in the stack.yaml file, so pass in the space_name and it will be resolved to the space_id, which will be consumed by the `spacelife_stack` resource.
space_name_to_id = {
for space in data.spacelift_spaces.all.spaces :
space.name => space.space_id
}

resolved_space_ids = {
for stack in local.stacks : stack => coalesce(
try(local.stack_configs[stack].space_id, null), # space_id always takes precedence since it's the most explicit
try(local.space_name_to_id[local.stack_configs[stack].space_name], null), # Then try to look up space_name from the stack.yaml to ID
var.space_id,
try(local.space_name_to_id[var.space_name], null), # Then try to look up the space_name global variable to ID
"root" # If no space_id or space_name is provided, default to the root space
)
}
}

check "spaces_enforce_mutual_exclusivity" {
assert {
condition = var.space_id == null || var.space_name == null
error_message = "space_id and space_name are mutually exclusive."
}
}

# Perform deep merge for common configurations and stack configurations
Expand Down Expand Up @@ -309,7 +333,7 @@ resource "spacelift_stack" "default" {
protect_from_deletion = try(local.stack_configs[each.key].protect_from_deletion, var.protect_from_deletion)
repository = try(local.stack_configs[each.key].repository, var.repository)
runner_image = try(local.stack_configs[each.key].runner_image, var.runner_image)
space_id = coalesce(try(local.stack_configs[each.key].space_id, null), var.space_id)
space_id = local.resolved_space_ids[each.key]
terraform_smart_sanitization = try(local.stack_configs[each.key].terraform_smart_sanitization, var.terraform_smart_sanitization)
terraform_version = try(local.stack_configs[each.key].terraform_version, var.terraform_version)
terraform_workflow_tool = var.terraform_workflow_tool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ stack_settings:
- echo 'World'
labels:
- default_example_label
space_name: mp-automation # Tests space_name gets translated to space_id (the Terraform resource attribute that is accepted)
Copy link
Member

Choose a reason for hiding this comment

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

We need to add space_name to our schema file. Mind doing that?


default_tf_workspace_enabled: true

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
stack_settings:
labels:
- test_label
space_id: direct-space-id-stack-yaml # Tests direct space_id precedence over global variable space_id
1 change: 1 addition & 0 deletions tests/fixtures/single-instance/root-module-b/stack.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ stack_settings:
labels:
- stack_label
project_root: "" # Root directory of the project. This means that Spacelift will pull the entire repository and run the stack from the root of the repository.
space_id: some-space-id # Test that space_id gets used as is
56 changes: 40 additions & 16 deletions tests/main.tftest.hcl
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
variables {
root_modules_path = "./tests/fixtures/multi-instance"
root_modules_path = "./tests/fixtures/multi-instance"
common_config_file = "common.yaml"
github_enterprise = {
namespace = "masterpointio"
}
repository = "terraform-spacelift-automation"
repository = "terraform-spacelift-automation"
all_root_modules_enabled = true
aws_integration_enabled = false
aws_integration_enabled = false
before_init = [
"echo 'Hello'"
]
Expand All @@ -20,7 +20,7 @@ run "test_labels_are_created_correctly" {
command = plan

assert {
condition = contains(local.labels["root-module-a-test"], "nobackend")
condition = contains(local.labels["root-module-a-test"], "nobackend")
error_message = "Global labels were not created correctly: ${jsonencode(local.labels)}"
}
}
Expand All @@ -30,7 +30,7 @@ run "test_root_module_fileset_collects_all_root_modules" {
command = plan

assert {
condition = contains(local._all_root_modules, "root-module-a")
condition = contains(local._all_root_modules, "root-module-a")
error_message = "Root module fileset was not created correctly: ${jsonencode(local._all_root_modules)}"
}
}
Expand All @@ -40,7 +40,7 @@ run "test_common_labels_are_appended_to_stack_labels" {
command = plan

assert {
condition = contains(local.configs["root-module-a-test"].stack_settings.labels, "common_label") && contains(local.configs["root-module-a-test"].stack_settings.labels, "test_label")
condition = contains(local.configs["root-module-a-test"].stack_settings.labels, "common_label") && contains(local.configs["root-module-a-test"].stack_settings.labels, "test_label")
error_message = "Common labels were not merged correctly: ${jsonencode(local.configs)}"
}
}
Expand All @@ -50,7 +50,7 @@ run "test_stacks_include_expected" {
command = plan

assert {
condition = contains(local.stacks, "root-module-a-test")
condition = contains(local.stacks, "root-module-a-test")
error_message = "Stack names were not created correctly: ${jsonencode(local.stacks)}"
}
}
Expand All @@ -60,7 +60,7 @@ run "test_folder_labels_are_correct_format" {
command = plan

assert {
condition = contains(local._folder_labels["root-module-a-test"], "folder:root-module-a/test")
condition = contains(local._folder_labels["root-module-a-test"], "folder:root-module-a/test")
error_message = "Folder label was not created correctly for root-module-a: ${jsonencode(local._folder_labels)}"
}
}
Expand All @@ -70,7 +70,7 @@ run "test_workspace_when_default_tf_workspace_enabled_is_false" {
command = plan

assert {
condition = local.configs["root-module-a-test"].terraform_workspace == "test"
condition = local.configs["root-module-a-test"].terraform_workspace == "test"
error_message = "Terraform workspace was not set correctly when default_tf_workspace_enabled is false: ${jsonencode(local.configs)}"
}
}
Expand All @@ -80,7 +80,7 @@ run "test_workspace_when_default_tf_workspace_enabled" {
command = plan

assert {
condition = local.configs["root-module-a-default-example"].terraform_workspace == "default"
condition = local.configs["root-module-a-default-example"].terraform_workspace == "default"
error_message = "Default Terraform workspace was not used correctly: ${jsonencode(local.configs)}"
}
}
Expand All @@ -90,7 +90,7 @@ run "test_administrative_label_is_added_to_stack" {
command = plan

assert {
condition = contains(local.labels["root-module-a-default-example"], "administrative")
condition = contains(local.labels["root-module-a-default-example"], "administrative")
error_message = "Administrative label was not added to the stack: ${jsonencode(local.labels)}"
}
}
Expand All @@ -100,7 +100,7 @@ run "test_administrative_label_is_not_added_to_stack_when_not_administrative" {
command = plan

assert {
condition = !contains(local.labels["root-module-a-test"], "administrative")
condition = !contains(local.labels["root-module-a-test"], "administrative")
error_message = "Administrative label was added to the stack when it should not have been: ${jsonencode(local.labels)}"
}
}
Expand All @@ -110,7 +110,7 @@ run "test_depends_on_label_is_added_to_stack" {
command = plan

assert {
condition = contains(local.labels["root-module-a-test"], "depends-on:spacelift-automation-default")
condition = contains(local.labels["root-module-a-test"], "depends-on:spacelift-automation-default")
error_message = "Depends-on label was not added to the stack: ${jsonencode(local.labels)}"
}
}
Expand All @@ -120,7 +120,7 @@ run "test_before_init_excludes_the_expected_tfvars_copy_command_when_tfvars_are_
command = plan

assert {
condition = !contains(local.before_init["root-module-a-default-example"], "cp tfvars/default-example.tfvars spacelift.auto.tfvars")
condition = !contains(local.before_init["root-module-a-default-example"], "cp tfvars/default-example.tfvars spacelift.auto.tfvars")
error_message = "Before_init was not created correctly: ${jsonencode(local.before_init)}"
}
}
Expand All @@ -130,7 +130,7 @@ run "test_before_init_includes_the_expected_tfvars_copy_command" {
command = plan

assert {
condition = contains(local.before_init["root-module-a-test"], "cp tfvars/test.tfvars spacelift.auto.tfvars")
condition = contains(local.before_init["root-module-a-test"], "cp tfvars/test.tfvars spacelift.auto.tfvars")
error_message = "Before_init was not created correctly: ${jsonencode(local.before_init)}"
}
}
Expand All @@ -140,7 +140,31 @@ run "test_before_init_includes_the_default_before_init_and_stack_before_init" {
command = plan

assert {
condition = contains(local.before_init["root-module-a-default-example"], "echo 'Hello'") && contains(local.before_init["root-module-a-default-example"], "echo 'World'")
condition = contains(local.before_init["root-module-a-default-example"], "echo 'Hello'") && contains(local.before_init["root-module-a-default-example"], "echo 'World'")
error_message = "Before_init was not created correctly: ${jsonencode(local.before_init)}"
}
}

# Test that space_name from stack settings resolves to correct ID
run "test_space_name_resolves_to_correct_id" {
command = plan

assert {
condition = local.resolved_space_ids["root-module-a-default-example"] == "mp-automation-01JEC2D4K2Q2V1AJQ0Y6BFGJJ3" # For the `masterpointio.app.spacelift.io`
error_message = "Space name not resolving to correct ID: ${jsonencode(local.resolved_space_ids)}"
}
}

# Test that space_id from stack settings takes precedence over space_id global variable
run "test_space_id_takes_precedence_over_space_id_global_variable" {
command = plan

variables {
space_id = "default-space-id-global"
}

assert {
condition = local.resolved_space_ids["root-module-a-test"] == "direct-space-id-stack-yaml"
error_message = "Space ID from stack settings not taking precedence over global variable space ID: ${jsonencode(local.resolved_space_ids)}"
}
}
Loading
Loading