From 18bc88e009255e73659b0fd3a0825f2bf5655c09 Mon Sep 17 00:00:00 2001 From: Hiromasa Kakehashi Date: Sat, 20 Apr 2024 17:58:11 +0900 Subject: [PATCH 1/3] Add generate-config-out flag for plan --- tfexec/options.go | 9 +++++++++ tfexec/plan.go | 40 ++++++++++++++++++++++++++-------------- tfexec/version.go | 1 + 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/tfexec/options.go b/tfexec/options.go index d783027a..4a3e1beb 100644 --- a/tfexec/options.go +++ b/tfexec/options.go @@ -172,6 +172,15 @@ func FromModule(source string) *FromModuleOption { return &FromModuleOption{source} } +type GenerateConfigOutOption struct { + generateConfigOut string +} + +// GenerateConfigOut represents the -generate-config-out flag. +func GenerateConfigOut(generateConfigOut string) *GenerateConfigOutOption { + return &GenerateConfigOutOption{generateConfigOut} +} + type GetOption struct { get bool } diff --git a/tfexec/plan.go b/tfexec/plan.go index 946ce8d0..532b1a97 100644 --- a/tfexec/plan.go +++ b/tfexec/plan.go @@ -12,20 +12,21 @@ import ( ) type planConfig struct { - destroy bool - dir string - lock bool - lockTimeout string - out string - parallelism int - reattachInfo ReattachInfo - refresh bool - refreshOnly bool - replaceAddrs []string - state string - targets []string - vars []string - varFiles []string + destroy bool + dir string + generateConfigOut string + lock bool + lockTimeout string + out string + parallelism int + reattachInfo ReattachInfo + refresh bool + refreshOnly bool + replaceAddrs []string + state string + targets []string + vars []string + varFiles []string } var defaultPlanOptions = planConfig{ @@ -97,6 +98,10 @@ func (opt *DestroyFlagOption) configurePlan(conf *planConfig) { conf.destroy = opt.destroy } +func (opt *GenerateConfigOutOption) configurePlan(conf *planConfig) { + conf.generateConfigOut = opt.generateConfigOut +} + // Plan executes `terraform plan` with the specified options and waits for it // to complete. // @@ -189,6 +194,13 @@ func (tf *Terraform) buildPlanArgs(ctx context.Context, c planConfig) ([]string, args := []string{"plan", "-no-color", "-input=false", "-detailed-exitcode"} // string opts: only pass if set + if c.generateConfigOut != "" { + err := tf.compatible(ctx, tf1_5_0, nil) + if err != nil { + return nil, fmt.Errorf("generate-config-out option was introduced in Terraform 1.5.0: %w", err) + } + args = append(args, "-generate-config-out="+c.generateConfigOut) + } if c.lockTimeout != "" { args = append(args, "-lock-timeout="+c.lockTimeout) } diff --git a/tfexec/version.go b/tfexec/version.go index 4ba4f6ea..07bec8f2 100644 --- a/tfexec/version.go +++ b/tfexec/version.go @@ -32,6 +32,7 @@ var ( tf0_15_4 = version.Must(version.NewVersion("0.15.4")) tf1_1_0 = version.Must(version.NewVersion("1.1.0")) tf1_4_0 = version.Must(version.NewVersion("1.4.0")) + tf1_5_0 = version.Must(version.NewVersion("1.5.0")) tf1_6_0 = version.Must(version.NewVersion("1.6.0")) ) From 79e075f90fe4f08cde9ab8f71f0af7dba5478859 Mon Sep 17 00:00:00 2001 From: Hiromasa Kakehashi Date: Sat, 20 Apr 2024 18:02:54 +0900 Subject: [PATCH 2/3] Unit test --- tfexec/internal/testutil/tfcache.go | 4 +-- tfexec/plan_test.go | 43 +++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/tfexec/internal/testutil/tfcache.go b/tfexec/internal/testutil/tfcache.go index ff9ceb00..00ceb643 100644 --- a/tfexec/internal/testutil/tfcache.go +++ b/tfexec/internal/testutil/tfcache.go @@ -22,8 +22,8 @@ const ( Latest015 = "0.15.5" Latest_v1 = "1.0.11" Latest_v1_1 = "1.1.9" - Latest_v1_5 = "1.5.3" - Latest_v1_6 = "1.6.0-alpha20230719" + Latest_v1_5 = "1.5.7" + Latest_v1_6 = "1.6.6" ) const appendUserAgent = "tfexec-testutil" diff --git a/tfexec/plan_test.go b/tfexec/plan_test.go index b0c404ef..c2cf1a29 100644 --- a/tfexec/plan_test.go +++ b/tfexec/plan_test.go @@ -13,7 +13,7 @@ import ( func TestPlanCmd(t *testing.T) { td := t.TempDir() - tf, err := NewTerraform(td, tfVersion(t, testutil.Latest_v1)) + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest_v1_5)) if err != nil { t.Fatal(err) } @@ -101,12 +101,31 @@ func TestPlanCmd(t *testing.T) { "-refresh-only", }, nil, planCmd) }) + + t.Run("run a generate-config-out plan", func(t *testing.T) { + planCmd, err := tf.planCmd(context.Background(), GenerateConfigOut("generated.tf")) + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "plan", + "-no-color", + "-input=false", + "-detailed-exitcode", + "-generate-config-out=generated.tf", + "-lock-timeout=0s", + "-lock=true", + "-parallelism=10", + "-refresh=true", + }, nil, planCmd) + }) } func TestPlanJSONCmd(t *testing.T) { td := t.TempDir() - tf, err := NewTerraform(td, tfVersion(t, testutil.Latest_v1)) + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest_v1_5)) if err != nil { t.Fatal(err) } @@ -177,4 +196,24 @@ func TestPlanJSONCmd(t *testing.T) { "earth", }, nil, planCmd) }) + + t.Run("generate-config-out", func(t *testing.T) { + planCmd, err := tf.planJSONCmd(context.Background(), GenerateConfigOut("generated.tf")) + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "plan", + "-no-color", + "-input=false", + "-detailed-exitcode", + "-generate-config-out=generated.tf", + "-lock-timeout=0s", + "-lock=true", + "-parallelism=10", + "-refresh=true", + "-json", + }, nil, planCmd) + }) } From dc4384e95528c763375b7cf30f011a29a852d1c6 Mon Sep 17 00:00:00 2001 From: Hiromasa Kakehashi Date: Sat, 20 Apr 2024 18:09:17 +0900 Subject: [PATCH 3/3] E2E test --- tfexec/internal/e2etest/plan_test.go | 25 +++++++++++++++++++ .../testdata/generate_config_out/main.tf | 4 +++ 2 files changed, 29 insertions(+) create mode 100644 tfexec/internal/e2etest/testdata/generate_config_out/main.tf diff --git a/tfexec/internal/e2etest/plan_test.go b/tfexec/internal/e2etest/plan_test.go index 16b7a432..313eef6b 100644 --- a/tfexec/internal/e2etest/plan_test.go +++ b/tfexec/internal/e2etest/plan_test.go @@ -15,6 +15,10 @@ import ( "github.com/hashicorp/terraform-exec/tfexec/internal/testutil" ) +var ( + generateConfigOutMinVersion = version.Must(version.NewVersion("1.5.0")) +) + func TestPlan(t *testing.T) { runTest(t, "basic", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) { err := tf.Init(context.Background()) @@ -92,3 +96,24 @@ func TestPlanJSON_TF015AndLater(t *testing.T) { } }) } + +func TestPlanGenerateConfigOut(t *testing.T) { + runTest(t, "generate_config_out", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) { + if tfv.LessThan(generateConfigOutMinVersion) { + t.Skip("terraform plan -generate-config-out was added in Terraform 1.5.0, so test is not valid") + } + + err := tf.Init(context.Background()) + if err != nil { + t.Fatalf("error running Init in test directory: %s", err) + } + + hasChanges, err := tf.Plan(context.Background(), tfexec.GenerateConfigOut("generated.tf")) + if err != nil { + t.Fatalf("error running Plan: %s", err) + } + if !hasChanges { + t.Fatalf("expected: true, got: %t", hasChanges) + } + }) +} diff --git a/tfexec/internal/e2etest/testdata/generate_config_out/main.tf b/tfexec/internal/e2etest/testdata/generate_config_out/main.tf new file mode 100644 index 00000000..8257ac5a --- /dev/null +++ b/tfexec/internal/e2etest/testdata/generate_config_out/main.tf @@ -0,0 +1,4 @@ +import { + id = "bar" + to = terraform_data.foo +}