diff --git a/atmos.yaml b/atmos.yaml
index a0302b74d..da699ccd7 100644
--- a/atmos.yaml
+++ b/atmos.yaml
@@ -19,6 +19,16 @@ base_path: "./examples/quick-start"
components:
terraform:
+ # Optional `command` specifies the executable to be called by `atmos` when running Terraform commands
+ # If not defined, `terraform` is used
+ # Examples:
+ # command: terraform
+ # command: /usr/local/bin/terraform
+ # command: /usr/local/bin/terraform-1.8
+ # command: tofu
+ # command: /usr/local/bin/tofu-1.7.1
+ # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_COMMAND' ENV var, or '--terraform-command' command-line argument
+ command: terraform
# Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_BASE_PATH' ENV var, or '--terraform-dir' command-line argument
# Supports both absolute and relative paths
base_path: "components/terraform"
diff --git a/examples/quick-start/Dockerfile b/examples/quick-start/Dockerfile
index dd587aa0d..a2b3cc97a 100644
--- a/examples/quick-start/Dockerfile
+++ b/examples/quick-start/Dockerfile
@@ -1,12 +1,12 @@
# Geodesic: https://github.com/cloudposse/geodesic/
-ARG GEODESIC_VERSION=2.9.6
+ARG GEODESIC_VERSION=2.11.2
ARG GEODESIC_OS=debian
# Atmos
# https://atmos.tools/
# https://github.com/cloudposse/atmos
# https://github.com/cloudposse/atmos/releases
-ARG ATMOS_VERSION=1.72.0
+ARG ATMOS_VERSION=1.73.0
# Terraform: https://github.com/hashicorp/terraform/releases
ARG TF_VERSION=1.8.1
diff --git a/examples/quick-start/atmos.yaml b/examples/quick-start/atmos.yaml
index a9d1b818a..2cfa41219 100644
--- a/examples/quick-start/atmos.yaml
+++ b/examples/quick-start/atmos.yaml
@@ -19,6 +19,16 @@ base_path: "."
components:
terraform:
+ # Optional `command` specifies the executable to be called by `atmos` when running Terraform commands
+ # If not defined, `terraform` is used
+ # Examples:
+ # command: terraform
+ # command: /usr/local/bin/terraform
+ # command: /usr/local/bin/terraform-1.8
+ # command: tofu
+ # command: /usr/local/bin/tofu-1.7.1
+ # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_COMMAND' ENV var, or '--terraform-command' command-line argument
+ command: terraform
# Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_BASE_PATH' ENV var, or '--terraform-dir' command-line argument
# Supports both absolute and relative paths
base_path: "components/terraform"
diff --git a/examples/quick-start/rootfs/usr/local/etc/atmos/atmos.yaml b/examples/quick-start/rootfs/usr/local/etc/atmos/atmos.yaml
index 7f64a9e1c..22235091d 100644
--- a/examples/quick-start/rootfs/usr/local/etc/atmos/atmos.yaml
+++ b/examples/quick-start/rootfs/usr/local/etc/atmos/atmos.yaml
@@ -19,6 +19,16 @@ base_path: ""
components:
terraform:
+ # Optional `command` specifies the executable to be called by `atmos` when running Terraform commands
+ # If not defined, `terraform` is used
+ # Examples:
+ # command: terraform
+ # command: /usr/local/bin/terraform
+ # command: /usr/local/bin/terraform-1.8
+ # command: tofu
+ # command: /usr/local/bin/tofu-1.7.1
+ # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_COMMAND' ENV var, or '--terraform-command' command-line argument
+ command: terraform
# Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_BASE_PATH' ENV var, or '--terraform-dir' command-line argument
# Supports both absolute and relative paths
base_path: "components/terraform"
diff --git a/examples/quick-start/stacks/orgs/acme/_defaults.yaml b/examples/quick-start/stacks/orgs/acme/_defaults.yaml
index 49e5b3a58..e513fa021 100644
--- a/examples/quick-start/stacks/orgs/acme/_defaults.yaml
+++ b/examples/quick-start/stacks/orgs/acme/_defaults.yaml
@@ -21,6 +21,7 @@ terraform:
atmos_stack: "{{ .atmos_stack }}"
atmos_manifest: "{{ .atmos_stack_file }}"
terraform_workspace: "{{ .workspace }}"
+ terraform_component: "{{ .component }}"
# Examples of using the Sprig and Gomplate functions
# https://masterminds.github.io/sprig/os.html
provisioned_by_user: '{{ env "USER" }}'
diff --git a/examples/tests/atmos.yaml b/examples/tests/atmos.yaml
index 9bc73a1cd..6b26f31cc 100644
--- a/examples/tests/atmos.yaml
+++ b/examples/tests/atmos.yaml
@@ -19,6 +19,16 @@ base_path: "."
components:
terraform:
+ # Optional `command` specifies the executable to be called by `atmos` when running Terraform commands
+ # If not defined, `terraform` is used
+ # Examples:
+ # command: terraform
+ # command: /usr/local/bin/terraform
+ # command: /usr/local/bin/terraform-1.8
+ # command: tofu
+ # command: /usr/local/bin/tofu-1.7.1
+ # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_COMMAND' ENV var, or '--terraform-command' command-line argument
+ command: terraform
# Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_BASE_PATH' ENV var, or '--terraform-dir' command-line argument
# Supports both absolute and relative paths
base_path: "components/terraform"
diff --git a/examples/tests/rootfs/usr/local/etc/atmos/atmos.yaml b/examples/tests/rootfs/usr/local/etc/atmos/atmos.yaml
index e10ebd063..468777a74 100644
--- a/examples/tests/rootfs/usr/local/etc/atmos/atmos.yaml
+++ b/examples/tests/rootfs/usr/local/etc/atmos/atmos.yaml
@@ -19,6 +19,16 @@ base_path: ""
components:
terraform:
+ # Optional `command` specifies the executable to be called by `atmos` when running Terraform commands
+ # If not defined, `terraform` is used
+ # Examples:
+ # command: terraform
+ # command: /usr/local/bin/terraform
+ # command: /usr/local/bin/terraform-1.8
+ # command: tofu
+ # command: /usr/local/bin/tofu-1.7.1
+ # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_COMMAND' ENV var, or '--terraform-command' command-line argument
+ command: terraform
# Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_BASE_PATH' ENV var, or '--terraform-dir' command-line argument
# Supports both absolute and relative paths
base_path: "components/terraform"
diff --git a/examples/tests/stacks/orgs/cp/_defaults.yaml b/examples/tests/stacks/orgs/cp/_defaults.yaml
index 12150f39a..77513a396 100644
--- a/examples/tests/stacks/orgs/cp/_defaults.yaml
+++ b/examples/tests/stacks/orgs/cp/_defaults.yaml
@@ -10,6 +10,7 @@ terraform:
atmos_stack: "{{ .atmos_stack }}"
atmos_manifest: "{{ .atmos_stack_file }}"
terraform_workspace: "{{ .workspace }}"
+ terraform_component: "{{ .component }}"
# Examples of using the Sprig and Gomplate functions
# https://masterminds.github.io/sprig/os.html
provisioned_by_user: '{{ env "USER" }}'
diff --git a/go.mod b/go.mod
index 0b71fb9b6..e85aca66f 100644
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@ require (
github.com/charmbracelet/bubbletea v0.26.2
github.com/charmbracelet/lipgloss v0.10.0
github.com/elewis787/boa v0.1.2
- github.com/fatih/color v1.16.0
+ github.com/fatih/color v1.17.0
github.com/go-git/go-git/v5 v5.12.0
github.com/google/go-containerregistry v0.19.1
github.com/google/go-github/v59 v59.0.0
@@ -20,7 +20,7 @@ require (
github.com/hashicorp/go-getter v1.7.4
github.com/hashicorp/hcl v1.0.0
github.com/hashicorp/hcl/v2 v2.20.1
- github.com/hashicorp/terraform-config-inspect v0.0.0-20240507135902-21dcc2942448
+ github.com/hashicorp/terraform-config-inspect v0.0.0-20240509232506-4708120f8f30
github.com/imdario/mergo v0.3.13
github.com/ivanpirog/coloredcobra v1.0.1
github.com/json-iterator/go v1.1.12
diff --git a/go.sum b/go.sum
index 85725dd98..a97874a78 100644
--- a/go.sum
+++ b/go.sum
@@ -492,8 +492,8 @@ github.com/evanphx/json-patch/v5 v5.5.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2Vvl
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
-github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
-github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
+github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
+github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
@@ -793,8 +793,8 @@ github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR
github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
-github.com/hashicorp/terraform-config-inspect v0.0.0-20240507135902-21dcc2942448 h1:rrpMPsKdq+AO9+dMOQDzTO1IBHYEGcYwF1/roaSDy2Y=
-github.com/hashicorp/terraform-config-inspect v0.0.0-20240507135902-21dcc2942448/go.mod h1:l8HcFPm9cQh6Q0KSWoYPiePqMvRFenybP1CH2MjKdlg=
+github.com/hashicorp/terraform-config-inspect v0.0.0-20240509232506-4708120f8f30 h1:0qwr2oZy9mIIJMWh7W9NTHLWGMbEF5KEQ+QqM9hym34=
+github.com/hashicorp/terraform-config-inspect v0.0.0-20240509232506-4708120f8f30/go.mod h1:Gz/z9Hbn+4KSp8A2FBtNszfLSdT2Tn/uAKGuVqqWmDI=
github.com/hashicorp/vault/api v1.6.0 h1:B8UUYod1y1OoiGHq9GtpiqSnGOUEWHaA26AY8RQEDY4=
github.com/hashicorp/vault/api v1.6.0/go.mod h1:h1K70EO2DgnBaTz5IsL6D5ERsNt5Pce93ueVS2+t0Xc=
github.com/hashicorp/vault/sdk v0.5.0 h1:EED7p0OCU3OY5SAqJwSANofY1YKMytm+jDHDQ2EzGVQ=
diff --git a/internal/exec/atlantis_generate_repo_config.go b/internal/exec/atlantis_generate_repo_config.go
index 90424f7b7..08be730c5 100644
--- a/internal/exec/atlantis_generate_repo_config.go
+++ b/internal/exec/atlantis_generate_repo_config.go
@@ -308,7 +308,7 @@ func ExecuteAtlantisGenerateRepoConfig(
// If 'component' attribute is present, it's the terraform component
// Otherwise, the Atmos component name is the terraform component (by default)
terraformComponent := componentName
- if componentAttribute, ok := componentSection["component"].(string); ok {
+ if componentAttribute, ok := componentSection[cfg.ComponentSectionName].(string); ok {
terraformComponent = componentAttribute
}
diff --git a/internal/exec/describe_affected_utils.go b/internal/exec/describe_affected_utils.go
index c4f8a89f7..d0c2acca9 100644
--- a/internal/exec/describe_affected_utils.go
+++ b/internal/exec/describe_affected_utils.go
@@ -572,7 +572,7 @@ func findAffected(
}
// Check the Terraform configuration of the component
- if component, ok := componentSection["component"].(string); ok && component != "" {
+ if component, ok := componentSection[cfg.ComponentSectionName].(string); ok && component != "" {
// Check if the component uses some external modules (on the local filesystem) that have changed
changed, err := areTerraformComponentModulesChanged(component, cliConfig, changedFiles)
if err != nil {
@@ -805,7 +805,7 @@ func findAffected(
}
// Check the Helmfile configuration of the component
- if component, ok := componentSection["component"].(string); ok && component != "" {
+ if component, ok := componentSection[cfg.ComponentSectionName].(string); ok && component != "" {
// Check if any files in the component's folder have changed
changed, err := isComponentFolderChanged(component, "helmfile", cliConfig, changedFiles)
if err != nil {
diff --git a/internal/exec/describe_stacks.go b/internal/exec/describe_stacks.go
index 32fd30e3a..bdc03a63d 100644
--- a/internal/exec/describe_stacks.go
+++ b/internal/exec/describe_stacks.go
@@ -135,8 +135,8 @@ func ExecuteDescribeStacks(
return nil, fmt.Errorf("invalid 'components.terraform.%s' section in the file '%s'", componentName, stackFileName)
}
- if comp, ok := componentSection["component"].(string); !ok || comp == "" {
- componentSection["component"] = componentName
+ if comp, ok := componentSection[cfg.ComponentSectionName].(string); !ok || comp == "" {
+ componentSection[cfg.ComponentSectionName] = componentName
}
// Find all derived components of the provided components and include them in the output
@@ -200,8 +200,8 @@ func ExecuteDescribeStacks(
},
}
- if comp, ok := configAndStacksInfo.ComponentSection["component"].(string); !ok || comp == "" {
- configAndStacksInfo.ComponentSection["component"] = componentName
+ if comp, ok := configAndStacksInfo.ComponentSection[cfg.ComponentSectionName].(string); !ok || comp == "" {
+ configAndStacksInfo.ComponentSection[cfg.ComponentSectionName] = componentName
}
// Stack name
@@ -309,8 +309,8 @@ func ExecuteDescribeStacks(
return nil, fmt.Errorf("invalid 'components.helmfile.%s' section in the file '%s'", componentName, stackFileName)
}
- if comp, ok := componentSection["component"].(string); !ok || comp == "" {
- componentSection["component"] = componentName
+ if comp, ok := componentSection[cfg.ComponentSectionName].(string); !ok || comp == "" {
+ componentSection[cfg.ComponentSectionName] = componentName
}
// Find all derived components of the provided components and include them in the output
@@ -374,8 +374,8 @@ func ExecuteDescribeStacks(
},
}
- if comp, ok := configAndStacksInfo.ComponentSection["component"].(string); !ok || comp == "" {
- configAndStacksInfo.ComponentSection["component"] = componentName
+ if comp, ok := configAndStacksInfo.ComponentSection[cfg.ComponentSectionName].(string); !ok || comp == "" {
+ configAndStacksInfo.ComponentSection[cfg.ComponentSectionName] = componentName
}
// Stack name
diff --git a/internal/exec/stack_utils.go b/internal/exec/stack_utils.go
index 8b557e3f8..7cc48aa17 100644
--- a/internal/exec/stack_utils.go
+++ b/internal/exec/stack_utils.go
@@ -64,7 +64,7 @@ func ProcessComponentMetadata(
var componentMetadata map[any]any
// Find base component in the `component` attribute
- if base, ok := componentSection["component"].(string); ok {
+ if base, ok := componentSection[cfg.ComponentSectionName].(string); ok {
baseComponentName = base
}
@@ -77,7 +77,7 @@ func ProcessComponentMetadata(
}
// Find base component in the `metadata.component` attribute
// `metadata.component` overrides `component`
- if componentMetadataComponent, componentMetadataComponentExists := componentMetadata["component"].(string); componentMetadataComponentExists {
+ if componentMetadataComponent, componentMetadataComponentExists := componentMetadata[cfg.ComponentSectionName].(string); componentMetadataComponentExists {
baseComponentName = componentMetadataComponent
}
}
@@ -155,7 +155,7 @@ func BuildComponentPath(
var componentPath string
- if stackComponentSection, ok := componentSectionMap["component"].(string); ok {
+ if stackComponentSection, ok := componentSectionMap[cfg.ComponentSectionName].(string); ok {
if componentType == "terraform" {
componentPath = path.Join(cliConfig.BasePath, cliConfig.Components.Terraform.BasePath, stackComponentSection)
} else if componentType == "helmfile" {
diff --git a/internal/exec/terraform_generate_backends.go b/internal/exec/terraform_generate_backends.go
index 464ae1c0b..755975b5e 100644
--- a/internal/exec/terraform_generate_backends.go
+++ b/internal/exec/terraform_generate_backends.go
@@ -123,7 +123,7 @@ func ExecuteTerraformGenerateBackends(cliConfig schema.CliConfiguration, fileTem
// If `component` attribute is present, it's the terraform component.
// Otherwise, the YAML component name is the terraform component.
terraformComponent := componentName
- if componentAttribute, ok := componentSection["component"].(string); ok {
+ if componentAttribute, ok := componentSection[cfg.ComponentSectionName].(string); ok {
terraformComponent = componentAttribute
}
diff --git a/internal/exec/terraform_generate_varfiles.go b/internal/exec/terraform_generate_varfiles.go
index fdfc47ce2..9135f98ea 100644
--- a/internal/exec/terraform_generate_varfiles.go
+++ b/internal/exec/terraform_generate_varfiles.go
@@ -114,7 +114,7 @@ func ExecuteTerraformGenerateVarfiles(cliConfig schema.CliConfiguration, fileTem
// If `component` attribute is present, it's the terraform component.
// Otherwise, the YAML component name is the terraform component.
terraformComponent := componentName
- if componentAttribute, ok := componentSection["component"].(string); ok {
+ if componentAttribute, ok := componentSection[cfg.ComponentSectionName].(string); ok {
terraformComponent = componentAttribute
}
diff --git a/internal/exec/utils.go b/internal/exec/utils.go
index 57eace027..8fbf86e42 100644
--- a/internal/exec/utils.go
+++ b/internal/exec/utils.go
@@ -26,7 +26,9 @@ var (
cfg.DryRunFlag,
cfg.SkipInitFlag,
cfg.KubeConfigConfigFlag,
+ cfg.TerraformCommandFlag,
cfg.TerraformDirFlag,
+ cfg.HelmfileCommandFlag,
cfg.HelmfileDirFlag,
cfg.CliConfigDirFlag,
cfg.StackDirFlag,
@@ -111,7 +113,7 @@ func ProcessComponentConfig(
if componentImportsSection, ok = stackSection["imports"].([]string); !ok {
componentImportsSection = nil
}
- if command, ok = componentSection["command"].(string); !ok {
+ if command, ok = componentSection[cfg.CommandSectionName].(string); !ok {
command = ""
}
if componentEnvSection, ok = componentSection[cfg.EnvSectionName].(map[any]any); !ok {
@@ -192,7 +194,9 @@ func processCommandLineArgs(
configAndStacksInfo.ComponentFromArg = argsAndFlagsInfo.ComponentFromArg
configAndStacksInfo.GlobalOptions = argsAndFlagsInfo.GlobalOptions
configAndStacksInfo.BasePath = argsAndFlagsInfo.BasePath
+ configAndStacksInfo.TerraformCommand = argsAndFlagsInfo.TerraformCommand
configAndStacksInfo.TerraformDir = argsAndFlagsInfo.TerraformDir
+ configAndStacksInfo.HelmfileCommand = argsAndFlagsInfo.HelmfileCommand
configAndStacksInfo.HelmfileDir = argsAndFlagsInfo.HelmfileDir
configAndStacksInfo.StacksDir = argsAndFlagsInfo.StacksDir
configAndStacksInfo.ConfigDir = argsAndFlagsInfo.ConfigDir
@@ -415,54 +419,6 @@ func ProcessStacks(
}
}
- if len(configAndStacksInfo.Command) == 0 {
- configAndStacksInfo.Command = configAndStacksInfo.ComponentType
- }
-
- // Process component path and name
- configAndStacksInfo.ComponentFolderPrefix = ""
- componentPathParts := strings.Split(configAndStacksInfo.ComponentFromArg, "/")
- componentPathPartsLength := len(componentPathParts)
- if componentPathPartsLength > 1 {
- componentFromArgPartsWithoutLast := componentPathParts[:componentPathPartsLength-1]
- configAndStacksInfo.ComponentFolderPrefix = strings.Join(componentFromArgPartsWithoutLast, "/")
- configAndStacksInfo.Component = componentPathParts[componentPathPartsLength-1]
- } else {
- configAndStacksInfo.Component = configAndStacksInfo.ComponentFromArg
- }
- configAndStacksInfo.ComponentFolderPrefixReplaced = strings.Replace(configAndStacksInfo.ComponentFolderPrefix, "/", "-", -1)
-
- // Process base component path and name
- if len(configAndStacksInfo.BaseComponentPath) > 0 {
- baseComponentPathParts := strings.Split(configAndStacksInfo.BaseComponentPath, "/")
- baseComponentPathPartsLength := len(baseComponentPathParts)
- if baseComponentPathPartsLength > 1 {
- baseComponentPartsWithoutLast := baseComponentPathParts[:baseComponentPathPartsLength-1]
- configAndStacksInfo.ComponentFolderPrefix = strings.Join(baseComponentPartsWithoutLast, "/")
- configAndStacksInfo.BaseComponent = baseComponentPathParts[baseComponentPathPartsLength-1]
- } else {
- configAndStacksInfo.ComponentFolderPrefix = ""
- configAndStacksInfo.BaseComponent = configAndStacksInfo.BaseComponentPath
- }
- configAndStacksInfo.ComponentFolderPrefixReplaced = strings.Replace(configAndStacksInfo.ComponentFolderPrefix, "/", "-", -1)
- }
-
- // Get the final component
- if len(configAndStacksInfo.BaseComponent) > 0 {
- configAndStacksInfo.FinalComponent = configAndStacksInfo.BaseComponent
- } else {
- configAndStacksInfo.FinalComponent = configAndStacksInfo.Component
- }
-
- // Terraform workspace
- workspace, err := BuildTerraformWorkspace(cliConfig, configAndStacksInfo)
- if err != nil {
- return configAndStacksInfo, err
- }
-
- configAndStacksInfo.TerraformWorkspace = workspace
- configAndStacksInfo.ComponentSection["workspace"] = workspace
-
// Add imports
configAndStacksInfo.ComponentSection["imports"] = configAndStacksInfo.ComponentImportsSection
@@ -480,26 +436,8 @@ func ProcessStacks(
configAndStacksInfo.ComponentSection["atmos_cli_config"] = atmosCliConfig
// If the command-line component does not inherit anything, then the Terraform/Helmfile component is the same as the provided one
- if comp, ok := configAndStacksInfo.ComponentSection["component"].(string); !ok || comp == "" {
- configAndStacksInfo.ComponentSection["component"] = configAndStacksInfo.ComponentFromArg
- }
-
- // Spacelift stack
- spaceliftStackName, err := BuildSpaceliftStackNameFromComponentConfig(cliConfig, configAndStacksInfo)
- if err != nil {
- return configAndStacksInfo, err
- }
- if spaceliftStackName != "" {
- configAndStacksInfo.ComponentSection["spacelift_stack"] = spaceliftStackName
- }
-
- // Atlantis project
- atlantisProjectName, err := BuildAtlantisProjectNameFromComponentConfig(cliConfig, configAndStacksInfo)
- if err != nil {
- return configAndStacksInfo, err
- }
- if atlantisProjectName != "" {
- configAndStacksInfo.ComponentSection["atlantis_project"] = atlantisProjectName
+ if comp, ok := configAndStacksInfo.ComponentSection[cfg.ComponentSectionName].(string); !ok || comp == "" {
+ configAndStacksInfo.ComponentSection[cfg.ComponentSectionName] = configAndStacksInfo.ComponentFromArg
}
// Add component info, including Terraform config
@@ -533,6 +471,15 @@ func ProcessStacks(
configAndStacksInfo.ComponentSection["deps"] = componentDeps
configAndStacksInfo.ComponentSection["deps_all"] = componentDepsAll
+ // Terraform workspace
+ workspace, err := BuildTerraformWorkspace(cliConfig, configAndStacksInfo)
+ if err != nil {
+ return configAndStacksInfo, err
+ }
+
+ configAndStacksInfo.TerraformWorkspace = workspace
+ configAndStacksInfo.ComponentSection["workspace"] = workspace
+
// Process `Go` templates in Atmos manifest sections
componentSectionStr, err := u.ConvertToYAML(configAndStacksInfo.ComponentSection)
if err != nil {
@@ -599,9 +546,75 @@ func ProcessStacks(
configAndStacksInfo.ComponentBackendType = i
}
+ if i, ok := configAndStacksInfo.ComponentSection[cfg.ComponentSectionName].(string); ok {
+ configAndStacksInfo.Component = i
+ }
+
+ // Spacelift stack
+ spaceliftStackName, err := BuildSpaceliftStackNameFromComponentConfig(cliConfig, configAndStacksInfo)
+ if err != nil {
+ return configAndStacksInfo, err
+ }
+ if spaceliftStackName != "" {
+ configAndStacksInfo.ComponentSection["spacelift_stack"] = spaceliftStackName
+ }
+
+ // Atlantis project
+ atlantisProjectName, err := BuildAtlantisProjectNameFromComponentConfig(cliConfig, configAndStacksInfo)
+ if err != nil {
+ return configAndStacksInfo, err
+ }
+ if atlantisProjectName != "" {
+ configAndStacksInfo.ComponentSection["atlantis_project"] = atlantisProjectName
+ }
+
+ // Process `command`
+ //if len(configAndStacksInfo.Command) == 0 {
+ // configAndStacksInfo.Command = configAndStacksInfo.ComponentType
+ //}
+
// Process the ENV variables from the `env` section
configAndStacksInfo.ComponentEnvList = u.ConvertEnvVars(configAndStacksInfo.ComponentEnvSection)
+ // Process component metadata
+ _, baseComponentName, _ := ProcessComponentMetadata(configAndStacksInfo.ComponentFromArg, configAndStacksInfo.ComponentSection)
+ configAndStacksInfo.BaseComponentPath = baseComponentName
+
+ // Process component path and name
+ configAndStacksInfo.ComponentFolderPrefix = ""
+ componentPathParts := strings.Split(configAndStacksInfo.ComponentFromArg, "/")
+ componentPathPartsLength := len(componentPathParts)
+ if componentPathPartsLength > 1 {
+ componentFromArgPartsWithoutLast := componentPathParts[:componentPathPartsLength-1]
+ configAndStacksInfo.ComponentFolderPrefix = strings.Join(componentFromArgPartsWithoutLast, "/")
+ configAndStacksInfo.Component = componentPathParts[componentPathPartsLength-1]
+ } else {
+ configAndStacksInfo.Component = configAndStacksInfo.ComponentFromArg
+ }
+ configAndStacksInfo.ComponentFolderPrefixReplaced = strings.Replace(configAndStacksInfo.ComponentFolderPrefix, "/", "-", -1)
+
+ // Process base component path and name
+ if len(configAndStacksInfo.BaseComponentPath) > 0 {
+ baseComponentPathParts := strings.Split(configAndStacksInfo.BaseComponentPath, "/")
+ baseComponentPathPartsLength := len(baseComponentPathParts)
+ if baseComponentPathPartsLength > 1 {
+ baseComponentPartsWithoutLast := baseComponentPathParts[:baseComponentPathPartsLength-1]
+ configAndStacksInfo.ComponentFolderPrefix = strings.Join(baseComponentPartsWithoutLast, "/")
+ configAndStacksInfo.BaseComponent = baseComponentPathParts[baseComponentPathPartsLength-1]
+ } else {
+ configAndStacksInfo.ComponentFolderPrefix = ""
+ configAndStacksInfo.BaseComponent = configAndStacksInfo.BaseComponentPath
+ }
+ configAndStacksInfo.ComponentFolderPrefixReplaced = strings.Replace(configAndStacksInfo.ComponentFolderPrefix, "/", "-", -1)
+ }
+
+ // Get the final component
+ if len(configAndStacksInfo.BaseComponent) > 0 {
+ configAndStacksInfo.FinalComponent = configAndStacksInfo.BaseComponent
+ } else {
+ configAndStacksInfo.FinalComponent = configAndStacksInfo.Component
+ }
+
return configAndStacksInfo, nil
}
@@ -623,6 +636,19 @@ func processArgsAndFlags(componentType string, inputArgsAndFlags []string) (sche
globalOptionsFlagIndex = i
}
+ if arg == cfg.TerraformCommandFlag {
+ if len(inputArgsAndFlags) <= (i + 1) {
+ return info, fmt.Errorf("invalid flag: %s", arg)
+ }
+ info.TerraformCommand = inputArgsAndFlags[i+1]
+ } else if strings.HasPrefix(arg+"=", cfg.TerraformCommandFlag) {
+ var terraformCommandFlagParts = strings.Split(arg, "=")
+ if len(terraformCommandFlagParts) != 2 {
+ return info, fmt.Errorf("invalid flag: %s", arg)
+ }
+ info.TerraformCommand = terraformCommandFlagParts[1]
+ }
+
if arg == cfg.TerraformDirFlag {
if len(inputArgsAndFlags) <= (i + 1) {
return info, fmt.Errorf("invalid flag: %s", arg)
@@ -636,6 +662,19 @@ func processArgsAndFlags(componentType string, inputArgsAndFlags []string) (sche
info.TerraformDir = terraformDirFlagParts[1]
}
+ if arg == cfg.HelmfileCommandFlag {
+ if len(inputArgsAndFlags) <= (i + 1) {
+ return info, fmt.Errorf("invalid flag: %s", arg)
+ }
+ info.HelmfileCommand = inputArgsAndFlags[i+1]
+ } else if strings.HasPrefix(arg+"=", cfg.HelmfileCommandFlag) {
+ var helmfileCommandFlagParts = strings.Split(arg, "=")
+ if len(helmfileCommandFlagParts) != 2 {
+ return info, fmt.Errorf("invalid flag: %s", arg)
+ }
+ info.HelmfileCommand = helmfileCommandFlagParts[1]
+ }
+
if arg == cfg.HelmfileDirFlag {
if len(inputArgsAndFlags) <= (i + 1) {
return info, fmt.Errorf("invalid flag: %s", arg)
diff --git a/internal/exec/validate_stacks.go b/internal/exec/validate_stacks.go
index 61447204f..262ce06f3 100644
--- a/internal/exec/validate_stacks.go
+++ b/internal/exec/validate_stacks.go
@@ -97,6 +97,7 @@ func ExecuteValidateStacksCmd(cmd *cobra.Command, args []string) error {
// Process and validate the stack manifest
componentStackMap := map[string]map[string][]string{}
_, err = s.ProcessStackConfig(
+ cliConfig,
cliConfig.StacksBaseAbsolutePath,
cliConfig.TerraformDirAbsolutePath,
cliConfig.HelmfileDirAbsolutePath,
diff --git a/pkg/config/const.go b/pkg/config/const.go
index 084330bc3..c2eaae2aa 100644
--- a/pkg/config/const.go
+++ b/pkg/config/const.go
@@ -11,7 +11,9 @@ const (
// https://github.com/roboll/helmfile#cli-reference
GlobalOptionsFlag = "--global-options"
+ TerraformCommandFlag = "--terraform-command"
TerraformDirFlag = "--terraform-dir"
+ HelmfileCommandFlag = "--helmfile-command"
HelmfileDirFlag = "--helmfile-dir"
CliConfigDirFlag = "--config-dir"
StackDirFlag = "--stacks-dir"
@@ -48,6 +50,8 @@ const (
BackendSectionName = "backend"
BackendTypeSectionName = "backend_type"
MetadataSectionName = "metadata"
+ ComponentSectionName = "component"
+ CommandSectionName = "command"
LogsLevelFlag = "--logs-level"
LogsFileFlag = "--logs-file"
diff --git a/pkg/config/utils.go b/pkg/config/utils.go
index db9daa142..3070e6919 100644
--- a/pkg/config/utils.go
+++ b/pkg/config/utils.go
@@ -186,6 +186,12 @@ func processEnvVars(cliConfig *schema.CliConfiguration) error {
cliConfig.Stacks.NameTemplate = stacksNameTemplate
}
+ componentsTerraformCommand := os.Getenv("ATMOS_COMPONENTS_TERRAFORM_COMMAND")
+ if len(componentsTerraformCommand) > 0 {
+ u.LogTrace(*cliConfig, fmt.Sprintf("Found ENV var ATMOS_COMPONENTS_TERRAFORM_COMMAND=%s", componentsTerraformCommand))
+ cliConfig.Components.Terraform.Command = componentsTerraformCommand
+ }
+
componentsTerraformBasePath := os.Getenv("ATMOS_COMPONENTS_TERRAFORM_BASE_PATH")
if len(componentsTerraformBasePath) > 0 {
u.LogTrace(*cliConfig, fmt.Sprintf("Found ENV var ATMOS_COMPONENTS_TERRAFORM_BASE_PATH=%s", componentsTerraformBasePath))
@@ -232,6 +238,12 @@ func processEnvVars(cliConfig *schema.CliConfiguration) error {
cliConfig.Components.Terraform.AutoGenerateBackendFile = componentsTerraformAutoGenerateBackendFileBool
}
+ componentsHelmfileCommand := os.Getenv("ATMOS_COMPONENTS_HELMFILE_COMMAND")
+ if len(componentsHelmfileCommand) > 0 {
+ u.LogTrace(*cliConfig, fmt.Sprintf("Found ENV var ATMOS_COMPONENTS_HELMFILE_COMMAND=%s", componentsHelmfileCommand))
+ cliConfig.Components.Helmfile.Command = componentsHelmfileCommand
+ }
+
componentsHelmfileBasePath := os.Getenv("ATMOS_COMPONENTS_HELMFILE_BASE_PATH")
if len(componentsHelmfileBasePath) > 0 {
u.LogTrace(*cliConfig, fmt.Sprintf("Found ENV var ATMOS_COMPONENTS_HELMFILE_BASE_PATH=%s", componentsHelmfileBasePath))
@@ -328,10 +340,18 @@ func processCommandLineArgs(cliConfig *schema.CliConfiguration, configAndStacksI
cliConfig.BasePath = configAndStacksInfo.BasePath
u.LogTrace(*cliConfig, fmt.Sprintf("Using command line argument '%s' as base path for stacks and components", configAndStacksInfo.BasePath))
}
+ if len(configAndStacksInfo.TerraformCommand) > 0 {
+ cliConfig.Components.Terraform.Command = configAndStacksInfo.TerraformCommand
+ u.LogTrace(*cliConfig, fmt.Sprintf("Using command line argument '%s' as terraform executable", configAndStacksInfo.TerraformCommand))
+ }
if len(configAndStacksInfo.TerraformDir) > 0 {
cliConfig.Components.Terraform.BasePath = configAndStacksInfo.TerraformDir
u.LogTrace(*cliConfig, fmt.Sprintf("Using command line argument '%s' as terraform directory", configAndStacksInfo.TerraformDir))
}
+ if len(configAndStacksInfo.HelmfileCommand) > 0 {
+ cliConfig.Components.Helmfile.Command = configAndStacksInfo.HelmfileCommand
+ u.LogTrace(*cliConfig, fmt.Sprintf("Using command line argument '%s' as helmfile executable", configAndStacksInfo.HelmfileCommand))
+ }
if len(configAndStacksInfo.HelmfileDir) > 0 {
cliConfig.Components.Helmfile.BasePath = configAndStacksInfo.HelmfileDir
u.LogTrace(*cliConfig, fmt.Sprintf("Using command line argument '%s' as helmfile directory", configAndStacksInfo.HelmfileDir))
diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go
index af38277e4..63cfbc2be 100644
--- a/pkg/schema/schema.go
+++ b/pkg/schema/schema.go
@@ -57,6 +57,7 @@ type Terraform struct {
DeployRunInit bool `yaml:"deploy_run_init" json:"deploy_run_init" mapstructure:"deploy_run_init"`
InitRunReconfigure bool `yaml:"init_run_reconfigure" json:"init_run_reconfigure" mapstructure:"init_run_reconfigure"`
AutoGenerateBackendFile bool `yaml:"auto_generate_backend_file" json:"auto_generate_backend_file" mapstructure:"auto_generate_backend_file"`
+ Command string `yaml:"command" json:"command" mapstructure:"command"`
}
type Helmfile struct {
@@ -65,6 +66,7 @@ type Helmfile struct {
KubeconfigPath string `yaml:"kubeconfig_path" json:"kubeconfig_path" mapstructure:"kubeconfig_path"`
HelmAwsProfilePattern string `yaml:"helm_aws_profile_pattern" json:"helm_aws_profile_pattern" mapstructure:"helm_aws_profile_pattern"`
ClusterNamePattern string `yaml:"cluster_name_pattern" json:"cluster_name_pattern" mapstructure:"cluster_name_pattern"`
+ Command string `yaml:"command" json:"command" mapstructure:"command"`
}
type Components struct {
@@ -111,7 +113,9 @@ type ArgsAndFlagsInfo struct {
SubCommand2 string
ComponentFromArg string
GlobalOptions []string
+ TerraformCommand string
TerraformDir string
+ HelmfileCommand string
HelmfileDir string
ConfigDir string
StacksDir string
@@ -161,7 +165,9 @@ type ConfigAndStacksInfo struct {
AdditionalArgsAndFlags []string
GlobalOptions []string
BasePath string
+ TerraformCommand string
TerraformDir string
+ HelmfileCommand string
HelmfileDir string
ConfigDir string
StacksDir string
diff --git a/pkg/spacelift/spacelift_stack_processor.go b/pkg/spacelift/spacelift_stack_processor.go
index 6c6ddb6a4..a7e2c8351 100644
--- a/pkg/spacelift/spacelift_stack_processor.go
+++ b/pkg/spacelift/spacelift_stack_processor.go
@@ -194,16 +194,16 @@ func TransformStackConfigToSpaceliftStacks(
contextPrefix = strings.Replace(stackName, "/", "-", -1)
}
- spaceliftConfig["component"] = component
+ spaceliftConfig[cfg.ComponentSectionName] = component
spaceliftConfig["stack"] = contextPrefix
spaceliftConfig["imports"] = imports
- spaceliftConfig["vars"] = componentVars
- spaceliftConfig["settings"] = componentSettings
- spaceliftConfig["env"] = componentEnv
+ spaceliftConfig[cfg.VarsSectionName] = componentVars
+ spaceliftConfig[cfg.SettingsSectionName] = componentSettings
+ spaceliftConfig[cfg.EnvSectionName] = componentEnv
spaceliftConfig["stacks"] = componentStacks
spaceliftConfig["inheritance"] = componentInheritance
spaceliftConfig["base_component"] = baseComponentName
- spaceliftConfig["metadata"] = componentMetadata
+ spaceliftConfig[cfg.MetadataSectionName] = componentMetadata
// backend
backendTypeName := ""
diff --git a/pkg/stack/stack_processor.go b/pkg/stack/stack_processor.go
index d61795f28..dac382780 100644
--- a/pkg/stack/stack_processor.go
+++ b/pkg/stack/stack_processor.go
@@ -98,6 +98,7 @@ func ProcessYAMLConfigFiles(
componentStackMap := map[string]map[string][]string{}
finalConfig, err := ProcessStackConfig(
+ cliConfig,
stackBasePath,
terraformComponentsBasePath,
helmfileComponentsBasePath,
@@ -462,6 +463,7 @@ func ProcessYAMLConfigFile(
// ProcessStackConfig takes a stack manifest, deep-merges all variables, settings, environments and backends,
// and returns the final stack configuration for all Terraform and helmfile components
func ProcessStackConfig(
+ cliConfig schema.CliConfiguration,
stacksBasePath string,
terraformComponentsBasePath string,
helmfileComponentsBasePath string,
@@ -548,7 +550,7 @@ func ProcessStackConfig(
}
// Terraform section
- if i, ok := globalTerraformSection["command"]; ok {
+ if i, ok := globalTerraformSection[cfg.CommandSectionName]; ok {
terraformCommand, ok = i.(string)
if !ok {
return nil, fmt.Errorf("invalid 'terraform.command' section in the file '%s'", stackName)
@@ -635,7 +637,7 @@ func ProcessStackConfig(
}
// Helmfile section
- if i, ok := globalHelmfileSection["command"]; ok {
+ if i, ok := globalHelmfileSection[cfg.CommandSectionName]; ok {
helmfileCommand, ok = i.(string)
if !ok {
return nil, fmt.Errorf("invalid 'helmfile.command' section in the file '%s'", stackName)
@@ -781,7 +783,7 @@ func ProcessStackConfig(
}
componentTerraformCommand := ""
- if i, ok := componentMap["command"]; ok {
+ if i, ok := componentMap[cfg.CommandSectionName]; ok {
componentTerraformCommand, ok = i.(string)
if !ok {
return nil, fmt.Errorf("invalid 'components.terraform.%s.command' attribute in the file '%s'", component, stackName)
@@ -819,7 +821,7 @@ func ProcessStackConfig(
}
}
- if i, ok = componentOverrides["command"]; ok {
+ if i, ok = componentOverrides[cfg.CommandSectionName]; ok {
if componentOverridesTerraformCommand, ok = i.(string); !ok {
return nil, fmt.Errorf("invalid 'components.terraform.%s.overrides.command' in the manifest '%s'", component, stackName)
}
@@ -848,7 +850,7 @@ func ProcessStackConfig(
var baseComponents []string
// Inheritance using the top-level `component` attribute
- if baseComponent, baseComponentExist := componentMap["component"]; baseComponentExist {
+ if baseComponent, baseComponentExist := componentMap[cfg.ComponentSectionName]; baseComponentExist {
baseComponentName, ok = baseComponent.(string)
if !ok {
return nil, fmt.Errorf("invalid 'components.terraform.%s.component' attribute in the file '%s'", component, stackName)
@@ -893,7 +895,7 @@ func ProcessStackConfig(
// will deep-merge all the base components of `componentA` (each component overriding its base),
// then all the base components of `componentB` (each component overriding its base),
// then the two results are deep-merged together (`componentB` inheritance chain will override values from 'componentA' inheritance chain).
- if baseComponentFromMetadata, baseComponentFromMetadataExist := componentMetadata["component"]; baseComponentFromMetadataExist {
+ if baseComponentFromMetadata, baseComponentFromMetadataExist := componentMetadata[cfg.ComponentSectionName]; baseComponentFromMetadataExist {
baseComponentName, ok = baseComponentFromMetadata.(string)
if !ok {
return nil, fmt.Errorf("invalid 'components.terraform.%s.metadata.component' attribute in the file '%s'", component, stackName)
@@ -1111,17 +1113,26 @@ func ProcessStackConfig(
}
// Final binary to execute
+ // Check for the binary in the following order:
+ // - `components.terraform.command` section in `atmos.yaml` CLI config file
+ // - global `terraform.command` section
+ // - base component(s) `command` section
+ // - component `command` section
+ // - `overrides.command` section
finalComponentTerraformCommand := "terraform"
- if len(terraformCommand) > 0 {
+ if cliConfig.Components.Terraform.Command != "" {
+ finalComponentTerraformCommand = cliConfig.Components.Terraform.Command
+ }
+ if terraformCommand != "" {
finalComponentTerraformCommand = terraformCommand
}
- if len(baseComponentTerraformCommand) > 0 {
+ if baseComponentTerraformCommand != "" {
finalComponentTerraformCommand = baseComponentTerraformCommand
}
- if len(componentTerraformCommand) > 0 {
+ if componentTerraformCommand != "" {
finalComponentTerraformCommand = componentTerraformCommand
}
- if len(componentOverridesTerraformCommand) > 0 {
+ if componentOverridesTerraformCommand != "" {
finalComponentTerraformCommand = componentOverridesTerraformCommand
}
@@ -1155,14 +1166,14 @@ func ProcessStackConfig(
comp[cfg.BackendSectionName] = finalComponentBackend
comp["remote_state_backend_type"] = finalComponentRemoteStateBackendType
comp["remote_state_backend"] = finalComponentRemoteStateBackend
- comp["command"] = finalComponentTerraformCommand
+ comp[cfg.CommandSectionName] = finalComponentTerraformCommand
comp["inheritance"] = componentInheritanceChain
comp[cfg.MetadataSectionName] = componentMetadata
comp[cfg.OverridesSectionName] = componentOverrides
comp[cfg.ProvidersSectionName] = finalComponentProviders
if baseComponentName != "" {
- comp["component"] = baseComponentName
+ comp[cfg.ComponentSectionName] = baseComponentName
}
terraformComponents[component] = comp
@@ -1222,7 +1233,7 @@ func ProcessStackConfig(
}
componentHelmfileCommand := ""
- if i, ok := componentMap["command"]; ok {
+ if i, ok := componentMap[cfg.CommandSectionName]; ok {
componentHelmfileCommand, ok = i.(string)
if !ok {
return nil, fmt.Errorf("invalid 'components.helmfile.%s.command' attribute in the file '%s'", component, stackName)
@@ -1259,7 +1270,7 @@ func ProcessStackConfig(
}
}
- if i, ok = componentOverrides["command"]; ok {
+ if i, ok = componentOverrides[cfg.CommandSectionName]; ok {
if componentOverridesHelmfileCommand, ok = i.(string); !ok {
return nil, fmt.Errorf("invalid 'components.helmfile.%s.overrides.command' in the manifest '%s'", component, stackName)
}
@@ -1277,7 +1288,7 @@ func ProcessStackConfig(
var baseComponents []string
// Inheritance using the top-level `component` attribute
- if baseComponent, baseComponentExist := componentMap["component"]; baseComponentExist {
+ if baseComponent, baseComponentExist := componentMap[cfg.ComponentSectionName]; baseComponentExist {
baseComponentName, ok = baseComponent.(string)
if !ok {
return nil, fmt.Errorf("invalid 'components.helmfile.%s.component' attribute in the file '%s'", component, stackName)
@@ -1317,7 +1328,7 @@ func ProcessStackConfig(
// will deep-merge all the base components of `componentA` (each component overriding its base),
// then all the base components of `componentB` (each component overriding its base),
// then the two results are deep-merged together (`componentB` inheritance chain will override values from 'componentA' inheritance chain).
- if baseComponentFromMetadata, baseComponentFromMetadataExist := componentMetadata["component"]; baseComponentFromMetadataExist {
+ if baseComponentFromMetadata, baseComponentFromMetadataExist := componentMetadata[cfg.ComponentSectionName]; baseComponentFromMetadataExist {
baseComponentName, ok = baseComponentFromMetadata.(string)
if !ok {
return nil, fmt.Errorf("invalid 'components.helmfile.%s.metadata.component' attribute in the file '%s'", component, stackName)
@@ -1404,17 +1415,26 @@ func ProcessStackConfig(
}
// Final binary to execute
+ // Check for the binary in the following order:
+ // - `components.helmfile.command` section in `atmos.yaml` CLI config file
+ // - global `helmfile.command` section
+ // - base component(s) `command` section
+ // - component `command` section
+ // - `overrides.command` section
finalComponentHelmfileCommand := "helmfile"
- if len(helmfileCommand) > 0 {
+ if cliConfig.Components.Helmfile.Command != "" {
+ finalComponentHelmfileCommand = cliConfig.Components.Helmfile.Command
+ }
+ if helmfileCommand != "" {
finalComponentHelmfileCommand = helmfileCommand
}
- if len(baseComponentHelmfileCommand) > 0 {
+ if baseComponentHelmfileCommand != "" {
finalComponentHelmfileCommand = baseComponentHelmfileCommand
}
- if len(componentHelmfileCommand) > 0 {
+ if componentHelmfileCommand != "" {
finalComponentHelmfileCommand = componentHelmfileCommand
}
- if len(componentOverridesHelmfileCommand) > 0 {
+ if componentOverridesHelmfileCommand != "" {
finalComponentHelmfileCommand = componentOverridesHelmfileCommand
}
@@ -1422,13 +1442,13 @@ func ProcessStackConfig(
comp[cfg.VarsSectionName] = finalComponentVars
comp[cfg.SettingsSectionName] = finalComponentSettings
comp[cfg.EnvSectionName] = finalComponentEnv
- comp["command"] = finalComponentHelmfileCommand
+ comp[cfg.CommandSectionName] = finalComponentHelmfileCommand
comp["inheritance"] = componentInheritanceChain
comp[cfg.MetadataSectionName] = componentMetadata
comp[cfg.OverridesSectionName] = componentOverrides
if baseComponentName != "" {
- comp["component"] = baseComponentName
+ comp[cfg.ComponentSectionName] = baseComponentName
}
helmfileComponents[component] = comp
diff --git a/pkg/stack/stack_processor_utils.go b/pkg/stack/stack_processor_utils.go
index 2660eafe2..7d077eb51 100644
--- a/pkg/stack/stack_processor_utils.go
+++ b/pkg/stack/stack_processor_utils.go
@@ -285,6 +285,7 @@ func CreateComponentStackMap(
}
finalConfig, err := ProcessStackConfig(
+ cliConfig,
stacksBasePath,
terraformComponentsBasePath,
helmfileComponentsBasePath,
@@ -533,7 +534,7 @@ func ProcessBaseComponentConfig(
}
// Base component `command`
- if baseComponentCommandSection, baseComponentCommandSectionExist := baseComponentMap["command"]; baseComponentCommandSectionExist {
+ if baseComponentCommandSection, baseComponentCommandSectionExist := baseComponentMap[cfg.CommandSectionName]; baseComponentCommandSectionExist {
baseComponentCommand, ok = baseComponentCommandSection.(string)
if !ok {
return fmt.Errorf("invalid '%s.command' section in the stack '%s'", baseComponent, stack)
@@ -627,7 +628,7 @@ func FindComponentsDerivedFromBaseComponents(
return nil, fmt.Errorf("invalid '%s' component section in the file '%s'", component, stack)
}
- if base, baseComponentExist := componentSection["component"]; baseComponentExist {
+ if base, baseComponentExist := componentSection[cfg.ComponentSectionName]; baseComponentExist {
baseComponent, ok := base.(string)
if !ok {
return nil, fmt.Errorf("invalid 'component' attribute in the component '%s' in the file '%s'", component, stack)
diff --git a/website/docs/cli/commands/terraform/terraform-generate-varfiles.mdx b/website/docs/cli/commands/terraform/terraform-generate-varfiles.mdx
index 0a35785fe..37a8c5b4d 100644
--- a/website/docs/cli/commands/terraform/terraform-generate-varfiles.mdx
+++ b/website/docs/cli/commands/terraform/terraform-generate-varfiles.mdx
@@ -5,6 +5,7 @@ sidebar_class_name: command
id: generate-varfiles
description: Use this command to generate the Terraform varfiles (`.tfvar`) for all Atmos terraform components in all stacks.
---
+
import Screengrab from '@site/src/components/Screengrab'
:::note Purpose
diff --git a/website/docs/cli/configuration.mdx b/website/docs/cli/configuration.mdx
index f7385700c..f5bd6a188 100644
--- a/website/docs/cli/configuration.mdx
+++ b/website/docs/cli/configuration.mdx
@@ -130,6 +130,17 @@ Specify the default behaviors for components.
```yaml title="atmos.yaml"
components:
terraform:
+ # Optional `command` specifies the executable to be called by `atmos` when running Terraform commands
+ # If not defined, `terraform` is used
+ # Examples:
+ # command: terraform
+ # command: /usr/local/bin/terraform
+ # command: /usr/local/bin/terraform-1.8
+ # command: tofu
+ # command: /usr/local/bin/tofu-1.7.1
+ # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_COMMAND' ENV var, or '--terraform-command' command-line argument
+ command: terraform
+
# Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_BASE_PATH' ENV var, or '--terraform-dir' command-line argument
# Supports both absolute and relative paths
base_path: "components/terraform"
@@ -147,6 +158,14 @@ components:
auto_generate_backend_file: true
helmfile:
+ # Optional `command` specifies the executable to be called by `atmos` when running Helmfile commands
+ # If not defined, `helmfile` is used
+ # Examples:
+ # command: helmfile
+ # command: /usr/local/bin/helmfile
+ # Can also be set using 'ATMOS_COMPONENTS_HELMFILE_COMMAND' ENV var, or '--helmfile-command' command-line argument
+ command: helmfile
+
# Can also be set using 'ATMOS_COMPONENTS_HELMFILE_BASE_PATH' ENV var, or '--helmfile-dir' command-line argument
# Supports both absolute and relative paths
base_path: "components/helmfile"
@@ -812,11 +831,13 @@ setting `ATMOS_STACKS_BASE_PATH` to a path in `/localhost` to your local develop
|:------------------------------------------------------|:------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ATMOS_CLI_CONFIG_PATH | N/A | Where to find `atmos.yaml`. Path to a folder where `atmos.yaml` CLI config file is located (e.g. `/config`) |
| ATMOS_BASE_PATH | base_path | Base path to `components` and `stacks` folders |
+| ATMOS_COMPONENTS_TERRAFORM_COMMAND | components.terraform.command | The executable to be called by `atmos` when running Terraform commands |
| ATMOS_COMPONENTS_TERRAFORM_BASE_PATH | components.terraform.base_path | Base path to Terraform components |
| ATMOS_COMPONENTS_TERRAFORM_APPLY_AUTO_APPROVE | components.terraform.apply_auto_approve | If set to `true`, auto-generate Terraform backend config files when executing `atmos terraform` commands |
| ATMOS_COMPONENTS_TERRAFORM_DEPLOY_RUN_INIT | components.terraform.deploy_run_init | Run `terraform init` when executing `atmos terraform deploy` command |
| ATMOS_COMPONENTS_TERRAFORM_INIT_RUN_RECONFIGURE | components.terraform.init_run_reconfigure | Run `terraform init -reconfigure` when executing `atmos terraform` commands |
| ATMOS_COMPONENTS_TERRAFORM_AUTO_GENERATE_BACKEND_FILE | components.terraform.auto_generate_backend_file | If set to `true`, auto-generate Terraform backend config files when executing `atmos terraform` commands |
+| ATMOS_COMPONENTS_HELMFILE_COMMAND | components.helmfile.command | The executable to be called by `atmos` when running Helmfile commands |
| ATMOS_COMPONENTS_HELMFILE_BASE_PATH | components.helmfile.base_path | Path to helmfile components |
| ATMOS_COMPONENTS_HELMFILE_USE_EKS | components.helmfile.use_eks | If set to `true`, download `kubeconfig` from EKS by running `aws eks update-kubeconfig` command before executing `atmos helmfile` commands |
| ATMOS_COMPONENTS_HELMFILE_KUBECONFIG_PATH | components.helmfile.kubeconfig_path | Path to write the `kubeconfig` file when executing `aws eks update-kubeconfig` command |
diff --git a/website/docs/core-concepts/stacks/templating.md b/website/docs/core-concepts/stacks/templating.md
index d3769d1f9..9c69500f0 100644
--- a/website/docs/core-concepts/stacks/templating.md
+++ b/website/docs/core-concepts/stacks/templating.md
@@ -305,11 +305,12 @@ You can use `Go` templates in the following Atmos sections to refer to values in
- `vars`
- `settings`
- `env`
- - `metadata`
- `providers`
- `overrides`
- `backend`
- `backend_type`
+ - `component`
+ - `metadata.component`
diff --git a/website/docs/integrations/atlantis.mdx b/website/docs/integrations/atlantis.mdx
index c9283bd5e..aca05f927 100644
--- a/website/docs/integrations/atlantis.mdx
+++ b/website/docs/integrations/atlantis.mdx
@@ -1,9 +1,10 @@
---
title: Atlantis Integration
-sidebar_position: 11
+sidebar_position: 10
sidebar_label: Atlantis
---
-import Terminal from '@site/src/components/Screengrab'
+
+import Terminal from '@site/src/components/Terminal'
Atmos natively supports [Atlantis](https://runatlantis.io) for Terraform Pull Request Automation.
@@ -76,7 +77,7 @@ integrations:
name: "{tenant}-{environment}-{stage}-{component}"
workspace: "{workspace}"
dir: "{component-path}"
- terraform_version: v1.2
+ terraform_version: v1.8
delete_source_branch_on_merge: true
autoplan:
enabled: true
@@ -128,7 +129,7 @@ projects:
workspace: test-component-override-3-workspace
workflow: workflow-1
dir: examples/tests/components/terraform/test/test-component
- terraform_version: v1.2
+ terraform_version: v1.8
delete_source_branch_on_merge: true
autoplan:
enabled: true
@@ -141,7 +142,7 @@ projects:
workspace: tenant1-ue2-staging
workflow: workflow-1
dir: examples/tests/components/terraform/infra/vpc
- terraform_version: v1.2
+ terraform_version: v1.8
delete_source_branch_on_merge: true
autoplan:
enabled: true
@@ -215,7 +216,7 @@ Configuring the Atlantis Integration in the `settings.atlantis` sections in the
delete_source_branch_on_merge: false
dir: '{component-path}'
name: '{tenant}-{environment}-{stage}-{component}'
- terraform_version: v1.3
+ terraform_version: v1.8
workflow: workflow-1
workspace: '{workspace}'
project_template_name: project-1
@@ -353,7 +354,7 @@ The Atlantis config template and project template can be defined in the `setting
name: "{tenant}-{environment}-{stage}-{component}"
workspace: "{workspace}"
dir: "{component-path}"
- terraform_version: v1.2
+ terraform_version: v1.8
delete_source_branch_on_merge: true
autoplan:
enabled: true
@@ -411,7 +412,7 @@ The Atlantis config template and project template can be defined in the `setting
workspace: "{workspace}"
workflow: "workflow-1"
dir: "{component-path}"
- terraform_version: v1.3
+ terraform_version: v1.8
delete_source_branch_on_merge: false
autoplan:
enabled: true
@@ -686,7 +687,7 @@ on:
branches: [ main ]
env:
- ATMOS_VERSION: 1.72.0
+ ATMOS_VERSION: 1.73.0
ATMOS_CLI_CONFIG_PATH: ./
jobs:
diff --git a/website/docs/integrations/aws.mdx b/website/docs/integrations/aws.mdx
index 31e74a63a..1bead836b 100644
--- a/website/docs/integrations/aws.mdx
+++ b/website/docs/integrations/aws.mdx
@@ -1,6 +1,6 @@
---
title: Amazon Web Services (AWS) Integration
-sidebar_position: 10
+sidebar_position: 5
sidebar_label: Amazon Web Services
---
diff --git a/website/docs/integrations/github-actions/setup-atmos.md b/website/docs/integrations/github-actions/setup-atmos.md
index f240418c4..0bb49e437 100644
--- a/website/docs/integrations/github-actions/setup-atmos.md
+++ b/website/docs/integrations/github-actions/setup-atmos.md
@@ -27,5 +27,5 @@ jobs:
uses: cloudposse/github-action-setup-atmos
with:
# Make sure to pin to the latest version of atmos
- atmos_version: 1.72.0
+ atmos_version: 1.73.0
```
diff --git a/website/docs/integrations/helmfile.md b/website/docs/integrations/helmfile.md
index aef3659a5..52316ad50 100644
--- a/website/docs/integrations/helmfile.md
+++ b/website/docs/integrations/helmfile.md
@@ -1,6 +1,6 @@
---
title: Helmfile Integration
-sidebar_position: 9
+sidebar_position: 4
sidebar_label: Helmfile
---
diff --git a/website/docs/integrations/opentofu.mdx b/website/docs/integrations/opentofu.mdx
new file mode 100644
index 000000000..19be2b09d
--- /dev/null
+++ b/website/docs/integrations/opentofu.mdx
@@ -0,0 +1,111 @@
+---
+title: OpenTofu Integration
+sidebar_position: 3
+sidebar_label: OpenTofu
+---
+
+import useBaseUrl from '@docusaurus/useBaseUrl';
+
+
+
+Atmos natively supports [OpenTofu](https://opentofu.org),
+similar to the way it supports [Terraform](/integrations/terraform). It's compatible with every version of `opentofu` and designed to
+work with multiple different versions of it concurrently, and can even work alongside with [HashiCorp Terraform](/integrations/terraform).
+
+Please see the complete configuration options for [Terraform](/integrations/terraform), as they are the same for OpenTofu. We'll focus
+only on what's different in this document, in order to utilize OpenTofu. Keep in mind that Atmos does not handle the downloading or installation
+of OpenTofu; it assumes that any required binaries for the commands are already installed on your system.
+
+Additionally, if using Spacelift together with Atmos, make sure you review the [Spacelift Integration](/integrations/spacelift) to make any necessary changes.
+
+## CLI Configuration
+
+All the default configuration settings to support OpenTofu are defined in the [Atmos CLI Configuration](/cli/configuration),
+but can also be overridden at any level of the [Stack](/core-concepts/stacks/#schema) configuration.
+
+To make OpenTofu the default command when running "terraform", modify [`atmos.yaml`](/cli/configuration) to configure the following global settings:
+
+```yaml
+components:
+ terraform:
+ # Use the `tofu` command when calling "terraform" in Atmos.
+ command: "/usr/bin/tofu" # or just `tofu`
+
+ # Optionally, specify a different path for OpenTofu components
+ base_path: "components/tofu"
+```
+
+:::important
+Atmos consistently utilizes the `terraform` keyword across all configurations, rather than `tofu` or `opentofu`.
+:::
+
+Additionally, if you prefer to run `atmos tofu` instead of `atmos terraform`, you can configure an alias.
+Just add the following configuration somewhere in the `atmos.yaml` CLI config file:
+
+```yaml
+aliases:
+ tofu: terraform
+```
+
+:::important
+Creating aliases for `tofu` only changes the CLI invocation of `atmos terraform` and does not directly
+influence the actual command that atmos executes when running Terraform. Atmos strictly adheres to the
+specific `command` set in the Stack configurations.
+:::
+
+## Stack Configuration for Components
+
+Settings for Terraform or OpenTofu can also be specified in stack configurations, where they are compatible with inheritance.
+This feature allows projects to tailor behavior according to individual component needs.
+
+While defaults for everything are defined in the `atmos.yaml`, the same settings, can be overridden by Stack configurations at any level:
+
+- `terraform`
+- `components.terraform`
+- `components.terraform._component_`
+
+For instance, you can modify the command executed for a specific component by overriding the `command` parameter.
+This flexibility is particularly valuable for gradually transitioning to OpenTofu or managing components that are
+compatible only with HashiCorp Terraform.
+
+```yaml
+components:
+ terraform:
+ vpc:
+ command: "/usr/local/bin/tofu-1.7"
+```
+
+## Example: Provision a Terraform Component with OpenTofu
+
+:::note
+In the following examples, we'll assume that `tofu` is an Atmos alias for the `terraform` command.
+
+```yaml
+aliases:
+ tofu: terraform
+```
+
+:::
+
+Once you've configured Atmos to utilize `tofu` — either by adjusting the default `terraform.command` in the `atmos.yaml`
+or by specifying the `command` for an individual component — provisioning any component follows the same procedure as
+you would typically use for Terraform.
+
+For example, to provision a Terraform component using OpenTofu, run the following commands:
+
+```console
+atmos tofu plan eks --stack=ue2-dev
+atmos tofu apply eks --stack=ue2-dev
+```
+
+where:
+
+- `eks` is the Terraform component to provision (from the `components/terraform` folder)
+- `--stack=ue2-dev` is the stack to provision the component into
+
+Short versions of all command-line arguments can be used:
+
+```console
+atmos tofu plan eks -s ue2-dev
+atmos tofu apply eks -s ue2-dev
+```
diff --git a/website/docs/integrations/spacelift.md b/website/docs/integrations/spacelift.md
index 2d74d23a5..329a45a09 100644
--- a/website/docs/integrations/spacelift.md
+++ b/website/docs/integrations/spacelift.md
@@ -1,6 +1,6 @@
---
title: Spacelift Integration
-sidebar_position: 3
+sidebar_position: 6
sidebar_label: Spacelift
---
@@ -60,6 +60,35 @@ components:
+
+## OpenTofu Support
+
+Spacelift is compatible with [OpenTofu](https://opentofu.org) and configurable on a global and per stack or component basis.
+
+To make OpenTofu the default, add the following to your top-level stack manifest:
+
+```yaml
+settings:
+ spacelift:
+ # Use OpenTofu
+ terraform_workflow_tool: OPEN_TOFU
+```
+
+Similarly, to override this behavior, or to only configure it on specific components, add the following to the component
+configuration:
+
+```yaml
+components:
+ terraform:
+ my-component:
+ settings:
+ spacelift:
+ # Use OpenTofu
+ terraform_workflow_tool: OPEN_TOFU
+```
+
+For more details on [Atmos support for OpenTofu](/integrations/opentofu) see our integration page.
+
## Spacelift Stack Dependencies
Atmos supports [Spacelift Stack Dependencies](https://docs.spacelift.io/concepts/stack/stack-dependencies) in component configurations.
diff --git a/website/docs/integrations/terraform.md b/website/docs/integrations/terraform.md
index 7cfc60b67..4b93c92af 100644
--- a/website/docs/integrations/terraform.md
+++ b/website/docs/integrations/terraform.md
@@ -1,40 +1,45 @@
---
title: Terraform Integration
-sidebar_position: 8
+sidebar_position: 2
sidebar_label: Terraform
---
-Atmos natively supports opinionated workflows for Terraform. It's compatible with every version of terraform and designed to work with multiple
-different versions of Terraform concurrently.
+Atmos natively supports opinionated workflows for Terraform and [OpenTofu](/integrations/opentofu).
+It's compatible with every version of terraform and designed to work with multiple different versions of Terraform
+concurrently. Keep in mind that Atmos does not handle the downloading or installation of Terraform; it assumes that any
+required commands are already installed on your system.
-Atmos provides many settings that are specific to Terraform.
+Atmos provides many settings that are specific to Terraform and OpenTofu.
-## Settings
+## CLI Configuration
-All of these settings are defined by default in the [Atmos CLI Configuration](/cli/configuration), but can be overridden at any level of
-the [Stack](/core-concepts/stacks/#schema) configuration.
+All of these settings are defined by default in the [Atmos CLI Configuration](/cli/configuration) found in `atmos.yaml`,
+but can also be overridden at any level of the [Stack](/core-concepts/stacks/#schema) configuration.
```yaml
-# The executable to be called by `atmos` when running terraform commands.
-command: "/usr/bin/terraform-1"
-# Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_BASE_PATH' ENV var, or '--terraform-dir' command-line argument
-# Supports both absolute and relative paths
-base_path: "components/terraform"
-# Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_APPLY_AUTO_APPROVE' ENV var
-apply_auto_approve: false
-# Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_DEPLOY_RUN_INIT' ENV var, or '--deploy-run-init' command-line argument
-deploy_run_init: true
-# Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_INIT_RUN_RECONFIGURE' ENV var, or '--init-run-reconfigure' command-line argument
-init_run_reconfigure: true
-# Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_AUTO_GENERATE_BACKEND_FILE' ENV var, or '--auto-generate-backend-file' command-line argument
-auto_generate_backend_file: false
+components:
+ terraform:
+ # The executable to be called by `atmos` when running Terraform commands
+ command: "/usr/bin/terraform-1"
+ # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_BASE_PATH' ENV var, or '--terraform-dir' command-line argument
+ # Supports both absolute and relative paths
+ base_path: "components/terraform"
+ # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_APPLY_AUTO_APPROVE' ENV var
+ apply_auto_approve: false
+ # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_DEPLOY_RUN_INIT' ENV var, or '--deploy-run-init' command-line argument
+ deploy_run_init: true
+ # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_INIT_RUN_RECONFIGURE' ENV var, or '--init-run-reconfigure' command-line argument
+ init_run_reconfigure: true
+ # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_AUTO_GENERATE_BACKEND_FILE' ENV var, or '--auto-generate-backend-file' command-line argument
+ auto_generate_backend_file: false
```
## Configuration
-The settings for terraform can be defined in multiple places and support inheritance. This ensures that projects can override the behavior.
+The settings for terraform can be defined in multiple places and support inheritance. This ensures that projects can
+override the behavior.
-The defaults everything are defined in the `atmos.yaml`.
+The defaults for everything are defined in the `atmos.yaml`.
```yaml
components:
@@ -59,7 +64,8 @@ components:
## Terraform Provider
-A Terraform provider (`cloudposse/terraform-provider-utils`) implements a `data` source that can read the YAML Stack configurations natively from
+A Terraform provider (`cloudposse/terraform-provider-utils`) implements a `data` source that can read the YAML Stack
+configurations natively from
within terraform.
## Terraform Module
@@ -103,7 +109,8 @@ atmos terraform plan eks -s ue2-dev
atmos terraform apply eks -s ue2-dev
```
-`terraform deploy` command executes `terraform apply -auto-approve` to provision components in stacks without user interaction:
+The `atmos terraform deploy` command executes `terraform apply -auto-approve` to provision components in stacks without
+user interaction:
```console
atmos terraform deploy eks -s ue2-dev
diff --git a/website/static/img/opentofu-icon.svg b/website/static/img/opentofu-icon.svg
new file mode 100644
index 000000000..2b48529a5
--- /dev/null
+++ b/website/static/img/opentofu-icon.svg
@@ -0,0 +1,61 @@
+
+