diff --git a/atmos.yaml b/atmos.yaml index 432b535f5..bc1d38ec1 100644 --- a/atmos.yaml +++ b/atmos.yaml @@ -26,6 +26,8 @@ components: 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 helmfile: diff --git a/examples/complete/Dockerfile b/examples/complete/Dockerfile index e3681e2e1..dce70cf9f 100644 --- a/examples/complete/Dockerfile +++ b/examples/complete/Dockerfile @@ -2,7 +2,7 @@ ARG GEODESIC_VERSION=0.152.2 ARG GEODESIC_OS=debian # atmos: https://github.com/cloudposse/atmos -ARG ATMOS_VERSION=1.4.1 +ARG ATMOS_VERSION=1.4.2 # Terraform ARG TF_VERSION=1.1.4 diff --git a/examples/complete/atmos.yaml b/examples/complete/atmos.yaml index 450be6234..effdcf8d1 100644 --- a/examples/complete/atmos.yaml +++ b/examples/complete/atmos.yaml @@ -26,6 +26,8 @@ components: 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 helmfile: diff --git a/examples/complete/rootfs/usr/local/etc/atmos/atmos.yaml b/examples/complete/rootfs/usr/local/etc/atmos/atmos.yaml index 95f536216..b9c6b7458 100644 --- a/examples/complete/rootfs/usr/local/etc/atmos/atmos.yaml +++ b/examples/complete/rootfs/usr/local/etc/atmos/atmos.yaml @@ -26,6 +26,8 @@ components: 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 helmfile: diff --git a/internal/exec/terraform.go b/internal/exec/terraform.go index 80ce9059d..04c19d183 100644 --- a/internal/exec/terraform.go +++ b/internal/exec/terraform.go @@ -47,7 +47,7 @@ func ExecuteTerraform(cmd *cobra.Command, args []string) error { } // Check if the component is allowed to be provisioned (`metadata.type` attribute) - if (info.SubCommand == "apply" || info.SubCommand == "deploy") && info.ComponentIsAbstract { + if (info.SubCommand == "plan" || info.SubCommand == "apply" || info.SubCommand == "deploy" || info.SubCommand == "workspace") && info.ComponentIsAbstract { return errors.New(fmt.Sprintf("Abstract component '%s' cannot be provisioned since it's explicitly prohibited from being deployed "+ "by 'metadata.type: abstract' attribute", path.Join(info.ComponentFolderPrefix, info.Component))) } @@ -153,7 +153,7 @@ func ExecuteTerraform(cmd *cobra.Command, args []string) error { } if runTerraformInit == true { initCommandWithArguments := []string{"init"} - if info.SubCommand == "workspace" { + if info.SubCommand == "workspace" || c.Config.Components.Terraform.InitRunReconfigure == true { initCommandWithArguments = []string{"init", "-reconfigure"} } err = execCommand(info.Command, initCommandWithArguments, componentPath, info.ComponentEnvList) @@ -302,12 +302,3 @@ func ExecuteTerraform(cmd *cobra.Command, args []string) error { return nil } - -func checkTerraformConfig() error { - if len(c.Config.Components.Terraform.BasePath) < 1 { - return errors.New("Base path to terraform components must be provided in 'components.terraform.base_path' config or " + - "'ATMOS_COMPONENTS_TERRAFORM_BASE_PATH' ENV variable") - } - - return nil -} diff --git a/internal/exec/terraform_utils.go b/internal/exec/terraform_utils.go new file mode 100644 index 000000000..576a078a4 --- /dev/null +++ b/internal/exec/terraform_utils.go @@ -0,0 +1,15 @@ +package exec + +import ( + c "github.com/cloudposse/atmos/pkg/config" + "github.com/pkg/errors" +) + +func checkTerraformConfig() error { + if len(c.Config.Components.Terraform.BasePath) < 1 { + return errors.New("Base path to terraform components must be provided in 'components.terraform.base_path' config or " + + "'ATMOS_COMPONENTS_TERRAFORM_BASE_PATH' ENV variable") + } + + return nil +} diff --git a/internal/exec/utils.go b/internal/exec/utils.go index c8a024b98..e8b94ef11 100644 --- a/internal/exec/utils.go +++ b/internal/exec/utils.go @@ -25,6 +25,7 @@ var ( g.BasePathFlag, g.GlobalOptionsFlag, g.DeployRunInitFlag, + g.InitRunReconfigure, g.AutoGenerateBackendFileFlag, g.FromPlanFlag, g.HelpFlag1, @@ -169,6 +170,7 @@ func processArgsConfigAndStacks(componentType string, cmd *cobra.Command, args [ configAndStacksInfo.ConfigDir = argsAndFlagsInfo.ConfigDir configAndStacksInfo.WorkflowsDir = argsAndFlagsInfo.WorkflowsDir configAndStacksInfo.DeployRunInit = argsAndFlagsInfo.DeployRunInit + configAndStacksInfo.InitRunReconfigure = argsAndFlagsInfo.InitRunReconfigure configAndStacksInfo.AutoGenerateBackendFile = argsAndFlagsInfo.AutoGenerateBackendFile configAndStacksInfo.UseTerraformPlan = argsAndFlagsInfo.UseTerraformPlan configAndStacksInfo.NeedHelp = argsAndFlagsInfo.NeedHelp @@ -549,6 +551,19 @@ func processArgsAndFlags(inputArgsAndFlags []string) (c.ArgsAndFlagsInfo, error) info.WorkflowsDir = workflowDirFlagParts[1] } + if arg == g.InitRunReconfigure { + if len(inputArgsAndFlags) <= (i + 1) { + return info, errors.New(fmt.Sprintf("invalid flag: %s", arg)) + } + info.InitRunReconfigure = inputArgsAndFlags[i+1] + } else if strings.HasPrefix(arg+"=", g.InitRunReconfigure) { + var initRunReconfigureParts = strings.Split(arg, "=") + if len(initRunReconfigureParts) != 2 { + return info, errors.New(fmt.Sprintf("invalid flag: %s", arg)) + } + info.InitRunReconfigure = initRunReconfigureParts[1] + } + if arg == g.FromPlanFlag { info.UseTerraformPlan = true } diff --git a/pkg/component/atmos.yaml b/pkg/component/atmos.yaml index 2b02b66f0..1d09a8d10 100644 --- a/pkg/component/atmos.yaml +++ b/pkg/component/atmos.yaml @@ -26,6 +26,8 @@ components: 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 helmfile: diff --git a/pkg/config/config.go b/pkg/config/config.go index 239e223d6..3505abf16 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -27,6 +27,7 @@ var ( BasePath: "components/terraform", ApplyAutoApprove: false, DeployRunInit: true, + InitRunReconfigure: true, AutoGenerateBackendFile: false, }, Helmfile: Helmfile{ diff --git a/pkg/config/schema.go b/pkg/config/schema.go index ccfcdc8e5..e77a93e83 100644 --- a/pkg/config/schema.go +++ b/pkg/config/schema.go @@ -4,6 +4,7 @@ type Terraform struct { BasePath string `yaml:"base_path" json:"base_path" mapstructure:"base_path"` ApplyAutoApprove bool `yaml:"apply_auto_approve" json:"apply_auto_approve" mapstructure:"apply_auto_approve"` 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"` } @@ -75,6 +76,7 @@ type ArgsAndFlagsInfo struct { WorkflowsDir string BasePath string DeployRunInit string + InitRunReconfigure string AutoGenerateBackendFile string UseTerraformPlan bool NeedHelp bool @@ -109,6 +111,7 @@ type ConfigAndStacksInfo struct { Context Context ContextPrefix string DeployRunInit string + InitRunReconfigure string AutoGenerateBackendFile string UseTerraformPlan bool ComponentInheritanceChain []string diff --git a/pkg/config/utils.go b/pkg/config/utils.go index 477c44f0e..d3bf6afc2 100644 --- a/pkg/config/utils.go +++ b/pkg/config/utils.go @@ -201,6 +201,16 @@ func processEnvVars() error { Config.Components.Terraform.DeployRunInit = deployRunInitBool } + componentsInitRunReconfigure := os.Getenv("ATMOS_COMPONENTS_TERRAFORM_INIT_RUN_RECONFIGURE") + if len(componentsInitRunReconfigure) > 0 { + color.Cyan("Found ENV var ATMOS_COMPONENTS_TERRAFORM_INIT_RUN_RECONFIGURE=%s", componentsInitRunReconfigure) + initRunReconfigureBool, err := strconv.ParseBool(componentsInitRunReconfigure) + if err != nil { + return err + } + Config.Components.Terraform.InitRunReconfigure = initRunReconfigureBool + } + componentsTerraformAutoGenerateBackendFile := os.Getenv("ATMOS_COMPONENTS_TERRAFORM_AUTO_GENERATE_BACKEND_FILE") if len(componentsTerraformAutoGenerateBackendFile) > 0 { color.Cyan("Found ENV var ATMOS_COMPONENTS_TERRAFORM_AUTO_GENERATE_BACKEND_FILE=%s", componentsTerraformAutoGenerateBackendFile) @@ -297,6 +307,14 @@ func processCommandLineArgs(configAndStacksInfo ConfigAndStacksInfo) error { Config.Workflows.BasePath = configAndStacksInfo.WorkflowsDir color.Cyan(fmt.Sprintf("Using command line argument '%s' as workflows directory", configAndStacksInfo.WorkflowsDir)) } + if len(configAndStacksInfo.InitRunReconfigure) > 0 { + initRunReconfigureBool, err := strconv.ParseBool(configAndStacksInfo.InitRunReconfigure) + if err != nil { + return err + } + Config.Components.Terraform.InitRunReconfigure = initRunReconfigureBool + color.Cyan(fmt.Sprintf("Using command line argument '%s=%s'", g.InitRunReconfigure, configAndStacksInfo.InitRunReconfigure)) + } return nil } @@ -352,7 +370,20 @@ func GetContextPrefix(stack string, context Context, stackNamePattern string) (s stackNamePatternParts := strings.Split(stackNamePattern, "-") for _, part := range stackNamePatternParts { - if part == "{tenant}" { + if part == "{namespace}" { + if len(context.Namespace) == 0 { + return "", + errors.New(fmt.Sprintf("The stack name pattern '%s' specifies 'namespace`, but the stack %s does not have a namespace defined", + stackNamePattern, + stack, + )) + } + if len(contextPrefix) == 0 { + contextPrefix = context.Namespace + } else { + contextPrefix = contextPrefix + "-" + context.Namespace + } + } else if part == "{tenant}" { if len(context.Tenant) == 0 { return "", errors.New(fmt.Sprintf("The stack name pattern '%s' specifies 'tenant`, but the stack %s does not have a tenant defined", diff --git a/pkg/globals/globals.go b/pkg/globals/globals.go index d84dccd90..7edeab5f2 100644 --- a/pkg/globals/globals.go +++ b/pkg/globals/globals.go @@ -19,6 +19,7 @@ const ( DeployRunInitFlag = "--deploy-run-init" AutoGenerateBackendFileFlag = "--auto-generate-backend-file" + InitRunReconfigure = "--init-run-reconfigure" FromPlanFlag = "--from-plan" diff --git a/pkg/spacelift/atmos.yaml b/pkg/spacelift/atmos.yaml index 2b02b66f0..1d09a8d10 100644 --- a/pkg/spacelift/atmos.yaml +++ b/pkg/spacelift/atmos.yaml @@ -26,6 +26,8 @@ components: 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 helmfile: diff --git a/pkg/spacelift/spacelift_stack_processor.go b/pkg/spacelift/spacelift_stack_processor.go index e82a5f411..1f6483b83 100644 --- a/pkg/spacelift/spacelift_stack_processor.go +++ b/pkg/spacelift/spacelift_stack_processor.go @@ -9,8 +9,6 @@ import ( "strings" ) -const defaultSpaceliftStackNamePattern = "{tenant}-{environment}-{stage}-{component}" - // CreateSpaceliftStacks takes a list of paths to YAML config files, processes and deep-merges all imports, // and returns a map of Spacelift stack configs func CreateSpaceliftStacks( @@ -248,7 +246,7 @@ func TransformStackConfigToSpaceliftStacks( res := map[string]interface{}{} var allStackNames []string - for _, stackConfig := range stacks { + for stackName, stackConfig := range stacks { config := stackConfig.(map[interface{}]interface{}) if i, ok := config["components"]; ok { @@ -277,8 +275,13 @@ func TransformStackConfigToSpaceliftStacks( // Spacelift stack name context := c.GetContextFromVars(componentVars) + contextPrefix, err := c.GetContextPrefix(stackName, context, stackNamePattern) + if err != nil { + return nil, err + } + context.Component = component - spaceliftStackName, _ := buildSpaceliftStackName(spaceliftSettings, context) + spaceliftStackName, _ := buildSpaceliftStackName(spaceliftSettings, context, contextPrefix) allStackNames = append(allStackNames, strings.Replace(spaceliftStackName, "/", "-", -1)) } } @@ -458,7 +461,7 @@ func TransformStackConfigToSpaceliftStacks( // Spacelift stack name context.Component = component - spaceliftStackName, spaceliftStackNamePattern := buildSpaceliftStackName(spaceliftSettings, context) + spaceliftStackName, spaceliftStackNamePattern := buildSpaceliftStackName(spaceliftSettings, context, contextPrefix) // Add Spacelift stack config to the final map spaceliftStackNameKey := strings.Replace(spaceliftStackName, "/", "-", -1) @@ -511,14 +514,11 @@ func buildSpaceliftDependsOnStackName( } // buildSpaceliftStackName build a Spacelift stack name from the provided context and state name pattern -func buildSpaceliftStackName(spaceliftSettings map[interface{}]interface{}, context c.Context) (string, string) { - var finalSpaceliftStackNamePattern string - +func buildSpaceliftStackName(spaceliftSettings map[interface{}]interface{}, context c.Context, contextPrefix string) (string, string) { if spaceliftStackNamePattern, ok := spaceliftSettings["stack_name_pattern"].(string); ok { - finalSpaceliftStackNamePattern = spaceliftStackNamePattern + return c.ReplaceContextTokens(context, spaceliftStackNamePattern), spaceliftStackNamePattern } else { - finalSpaceliftStackNamePattern = defaultSpaceliftStackNamePattern + defaultSpaceliftStackNamePattern := fmt.Sprintf("%s-%s", contextPrefix, context.Component) + return strings.Replace(defaultSpaceliftStackNamePattern, "/", "-", -1), contextPrefix } - - return c.ReplaceContextTokens(context, finalSpaceliftStackNamePattern), finalSpaceliftStackNamePattern }