diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 3945246..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Prerequisites: -# brew install pre-commit terraform-docs -repos: - - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.88.0 - hooks: - - id: terraform_docs diff --git a/.terraform-docs.yml b/.terraform-docs.yml new file mode 100644 index 0000000..48fe47d --- /dev/null +++ b/.terraform-docs.yml @@ -0,0 +1,14 @@ +formatter: markdown table + +recursive: + enabled: false + path: modules + include-main: true + +output: + file: README.md + mode: inject + template: |- + + {{ .Content }} + diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 7860320..90147aa 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -2,7 +2,7 @@ # To learn more about the format of this file, see https://docs.trunk.io/reference/trunk-yaml version: 0.1 cli: - version: 1.22.9 + version: 1.22.8 # Trunk provides extensibility via plugins. (https://docs.trunk.io/plugins) plugins: sources: @@ -39,6 +39,7 @@ lint: - CHANGELOG.md actions: enabled: + - terraform-docs - trunk-announce - trunk-check-pre-push - trunk-fmt-pre-commit diff --git a/README.md b/README.md index 7292aed..07b0f75 100644 --- a/README.md +++ b/README.md @@ -223,94 +223,102 @@ automation_settings: This is to support easy local and outside-spacelift operations. Keeping variable values in a `tfvars` file per workspace allows you to simply pass that file to the relevant CLI command locally via the `-var-file` option so that you don't need to provide values individually. e.g. `tofu plan -var-file=tfvars/dev.tfvars` - - + + + ## Requirements -| Name | Version | -| ------------------------------------------------------------------------ | ------- | -| [terraform](#requirement_terraform) | >= 1.9 | -| [spacelift](#requirement_spacelift) | >= 1.14 | +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.9 | +| [jsonschema](#requirement\_jsonschema) | >= 0.2.1 | +| [spacelift](#requirement\_spacelift) | >= 1.14 | ## Providers -| Name | Version | -| ------------------------------------------------------------------ | ------- | -| [spacelift](#provider_spacelift) | 1.19.1 | +| Name | Version | +|------|---------| +| [jsonschema](#provider\_jsonschema) | 0.2.1 | +| [spacelift](#provider\_spacelift) | 1.19.1 | ## Modules -| Name | Source | Version | -| ----------------------------------------------- | ----------------------------------------- | ------- | -| [deep](#module_deep) | cloudposse/config/yaml//modules/deepmerge | 1.0.2 | +| Name | Source | Version | +|------|--------|---------| +| [deep](#module\_deep) | cloudposse/config/yaml//modules/deepmerge | 1.0.2 | ## Resources -| Name | Type | -| --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| Name | Type | +|------|------| | [spacelift_aws_integration_attachment.default](https://registry.terraform.io/providers/spacelift-io/spacelift/latest/docs/resources/aws_integration_attachment) | resource | -| [spacelift_drift_detection.default](https://registry.terraform.io/providers/spacelift-io/spacelift/latest/docs/resources/drift_detection) | resource | -| [spacelift_stack.default](https://registry.terraform.io/providers/spacelift-io/spacelift/latest/docs/resources/stack) | resource | -| [spacelift_stack_destructor.default](https://registry.terraform.io/providers/spacelift-io/spacelift/latest/docs/resources/stack_destructor) | resource | +| [spacelift_drift_detection.default](https://registry.terraform.io/providers/spacelift-io/spacelift/latest/docs/resources/drift_detection) | resource | +| [spacelift_stack.default](https://registry.terraform.io/providers/spacelift-io/spacelift/latest/docs/resources/stack) | resource | +| [spacelift_stack_destructor.default](https://registry.terraform.io/providers/spacelift-io/spacelift/latest/docs/resources/stack_destructor) | resource | +| [jsonschema_validator.runtime_overrides](https://registry.terraform.io/providers/bpedman/jsonschema/latest/docs/data-sources/validator) | data source | +| [spacelift_spaces.all](https://registry.terraform.io/providers/spacelift-io/spacelift/latest/docs/data-sources/spaces) | data source | ## Inputs -| Name | Description | Type | Default | Required | -| --------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | :------: | -| [additional_project_globs](#input_additional_project_globs) | Project globs is an optional list of paths to track stack changes of outside of the project root. Push policies are another alternative to track changes in additional paths. | `set(string)` | `[]` | no | -| [administrative](#input_administrative) | Flag to mark the stack as administrative | `bool` | `false` | no | -| [after_apply](#input_after_apply) | List of after-apply scripts | `list(string)` | `[]` | no | -| [after_destroy](#input_after_destroy) | List of after-destroy scripts | `list(string)` | `[]` | no | -| [after_init](#input_after_init) | List of after-init scripts | `list(string)` | `[]` | no | -| [after_perform](#input_after_perform) | List of after-perform scripts | `list(string)` | `[]` | no | -| [after_plan](#input_after_plan) | List of after-plan scripts | `list(string)` | `[]` | no | -| [all_root_modules_enabled](#input_all_root_modules_enabled) | When set to true, all subdirectories in root_modules_path will be treated as root modules. | `bool` | `false` | no | -| [autodeploy](#input_autodeploy) | Flag to enable/disable automatic deployment of the stack | `bool` | `true` | no | -| [autoretry](#input_autoretry) | Flag to enable/disable automatic retry of the stack | `bool` | `false` | no | -| [aws_integration_attachment_read](#input_aws_integration_attachment_read) | Indicates whether this attachment is used for read operations. | `bool` | `true` | no | -| [aws_integration_attachment_write](#input_aws_integration_attachment_write) | Indicates whether this attachment is used for write operations. | `bool` | `true` | no | -| [aws_integration_enabled](#input_aws_integration_enabled) | Indicates whether the AWS integration is enabled. | `bool` | `false` | no | -| [aws_integration_id](#input_aws_integration_id) | ID of the AWS integration to attach. | `string` | `null` | no | -| [before_apply](#input_before_apply) | List of before-apply scripts | `list(string)` | `[]` | no | -| [before_destroy](#input_before_destroy) | List of before-destroy scripts | `list(string)` | `[]` | no | -| [before_init](#input_before_init) | List of before-init scripts | `list(string)` | `[]` | no | -| [before_perform](#input_before_perform) | List of before-perform scripts | `list(string)` | `[]` | no | -| [before_plan](#input_before_plan) | List of before-plan scripts | `list(string)` | `[]` | no | -| [branch](#input_branch) | Specify which branch to use within the infrastructure repository. | `string` | `"main"` | no | -| [common_config_file](#input_common_config_file) | Name of the common configuration file for the stack across a root module. | `string` | `"common.yaml"` | no | -| [default_tf_workspace_enabled](#input_default_tf_workspace_enabled) | Enables the use of `default` Terraform workspace instead of managing multiple workspaces within a root module.

