diff --git a/tfexec/apply.go b/tfexec/apply.go index a6057fd1..d35e4e31 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 string + 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,6 +81,10 @@ 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...)) @@ -133,5 +138,10 @@ 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 != "" { + mergeEnv[reattachEnvVar] = c.reattachInfo + } + + return tf.buildTerraformCmd(ctx, mergeEnv, args...) } diff --git a/tfexec/cmd.go b/tfexec/cmd.go index d9422404..eb15ae40 100644 --- a/tfexec/cmd.go +++ b/tfexec/cmd.go @@ -67,10 +67,12 @@ func envSlice(environ map[string]string) []string { } func (tf *Terraform) buildEnv(mergeEnv map[string]string) []string { + osEnv := envMap(os.Environ()) + // set Terraform level env, if env is nil, fall back to os.Environ var env map[string]string if tf.env == nil { - env = envMap(os.Environ()) + env = osEnv } else { env = make(map[string]string, len(tf.env)) for k, v := range tf.env { @@ -114,12 +116,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..d2c9ed09 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 string + 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,6 +82,10 @@ 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...)) @@ -134,5 +139,10 @@ 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 != "" { + mergeEnv[reattachEnvVar] = c.reattachInfo + } + + return tf.buildTerraformCmd(ctx, mergeEnv, args...) } diff --git a/tfexec/import.go b/tfexec/import.go index 62d61ffc..8e657537 100644 --- a/tfexec/import.go +++ b/tfexec/import.go @@ -14,6 +14,7 @@ type importConfig struct { allowMissingConfig bool lock bool lockTimeout string + reattachInfo string 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 } @@ -119,5 +124,11 @@ 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 != "" { + mergeEnv[reattachEnvVar] = c.reattachInfo + } + + return tf.buildTerraformCmd(ctx, mergeEnv, args...) + } diff --git a/tfexec/init.go b/tfexec/init.go index fa8d9096..10775fb6 100644 --- a/tfexec/init.go +++ b/tfexec/init.go @@ -17,6 +17,7 @@ type initConfig struct { lock bool lockTimeout string pluginDir []string + reattachInfo string 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 } @@ -139,5 +144,10 @@ 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 != "" { + mergeEnv[reattachEnvVar] = c.reattachInfo + } + + return tf.buildTerraformCmd(ctx, mergeEnv, args...) } diff --git a/tfexec/options.go b/tfexec/options.go index 2a6e7542..549ee8e1 100644 --- a/tfexec/options.go +++ b/tfexec/options.go @@ -172,6 +172,14 @@ func PluginDir(pluginDir string) *PluginDirOption { return &PluginDirOption{pluginDir} } +type ReattachOption struct { + info string +} + +func Reattach(reattach string) *ReattachOption { + return &ReattachOption{reattach} +} + 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..9cff8b5c 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 string + 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 } @@ -145,5 +150,11 @@ 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 != "" { + mergeEnv[reattachEnvVar] = c.reattachInfo + } + + return tf.buildTerraformCmd(ctx, mergeEnv, args...) + } 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..8a4b9225 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 string + 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 } @@ -115,5 +120,11 @@ 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 != "" { + mergeEnv[reattachEnvVar] = c.reattachInfo + } + + return tf.buildTerraformCmd(ctx, mergeEnv, args...) + } diff --git a/tfexec/show.go b/tfexec/show.go index a046b0fa..05dcc390 100644 --- a/tfexec/show.go +++ b/tfexec/show.go @@ -8,15 +8,40 @@ import ( tfjson "github.com/hashicorp/terraform-json" ) +type showConfig struct { + reattachInfo string +} + +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 != "" { + mergeEnv[reattachEnvVar] = c.reattachInfo + } + + showCmd := tf.showCmd(ctx, mergeEnv) var ret tfjson.State err = tf.runTerraformCmdJSON(showCmd, &ret) @@ -33,7 +58,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 +68,18 @@ 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 != "" { + mergeEnv[reattachEnvVar] = c.reattachInfo + } + + showCmd := tf.showCmd(ctx, mergeEnv, statePath) var ret tfjson.State err = tf.runTerraformCmdJSON(showCmd, &ret) @@ -60,7 +96,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 +106,18 @@ 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 != "" { + mergeEnv[reattachEnvVar] = c.reattachInfo + } + + showCmd := tf.showCmd(ctx, mergeEnv, planPath) var ret tfjson.Plan err = tf.runTerraformCmdJSON(showCmd, &ret) @@ -87,9 +134,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/version.go b/tfexec/version.go index 88184a64..d168f80f 100644 --- a/tfexec/version.go +++ b/tfexec/version.go @@ -37,7 +37,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)) }