diff --git a/CHANGELOG.md b/CHANGELOG.md index b83a3cf3..443e17fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ BUG FIXES: - Fix bug in which the `TF_WORKSPACE` env var was set to an empty string, instead of being unset as intended. [GH-388] +ENHANCEMENTS: + +- tfexec: Add `-refresh-only` to `(Terraform).Apply()` and `(Terraform).Plan()` methods ([#402](https://github.com/hashicorp/terraform-exec/pull/402)) + # 0.18.1 (March 01, 2023) BUG FIXES: diff --git a/tfexec/apply.go b/tfexec/apply.go index 43c8bb6f..f7ba3517 100644 --- a/tfexec/apply.go +++ b/tfexec/apply.go @@ -21,6 +21,7 @@ type applyConfig struct { parallelism int reattachInfo ReattachInfo refresh bool + refreshOnly bool replaceAddrs []string state string stateOut string @@ -78,6 +79,10 @@ func (opt *RefreshOption) configureApply(conf *applyConfig) { conf.refresh = opt.refresh } +func (opt *RefreshOnlyOption) configureApply(conf *applyConfig) { + conf.refreshOnly = opt.refreshOnly +} + func (opt *ReplaceOption) configureApply(conf *applyConfig) { conf.replaceAddrs = append(conf.replaceAddrs, opt.address) } @@ -181,6 +186,17 @@ func (tf *Terraform) buildApplyArgs(ctx context.Context, c applyConfig) ([]strin args = append(args, "-parallelism="+fmt.Sprint(c.parallelism)) args = append(args, "-refresh="+strconv.FormatBool(c.refresh)) + if c.refreshOnly { + err := tf.compatible(ctx, tf0_15_4, nil) + if err != nil { + return nil, fmt.Errorf("refresh-only option was introduced in Terraform 0.15.4: %w", err) + } + if !c.refresh { + return nil, fmt.Errorf("you cannot use refresh=false in refresh-only planning mode") + } + args = append(args, "-refresh-only") + } + // string slice opts: split into separate args if c.replaceAddrs != nil { err := tf.compatible(ctx, tf0_15_2, nil) diff --git a/tfexec/apply_test.go b/tfexec/apply_test.go index 28e6b58f..87a6f8e3 100644 --- a/tfexec/apply_test.go +++ b/tfexec/apply_test.go @@ -67,6 +67,26 @@ func TestApplyCmd(t *testing.T) { "testfile", }, nil, applyCmd) }) + + t.Run("refresh-only operation", func(t *testing.T) { + applyCmd, err := tf.applyCmd(context.Background(), + RefreshOnly(true), + ) + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "apply", + "-no-color", + "-auto-approve", + "-input=false", + "-lock=true", + "-parallelism=10", + "-refresh=true", + "-refresh-only", + }, nil, applyCmd) + }) } func TestApplyJSONCmd(t *testing.T) { diff --git a/tfexec/options.go b/tfexec/options.go index a9bade05..7885ebf9 100644 --- a/tfexec/options.go +++ b/tfexec/options.go @@ -327,6 +327,14 @@ func Refresh(refresh bool) *RefreshOption { return &RefreshOption{refresh} } +type RefreshOnlyOption struct { + refreshOnly bool +} + +func RefreshOnly(refreshOnly bool) *RefreshOnlyOption { + return &RefreshOnlyOption{refreshOnly} +} + type ReplaceOption struct { address string } diff --git a/tfexec/plan.go b/tfexec/plan.go index a4aacfbd..946ce8d0 100644 --- a/tfexec/plan.go +++ b/tfexec/plan.go @@ -20,6 +20,7 @@ type planConfig struct { parallelism int reattachInfo ReattachInfo refresh bool + refreshOnly bool replaceAddrs []string state string targets []string @@ -68,6 +69,10 @@ func (opt *RefreshOption) configurePlan(conf *planConfig) { conf.refresh = opt.refresh } +func (opt *RefreshOnlyOption) configurePlan(conf *planConfig) { + conf.refreshOnly = opt.refreshOnly +} + func (opt *ReplaceOption) configurePlan(conf *planConfig) { conf.replaceAddrs = append(conf.replaceAddrs, opt.address) } @@ -202,6 +207,17 @@ func (tf *Terraform) buildPlanArgs(ctx context.Context, c planConfig) ([]string, args = append(args, "-parallelism="+fmt.Sprint(c.parallelism)) args = append(args, "-refresh="+strconv.FormatBool(c.refresh)) + if c.refreshOnly { + err := tf.compatible(ctx, tf0_15_4, nil) + if err != nil { + return nil, fmt.Errorf("refresh-only option was introduced in Terraform 0.15.4: %w", err) + } + if !c.refresh { + return nil, fmt.Errorf("you cannot use refresh=false in refresh-only planning mode") + } + args = append(args, "-refresh-only") + } + // unary flags: pass if true if c.replaceAddrs != nil { err := tf.compatible(ctx, tf0_15_2, nil) diff --git a/tfexec/plan_test.go b/tfexec/plan_test.go index 8abfb8e7..b0c404ef 100644 --- a/tfexec/plan_test.go +++ b/tfexec/plan_test.go @@ -82,6 +82,25 @@ func TestPlanCmd(t *testing.T) { "earth", }, nil, planCmd) }) + + t.Run("run a refresh-only plan", func(t *testing.T) { + planCmd, err := tf.planCmd(context.Background(), RefreshOnly(true)) + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "plan", + "-no-color", + "-input=false", + "-detailed-exitcode", + "-lock-timeout=0s", + "-lock=true", + "-parallelism=10", + "-refresh=true", + "-refresh-only", + }, nil, planCmd) + }) } func TestPlanJSONCmd(t *testing.T) { diff --git a/tfexec/version.go b/tfexec/version.go index ec4d5221..d44901d6 100644 --- a/tfexec/version.go +++ b/tfexec/version.go @@ -29,6 +29,7 @@ var ( tf0_15_0 = version.Must(version.NewVersion("0.15.0")) tf0_15_2 = version.Must(version.NewVersion("0.15.2")) tf0_15_3 = version.Must(version.NewVersion("0.15.3")) + 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")) )