From c8054ad516c16b45d6feeb5dae18c4961029c104 Mon Sep 17 00:00:00 2001 From: Piotr Truszkowski Date: Mon, 20 May 2024 20:26:54 +0200 Subject: [PATCH] Terragrunt OpenTofu: reset terraform_version when changing tool (#552) * recompute terraform_version if tool is changed * reset version for terraform_workflow_tool too --- spacelift/internal/testhelpers/helpers.go | 11 ++ spacelift/resource_stack.go | 39 ++++++ spacelift/resource_stack_test.go | 145 ++++++++++++++++++++++ 3 files changed, 195 insertions(+) diff --git a/spacelift/internal/testhelpers/helpers.go b/spacelift/internal/testhelpers/helpers.go index fbcdf76c..6fc30148 100644 --- a/spacelift/internal/testhelpers/helpers.go +++ b/spacelift/internal/testhelpers/helpers.go @@ -179,6 +179,17 @@ func Equals(expected string) ValueCheck { } } +// NotEquals checks for inequality against the unexpected value. +func NotEquals(unexpected string) ValueCheck { + return func(actual string) error { + if actual != unexpected { + return nil + } + + return errors.Errorf("unexpectedly got %q", actual) + } +} + // IsEmpty checks that the expected value is empty. func IsEmpty() ValueCheck { return func(actual string) error { diff --git a/spacelift/resource_stack.go b/spacelift/resource_stack.go index a09a4461..20372a9c 100644 --- a/spacelift/resource_stack.go +++ b/spacelift/resource_stack.go @@ -973,6 +973,10 @@ func getVendorConfig(d *schema.ResourceData) *structs.VendorConfigInput { terragruntConfig.Tool = toOptionalString(tool) } + if shouldWeReComputeTerraformVersionForTerragrunt(d) { + terragruntConfig.TerraformVersion = nil + } + return &structs.VendorConfigInput{ TerragruntInput: &terragruntConfig, } @@ -986,6 +990,9 @@ func getVendorConfig(d *schema.ResourceData) *structs.VendorConfigInput { if terraformWorkflowTool, ok := d.GetOk("terraform_workflow_tool"); ok { terraformConfig.WorkflowTool = toOptionalString(terraformWorkflowTool) + if shouldWeReComputeTerraformVersionForTerraformWorkflowTool(d) { + terraformConfig.Version = nil + } } if terraformWorkspace, ok := d.GetOk("terraform_workspace"); ok { @@ -1007,6 +1014,38 @@ func getVendorConfig(d *schema.ResourceData) *structs.VendorConfigInput { return &structs.VendorConfigInput{Terraform: terraformConfig} } +func shouldWeReComputeTerraformVersionForTerragrunt(d *schema.ResourceData) bool { + // When tool is changed, we need to recompute terraform version + oldTool, newTool := d.GetChange("terragrunt.0.tool") + if oldTool.(string) != newTool.(string) { + // but only if version isn't provided manually in the config + inConf := d.GetRawConfig().AsValueMap()["terragrunt"].AsValueSlice()[0].AsValueMap() + if value, ok := inConf["terraform_version"]; ok { + if value.IsNull() || value.AsString() == "" { + return true + } + } + } + + return false +} + +func shouldWeReComputeTerraformVersionForTerraformWorkflowTool(d *schema.ResourceData) bool { + // When tool is changed, we need to recompute terraform version + oldTool, newTool := d.GetChange("terraform_workflow_tool") + if oldTool.(string) != newTool.(string) { + // but only if version isn't provided manually in the config + inConfig := d.GetRawConfig().AsValueMap() + if value, ok := inConfig["terraform_version"]; ok { + if value.IsNull() || value.AsString() == "" { + return true + } + } + } + + return false +} + func getStrings(d *schema.ResourceData, fieldName string) []graphql.String { values := []graphql.String{} if commands, ok := d.GetOk(fieldName); ok { diff --git a/spacelift/resource_stack_test.go b/spacelift/resource_stack_test.go index 98950515..1839f143 100644 --- a/spacelift/resource_stack_test.go +++ b/spacelift/resource_stack_test.go @@ -663,6 +663,114 @@ func TestStackResource(t *testing.T) { }) }) + t.Run("with GitHub and Terragrunt changing configuration scenarios", func(t *testing.T) { + name := "terragrunt-switch-testing-" + acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + config := func(body string) string { + return ` + resource "spacelift_stack" "test" { + administrative = true + branch = "master" + name = "` + name + `" + project_root = "root" + repository = "demo" + runner_image = "custom_image:runner" + autodeploy = true + terragrunt { + ` + body + ` + } + } + ` + } + testSteps(t, []resource.TestStep{ + { + Config: config(` + tool = "TERRAFORM_FOSS" + `), + Check: Resource( + "spacelift_stack.test", + Attribute("terragrunt.0.tool", Equals("TERRAFORM_FOSS")), + Attribute("terragrunt.0.terraform_version", IsNotEmpty()), + ), + }, + { // Change to OPEN_TOFU + Config: config(` + tool = "OPEN_TOFU" + `), + Check: Resource( + "spacelift_stack.test", + Attribute("terragrunt.0.tool", Equals("OPEN_TOFU")), + Attribute("terragrunt.0.terraform_version", IsNotEmpty()), + ), + }, + { // Change to TERRAFORM with specific version + Config: config(` + tool = "TERRAFORM_FOSS" + terraform_version = "1.5.6" + `), + Check: Resource( + "spacelift_stack.test", + Attribute("terragrunt.0.tool", Equals("TERRAFORM_FOSS")), + Attribute("terragrunt.0.terraform_version", Equals("1.5.6")), + ), + }, + { // Change to OPEN_TOFU with specific version + Config: config(` + tool = "OPEN_TOFU" + terraform_version = "1.6.2" + `), + Check: Resource( + "spacelift_stack.test", + Attribute("terragrunt.0.tool", Equals("OPEN_TOFU")), + Attribute("terragrunt.0.terraform_version", Equals("1.6.2")), + ), + }, + { // Change to TERRAFORM without version specified + Config: config(` + tool = "TERRAFORM_FOSS" + `), + Check: Resource( + "spacelift_stack.test", + Attribute("terragrunt.0.tool", Equals("TERRAFORM_FOSS")), + Attribute("terragrunt.0.terraform_version", NotEquals("1.6.2")), + ), + }, + { // Change to OPEN_TODU with invalid version + Config: config(` + tool = "OPEN_TOFU" + terraform_version = "1.5.6" + `), + ExpectError: regexp.MustCompile(`could not update stack: stack has 1 error: terragrunt: no supported OpenTofu version ([^ ]* - [^ ]*) satisfies constraints "1.5.6"`), + }, + { // Change to TERRAFORM with invalid version + Config: config(` + tool = "TERRAFORM_FOSS" + terraform_version = "1.6.2" + `), + ExpectError: regexp.MustCompile(`could not update stack: stack has 1 error: terragrunt: no supported Terraform version ([^ ]* - [^ ]*) satisfies constraints "1.6.2"`), + }, + { // Change to MANUALLY PROVISIONED + Config: config(` + tool = "MANUALLY_PROVISIONED" + `), + Check: Resource( + "spacelift_stack.test", + Attribute("terragrunt.0.tool", Equals("MANUALLY_PROVISIONED")), + Attribute("terragrunt.0.terraform_version", IsEmpty()), + ), + }, + { // Back to OPEN_TOFU + Config: config(` + tool = "OPEN_TOFU" + `), + Check: Resource( + "spacelift_stack.test", + Attribute("terragrunt.0.tool", Equals("OPEN_TOFU")), + Attribute("terragrunt.0.terraform_version", IsNotEmpty()), + ), + }, + }) + }) + t.Run("with GitHub and no vendor-specific configuration", func(t *testing.T) { testSteps(t, []resource.TestStep{ { @@ -1380,6 +1488,43 @@ func TestStackResourceSpace(t *testing.T) { Attribute("terraform_workflow_tool", Equals("CUSTOM")), ), }, + // Check to change from TERRAFORM_FOSS to CUSTOM with a specific version + // Actually, we don't need to specify the version, but when we don't do it, it will be + // evaluated on the first run. So, it's simpler just to specify a version for that test case. + { + Config: fmt.Sprintf(` + resource "spacelift_stack" "terraform_workflow_tool_custom_with_run" { + branch = "master" + name = "Provider test stack workflow_tool with run %s" + project_root = "root" + repository = "demo" + terraform_workflow_tool = "TERRAFORM_FOSS" + terraform_version = "1.5.7" + autodeploy = true + } + `, randomID), + Check: Resource( + "spacelift_stack.terraform_workflow_tool_custom_with_run", + Attribute("terraform_workflow_tool", Equals("TERRAFORM_FOSS")), + ), + }, + { + Config: fmt.Sprintf(` + resource "spacelift_stack" "terraform_workflow_tool_custom_with_run" { + branch = "master" + name = "Provider test stack workflow_tool with run %s" + project_root = "root" + repository = "demo" + terraform_workflow_tool = "CUSTOM" + terraform_version = "" + autodeploy = true + } + `, randomID), + Check: Resource( + "spacelift_stack.terraform_workflow_tool_custom_with_run", + Attribute("terraform_workflow_tool", Equals("CUSTOM")), + ), + }, }) }) }