diff --git a/tfexec/apply.go b/tfexec/apply.go index a6057fd1..00093271 100644 --- a/tfexec/apply.go +++ b/tfexec/apply.go @@ -13,12 +13,13 @@ type applyConfig struct { lock bool // LockTimeout must be a string with time unit, e.g. '10s' - lockTimeout string - parallelism int - refresh bool - state string - stateOut string - targets []string + lockTimeout string + parallelism int + reattachInfo ReattachInfo + refresh bool + state string + stateOut string + targets []string // Vars: each var must be supplied as a single string, e.g. 'foo=bar' vars []string @@ -80,12 +81,20 @@ func (opt *DirOrPlanOption) configureApply(conf *applyConfig) { conf.dirOrPlan = opt.path } +func (opt *ReattachOption) configureApply(conf *applyConfig) { + conf.reattachInfo = opt.info +} + // Apply represents the terraform apply subcommand. func (tf *Terraform) Apply(ctx context.Context, opts ...ApplyOption) error { - return tf.runTerraformCmd(tf.applyCmd(ctx, opts...)) + cmd, err := tf.applyCmd(ctx, opts...) + if err != nil { + return err + } + return tf.runTerraformCmd(cmd) } -func (tf *Terraform) applyCmd(ctx context.Context, opts ...ApplyOption) *exec.Cmd { +func (tf *Terraform) applyCmd(ctx context.Context, opts ...ApplyOption) (*exec.Cmd, error) { c := defaultApplyOptions for _, o := range opts { @@ -133,5 +142,14 @@ func (tf *Terraform) applyCmd(ctx context.Context, opts ...ApplyOption) *exec.Cm args = append(args, c.dirOrPlan) } - return tf.buildTerraformCmd(ctx, args...) + mergeEnv := map[string]string{} + if c.reattachInfo != nil { + reattachStr, err := marshalReattachString(c.reattachInfo) + if err != nil { + return nil, err + } + mergeEnv[reattachEnvVar] = reattachStr + } + + return tf.buildTerraformCmd(ctx, mergeEnv, args...), nil } diff --git a/tfexec/apply_test.go b/tfexec/apply_test.go index 38e78785..39bde06f 100644 --- a/tfexec/apply_test.go +++ b/tfexec/apply_test.go @@ -21,7 +21,7 @@ func TestApplyCmd(t *testing.T) { tf.SetEnv(map[string]string{}) t.Run("basic", func(t *testing.T) { - applyCmd := tf.applyCmd(context.Background(), + applyCmd, err := tf.applyCmd(context.Background(), Backup("testbackup"), LockTimeout("200s"), State("teststate"), @@ -37,6 +37,9 @@ func TestApplyCmd(t *testing.T) { Var("var2=bar"), DirOrPlan("testfile"), ) + if err != nil { + t.Fatal(err) + } assertCmd(t, []string{ "apply", diff --git a/tfexec/cmd.go b/tfexec/cmd.go index 1ea88921..b2ebf7d7 100644 --- a/tfexec/cmd.go +++ b/tfexec/cmd.go @@ -114,12 +114,20 @@ func (tf *Terraform) buildEnv(mergeEnv map[string]string) []string { // force usage of workspace methods for switching env[workspaceEnvVar] = "" + if tf.disablePluginTLS { + env[disablePluginTLSEnvVar] = "1" + } + + if tf.skipProviderVerify { + env[skipProviderVerifyEnvVar] = "1" + } + return envSlice(env) } -func (tf *Terraform) buildTerraformCmd(ctx context.Context, args ...string) *exec.Cmd { +func (tf *Terraform) buildTerraformCmd(ctx context.Context, mergeEnv map[string]string, args ...string) *exec.Cmd { cmd := exec.CommandContext(ctx, tf.execPath, args...) - cmd.Env = tf.buildEnv(nil) + cmd.Env = tf.buildEnv(mergeEnv) cmd.Dir = tf.workingDir tf.logger.Printf("[INFO] running Terraform command: %s", cmdString(cmd)) diff --git a/tfexec/destroy.go b/tfexec/destroy.go index 4590cb88..bdc1e5f8 100644 --- a/tfexec/destroy.go +++ b/tfexec/destroy.go @@ -13,12 +13,13 @@ type destroyConfig struct { lock bool // LockTimeout must be a string with time unit, e.g. '10s' - lockTimeout string - parallelism int - refresh bool - state string - stateOut string - targets []string + lockTimeout string + parallelism int + reattachInfo ReattachInfo + refresh bool + state string + stateOut string + targets []string // Vars: each var must be supplied as a single string, e.g. 'foo=bar' vars []string @@ -81,12 +82,20 @@ func (opt *VarOption) configureDestroy(conf *destroyConfig) { conf.vars = append(conf.vars, opt.assignment) } +func (opt *ReattachOption) configureDestroy(conf *destroyConfig) { + conf.reattachInfo = opt.info +} + // Destroy represents the terraform destroy subcommand. func (tf *Terraform) Destroy(ctx context.Context, opts ...DestroyOption) error { - return tf.runTerraformCmd(tf.destroyCmd(ctx, opts...)) + cmd, err := tf.destroyCmd(ctx, opts...) + if err != nil { + return err + } + return tf.runTerraformCmd(cmd) } -func (tf *Terraform) destroyCmd(ctx context.Context, opts ...DestroyOption) *exec.Cmd { +func (tf *Terraform) destroyCmd(ctx context.Context, opts ...DestroyOption) (*exec.Cmd, error) { c := defaultDestroyOptions for _, o := range opts { @@ -134,5 +143,14 @@ func (tf *Terraform) destroyCmd(ctx context.Context, opts ...DestroyOption) *exe args = append(args, c.dir) } - return tf.buildTerraformCmd(ctx, args...) + mergeEnv := map[string]string{} + if c.reattachInfo != nil { + reattachStr, err := marshalReattachString(c.reattachInfo) + if err != nil { + return nil, err + } + mergeEnv[reattachEnvVar] = reattachStr + } + + return tf.buildTerraformCmd(ctx, mergeEnv, args...), nil } diff --git a/tfexec/destroy_test.go b/tfexec/destroy_test.go index 9fa24fc8..ef6f7439 100644 --- a/tfexec/destroy_test.go +++ b/tfexec/destroy_test.go @@ -21,7 +21,10 @@ func TestDestroyCmd(t *testing.T) { tf.SetEnv(map[string]string{}) t.Run("defaults", func(t *testing.T) { - destroyCmd := tf.destroyCmd(context.Background()) + destroyCmd, err := tf.destroyCmd(context.Background()) + if err != nil { + t.Fatal(err) + } assertCmd(t, []string{ "destroy", @@ -36,7 +39,10 @@ func TestDestroyCmd(t *testing.T) { }) t.Run("override all defaults", func(t *testing.T) { - destroyCmd := tf.destroyCmd(context.Background(), Backup("testbackup"), LockTimeout("200s"), State("teststate"), StateOut("teststateout"), VarFile("testvarfile"), Lock(false), Parallelism(99), Refresh(false), Target("target1"), Target("target2"), Var("var1=foo"), Var("var2=bar"), Dir("destroydir")) + destroyCmd, err := tf.destroyCmd(context.Background(), Backup("testbackup"), LockTimeout("200s"), State("teststate"), StateOut("teststateout"), VarFile("testvarfile"), Lock(false), Parallelism(99), Refresh(false), Target("target1"), Target("target2"), Var("var1=foo"), Var("var2=bar"), Dir("destroydir")) + if err != nil { + t.Fatal(err) + } assertCmd(t, []string{ "destroy", diff --git a/tfexec/import.go b/tfexec/import.go index 62d61ffc..7a06cf97 100644 --- a/tfexec/import.go +++ b/tfexec/import.go @@ -14,6 +14,7 @@ type importConfig struct { allowMissingConfig bool lock bool lockTimeout string + reattachInfo ReattachInfo state string stateOut string vars []string @@ -51,6 +52,10 @@ func (opt *LockTimeoutOption) configureImport(conf *importConfig) { conf.lockTimeout = opt.timeout } +func (opt *ReattachOption) configureImport(conf *importConfig) { + conf.reattachInfo = opt.info +} + func (opt *StateOption) configureImport(conf *importConfig) { conf.state = opt.path } @@ -69,10 +74,14 @@ func (opt *VarFileOption) configureImport(conf *importConfig) { // Import represents the terraform import subcommand. func (tf *Terraform) Import(ctx context.Context, address, id string, opts ...ImportOption) error { - return tf.runTerraformCmd(tf.importCmd(ctx, address, id, opts...)) + cmd, err := tf.importCmd(ctx, address, id, opts...) + if err != nil { + return err + } + return tf.runTerraformCmd(cmd) } -func (tf *Terraform) importCmd(ctx context.Context, address, id string, opts ...ImportOption) *exec.Cmd { +func (tf *Terraform) importCmd(ctx context.Context, address, id string, opts ...ImportOption) (*exec.Cmd, error) { c := defaultImportOptions for _, o := range opts { @@ -119,5 +128,14 @@ func (tf *Terraform) importCmd(ctx context.Context, address, id string, opts ... // required args, always pass args = append(args, address, id) - return tf.buildTerraformCmd(ctx, args...) + mergeEnv := map[string]string{} + if c.reattachInfo != nil { + reattachStr, err := marshalReattachString(c.reattachInfo) + if err != nil { + return nil, err + } + mergeEnv[reattachEnvVar] = reattachStr + } + + return tf.buildTerraformCmd(ctx, mergeEnv, args...), nil } diff --git a/tfexec/import_test.go b/tfexec/import_test.go index 3f16c2f3..db4d5add 100644 --- a/tfexec/import_test.go +++ b/tfexec/import_test.go @@ -21,7 +21,10 @@ func TestImportCmd(t *testing.T) { tf.SetEnv(map[string]string{}) t.Run("defaults", func(t *testing.T) { - importCmd := tf.importCmd(context.Background(), "my-addr", "my-id") + importCmd, err := tf.importCmd(context.Background(), "my-addr", "my-id") + if err != nil { + t.Fatal(err) + } assertCmd(t, []string{ "import", @@ -35,7 +38,7 @@ func TestImportCmd(t *testing.T) { }) t.Run("override all defaults", func(t *testing.T) { - importCmd := tf.importCmd(context.Background(), "my-addr2", "my-id2", + importCmd, err := tf.importCmd(context.Background(), "my-addr2", "my-id2", Backup("testbackup"), LockTimeout("200s"), State("teststate"), @@ -46,6 +49,9 @@ func TestImportCmd(t *testing.T) { Var("var2=bar"), AllowMissingConfig(true), ) + if err != nil { + t.Fatal(err) + } assertCmd(t, []string{ "import", diff --git a/tfexec/init.go b/tfexec/init.go index fa8d9096..7a453bdd 100644 --- a/tfexec/init.go +++ b/tfexec/init.go @@ -17,6 +17,7 @@ type initConfig struct { lock bool lockTimeout string pluginDir []string + reattachInfo ReattachInfo reconfigure bool upgrade bool verifyPlugins bool @@ -75,6 +76,10 @@ func (opt *PluginDirOption) configureInit(conf *initConfig) { conf.pluginDir = append(conf.pluginDir, opt.pluginDir) } +func (opt *ReattachOption) configureInit(conf *initConfig) { + conf.reattachInfo = opt.info +} + func (opt *ReconfigureOption) configureInit(conf *initConfig) { conf.reconfigure = opt.reconfigure } @@ -89,10 +94,14 @@ func (opt *VerifyPluginsOption) configureInit(conf *initConfig) { // Init represents the terraform init subcommand. func (tf *Terraform) Init(ctx context.Context, opts ...InitOption) error { - return tf.runTerraformCmd(tf.initCmd(ctx, opts...)) + cmd, err := tf.initCmd(ctx, opts...) + if err != nil { + return err + } + return tf.runTerraformCmd(cmd) } -func (tf *Terraform) initCmd(ctx context.Context, opts ...InitOption) *exec.Cmd { +func (tf *Terraform) initCmd(ctx context.Context, opts ...InitOption) (*exec.Cmd, error) { c := defaultInitOptions for _, o := range opts { @@ -139,5 +148,14 @@ func (tf *Terraform) initCmd(ctx context.Context, opts ...InitOption) *exec.Cmd args = append(args, c.dir) } - return tf.buildTerraformCmd(ctx, args...) + mergeEnv := map[string]string{} + if c.reattachInfo != nil { + reattachStr, err := marshalReattachString(c.reattachInfo) + if err != nil { + return nil, err + } + mergeEnv[reattachEnvVar] = reattachStr + } + + return tf.buildTerraformCmd(ctx, mergeEnv, args...), nil } diff --git a/tfexec/init_test.go b/tfexec/init_test.go index 3eb0c1bf..1306f1cc 100644 --- a/tfexec/init_test.go +++ b/tfexec/init_test.go @@ -22,7 +22,7 @@ func TestInitCmd(t *testing.T) { t.Run("defaults", func(t *testing.T) { // defaults - initCmd := tf.initCmd(context.Background()) + initCmd, err := tf.initCmd(context.Background()) if err != nil { t.Fatal(err) } @@ -43,7 +43,10 @@ func TestInitCmd(t *testing.T) { }) t.Run("override all defaults", func(t *testing.T) { - initCmd := tf.initCmd(context.Background(), Backend(false), BackendConfig("confpath1"), BackendConfig("confpath2"), FromModule("testsource"), Get(false), GetPlugins(false), Lock(false), LockTimeout("999s"), PluginDir("testdir1"), PluginDir("testdir2"), Reconfigure(true), Upgrade(true), VerifyPlugins(false), Dir("initdir")) + initCmd, err := tf.initCmd(context.Background(), Backend(false), BackendConfig("confpath1"), BackendConfig("confpath2"), FromModule("testsource"), Get(false), GetPlugins(false), Lock(false), LockTimeout("999s"), PluginDir("testdir1"), PluginDir("testdir2"), Reconfigure(true), Upgrade(true), VerifyPlugins(false), Dir("initdir")) + if err != nil { + t.Fatal(err) + } assertCmd(t, []string{ "init", diff --git a/tfexec/options.go b/tfexec/options.go index 2a6e7542..b8179259 100644 --- a/tfexec/options.go +++ b/tfexec/options.go @@ -1,5 +1,9 @@ package tfexec +import ( + "encoding/json" +) + // AllowMissingConfigOption represents the -allow-missing-config flag. type AllowMissingConfigOption struct { allowMissingConfig bool @@ -172,6 +176,39 @@ func PluginDir(pluginDir string) *PluginDirOption { return &PluginDirOption{pluginDir} } +type ReattachInfo map[string]ReattachConfig + +// ReattachConfig holds the information Terraform needs to be able to attach +// itself to a provider process, so it can drive the process. +type ReattachConfig struct { + Protocol string + Pid int + Test bool + Addr ReattachConfigAddr +} + +// ReattachConfigAddr is a JSON-encoding friendly version of net.Addr. +type ReattachConfigAddr struct { + Network string + String string +} + +type ReattachOption struct { + info ReattachInfo +} + +func marshalReattachString(info ReattachInfo) (string, error) { + reattachStr, err := json.Marshal(info) + if err != nil { + return "", err + } + return string(reattachStr), nil +} + +func Reattach(info ReattachInfo) *ReattachOption { + return &ReattachOption{info} +} + type ReconfigureOption struct { reconfigure bool } diff --git a/tfexec/output.go b/tfexec/output.go index 74df87ed..4c204025 100644 --- a/tfexec/output.go +++ b/tfexec/output.go @@ -59,5 +59,5 @@ func (tf *Terraform) outputCmd(ctx context.Context, opts ...OutputOption) *exec. args = append(args, "-state="+c.state) } - return tf.buildTerraformCmd(ctx, args...) + return tf.buildTerraformCmd(ctx, nil, args...) } diff --git a/tfexec/plan.go b/tfexec/plan.go index 1f9126d2..3d907e0a 100644 --- a/tfexec/plan.go +++ b/tfexec/plan.go @@ -8,17 +8,18 @@ import ( ) type planConfig struct { - destroy bool - dir string - lock bool - lockTimeout string - out string - parallelism int - refresh bool - state string - targets []string - vars []string - varFiles []string + destroy bool + dir string + lock bool + lockTimeout string + out string + parallelism int + reattachInfo ReattachInfo + refresh bool + state string + targets []string + vars []string + varFiles []string } var defaultPlanOptions = planConfig{ @@ -54,6 +55,10 @@ func (opt *StateOption) configurePlan(conf *planConfig) { conf.state = opt.path } +func (opt *ReattachOption) configurePlan(conf *planConfig) { + conf.reattachInfo = opt.info +} + func (opt *RefreshOption) configurePlan(conf *planConfig) { conf.refresh = opt.refresh } @@ -87,15 +92,18 @@ func (opt *DestroyFlagOption) configurePlan(conf *planConfig) { // The returned error is nil if `terraform plan` has been executed and exits // with either 0 or 2. func (tf *Terraform) Plan(ctx context.Context, opts ...PlanOption) (bool, error) { - cmd := tf.planCmd(ctx, opts...) - err := tf.runTerraformCmd(cmd) + cmd, err := tf.planCmd(ctx, opts...) + if err != nil { + return false, err + } + err = tf.runTerraformCmd(cmd) if err != nil && cmd.ProcessState.ExitCode() == 2 { return true, nil } return false, err } -func (tf *Terraform) planCmd(ctx context.Context, opts ...PlanOption) *exec.Cmd { +func (tf *Terraform) planCmd(ctx context.Context, opts ...PlanOption) (*exec.Cmd, error) { c := defaultPlanOptions for _, o := range opts { @@ -145,5 +153,14 @@ func (tf *Terraform) planCmd(ctx context.Context, opts ...PlanOption) *exec.Cmd args = append(args, c.dir) } - return tf.buildTerraformCmd(ctx, args...) + mergeEnv := map[string]string{} + if c.reattachInfo != nil { + reattachStr, err := marshalReattachString(c.reattachInfo) + if err != nil { + return nil, err + } + mergeEnv[reattachEnvVar] = reattachStr + } + + return tf.buildTerraformCmd(ctx, mergeEnv, args...), nil } diff --git a/tfexec/plan_test.go b/tfexec/plan_test.go index fa898214..a5ad9cbc 100644 --- a/tfexec/plan_test.go +++ b/tfexec/plan_test.go @@ -21,7 +21,10 @@ func TestPlanCmd(t *testing.T) { tf.SetEnv(map[string]string{}) t.Run("defaults", func(t *testing.T) { - planCmd := tf.planCmd(context.Background()) + planCmd, err := tf.planCmd(context.Background()) + if err != nil { + t.Fatal(err) + } assertCmd(t, []string{ "plan", @@ -36,7 +39,10 @@ func TestPlanCmd(t *testing.T) { }) t.Run("override all defaults", func(t *testing.T) { - planCmd := tf.planCmd(context.Background(), Destroy(true), Lock(false), LockTimeout("22s"), Out("whale"), Parallelism(42), Refresh(false), State("marvin"), Target("zaphod"), Target("beeblebrox"), Var("android=paranoid"), Var("brain_size=planet"), VarFile("trillian"), Dir("earth")) + planCmd, err := tf.planCmd(context.Background(), Destroy(true), Lock(false), LockTimeout("22s"), Out("whale"), Parallelism(42), Refresh(false), State("marvin"), Target("zaphod"), Target("beeblebrox"), Var("android=paranoid"), Var("brain_size=planet"), VarFile("trillian"), Dir("earth")) + if err != nil { + t.Fatal(err) + } assertCmd(t, []string{ "plan", diff --git a/tfexec/providers_schema.go b/tfexec/providers_schema.go index a02d7d55..75e593a9 100644 --- a/tfexec/providers_schema.go +++ b/tfexec/providers_schema.go @@ -29,5 +29,5 @@ func (tf *Terraform) providersSchemaCmd(ctx context.Context, args ...string) *ex allArgs := []string{"providers", "schema", "-json", "-no-color"} allArgs = append(allArgs, args...) - return tf.buildTerraformCmd(ctx, allArgs...) + return tf.buildTerraformCmd(ctx, nil, allArgs...) } diff --git a/tfexec/refresh.go b/tfexec/refresh.go index 58b286ff..0eb48a7f 100644 --- a/tfexec/refresh.go +++ b/tfexec/refresh.go @@ -7,15 +7,16 @@ import ( ) type refreshConfig struct { - backup string - dir string - lock bool - lockTimeout string - state string - stateOut string - targets []string - vars []string - varFiles []string + backup string + dir string + lock bool + lockTimeout string + reattachInfo ReattachInfo + state string + stateOut string + targets []string + vars []string + varFiles []string } var defaultRefreshOptions = refreshConfig{ @@ -44,6 +45,10 @@ func (opt *LockTimeoutOption) configureRefresh(conf *refreshConfig) { conf.lockTimeout = opt.timeout } +func (opt *ReattachOption) configureRefresh(conf *refreshConfig) { + conf.reattachInfo = opt.info +} + func (opt *StateOption) configureRefresh(conf *refreshConfig) { conf.state = opt.path } @@ -66,10 +71,14 @@ func (opt *VarFileOption) configureRefresh(conf *refreshConfig) { // Refresh represents the terraform refresh subcommand. func (tf *Terraform) Refresh(ctx context.Context, opts ...RefreshCmdOption) error { - return tf.runTerraformCmd(tf.refreshCmd(ctx, opts...)) + cmd, err := tf.refreshCmd(ctx, opts...) + if err != nil { + return err + } + return tf.runTerraformCmd(cmd) } -func (tf *Terraform) refreshCmd(ctx context.Context, opts ...RefreshCmdOption) *exec.Cmd { +func (tf *Terraform) refreshCmd(ctx context.Context, opts ...RefreshCmdOption) (*exec.Cmd, error) { c := defaultRefreshOptions for _, o := range opts { @@ -115,5 +124,14 @@ func (tf *Terraform) refreshCmd(ctx context.Context, opts ...RefreshCmdOption) * args = append(args, c.dir) } - return tf.buildTerraformCmd(ctx, args...) + mergeEnv := map[string]string{} + if c.reattachInfo != nil { + reattachStr, err := marshalReattachString(c.reattachInfo) + if err != nil { + return nil, err + } + mergeEnv[reattachEnvVar] = reattachStr + } + + return tf.buildTerraformCmd(ctx, mergeEnv, args...), nil } diff --git a/tfexec/refresh_test.go b/tfexec/refresh_test.go index 9d47ceac..67f6d5ee 100644 --- a/tfexec/refresh_test.go +++ b/tfexec/refresh_test.go @@ -21,7 +21,10 @@ func TestRefreshCmd(t *testing.T) { tf.SetEnv(map[string]string{}) t.Run("defaults", func(t *testing.T) { - refreshCmd := tf.refreshCmd(context.Background()) + refreshCmd, err := tf.refreshCmd(context.Background()) + if err != nil { + t.Fatal(err) + } assertCmd(t, []string{ "refresh", @@ -33,7 +36,10 @@ func TestRefreshCmd(t *testing.T) { }) t.Run("override all defaults", func(t *testing.T) { - refreshCmd := tf.refreshCmd(context.Background(), Backup("testbackup"), LockTimeout("200s"), State("teststate"), StateOut("teststateout"), VarFile("testvarfile"), Lock(false), Target("target1"), Target("target2"), Var("var1=foo"), Var("var2=bar"), Dir("refreshdir")) + refreshCmd, err := tf.refreshCmd(context.Background(), Backup("testbackup"), LockTimeout("200s"), State("teststate"), StateOut("teststateout"), VarFile("testvarfile"), Lock(false), Target("target1"), Target("target2"), Var("var1=foo"), Var("var2=bar"), Dir("refreshdir")) + if err != nil { + t.Fatal(err) + } assertCmd(t, []string{ "refresh", diff --git a/tfexec/show.go b/tfexec/show.go index a046b0fa..b133ef4b 100644 --- a/tfexec/show.go +++ b/tfexec/show.go @@ -8,15 +8,44 @@ import ( tfjson "github.com/hashicorp/terraform-json" ) +type showConfig struct { + reattachInfo ReattachInfo +} + +var defaultShowOptions = showConfig{} + +type ShowOption interface { + configureShow(*showConfig) +} + +func (opt *ReattachOption) configureShow(conf *showConfig) { + conf.reattachInfo = opt.info +} + // Show reads the default state path and outputs the state. // To read a state or plan file, ShowState or ShowPlan must be used instead. -func (tf *Terraform) Show(ctx context.Context) (*tfjson.State, error) { +func (tf *Terraform) Show(ctx context.Context, opts ...ShowOption) (*tfjson.State, error) { err := tf.compatible(ctx, tf0_12_0, nil) if err != nil { return nil, fmt.Errorf("terraform show -json was added in 0.12.0: %w", err) } - showCmd := tf.showCmd(ctx) + c := defaultShowOptions + + for _, o := range opts { + o.configureShow(&c) + } + + mergeEnv := map[string]string{} + if c.reattachInfo != nil { + reattachStr, err := marshalReattachString(c.reattachInfo) + if err != nil { + return nil, err + } + mergeEnv[reattachEnvVar] = reattachStr + } + + showCmd := tf.showCmd(ctx, mergeEnv) var ret tfjson.State err = tf.runTerraformCmdJSON(showCmd, &ret) @@ -33,7 +62,7 @@ func (tf *Terraform) Show(ctx context.Context) (*tfjson.State, error) { } // ShowStateFile reads a given state file and outputs the state. -func (tf *Terraform) ShowStateFile(ctx context.Context, statePath string) (*tfjson.State, error) { +func (tf *Terraform) ShowStateFile(ctx context.Context, statePath string, opts ...ShowOption) (*tfjson.State, error) { err := tf.compatible(ctx, tf0_12_0, nil) if err != nil { return nil, fmt.Errorf("terraform show -json was added in 0.12.0: %w", err) @@ -43,7 +72,22 @@ func (tf *Terraform) ShowStateFile(ctx context.Context, statePath string) (*tfjs return nil, fmt.Errorf("statePath cannot be blank: use Show() if not passing statePath") } - showCmd := tf.showCmd(ctx, statePath) + c := defaultShowOptions + + for _, o := range opts { + o.configureShow(&c) + } + + mergeEnv := map[string]string{} + if c.reattachInfo != nil { + reattachStr, err := marshalReattachString(c.reattachInfo) + if err != nil { + return nil, err + } + mergeEnv[reattachEnvVar] = reattachStr + } + + showCmd := tf.showCmd(ctx, mergeEnv, statePath) var ret tfjson.State err = tf.runTerraformCmdJSON(showCmd, &ret) @@ -60,7 +104,7 @@ func (tf *Terraform) ShowStateFile(ctx context.Context, statePath string) (*tfjs } // ShowPlanFile reads a given plan file and outputs the plan. -func (tf *Terraform) ShowPlanFile(ctx context.Context, planPath string) (*tfjson.Plan, error) { +func (tf *Terraform) ShowPlanFile(ctx context.Context, planPath string, opts ...ShowOption) (*tfjson.Plan, error) { err := tf.compatible(ctx, tf0_12_0, nil) if err != nil { return nil, fmt.Errorf("terraform show -json was added in 0.12.0: %w", err) @@ -70,7 +114,22 @@ func (tf *Terraform) ShowPlanFile(ctx context.Context, planPath string) (*tfjson return nil, fmt.Errorf("planPath cannot be blank: use Show() if not passing planPath") } - showCmd := tf.showCmd(ctx, planPath) + c := defaultShowOptions + + for _, o := range opts { + o.configureShow(&c) + } + + mergeEnv := map[string]string{} + if c.reattachInfo != nil { + reattachStr, err := marshalReattachString(c.reattachInfo) + if err != nil { + return nil, err + } + mergeEnv[reattachEnvVar] = reattachStr + } + + showCmd := tf.showCmd(ctx, mergeEnv, planPath) var ret tfjson.Plan err = tf.runTerraformCmdJSON(showCmd, &ret) @@ -87,9 +146,9 @@ func (tf *Terraform) ShowPlanFile(ctx context.Context, planPath string) (*tfjson } -func (tf *Terraform) showCmd(ctx context.Context, args ...string) *exec.Cmd { +func (tf *Terraform) showCmd(ctx context.Context, mergeEnv map[string]string, args ...string) *exec.Cmd { allArgs := []string{"show", "-json", "-no-color"} allArgs = append(allArgs, args...) - return tf.buildTerraformCmd(ctx, allArgs...) + return tf.buildTerraformCmd(ctx, mergeEnv, allArgs...) } diff --git a/tfexec/show_test.go b/tfexec/show_test.go index 381f6300..b5cdb061 100644 --- a/tfexec/show_test.go +++ b/tfexec/show_test.go @@ -21,7 +21,7 @@ func TestShowCmd(t *testing.T) { tf.SetEnv(map[string]string{}) // defaults - showCmd := tf.showCmd(context.Background()) + showCmd := tf.showCmd(context.Background(), nil) assertCmd(t, []string{ "show", @@ -42,7 +42,7 @@ func TestShowStateFileCmd(t *testing.T) { // empty env, to avoid environ mismatch in testing tf.SetEnv(map[string]string{}) - showCmd := tf.showCmd(context.Background(), "statefilepath") + showCmd := tf.showCmd(context.Background(), nil, "statefilepath") assertCmd(t, []string{ "show", @@ -64,7 +64,7 @@ func TestShowPlanFileCmd(t *testing.T) { // empty env, to avoid environ mismatch in testing tf.SetEnv(map[string]string{}) - showCmd := tf.showCmd(context.Background(), "planfilepath") + showCmd := tf.showCmd(context.Background(), nil, "planfilepath") assertCmd(t, []string{ "show", diff --git a/tfexec/terraform_test.go b/tfexec/terraform_test.go index 7dfffb47..93b8924f 100644 --- a/tfexec/terraform_test.go +++ b/tfexec/terraform_test.go @@ -85,7 +85,10 @@ func TestCheckpointDisablePropagation(t *testing.T) { t.Fatal(err) } - initCmd := tf.initCmd(context.Background()) + initCmd, err := tf.initCmd(context.Background()) + if err != nil { + t.Fatal(err) + } assertCmd(t, []string{ "init", @@ -114,7 +117,10 @@ func TestCheckpointDisablePropagation(t *testing.T) { t.Fatal(err) } - initCmd := tf.initCmd(context.Background()) + initCmd, err := tf.initCmd(context.Background()) + if err != nil { + t.Fatal(err) + } assertCmd(t, []string{ "init", diff --git a/tfexec/version.go b/tfexec/version.go index aadf369a..ae1dd5fa 100644 --- a/tfexec/version.go +++ b/tfexec/version.go @@ -38,7 +38,7 @@ func (tf *Terraform) version(ctx context.Context) (*version.Version, map[string] // TODO: 0.13.0-beta2? and above supports a `-json` on the version command, should add support // for that here and fallback to string parsing - versionCmd := tf.buildTerraformCmd(ctx, "version") + versionCmd := tf.buildTerraformCmd(ctx, nil, "version") var outBuf bytes.Buffer versionCmd.Stdout = &outBuf diff --git a/tfexec/workspace_list.go b/tfexec/workspace_list.go index cb68a2f0..edf9adab 100644 --- a/tfexec/workspace_list.go +++ b/tfexec/workspace_list.go @@ -9,7 +9,7 @@ import ( // WorkspaceList represents the workspace list subcommand to the Terraform CLI. func (tf *Terraform) WorkspaceList(ctx context.Context) ([]string, string, error) { // TODO: [DIR] param option - wlCmd := tf.buildTerraformCmd(ctx, "workspace", "list", "-no-color") + wlCmd := tf.buildTerraformCmd(ctx, nil, "workspace", "list", "-no-color") var outBuf bytes.Buffer wlCmd.Stdout = &outBuf diff --git a/tfexec/workspace_new.go b/tfexec/workspace_new.go index 4778a8c2..1925c286 100644 --- a/tfexec/workspace_new.go +++ b/tfexec/workspace_new.go @@ -77,7 +77,7 @@ func (tf *Terraform) workspaceNewCmd(ctx context.Context, workspace string, opts args = append(args, workspace) - cmd := tf.buildTerraformCmd(ctx, args...) + cmd := tf.buildTerraformCmd(ctx, nil, args...) return cmd, nil } diff --git a/tfexec/workspace_select.go b/tfexec/workspace_select.go index 90ee6022..87f5301e 100644 --- a/tfexec/workspace_select.go +++ b/tfexec/workspace_select.go @@ -6,5 +6,5 @@ import "context" func (tf *Terraform) WorkspaceSelect(ctx context.Context, workspace string) error { // TODO: [DIR] param option - return tf.runTerraformCmd(tf.buildTerraformCmd(ctx, "workspace", "select", "-no-color", workspace)) + return tf.runTerraformCmd(tf.buildTerraformCmd(ctx, nil, "workspace", "select", "-no-color", workspace)) }