NOTE: We encourage the use of Terraform workspaces to manage multiple environments.
However, you will want to disable this behavior if you're utilizing different backends for each instance
of your root modules (we call this "Dynamic Backends"). | `bool` | `false` | no | -| [description](#input_description) | A description for the created Stacks. This is a template string that will be rendered with the final config object for the stack.
See the main.tf for full internals of that object and the documentation on templatestring for usage.
https://opentofu.org/docs/language/functions/templatestring/ | `string` | `"Root Module: ${root_module}\nProject Root: ${project_root}\nWorkspace: ${terraform_workspace}\nManaged by spacelift-automation Terraform root module."` | no | -| [destructor_enabled](#input_destructor_enabled) | Flag to enable/disable the destructor for the Stack. | `bool` | `false` | no | -| [drift_detection_enabled](#input_drift_detection_enabled) | Flag to enable/disable Drift Detection configuration for a Stack. | `bool` | `false` | no | -| [drift_detection_ignore_state](#input_drift_detection_ignore_state) | Controls whether drift detection should be performed on a stack
in any final state instead of just 'Finished'. | `bool` | `false` | no | -| [drift_detection_reconcile](#input_drift_detection_reconcile) | Flag to enable/disable automatic reconciliation of drifts. | `bool` | `false` | no | -| [drift_detection_schedule](#input_drift_detection_schedule) | The schedule for drift detection. | `list(string)` |
[
"0 4 * * *"
]
| no | -| [drift_detection_timezone](#input_drift_detection_timezone) | The timezone for drift detection. | `string` | `"UTC"` | no | -| [enable_local_preview](#input_enable_local_preview) | Indicates whether local preview runs can be triggered on this Stack. | `bool` | `false` | no | -| [enable_well_known_secret_masking](#input_enable_well_known_secret_masking) | Indicates whether well-known secret masking is enabled. | `bool` | `true` | no | -| [enabled_root_modules](#input_enabled_root_modules) | List of root modules where to look for stack config files.
Ignored when all_root_modules_enabled is true.
Example: ["spacelift-automation", "k8s-cluster"] | `list(string)` | `[]` | no | -| [github_action_deploy](#input_github_action_deploy) | Indicates whether GitHub users can deploy from the Checks API. | `bool` | `true` | no | -| [github_enterprise](#input_github_enterprise) | The GitHub VCS settings |
object({
namespace = string
id = optional(string)
})
| `null` | no | -| [labels](#input_labels) | List of labels to apply to the stacks. | `list(string)` | `[]` | no | -| [manage_state](#input_manage_state) | Determines if Spacelift should manage state for this stack. | `bool` | `false` | no | -| [protect_from_deletion](#input_protect_from_deletion) | Protect this stack from accidental deletion. If set, attempts to delete this stack will fail. | `bool` | `false` | no | -| [repository](#input_repository) | The name of your infrastructure repo | `string` | n/a | yes | -| [root_module_structure](#input_root_module_structure) | The root module structure of the Stacks that you're reading in. See README for full details.

MultiInstance - You're using Workspaces or Dynamic Backend configuration to create multiple instances of the same root module code.
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 | -| [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 | -| [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 | -| [space_id](#input_space_id) | Place the created stacks in the specified space_id. | `string` | `"root"` | no | -| [terraform_smart_sanitization](#input_terraform_smart_sanitization) | Indicates whether runs on this will use terraform's sensitive value system to sanitize
the outputs of Terraform state and plans in spacelift instead of sanitizing all fields. | `bool` | `false` | no | -| [terraform_version](#input_terraform_version) | Terraform version to use. | `string` | `"1.7.2"` | no | -| [terraform_workflow_tool](#input_terraform_workflow_tool) | Defines the tool that will be used to execute the workflow.
This can be one of OPEN_TOFU, TERRAFORM_FOSS or CUSTOM. | `string` | `"OPEN_TOFU"` | no | -| [worker_pool_id](#input_worker_pool_id) | ID of the worker pool to use.
NOTE: worker_pool_id is required when using a self-hosted instance of Spacelift. | `string` | `null` | no | +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [additional\_project\_globs](#input\_additional\_project\_globs) | Project globs is an optional list of paths to track stack changes of outside of the project root. Push policies are another alternative to track changes in additional paths. | `set(string)` | `[]` | no | +| [administrative](#input\_administrative) | Flag to mark the stack as administrative | `bool` | `false` | no | +| [after\_apply](#input\_after\_apply) | List of after-apply scripts | `list(string)` | `[]` | no | +| [after\_destroy](#input\_after\_destroy) | List of after-destroy scripts | `list(string)` | `[]` | no | +| [after\_init](#input\_after\_init) | List of after-init scripts | `list(string)` | `[]` | no | +| [after\_perform](#input\_after\_perform) | List of after-perform scripts | `list(string)` | `[]` | no | +| [after\_plan](#input\_after\_plan) | List of after-plan scripts | `list(string)` | `[]` | no | +| [all\_root\_modules\_enabled](#input\_all\_root\_modules\_enabled) | When set to true, all subdirectories in root\_modules\_path will be treated as root modules. | `bool` | `false` | no | +| [autodeploy](#input\_autodeploy) | Flag to enable/disable automatic deployment of the stack | `bool` | `true` | no | +| [autoretry](#input\_autoretry) | Flag to enable/disable automatic retry of the stack | `bool` | `false` | no | +| [aws\_integration\_attachment\_read](#input\_aws\_integration\_attachment\_read) | Indicates whether this attachment is used for read operations. | `bool` | `true` | no | +| [aws\_integration\_attachment\_write](#input\_aws\_integration\_attachment\_write) | Indicates whether this attachment is used for write operations. | `bool` | `true` | no | +| [aws\_integration\_enabled](#input\_aws\_integration\_enabled) | Indicates whether the AWS integration is enabled. | `bool` | `false` | no | +| [aws\_integration\_id](#input\_aws\_integration\_id) | ID of the AWS integration to attach. | `string` | `null` | no | +| [before\_apply](#input\_before\_apply) | List of before-apply scripts | `list(string)` | `[]` | no | +| [before\_destroy](#input\_before\_destroy) | List of before-destroy scripts | `list(string)` | `[]` | no | +| [before\_init](#input\_before\_init) | List of before-init scripts | `list(string)` | `[]` | no | +| [before\_perform](#input\_before\_perform) | List of before-perform scripts | `list(string)` | `[]` | no | +| [before\_plan](#input\_before\_plan) | List of before-plan scripts | `list(string)` | `[]` | no | +| [branch](#input\_branch) | Specify which branch to use within the infrastructure repository. | `string` | `"main"` | no | +| [common\_config\_file](#input\_common\_config\_file) | Name of the common configuration file for the stack across a root module. | `string` | `"common.yaml"` | no | +| [default\_tf\_workspace\_enabled](#input\_default\_tf\_workspace\_enabled) | Enables the use of `default` Terraform workspace instead of managing multiple workspaces within a root module.

NOTE: We encourage the use of Terraform workspaces to manage multiple environments.
However, you will want to disable this behavior if you're utilizing different backends for each instance
of your root modules (we call this "Dynamic Backends"). | `bool` | `false` | no | +| [description](#input\_description) | A description for the created Stacks. This is a template string that will be rendered with the final config object for the stack.
See the main.tf for full internals of that object and the documentation on templatestring for usage.
https://opentofu.org/docs/language/functions/templatestring/ | `string` | `"Root Module: ${root_module}\nProject Root: ${project_root}\nWorkspace: ${terraform_workspace}\nManaged by spacelift-automation Terraform root module."` | no | +| [destructor\_enabled](#input\_destructor\_enabled) | Flag to enable/disable the destructor for the Stack. | `bool` | `false` | no | +| [drift\_detection\_enabled](#input\_drift\_detection\_enabled) | Flag to enable/disable Drift Detection configuration for a Stack. | `bool` | `false` | no | +| [drift\_detection\_ignore\_state](#input\_drift\_detection\_ignore\_state) | Controls whether drift detection should be performed on a stack
in any final state instead of just 'Finished'. | `bool` | `false` | no | +| [drift\_detection\_reconcile](#input\_drift\_detection\_reconcile) | Flag to enable/disable automatic reconciliation of drifts. | `bool` | `false` | no | +| [drift\_detection\_schedule](#input\_drift\_detection\_schedule) | The schedule for drift detection. | `list(string)` |
[
"0 4 * * *"
]
| no | +| [drift\_detection\_timezone](#input\_drift\_detection\_timezone) | The timezone for drift detection. | `string` | `"UTC"` | no | +| [enable\_local\_preview](#input\_enable\_local\_preview) | Indicates whether local preview runs can be triggered on this Stack. | `bool` | `false` | no | +| [enable\_well\_known\_secret\_masking](#input\_enable\_well\_known\_secret\_masking) | Indicates whether well-known secret masking is enabled. | `bool` | `true` | no | +| [enabled\_root\_modules](#input\_enabled\_root\_modules) | List of root modules where to look for stack config files.
Ignored when all\_root\_modules\_enabled is true.
Example: ["spacelift-automation", "k8s-cluster"] | `list(string)` | `[]` | no | +| [github\_action\_deploy](#input\_github\_action\_deploy) | Indicates whether GitHub users can deploy from the Checks API. | `bool` | `true` | no | +| [github\_enterprise](#input\_github\_enterprise) | The GitHub VCS settings |
object({
namespace = string
id = optional(string)
})
| `null` | no | +| [labels](#input\_labels) | List of labels to apply to the stacks. | `list(string)` | `[]` | no | +| [manage\_state](#input\_manage\_state) | Determines if Spacelift should manage state for this stack. | `bool` | `false` | no | +| [protect\_from\_deletion](#input\_protect\_from\_deletion) | Protect this stack from accidental deletion. If set, attempts to delete this stack will fail. | `bool` | `false` | no | +| [repository](#input\_repository) | The name of your infrastructure repo | `string` | n/a | yes | +| [root\_module\_structure](#input\_root\_module\_structure) | The root module structure of the Stacks that you're reading in. See README for full details.

MultiInstance - You're using Workspaces or Dynamic Backend configuration to create multiple instances of the same root module code.
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 | +| [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 | +| [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 | +| [runtime\_overrides](#input\_runtime\_overrides) | Runtime overrides that are merged into the stack config.
This allows for per-root-module overrides of the stack resources at runtime
so you have more flexibility beyond the variable defaults and the static stack config files.
Keys are the root module names and values match the StackConfig schema.
See `stack-config.schema.json` for full details on the schema and
`tests/fixtures/multi-instance/root-module-a/stacks/default-example.yaml` for a complete example. | `any` | `{}` | no | +| [space\_id](#input\_space\_id) | Place the created stacks in the specified space\_id. Mutually exclusive with space\_name. | `string` | `null` | no | +| [space\_name](#input\_space\_name) | Place the created stacks in the specified space\_name. Mutually exclusive with space\_id. | `string` | `null` | no | +| [terraform\_smart\_sanitization](#input\_terraform\_smart\_sanitization) | Indicates whether runs on this will use terraform's sensitive value system to sanitize
the outputs of Terraform state and plans in spacelift instead of sanitizing all fields. | `bool` | `false` | no | +| [terraform\_version](#input\_terraform\_version) | Terraform version to use. | `string` | `"1.7.2"` | no | +| [terraform\_workflow\_tool](#input\_terraform\_workflow\_tool) | Defines the tool that will be used to execute the workflow.
This can be one of OPEN\_TOFU, TERRAFORM\_FOSS or CUSTOM. | `string` | `"OPEN_TOFU"` | no | +| [worker\_pool\_id](#input\_worker\_pool\_id) | ID of the worker pool to use.
NOTE: worker\_pool\_id is required when using a self-hosted instance of Spacelift. | `string` | `null` | no | ## Outputs -| Name | Description | -| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| [spacelift_stacks](#output_spacelift_stacks) | A map of Spacelift stacks with selected attributes.
To reduce the risk of accidentally exporting sensitive data, only a subset of attributes is exported. | - - +| Name | Description | +|------|-------------| +| [spacelift\_stacks](#output\_spacelift\_stacks) | A map of Spacelift stacks with selected attributes.
To reduce the risk of accidentally exporting sensitive data, only a subset of attributes is exported. | + + + ## Contributing @@ -318,9 +326,11 @@ Contributions are welcome and appreciated! Found an issue or want to request a feature? [Open an issue](https://github.com/masterpointio/terraform-spacelift-automation/issues/new) -Want to fix a bug you found or add some functionality? Fork, clone, commit, push, and PR and we'll check it out. +Want to fix a bug you found or add some functionality? +Fork, clone, commit, push, and PR and we'll check it out. -If you have any issues or are waiting a long time for a PR to get merged then feel free to ping us at [hello@masterpoint.io](mailto:hello@masterpoint.io). +If you have any issues or are waiting a long time for a PR to get merged then +feel free to ping us at [hello@masterpoint.io](mailto:hello@masterpoint.io). ## Built By diff --git a/data.tf b/data.tf index 5c9c359..982c7f8 100644 --- a/data.tf +++ b/data.tf @@ -1,2 +1,13 @@ # Look up all spaces in order to map space names to space IDs data "spacelift_spaces" "all" {} + +# Validate the runtime overrides against the schema +# Frustrating that we have to do this, but this successfully validates the typing +# of the given runtime overrides since we need to use `any` for the variable type :( +# See https://github.com/masterpointio/terraform-spacelift-automation/pull/44 for full details +data "jsonschema_validator" "runtime_overrides" { + for_each = var.runtime_overrides + + document = jsonencode(each.value) + schema = "${path.module}/stack-config.schema.json" +} diff --git a/main.tf b/main.tf index cec0183..2674aba 100644 --- a/main.tf +++ b/main.tf @@ -148,7 +148,8 @@ locals { # `yaml` is intentionally used here as we require Stack and `tfvars` config files to be named equally "tfvars_file_name" = trimsuffix(file, ".yaml"), }, - content + content, + try(jsondecode(data.jsonschema_validator.runtime_overrides[module].validated), {}), ) if file != var.common_config_file } ]...) @@ -267,6 +268,8 @@ locals { )) } + ## Handle space lookups + # 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 = { @@ -283,6 +286,18 @@ locals { "root" # If no space_id or space_name is provided, default to the root space ) } + + ## Filter integration + drift detection stacks + + aws_integration_stacks = { + for stack, config in local.stack_configs : + stack => config if try(config.aws_integration_enabled, var.aws_integration_enabled) + } + + drift_detection_stacks = { + for stack, config in local.stack_configs : + stack => config if try(config.drift_detection_enabled, var.drift_detection_enabled) + } } check "spaces_enforce_mutual_exclusivity" { @@ -376,10 +391,8 @@ resource "spacelift_stack_destructor" "default" { } resource "spacelift_aws_integration_attachment" "default" { - for_each = { - for stack, configs in local.stack_configs : stack => configs - if try(configs.aws_integration_enabled, var.aws_integration_enabled) - } + for_each = local.aws_integration_stacks + integration_id = try(local.stack_configs[each.key].aws_integration_id, var.aws_integration_id) stack_id = spacelift_stack.default[each.key].id read = var.aws_integration_attachment_read @@ -387,10 +400,7 @@ resource "spacelift_aws_integration_attachment" "default" { } resource "spacelift_drift_detection" "default" { - for_each = { - for stack, configs in local.stack_configs : stack => configs - if try(configs.drift_detection_enabled, var.drift_detection_enabled) - } + for_each = local.drift_detection_stacks stack_id = spacelift_stack.default[each.key].id ignore_state = try(local.stack_configs[each.key].drift_detection_ignore_state, var.drift_detection_ignore_state) diff --git a/stack-config.schema.json b/stack-config.schema.json index d716884..07be818 100644 --- a/stack-config.schema.json +++ b/stack-config.schema.json @@ -9,7 +9,7 @@ "title": "Masterpoint Stack Config schema. Version 1.0. https://masterpoint.io", "description": "Schema for Masterpoint's spacelift-automation stack configuration files. This is used to override stack configurations for the https://github.com/masterpointio/terraform-spacelift-automation module.", "type": "object", - "required": ["kind", "stack_settings"], + "required": [], "properties": { "kind": { "type": "string", @@ -174,6 +174,10 @@ "type": "string", "description": "Spacelift space ID" }, + "space_name": { + "type": "string", + "description": "Spacelift space name, this will be translated to a space_id. Mutually exclusive with space_id" + }, "terraform_smart_sanitization": { "type": "boolean", "description": "Whether to enable smart sanitization" diff --git a/tests/fixtures/multi-instance/root-module-a/stacks/default-example.yaml b/tests/fixtures/multi-instance/root-module-a/stacks/default-example.yaml index 4c3aa2b..d250819 100644 --- a/tests/fixtures/multi-instance/root-module-a/stacks/default-example.yaml +++ b/tests/fixtures/multi-instance/root-module-a/stacks/default-example.yaml @@ -1,12 +1,45 @@ kind: StackConfigV1 stack_settings: administrative: true + additional_project_globs: [glob/*] + after_apply: [echo 'after_apply'] + after_destroy: [echo 'after_destroy'] + after_init: [echo 'after_init'] + after_perform: [echo 'after_perform'] + after_plan: [echo 'after_plan'] + autodeploy: false + autoretry: true + before_apply: [echo 'before_apply'] + before_destroy: [echo 'before_destroy'] + before_init: [echo 'before_init'] + before_perform: [echo 'before_perform'] + before_plan: [echo 'before_plan'] + branch: prod description: This is a test of the emergency broadcast system - before_init: - - echo 'World' + enable_local_preview: true + enable_well_known_secret_masking: false + github_action_deploy: false + manage_state: true + protect_from_deletion: true + runner_image: masterpointio/spacelift-runner:latest + space_name: mp-automation # Tests space_name gets translated to space_id (the Terraform resource attribute that is accepted) + terraform_smart_sanitization: true + terraform_version: 1.9.0 + worker_pool_id: "1234567890" + + destructor_enabled: true + + aws_integration_enabled: true + aws_integration_id: "1234567890" + + drift_detection_enabled: true + drift_detection_ignore_state: true + drift_detection_reconcile: true + drift_detection_schedule: [0 0 * * *] + drift_detection_timezone: America/Denver + labels: - default_example_label - space_name: mp-automation # Tests space_name gets translated to space_id (the Terraform resource attribute that is accepted) automation_settings: default_tf_workspace_enabled: true diff --git a/tests/fixtures/multi-instance/root-module-a/stacks/test.yaml b/tests/fixtures/multi-instance/root-module-a/stacks/test.yaml index c7c32e8..82a2ffb 100644 --- a/tests/fixtures/multi-instance/root-module-a/stacks/test.yaml +++ b/tests/fixtures/multi-instance/root-module-a/stacks/test.yaml @@ -1,5 +1,5 @@ kind: StackConfigV1 stack_settings: + space_id: direct-space-id-stack-yaml # Tests direct space_id precedence over global variable space_id labels: - test_label - space_id: direct-space-id-stack-yaml # Tests direct space_id precedence over global variable space_id diff --git a/tests/main.tftest.hcl b/tests/main.tftest.hcl index 71e4f15..866d41d 100644 --- a/tests/main.tftest.hcl +++ b/tests/main.tftest.hcl @@ -15,7 +15,476 @@ variables { ] } -#Test that the global labels are created correctly +# Test the default-example stack results in all the right final resource values +# We don't test labels here because we test them below. +run "test_default_example_stack_final_values" { + command = plan + + # administrative + assert { + condition = spacelift_stack.default["root-module-a-default-example"].administrative == true + error_message = "Administrative was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # additional_project_globs + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].additional_project_globs, "glob/*") + error_message = "additional_project_globs was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # after_apply + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].after_apply, "echo 'after_apply'") + error_message = "after_apply was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # after_destroy + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].after_destroy, "echo 'after_destroy'") + error_message = "after_destroy was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # after_init + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].after_init, "echo 'after_init'") + error_message = "after_init was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # after_perform + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].after_perform, "echo 'after_perform'") + error_message = "after_perform was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # after_plan + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].after_plan, "echo 'after_plan'") + error_message = "after_plan was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # autodeploy + assert { + condition = spacelift_stack.default["root-module-a-default-example"].autodeploy == false + error_message = "autodeploy was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # autoretry + assert { + condition = spacelift_stack.default["root-module-a-default-example"].autoretry == true + error_message = "autoretry was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # before_apply + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].before_apply, "echo 'before_apply'") + error_message = "before_apply was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # before_destroy + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].before_destroy, "echo 'before_destroy'") + error_message = "before_destroy was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # before_init + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].before_init, "echo 'before_init'") + error_message = "before_init was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # before_perform + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].before_perform, "echo 'before_perform'") + error_message = "before_perform was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # before_plan + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].before_plan, "echo 'before_plan'") + error_message = "before_plan was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # branch + assert { + condition = spacelift_stack.default["root-module-a-default-example"].branch == "prod" + error_message = "branch was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # description + assert { + condition = spacelift_stack.default["root-module-a-default-example"].description == "This is a test of the emergency broadcast system" + error_message = "description was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # enable_local_preview + assert { + condition = spacelift_stack.default["root-module-a-default-example"].enable_local_preview == true + error_message = "enable_local_preview was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # enable_well_known_secret_masking + assert { + condition = spacelift_stack.default["root-module-a-default-example"].enable_well_known_secret_masking == false + error_message = "enable_well_known_secret_masking was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # github_action_deploy + assert { + condition = spacelift_stack.default["root-module-a-default-example"].github_action_deploy == false + error_message = "github_action_deploy was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # manage_state + assert { + condition = spacelift_stack.default["root-module-a-default-example"].manage_state == true + error_message = "manage_state was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # protect_from_deletion + assert { + condition = spacelift_stack.default["root-module-a-default-example"].protect_from_deletion == true + error_message = "protect_from_deletion was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # runner_image + assert { + condition = spacelift_stack.default["root-module-a-default-example"].runner_image == "masterpointio/spacelift-runner:latest" + error_message = "runner_image was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # space_id + assert { + condition = spacelift_stack.default["root-module-a-default-example"].space_id == "mp-automation-01JEC2D4K2Q2V1AJQ0Y6BFGJJ3" + error_message = "space_id was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # terraform_smart_sanitization + assert { + condition = spacelift_stack.default["root-module-a-default-example"].terraform_smart_sanitization == true + error_message = "terraform_smart_sanitization was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # terraform_version + assert { + condition = spacelift_stack.default["root-module-a-default-example"].terraform_version == "1.9.0" + error_message = "Terraform version was not set correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # worker_pool_id + assert { + condition = spacelift_stack.default["root-module-a-default-example"].worker_pool_id == "1234567890" + error_message = "worker_pool_id was not correct on the default-example stack: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # destructor_enabled + assert { + condition = spacelift_stack_destructor.default["root-module-a-default-example"].deactivated == false + error_message = "destructor_enabled was not correct on the default-example stack: ${jsonencode(spacelift_stack_destructor.default["root-module-a-default-example"])}" + } + + # aws_integration_id + assert { + condition = spacelift_aws_integration_attachment.default["root-module-a-default-example"].integration_id == "1234567890" + error_message = "aws_integration_id was not correct on the default-example stack: ${jsonencode(spacelift_aws_integration_attachment.default["root-module-a-default-example"])}" + } + + # drift_detection_ignore_state + assert { + condition = spacelift_drift_detection.default["root-module-a-default-example"].ignore_state == true + error_message = "drift_detection_ignore_state was not correct on the default-example stack: ${jsonencode(spacelift_drift_detection.default["root-module-a-default-example"])}" + } + + # drift_detection_reconcile + assert { + condition = spacelift_drift_detection.default["root-module-a-default-example"].reconcile == true + error_message = "drift_detection_reconcile was not correct on the default-example stack: ${jsonencode(spacelift_drift_detection.default["root-module-a-default-example"])}" + } + + # drift_detection_schedule + assert { + condition = contains(spacelift_drift_detection.default["root-module-a-default-example"].schedule, "0 0 * * *") + error_message = "drift_detection_schedule was not correct on the default-example stack: ${jsonencode(spacelift_drift_detection.default["root-module-a-default-example"])}" + } + + # drift_detection_timezone + assert { + condition = spacelift_drift_detection.default["root-module-a-default-example"].timezone == "America/Denver" + error_message = "drift_detection_timezone was not correct on the default-example stack: ${jsonencode(spacelift_drift_detection.default["root-module-a-default-example"])}" + } +} + +# Test the default-example stack with runtime overrides +run "test_default_example_stack_runtime_overrides" { + command = plan + + variables { + runtime_overrides = { + root-module-a = { + stack_settings = { + administrator = false + additional_project_globs = ["changed/*"] + after_apply = ["echo 'changed_after_apply'"] + after_destroy = ["echo 'changed_after_destroy'"] + after_init = ["echo 'changed_after_init'"] + after_perform = ["echo 'changed_after_perform'"] + after_plan = ["echo 'changed_after_plan'"] + autodeploy = true + autoretry = false + before_apply = ["echo 'changed_before_apply'"] + before_destroy = ["echo 'changed_before_destroy'"] + before_init = ["echo 'changed_before_init'"] + before_perform = ["echo 'changed_before_perform'"] + before_plan = ["echo 'changed_before_plan'"] + branch = "dev" + description = "This is a changed test of the emergency broadcast system" + enable_local_preview = false + enable_well_known_secret_masking = true + github_action_deploy = true + manage_state = false + protect_from_deletion = false + runner_image = "masterpointio/spacelift-runner:dev" + space_id = "555" + terraform_smart_sanitization = false + terraform_version = "1.9.1" + worker_pool_id = "555" + + destructor_enabled = false + + aws_integration_enabled = true + aws_integration_id = "999" + + drift_detection_enabled = true + drift_detection_ignore_state = false + drift_detection_reconcile = false + drift_detection_schedule = ["0 7 * * *"] + drift_detection_timezone = "America/Denver" + } + } + } + } + + # administrative + assert { + condition = spacelift_stack.default["root-module-a-default-example"].administrative == false + error_message = "Administrative override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # additional_project_globs + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].additional_project_globs, "changed/*") + error_message = "additional_project_globs override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # after_apply + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].after_apply, "echo 'changed_after_apply'") + error_message = "after_apply override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # after_destroy + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].after_destroy, "echo 'changed_after_destroy'") + error_message = "after_destroy override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # after_init + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].after_init, "echo 'changed_after_init'") + error_message = "after_init override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # after_perform + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].after_perform, "echo 'changed_after_perform'") + error_message = "after_perform override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # after_plan + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].after_plan, "echo 'changed_after_plan'") + error_message = "after_plan override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # autodeploy + assert { + condition = spacelift_stack.default["root-module-a-default-example"].autodeploy == true + error_message = "autodeploy override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # autoretry + assert { + condition = spacelift_stack.default["root-module-a-default-example"].autoretry == false + error_message = "autoretry override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # before_apply + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].before_apply, "echo 'changed_before_apply'") + error_message = "before_apply override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # before_destroy + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].before_destroy, "echo 'changed_before_destroy'") + error_message = "before_destroy override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # before_init + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].before_init, "echo 'changed_before_init'") + error_message = "before_init override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # before_perform + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].before_perform, "echo 'changed_before_perform'") + error_message = "before_perform override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # before_plan + assert { + condition = contains(spacelift_stack.default["root-module-a-default-example"].before_plan, "echo 'changed_before_plan'") + error_message = "before_plan override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # branch + assert { + condition = spacelift_stack.default["root-module-a-default-example"].branch == "dev" + error_message = "branch override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # description + assert { + condition = spacelift_stack.default["root-module-a-default-example"].description == "This is a changed test of the emergency broadcast system" + error_message = "description override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # enable_local_preview + assert { + condition = spacelift_stack.default["root-module-a-default-example"].enable_local_preview == false + error_message = "enable_local_preview override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # enable_well_known_secret_masking + assert { + condition = spacelift_stack.default["root-module-a-default-example"].enable_well_known_secret_masking == true + error_message = "enable_well_known_secret_masking override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # github_action_deploy + assert { + condition = spacelift_stack.default["root-module-a-default-example"].github_action_deploy == true + error_message = "github_action_deploy override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # manage_state + assert { + condition = spacelift_stack.default["root-module-a-default-example"].manage_state == false + error_message = "manage_state override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # protect_from_deletion + assert { + condition = spacelift_stack.default["root-module-a-default-example"].protect_from_deletion == false + error_message = "protect_from_deletion override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # runner_image + assert { + condition = spacelift_stack.default["root-module-a-default-example"].runner_image == "masterpointio/spacelift-runner:dev" + error_message = "runner_image override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # space_id + assert { + condition = spacelift_stack.default["root-module-a-default-example"].space_id == "555" + error_message = "space_id override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # terraform_smart_sanitization + assert { + condition = spacelift_stack.default["root-module-a-default-example"].terraform_smart_sanitization == false + error_message = "terraform_smart_sanitization override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # terraform_version + assert { + condition = spacelift_stack.default["root-module-a-default-example"].terraform_version == "1.9.1" + error_message = "terraform_version override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # worker_pool_id + assert { + condition = spacelift_stack.default["root-module-a-default-example"].worker_pool_id == "555" + error_message = "worker_pool_id override was not applied correctly: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + } + + # destructor_enabled + assert { + condition = spacelift_stack_destructor.default["root-module-a-default-example"].deactivated == true + error_message = "destructor_enabled override was not applied correctly: ${jsonencode(spacelift_stack_destructor.default["root-module-a-default-example"])}" + } + + # aws_integration_id + assert { + condition = spacelift_aws_integration_attachment.default["root-module-a-default-example"].integration_id == "999" + error_message = "aws_integration_id override was not applied correctly: ${jsonencode(spacelift_aws_integration_attachment.default["root-module-a-default-example"])}" + } + + # drift_detection_ignore_state + assert { + condition = spacelift_drift_detection.default["root-module-a-default-example"].ignore_state == false + error_message = "drift_detection_ignore_state override was not applied correctly: ${jsonencode(spacelift_drift_detection.default["root-module-a-default-example"])}" + } + + # drift_detection_reconcile + assert { + condition = spacelift_drift_detection.default["root-module-a-default-example"].reconcile == false + error_message = "drift_detection_reconcile override was not applied correctly: ${jsonencode(spacelift_drift_detection.default["root-module-a-default-example"])}" + } + + # drift_detection_schedule + assert { + condition = contains(spacelift_drift_detection.default["root-module-a-default-example"].schedule, "0 7 * * *") + error_message = "drift_detection_schedule override was not applied correctly: ${jsonencode(spacelift_drift_detection.default["root-module-a-default-example"])}" + } + + # drift_detection_timezone + assert { + condition = spacelift_drift_detection.default["root-module-a-default-example"].timezone == "America/Denver" + error_message = "drift_detection_timezone override was not applied correctly: ${jsonencode(spacelift_drift_detection.default["root-module-a-default-example"])}" + } +} + +# Test the default-example stack with only 1 runtime override +run "test_default_example_stack_partial_runtime_overrides" { + command = plan + + variables { + runtime_overrides = { + root-module-a = { + stack_settings = { + administrative = true + } + } + } + } + + # administrative + assert { + condition = spacelift_stack.default["root-module-a-default-example"].administrative == true + error_message = "Administrative is true because it's an override: ${jsonencode(spacelift_stack.default["root-module-a-default-example"])}" + + } + + # common_label + assert { + condition = contains(local.configs["root-module-a-default-example"].stack_settings.labels, "common_label") + error_message = "labels include 'common_label' because it's set in the common.yaml file: ${jsonencode(local.configs)}" + } +} + +# Test that the global labels are created correctly run "test_labels_are_created_correctly" { command = plan @@ -150,7 +619,7 @@ 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 'before_init'") error_message = "Before_init was not created correctly: ${jsonencode(local.before_init)}" } } diff --git a/tests/schema-validation.tftest.hcl b/tests/schema-validation.tftest.hcl new file mode 100644 index 0000000..a49a96a --- /dev/null +++ b/tests/schema-validation.tftest.hcl @@ -0,0 +1,14 @@ +# Confirm our StackConfig files in fixtures follow the schema. +# If this test fails, check the schema! +run "test_stack_configs_schema_validation" { + command = plan + + module { + source = "./tests/schema-validator" + } + + assert { + condition = length(data.jsonschema_validator.stack_configs) == 5 + error_message = "The fixture Stack Configs did not validate against the schema: ${jsonencode(data.jsonschema_validator.stack_configs)}" + } +} diff --git a/tests/schema-validator/main.tf b/tests/schema-validator/main.tf new file mode 100644 index 0000000..ba40209 --- /dev/null +++ b/tests/schema-validator/main.tf @@ -0,0 +1,41 @@ +locals { + # Getting these file sets is a bit of a hack because the paths are all sorts of gunked up. + # We fix this below with the normalize_paths and stack_config_contents locals, but it's goofy. + multi_instance_stack_configs = fileset("${path.root}/../**/stacks", "*.yaml") + single_instance_stack_configs = fileset("${path.root}/../**", "stack.yaml") + + stack_configs = toset(concat( + tolist(local.multi_instance_stack_configs), + tolist(local.single_instance_stack_configs))) + + normalize_paths = [ + for stack_config in local.stack_configs : + replace(stack_config, "../", "") + ] + + stack_config_contents = { + for stack_config in local.normalize_paths : + stack_config => file("./tests/${stack_config}") + } +} + +data "jsonschema_validator" "stack_configs" { + for_each = local.stack_config_contents + + document = jsonencode(yamldecode(each.value)) + schema = "${path.module}/../../stack-config.schema.json" +} + +output "validated_stack_configs" { + value = data.jsonschema_validator.stack_configs +} + +terraform { + required_version = ">= 1.7" + required_providers { + jsonschema = { + source = "bpedman/jsonschema" + version = ">= 0.2.1" + } + } +} diff --git a/variables.tf b/variables.tf index c24a44e..8c04ea2 100644 --- a/variables.tf +++ b/variables.tf @@ -14,6 +14,19 @@ variable "root_module_structure" { } } +variable "runtime_overrides" { + type = any + description = <