From 2ef0e98e9ab57b20bdd86f494fbc8fb9539dcced Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Mon, 19 Oct 2020 12:40:41 -0400 Subject: [PATCH] tfexec: Initial support for Terraform -chdir global option Reference: https://github.com/hashicorp/terraform-exec/issues/99 --- tfexec/apply.go | 31 +++++++- tfexec/apply_test.go | 44 ++++++++++- tfexec/destroy.go | 22 +++++- tfexec/destroy_test.go | 24 +++++- tfexec/fmt.go | 21 +++++- tfexec/fmt_test.go | 32 +++++++- tfexec/import.go | 23 +++++- tfexec/import_test.go | 25 ++++++- tfexec/init.go | 19 ++++- tfexec/init_test.go | 27 ++++++- tfexec/options.go | 28 +++++++ tfexec/output.go | 35 +++++++-- tfexec/output_test.go | 31 +++++++- tfexec/plan.go | 22 +++++- tfexec/plan_test.go | 24 +++++- tfexec/providers_schema.go | 51 +++++++++++-- tfexec/providers_schema_test.go | 40 ++++++++-- tfexec/refresh.go | 14 +++- tfexec/refresh_test.go | 21 +++++- tfexec/show.go | 83 ++++++++++++++++++--- tfexec/show_test.go | 127 +++++++++++++++++++++++++++++++- tfexec/version.go | 1 + tfexec/workspace_list.go | 58 ++++++++++++++- tfexec/workspace_list_test.go | 47 ++++++++++++ tfexec/workspace_new.go | 29 ++++++-- tfexec/workspace_new_test.go | 16 +++- tfexec/workspace_select.go | 62 +++++++++++++++- tfexec/workspace_select_test.go | 54 ++++++++++++++ 28 files changed, 944 insertions(+), 67 deletions(-) create mode 100644 tfexec/workspace_select_test.go diff --git a/tfexec/apply.go b/tfexec/apply.go index 82d09d5f..99373a19 100644 --- a/tfexec/apply.go +++ b/tfexec/apply.go @@ -9,12 +9,14 @@ import ( type applyConfig struct { backup string + chdir string dirOrPlan string lock bool // LockTimeout must be a string with time unit, e.g. '10s' lockTimeout string parallelism int + planArg string reattachInfo ReattachInfo refresh bool state string @@ -77,10 +79,18 @@ func (opt *VarOption) configureApply(conf *applyConfig) { conf.vars = append(conf.vars, opt.assignment) } +func (opt *ChdirOption) configureApply(conf *applyConfig) { + conf.chdir = opt.path +} + func (opt *DirOrPlanOption) configureApply(conf *applyConfig) { conf.dirOrPlan = opt.path } +func (opt *PlanArgOption) configureApply(conf *applyConfig) { + conf.planArg = opt.path +} + func (opt *ReattachOption) configureApply(conf *applyConfig) { conf.reattachInfo = opt.info } @@ -98,10 +108,25 @@ func (tf *Terraform) applyCmd(ctx context.Context, opts ...ApplyOption) (*exec.C c := defaultApplyOptions for _, o := range opts { + switch o.(type) { + case *ChdirOption: + err := tf.compatible(ctx, tf0_14_0, nil) + if err != nil { + return nil, fmt.Errorf("-chdir was added in Terraform 0.14: %w", err) + } + } + o.configureApply(&c) } - args := []string{"apply", "-no-color", "-auto-approve", "-input=false"} + var args []string + + // global opts + if c.chdir != "" { + args = append(args, "-chdir="+c.chdir) + } + + args = append(args, []string{"apply", "-no-color", "-auto-approve", "-input=false"}...) // string opts: only pass if set if c.backup != "" { @@ -142,6 +167,10 @@ func (tf *Terraform) applyCmd(ctx context.Context, opts ...ApplyOption) (*exec.C args = append(args, c.dirOrPlan) } + if c.planArg != "" { + args = append(args, c.planArg) + } + mergeEnv := map[string]string{} if c.reattachInfo != nil { reattachStr, err := c.reattachInfo.marshalString() diff --git a/tfexec/apply_test.go b/tfexec/apply_test.go index f1c6a1f2..e66b06ff 100644 --- a/tfexec/apply_test.go +++ b/tfexec/apply_test.go @@ -10,7 +10,7 @@ import ( func TestApplyCmd(t *testing.T) { td := testTempDir(t) - tf, err := NewTerraform(td, tfVersion(t, testutil.Latest012)) + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest014)) if err != nil { t.Fatal(err) } @@ -60,4 +60,46 @@ func TestApplyCmd(t *testing.T) { "testfile", }, nil, applyCmd) }) + + t.Run("chdir", func(t *testing.T) { + applyCmd, err := tf.applyCmd(context.Background(), + Chdir("testpath"), + ) + + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "-chdir=testpath", + "apply", + "-no-color", + "-auto-approve", + "-input=false", + "-lock=true", + "-parallelism=10", + "-refresh=true", + }, nil, applyCmd) + }) + + t.Run("plan", func(t *testing.T) { + applyCmd, err := tf.applyCmd(context.Background(), + PlanArg("testplan"), + ) + + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "apply", + "-no-color", + "-auto-approve", + "-input=false", + "-lock=true", + "-parallelism=10", + "-refresh=true", + "testplan", + }, nil, applyCmd) + }) } diff --git a/tfexec/destroy.go b/tfexec/destroy.go index 8011c0ba..f1513c3d 100644 --- a/tfexec/destroy.go +++ b/tfexec/destroy.go @@ -9,6 +9,7 @@ import ( type destroyConfig struct { backup string + chdir string dir string lock bool @@ -38,6 +39,10 @@ type DestroyOption interface { configureDestroy(*destroyConfig) } +func (opt *ChdirOption) configureDestroy(conf *destroyConfig) { + conf.chdir = opt.path +} + func (opt *DirOption) configureDestroy(conf *destroyConfig) { conf.dir = opt.path } @@ -99,10 +104,25 @@ func (tf *Terraform) destroyCmd(ctx context.Context, opts ...DestroyOption) (*ex c := defaultDestroyOptions for _, o := range opts { + switch o.(type) { + case *ChdirOption: + err := tf.compatible(ctx, tf0_14_0, nil) + if err != nil { + return nil, fmt.Errorf("-chdir was added in Terraform 0.14: %w", err) + } + } + o.configureDestroy(&c) } - args := []string{"destroy", "-no-color", "-auto-approve", "-input=false"} + var args []string + + // global opts + if c.chdir != "" { + args = append(args, "-chdir="+c.chdir) + } + + args = append(args, []string{"destroy", "-no-color", "-auto-approve", "-input=false"}...) // string opts: only pass if set if c.backup != "" { diff --git a/tfexec/destroy_test.go b/tfexec/destroy_test.go index 7ee20793..be5b7331 100644 --- a/tfexec/destroy_test.go +++ b/tfexec/destroy_test.go @@ -10,7 +10,7 @@ import ( func TestDestroyCmd(t *testing.T) { td := testTempDir(t) - tf, err := NewTerraform(td, tfVersion(t, testutil.Latest012)) + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest014)) if err != nil { t.Fatal(err) } @@ -62,4 +62,26 @@ func TestDestroyCmd(t *testing.T) { "destroydir", }, nil, destroyCmd) }) + + t.Run("chdir", func(t *testing.T) { + destroyCmd, err := tf.destroyCmd(context.Background(), + Chdir("testpath"), + ) + + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "-chdir=testpath", + "destroy", + "-no-color", + "-auto-approve", + "-input=false", + "-lock-timeout=0s", + "-lock=true", + "-parallelism=10", + "-refresh=true", + }, nil, destroyCmd) + }) } diff --git a/tfexec/fmt.go b/tfexec/fmt.go index 10f6cb4c..bcbcd4ff 100644 --- a/tfexec/fmt.go +++ b/tfexec/fmt.go @@ -11,8 +11,9 @@ import ( ) type formatConfig struct { - recursive bool + chdir string dir string + recursive bool } var defaultFormatConfig = formatConfig{ @@ -23,14 +24,18 @@ type FormatOption interface { configureFormat(*formatConfig) } -func (opt *RecursiveOption) configureFormat(conf *formatConfig) { - conf.recursive = opt.recursive +func (opt *ChdirOption) configureFormat(conf *formatConfig) { + conf.chdir = opt.path } func (opt *DirOption) configureFormat(conf *formatConfig) { conf.dir = opt.path } +func (opt *RecursiveOption) configureFormat(conf *formatConfig) { + conf.recursive = opt.recursive +} + // FormatString formats a passed string, given a path to Terraform. func FormatString(ctx context.Context, execPath string, content string) (string, error) { tf, err := NewTerraform(filepath.Dir(execPath), execPath) @@ -136,6 +141,11 @@ func (tf *Terraform) formatCmd(ctx context.Context, args []string, opts ...Forma for _, o := range opts { switch o.(type) { + case *ChdirOption: + err := tf.compatible(ctx, tf0_14_0, nil) + if err != nil { + return nil, fmt.Errorf("-chdir was added in Terraform 0.14: %w", err) + } case *RecursiveOption: err := tf.compatible(ctx, tf0_12_0, nil) if err != nil { @@ -148,6 +158,11 @@ func (tf *Terraform) formatCmd(ctx context.Context, args []string, opts ...Forma args = append([]string{"fmt", "-no-color"}, args...) + // global opts + if c.chdir != "" { + args = append([]string{"-chdir=" + c.chdir}, args...) + } + if c.recursive { args = append(args, "-recursive") } diff --git a/tfexec/fmt_test.go b/tfexec/fmt_test.go index 4abe1128..3b150c6c 100644 --- a/tfexec/fmt_test.go +++ b/tfexec/fmt_test.go @@ -4,12 +4,14 @@ import ( "context" "errors" "testing" + + "github.com/hashicorp/terraform-exec/tfexec/internal/testutil" ) func TestFormat(t *testing.T) { td := testTempDir(t) - tf, err := NewTerraform(td, tfVersion(t, "0.7.6")) + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest014)) if err != nil { t.Fatal(err) } @@ -17,8 +19,34 @@ func TestFormat(t *testing.T) { // empty env, to avoid environ mismatch in testing tf.SetEnv(map[string]string{}) + t.Run("chdir", func(t *testing.T) { + fmtCmd, err := tf.formatCmd(context.Background(), + []string{"testfile"}, + Chdir("testpath"), + ) + + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "-chdir=testpath", + "fmt", + "-no-color", + "testfile", + }, nil, fmtCmd) + }) + t.Run("too old version", func(t *testing.T) { - _, err := tf.formatCmd(context.Background(), []string{}) + tf, err := NewTerraform(td, tfVersion(t, "0.7.6")) + if err != nil { + t.Fatal(err) + } + + // empty env, to avoid environ mismatch in testing + tf.SetEnv(map[string]string{}) + + _, err = tf.formatCmd(context.Background(), []string{}) if err == nil { t.Fatal("expected old version to fail") } diff --git a/tfexec/import.go b/tfexec/import.go index e243d728..f70c72ac 100644 --- a/tfexec/import.go +++ b/tfexec/import.go @@ -2,6 +2,7 @@ package tfexec import ( "context" + "fmt" "os/exec" "strconv" ) @@ -10,6 +11,7 @@ type importConfig struct { addr string id string backup string + chdir string config string allowMissingConfig bool lock bool @@ -36,6 +38,10 @@ func (opt *BackupOption) configureImport(conf *importConfig) { conf.backup = opt.path } +func (opt *ChdirOption) configureImport(conf *importConfig) { + conf.chdir = opt.path +} + func (opt *ConfigOption) configureImport(conf *importConfig) { conf.config = opt.path } @@ -85,10 +91,25 @@ func (tf *Terraform) importCmd(ctx context.Context, address, id string, opts ... c := defaultImportOptions for _, o := range opts { + switch o.(type) { + case *ChdirOption: + err := tf.compatible(ctx, tf0_14_0, nil) + if err != nil { + return nil, fmt.Errorf("-chdir was added in Terraform 0.14: %w", err) + } + } + o.configureImport(&c) } - args := []string{"import", "-no-color", "-input=false"} + var args []string + + // global opts + if c.chdir != "" { + args = append(args, "-chdir="+c.chdir) + } + + args = append(args, []string{"import", "-no-color", "-input=false"}...) // string opts: only pass if set if c.backup != "" { diff --git a/tfexec/import_test.go b/tfexec/import_test.go index 2f81e768..286ca046 100644 --- a/tfexec/import_test.go +++ b/tfexec/import_test.go @@ -10,7 +10,7 @@ import ( func TestImportCmd(t *testing.T) { td := testTempDir(t) - tf, err := NewTerraform(td, tfVersion(t, testutil.Latest012)) + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest014)) if err != nil { t.Fatal(err) } @@ -68,4 +68,27 @@ func TestImportCmd(t *testing.T) { "my-id2", }, nil, importCmd) }) + + t.Run("chdir", func(t *testing.T) { + importCmd, err := tf.importCmd(context.Background(), + "my-addr", + "my-id", + Chdir("testpath"), + ) + + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "-chdir=testpath", + "import", + "-no-color", + "-input=false", + "-lock-timeout=0s", + "-lock=true", + "my-addr", + "my-id", + }, nil, importCmd) + }) } diff --git a/tfexec/init.go b/tfexec/init.go index bff9ecd3..dc13ed00 100644 --- a/tfexec/init.go +++ b/tfexec/init.go @@ -9,6 +9,7 @@ import ( type initConfig struct { backend bool backendConfig []string + chdir string dir string forceCopy bool fromModule string @@ -48,6 +49,10 @@ func (opt *BackendConfigOption) configureInit(conf *initConfig) { conf.backendConfig = append(conf.backendConfig, opt.path) } +func (opt *ChdirOption) configureInit(conf *initConfig) { + conf.chdir = opt.path +} + func (opt *DirOption) configureInit(conf *initConfig) { conf.dir = opt.path } @@ -106,6 +111,11 @@ func (tf *Terraform) initCmd(ctx context.Context, opts ...InitOption) (*exec.Cmd for _, o := range opts { switch o.(type) { + case *ChdirOption: + err := tf.compatible(ctx, tf0_14_0, nil) + if err != nil { + return nil, fmt.Errorf("-chdir was added in Terraform 0.14: %w", err) + } case *LockOption, *LockTimeoutOption, *VerifyPluginsOption, *GetPluginsOption: err := tf.compatible(ctx, nil, tf0_15_0) if err != nil { @@ -116,7 +126,14 @@ func (tf *Terraform) initCmd(ctx context.Context, opts ...InitOption) (*exec.Cmd o.configureInit(&c) } - args := []string{"init", "-no-color", "-force-copy", "-input=false"} + var args []string + + // global opts + if c.chdir != "" { + args = append(args, "-chdir="+c.chdir) + } + + args = append(args, []string{"init", "-no-color", "-force-copy", "-input=false"}...) // string opts: only pass if set if c.fromModule != "" { diff --git a/tfexec/init_test.go b/tfexec/init_test.go index 5eea7ef1..7eb9db9e 100644 --- a/tfexec/init_test.go +++ b/tfexec/init_test.go @@ -10,7 +10,7 @@ import ( func TestInitCmd(t *testing.T) { td := testTempDir(t) - tf, err := NewTerraform(td, tfVersion(t, testutil.Latest012)) + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest014)) if err != nil { t.Fatal(err) } @@ -67,4 +67,29 @@ func TestInitCmd(t *testing.T) { "initdir", }, nil, initCmd) }) + + t.Run("chdir", func(t *testing.T) { + initCmd, err := tf.initCmd(context.Background(), + Chdir("testpath"), + ) + + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "-chdir=testpath", + "init", + "-no-color", + "-force-copy", + "-input=false", + "-lock-timeout=0s", + "-backend=true", + "-get=true", + "-upgrade=false", + "-lock=true", + "-get-plugins=true", + "-verify-plugins=true", + }, nil, initCmd) + }) } diff --git a/tfexec/options.go b/tfexec/options.go index 6d20869b..20ee9010 100644 --- a/tfexec/options.go +++ b/tfexec/options.go @@ -58,6 +58,16 @@ func DisableBackup() *BackupOption { return &BackupOption{"-"} } +// ChdirOption represents the -chdir flag. +type ChdirOption struct { + path string +} + +// Chdir represents the -chdir flag. +func Chdir(path string) *ChdirOption { + return &ChdirOption{path} +} + // ConfigOption represents the -config flag. type ConfigOption struct { path string @@ -84,6 +94,7 @@ type DirOption struct { path string } +// Deprecated: Terraform 0.14.0 and later should use Chdir() func Dir(path string) *DirOption { return &DirOption{path} } @@ -92,6 +103,7 @@ type DirOrPlanOption struct { path string } +// Deprecated: Terraform 0.14.0 and later should use Chdir() and/or PlanArg() func DirOrPlan(path string) *DirOrPlanOption { return &DirOrPlanOption{path} } @@ -194,6 +206,14 @@ func Parallelism(n int) *ParallelismOption { return &ParallelismOption{n} } +type PlanArgOption struct { + path string +} + +func PlanArg(path string) *PlanArgOption { + return &PlanArgOption{path} +} + type PluginDirOption struct { pluginDir string } @@ -273,6 +293,14 @@ func State(path string) *StateOption { return &StateOption{path} } +type StateArgOption struct { + path string +} + +func StateArg(path string) *StateArgOption { + return &StateArgOption{path} +} + type StateOutOption struct { path string } diff --git a/tfexec/output.go b/tfexec/output.go index b16b8b72..db70fa4a 100644 --- a/tfexec/output.go +++ b/tfexec/output.go @@ -3,10 +3,12 @@ package tfexec import ( "context" "encoding/json" + "fmt" "os/exec" ) type outputConfig struct { + chdir string state string json bool } @@ -18,6 +20,10 @@ type OutputOption interface { configureOutput(*outputConfig) } +func (opt *ChdirOption) configureOutput(conf *outputConfig) { + conf.chdir = opt.path +} + func (opt *StateOption) configureOutput(conf *outputConfig) { conf.state = opt.path } @@ -34,10 +40,14 @@ type OutputMeta struct { // Output represents the terraform output subcommand. func (tf *Terraform) Output(ctx context.Context, opts ...OutputOption) (map[string]OutputMeta, error) { - outputCmd := tf.outputCmd(ctx, opts...) + outputCmd, err := tf.outputCmd(ctx, opts...) + + if err != nil { + return nil, err + } outputs := map[string]OutputMeta{} - err := tf.runTerraformCmdJSON(ctx, outputCmd, &outputs) + err = tf.runTerraformCmdJSON(ctx, outputCmd, &outputs) if err != nil { return nil, err } @@ -45,19 +55,34 @@ func (tf *Terraform) Output(ctx context.Context, opts ...OutputOption) (map[stri return outputs, nil } -func (tf *Terraform) outputCmd(ctx context.Context, opts ...OutputOption) *exec.Cmd { +func (tf *Terraform) outputCmd(ctx context.Context, opts ...OutputOption) (*exec.Cmd, error) { c := defaultOutputOptions for _, o := range opts { + switch o.(type) { + case *ChdirOption: + err := tf.compatible(ctx, tf0_14_0, nil) + if err != nil { + return nil, fmt.Errorf("-chdir was added in Terraform 0.14: %w", err) + } + } + o.configureOutput(&c) } - args := []string{"output", "-no-color", "-json"} + var args []string + + // global opts + if c.chdir != "" { + args = append(args, "-chdir="+c.chdir) + } + + args = append(args, []string{"output", "-no-color", "-json"}...) // string opts: only pass if set if c.state != "" { args = append(args, "-state="+c.state) } - return tf.buildTerraformCmd(ctx, nil, args...) + return tf.buildTerraformCmd(ctx, nil, args...), nil } diff --git a/tfexec/output_test.go b/tfexec/output_test.go index 01249b53..a496316c 100644 --- a/tfexec/output_test.go +++ b/tfexec/output_test.go @@ -10,7 +10,7 @@ import ( func TestOutputCmd(t *testing.T) { td := testTempDir(t) - tf, err := NewTerraform(td, tfVersion(t, testutil.Latest012)) + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest014)) if err != nil { t.Fatal(err) } @@ -19,7 +19,11 @@ func TestOutputCmd(t *testing.T) { tf.SetEnv(map[string]string{}) t.Run("defaults", func(t *testing.T) { - outputCmd := tf.outputCmd(context.Background()) + outputCmd, err := tf.outputCmd(context.Background()) + + if err != nil { + t.Fatal(err) + } assertCmd(t, []string{ "output", @@ -29,9 +33,13 @@ func TestOutputCmd(t *testing.T) { }) t.Run("override all defaults", func(t *testing.T) { - outputCmd := tf.outputCmd(context.Background(), + outputCmd, err := tf.outputCmd(context.Background(), State("teststate")) + if err != nil { + t.Fatal(err) + } + assertCmd(t, []string{ "output", "-no-color", @@ -39,4 +47,21 @@ func TestOutputCmd(t *testing.T) { "-state=teststate", }, nil, outputCmd) }) + + t.Run("chdir", func(t *testing.T) { + outputCmd, err := tf.outputCmd(context.Background(), + Chdir("testpath"), + ) + + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "-chdir=testpath", + "output", + "-no-color", + "-json", + }, nil, outputCmd) + }) } diff --git a/tfexec/plan.go b/tfexec/plan.go index bfe77db7..8ee5b201 100644 --- a/tfexec/plan.go +++ b/tfexec/plan.go @@ -8,6 +8,7 @@ import ( ) type planConfig struct { + chdir string destroy bool dir string lock bool @@ -35,6 +36,10 @@ type PlanOption interface { configurePlan(*planConfig) } +func (opt *ChdirOption) configurePlan(conf *planConfig) { + conf.chdir = opt.path +} + func (opt *DirOption) configurePlan(conf *planConfig) { conf.dir = opt.path } @@ -107,10 +112,25 @@ func (tf *Terraform) planCmd(ctx context.Context, opts ...PlanOption) (*exec.Cmd c := defaultPlanOptions for _, o := range opts { + switch o.(type) { + case *ChdirOption: + err := tf.compatible(ctx, tf0_14_0, nil) + if err != nil { + return nil, fmt.Errorf("-chdir was added in Terraform 0.14: %w", err) + } + } + o.configurePlan(&c) } - args := []string{"plan", "-no-color", "-input=false", "-detailed-exitcode"} + var args []string + + // global opts + if c.chdir != "" { + args = append(args, "-chdir="+c.chdir) + } + + args = append(args, []string{"plan", "-no-color", "-input=false", "-detailed-exitcode"}...) // string opts: only pass if set if c.lockTimeout != "" { diff --git a/tfexec/plan_test.go b/tfexec/plan_test.go index b30c74f8..714aeaab 100644 --- a/tfexec/plan_test.go +++ b/tfexec/plan_test.go @@ -10,7 +10,7 @@ import ( func TestPlanCmd(t *testing.T) { td := testTempDir(t) - tf, err := NewTerraform(td, tfVersion(t, testutil.Latest012)) + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest014)) if err != nil { t.Fatal(err) } @@ -62,4 +62,26 @@ func TestPlanCmd(t *testing.T) { "earth", }, nil, planCmd) }) + + t.Run("chdir", func(t *testing.T) { + planCmd, err := tf.planCmd(context.Background(), + Chdir("testpath"), + ) + + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "-chdir=testpath", + "plan", + "-no-color", + "-input=false", + "-detailed-exitcode", + "-lock-timeout=0s", + "-lock=true", + "-parallelism=10", + "-refresh=true", + }, nil, planCmd) + }) } diff --git a/tfexec/providers_schema.go b/tfexec/providers_schema.go index 52efc5db..ad1efcce 100644 --- a/tfexec/providers_schema.go +++ b/tfexec/providers_schema.go @@ -2,17 +2,36 @@ package tfexec import ( "context" + "fmt" "os/exec" tfjson "github.com/hashicorp/terraform-json" ) +type providersSchemaConfig struct { + chdir string +} + +var defaultProvidersSchemaOptions = providersSchemaConfig{} + +type ProvidersSchemaOption interface { + configureProvidersSchema(*providersSchemaConfig) +} + +func (opt *ChdirOption) configureProvidersSchema(conf *providersSchemaConfig) { + conf.chdir = opt.path +} + // ProvidersSchema represents the terraform providers schema -json subcommand. func (tf *Terraform) ProvidersSchema(ctx context.Context) (*tfjson.ProviderSchemas, error) { - schemaCmd := tf.providersSchemaCmd(ctx) + schemaCmd, err := tf.providersSchemaCmd(ctx) + + if err != nil { + return nil, err + } var ret tfjson.ProviderSchemas - err := tf.runTerraformCmdJSON(ctx, schemaCmd, &ret) + err = tf.runTerraformCmdJSON(ctx, schemaCmd, &ret) if err != nil { return nil, err } @@ -25,9 +44,29 @@ func (tf *Terraform) ProvidersSchema(ctx context.Context) (*tfjson.ProviderSchem return &ret, nil } -func (tf *Terraform) providersSchemaCmd(ctx context.Context, args ...string) *exec.Cmd { - allArgs := []string{"providers", "schema", "-json", "-no-color"} - allArgs = append(allArgs, args...) +func (tf *Terraform) providersSchemaCmd(ctx context.Context, opts ...ProvidersSchemaOption) (*exec.Cmd, error) { + c := defaultProvidersSchemaOptions + + for _, o := range opts { + switch o.(type) { + case *ChdirOption: + err := tf.compatible(ctx, tf0_14_0, nil) + if err != nil { + return nil, fmt.Errorf("-chdir was added in Terraform 0.14: %w", err) + } + } + + o.configureProvidersSchema(&c) + } + + var args []string + + // global opts + if c.chdir != "" { + args = append(args, "-chdir="+c.chdir) + } + + args = append(args, []string{"providers", "schema", "-json", "-no-color"}...) - return tf.buildTerraformCmd(ctx, nil, allArgs...) + return tf.buildTerraformCmd(ctx, nil, args...), nil } diff --git a/tfexec/providers_schema_test.go b/tfexec/providers_schema_test.go index 640e2661..20005746 100644 --- a/tfexec/providers_schema_test.go +++ b/tfexec/providers_schema_test.go @@ -10,7 +10,7 @@ import ( func TestProvidersSchemaCmd(t *testing.T) { td := testTempDir(t) - tf, err := NewTerraform(td, tfVersion(t, testutil.Latest012)) + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest014)) if err != nil { t.Fatal(err) } @@ -18,12 +18,36 @@ func TestProvidersSchemaCmd(t *testing.T) { // empty env, to avoid environ mismatch in testing tf.SetEnv(map[string]string{}) - schemaCmd := tf.providersSchemaCmd(context.Background()) + t.Run("defaults", func(t *testing.T) { + schemaCmd, err := tf.providersSchemaCmd(context.Background()) - assertCmd(t, []string{ - "providers", - "schema", - "-json", - "-no-color", - }, nil, schemaCmd) + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "providers", + "schema", + "-json", + "-no-color", + }, nil, schemaCmd) + }) + + t.Run("chdir", func(t *testing.T) { + schemaCmd, err := tf.providersSchemaCmd(context.Background(), + Chdir("testpath"), + ) + + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "-chdir=testpath", + "providers", + "schema", + "-json", + "-no-color", + }, nil, schemaCmd) + }) } diff --git a/tfexec/refresh.go b/tfexec/refresh.go index 78f6b4b5..e939fcfd 100644 --- a/tfexec/refresh.go +++ b/tfexec/refresh.go @@ -8,6 +8,7 @@ import ( type refreshConfig struct { backup string + chdir string dir string lock bool lockTimeout string @@ -33,6 +34,10 @@ func (opt *BackupOption) configureRefresh(conf *refreshConfig) { conf.backup = opt.path } +func (opt *ChdirOption) configureRefresh(conf *refreshConfig) { + conf.chdir = opt.path +} + func (opt *DirOption) configureRefresh(conf *refreshConfig) { conf.dir = opt.path } @@ -85,7 +90,14 @@ func (tf *Terraform) refreshCmd(ctx context.Context, opts ...RefreshCmdOption) ( o.configureRefresh(&c) } - args := []string{"refresh", "-no-color", "-input=false"} + var args []string + + // global opts + if c.chdir != "" { + args = append(args, "-chdir="+c.chdir) + } + + args = append(args, []string{"refresh", "-no-color", "-input=false"}...) // string opts: only pass if set if c.backup != "" { diff --git a/tfexec/refresh_test.go b/tfexec/refresh_test.go index e17d9938..198e55dd 100644 --- a/tfexec/refresh_test.go +++ b/tfexec/refresh_test.go @@ -10,7 +10,7 @@ import ( func TestRefreshCmd(t *testing.T) { td := testTempDir(t) - tf, err := NewTerraform(td, tfVersion(t, testutil.Latest013)) + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest014)) if err != nil { t.Fatal(err) } @@ -56,4 +56,23 @@ func TestRefreshCmd(t *testing.T) { "refreshdir", }, nil, refreshCmd) }) + + t.Run("chdir", func(t *testing.T) { + refreshCmd, err := tf.refreshCmd(context.Background(), + Chdir("testpath"), + ) + + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "-chdir=testpath", + "refresh", + "-no-color", + "-input=false", + "-lock-timeout=0s", + "-lock=true", + }, nil, refreshCmd) + }) } diff --git a/tfexec/show.go b/tfexec/show.go index a8d67f1a..77c57416 100644 --- a/tfexec/show.go +++ b/tfexec/show.go @@ -10,7 +10,10 @@ import ( ) type showConfig struct { + chdir string + planArg string reattachInfo ReattachInfo + stateArg string } var defaultShowOptions = showConfig{} @@ -19,6 +22,18 @@ type ShowOption interface { configureShow(*showConfig) } +func (opt *ChdirOption) configureShow(conf *showConfig) { + conf.chdir = opt.path +} + +func (opt *PlanArgOption) configureShow(conf *showConfig) { + conf.planArg = opt.path +} + +func (opt *StateArgOption) configureShow(conf *showConfig) { + conf.stateArg = opt.path +} + func (opt *ReattachOption) configureShow(conf *showConfig) { conf.reattachInfo = opt.info } @@ -46,7 +61,11 @@ func (tf *Terraform) Show(ctx context.Context, opts ...ShowOption) (*tfjson.Stat mergeEnv[reattachEnvVar] = reattachStr } - showCmd := tf.showCmd(ctx, true, mergeEnv) + showCmd, err := tf.showCmd(ctx, true, mergeEnv) + + if err != nil { + return nil, err + } var ret tfjson.State ret.UseJSONNumber(true) @@ -89,7 +108,11 @@ func (tf *Terraform) ShowStateFile(ctx context.Context, statePath string, opts . mergeEnv[reattachEnvVar] = reattachStr } - showCmd := tf.showCmd(ctx, true, mergeEnv, statePath) + showCmd, err := tf.showCmd(ctx, true, mergeEnv, append(opts, StateArg(statePath))...) + + if err != nil { + return nil, err + } var ret tfjson.State ret.UseJSONNumber(true) @@ -132,7 +155,11 @@ func (tf *Terraform) ShowPlanFile(ctx context.Context, planPath string, opts ... mergeEnv[reattachEnvVar] = reattachStr } - showCmd := tf.showCmd(ctx, true, mergeEnv, planPath) + showCmd, err := tf.showCmd(ctx, true, mergeEnv, append(opts, PlanArg(planPath))...) + + if err != nil { + return nil, err + } var ret tfjson.Plan err = tf.runTerraformCmdJSON(ctx, showCmd, &ret) @@ -171,11 +198,15 @@ func (tf *Terraform) ShowPlanFileRaw(ctx context.Context, planPath string, opts mergeEnv[reattachEnvVar] = reattachStr } - showCmd := tf.showCmd(ctx, false, mergeEnv, planPath) + showCmd, err := tf.showCmd(ctx, false, mergeEnv, append(opts, PlanArg(planPath))...) + + if err != nil { + return "", err + } var ret bytes.Buffer showCmd.Stdout = &ret - err := tf.runTerraformCmd(ctx, showCmd) + err = tf.runTerraformCmd(ctx, showCmd) if err != nil { return "", err } @@ -184,13 +215,43 @@ func (tf *Terraform) ShowPlanFileRaw(ctx context.Context, planPath string, opts } -func (tf *Terraform) showCmd(ctx context.Context, jsonOutput bool, mergeEnv map[string]string, args ...string) *exec.Cmd { - allArgs := []string{"show"} +func (tf *Terraform) showCmd(ctx context.Context, jsonOutput bool, mergeEnv map[string]string, opts ...ShowOption) (*exec.Cmd, error) { + c := defaultShowOptions + + for _, o := range opts { + switch o.(type) { + case *ChdirOption: + err := tf.compatible(ctx, tf0_14_0, nil) + if err != nil { + return nil, fmt.Errorf("-chdir was added in Terraform 0.14: %w", err) + } + } + + o.configureShow(&c) + } + + var args []string + + // global opts + if c.chdir != "" { + args = append(args, "-chdir="+c.chdir) + } + + args = append(args, "show") + if jsonOutput { - allArgs = append(allArgs, "-json") + args = append(args, "-json") + } + + args = append(args, "-no-color") + + if c.planArg != "" { + args = append(args, c.planArg) + } + + if c.stateArg != "" { + args = append(args, c.stateArg) } - allArgs = append(allArgs, "-no-color") - allArgs = append(allArgs, args...) - return tf.buildTerraformCmd(ctx, mergeEnv, allArgs...) + return tf.buildTerraformCmd(ctx, mergeEnv, args...), nil } diff --git a/tfexec/show_test.go b/tfexec/show_test.go index b33ab6c2..0de9de73 100644 --- a/tfexec/show_test.go +++ b/tfexec/show_test.go @@ -19,9 +19,39 @@ func TestShowCmd(t *testing.T) { tf.SetEnv(map[string]string{}) // defaults - showCmd := tf.showCmd(context.Background(), true, nil) + showCmd, err := tf.showCmd(context.Background(), true, nil) + + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "show", + "-json", + "-no-color", + }, nil, showCmd) +} + +func TestShowCmdChdir(t *testing.T) { + td := testTempDir(t) + + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest014)) + if err != nil { + t.Fatal(err) + } + + // empty env, to avoid environ mismatch in testing + tf.SetEnv(map[string]string{}) + + // defaults + showCmd, err := tf.showCmd(context.Background(), true, nil, Chdir("testpath")) + + if err != nil { + t.Fatal(err) + } assertCmd(t, []string{ + "-chdir=testpath", "show", "-json", "-no-color", @@ -39,9 +69,39 @@ func TestShowStateFileCmd(t *testing.T) { // empty env, to avoid environ mismatch in testing tf.SetEnv(map[string]string{}) - showCmd := tf.showCmd(context.Background(), true, nil, "statefilepath") + showCmd, err := tf.showCmd(context.Background(), true, nil, StateArg("statefilepath")) + + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "show", + "-json", + "-no-color", + "statefilepath", + }, nil, showCmd) +} + +func TestShowStateFileCmdChdir(t *testing.T) { + td := testTempDir(t) + + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest014)) + if err != nil { + t.Fatal(err) + } + + // empty env, to avoid environ mismatch in testing + tf.SetEnv(map[string]string{}) + + showCmd, err := tf.showCmd(context.Background(), true, nil, Chdir("testpath"), StateArg("statefilepath")) + + if err != nil { + t.Fatal(err) + } assertCmd(t, []string{ + "-chdir=testpath", "show", "-json", "-no-color", @@ -60,9 +120,39 @@ func TestShowPlanFileCmd(t *testing.T) { // empty env, to avoid environ mismatch in testing tf.SetEnv(map[string]string{}) - showCmd := tf.showCmd(context.Background(), true, nil, "planfilepath") + showCmd, err := tf.showCmd(context.Background(), true, nil, PlanArg("planfilepath")) + + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "show", + "-json", + "-no-color", + "planfilepath", + }, nil, showCmd) +} + +func TestShowPlanFileCmdChdir(t *testing.T) { + td := testTempDir(t) + + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest014)) + if err != nil { + t.Fatal(err) + } + + // empty env, to avoid environ mismatch in testing + tf.SetEnv(map[string]string{}) + + showCmd, err := tf.showCmd(context.Background(), true, nil, Chdir("testpath"), PlanArg("planfilepath")) + + if err != nil { + t.Fatal(err) + } assertCmd(t, []string{ + "-chdir=testpath", "show", "-json", "-no-color", @@ -81,9 +171,38 @@ func TestShowPlanFileRawCmd(t *testing.T) { // empty env, to avoid environ mismatch in testing tf.SetEnv(map[string]string{}) - showCmd := tf.showCmd(context.Background(), false, nil, "planfilepath") + showCmd, err := tf.showCmd(context.Background(), false, nil, PlanArg("planfilepath")) + + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "show", + "-no-color", + "planfilepath", + }, nil, showCmd) +} + +func TestShowPlanFileRawCmdChdir(t *testing.T) { + td := testTempDir(t) + + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest014)) + if err != nil { + t.Fatal(err) + } + + // empty env, to avoid environ mismatch in testing + tf.SetEnv(map[string]string{}) + + showCmd, err := tf.showCmd(context.Background(), false, nil, Chdir("testpath"), PlanArg("planfilepath")) + + if err != nil { + t.Fatal(err) + } assertCmd(t, []string{ + "-chdir=testpath", "show", "-no-color", "planfilepath", diff --git a/tfexec/version.go b/tfexec/version.go index 9e05b056..f2ae89bf 100644 --- a/tfexec/version.go +++ b/tfexec/version.go @@ -15,6 +15,7 @@ var ( tf0_7_7 = version.Must(version.NewVersion("0.7.7")) tf0_12_0 = version.Must(version.NewVersion("0.12.0")) tf0_13_0 = version.Must(version.NewVersion("0.13.0")) + tf0_14_0 = version.Must(version.NewVersion("0.14.0")) tf0_15_0 = version.Must(version.NewVersion("0.15.0")) ) diff --git a/tfexec/workspace_list.go b/tfexec/workspace_list.go index b8d03094..9f88f352 100644 --- a/tfexec/workspace_list.go +++ b/tfexec/workspace_list.go @@ -3,18 +3,39 @@ package tfexec import ( "bytes" "context" + "fmt" + "os/exec" "strings" ) +type workspaceListConfig struct { + chdir string +} + +var defaultWorkspaceListOptions = workspaceListConfig{} + +// WorkspaceListCmdOption represents options that are applicable to the WorkspaceList method. +type WorkspaceListCmdOption interface { + configureWorkspaceList(*workspaceListConfig) +} + +func (opt *ChdirOption) configureWorkspaceList(conf *workspaceListConfig) { + conf.chdir = opt.path +} + // WorkspaceList represents the workspace list subcommand to the Terraform CLI. -func (tf *Terraform) WorkspaceList(ctx context.Context) ([]string, string, error) { +func (tf *Terraform) WorkspaceList(ctx context.Context, opts ...WorkspaceListCmdOption) ([]string, string, error) { // TODO: [DIR] param option - wlCmd := tf.buildTerraformCmd(ctx, nil, "workspace", "list", "-no-color") + wlCmd, err := tf.workspaceListCmd(ctx, opts...) + + if err != nil { + return nil, "", err + } var outBuf bytes.Buffer wlCmd.Stdout = &outBuf - err := tf.runTerraformCmd(ctx, wlCmd) + err = tf.runTerraformCmd(ctx, wlCmd) if err != nil { return nil, "", err } @@ -24,6 +45,37 @@ func (tf *Terraform) WorkspaceList(ctx context.Context) ([]string, string, error return ws, current, nil } +func (tf *Terraform) workspaceListCmd(ctx context.Context, opts ...WorkspaceListCmdOption) (*exec.Cmd, error) { + // TODO: [DIR] param option + + c := defaultWorkspaceListOptions + + for _, o := range opts { + switch o.(type) { + case *ChdirOption: + err := tf.compatible(ctx, tf0_14_0, nil) + if err != nil { + return nil, fmt.Errorf("-chdir was added in Terraform 0.14: %w", err) + } + } + + o.configureWorkspaceList(&c) + } + + var args []string + + // global opts + if c.chdir != "" { + args = append(args, "-chdir="+c.chdir) + } + + args = append(args, []string{"workspace", "list", "-no-color"}...) + + cmd := tf.buildTerraformCmd(ctx, nil, args...) + + return cmd, nil +} + const currentWorkspacePrefix = "* " func parseWorkspaceList(stdout string) ([]string, string) { diff --git a/tfexec/workspace_list_test.go b/tfexec/workspace_list_test.go index 9064418a..2df345af 100644 --- a/tfexec/workspace_list_test.go +++ b/tfexec/workspace_list_test.go @@ -1,9 +1,13 @@ package tfexec import ( + "context" "fmt" + "os" "reflect" "testing" + + "github.com/hashicorp/terraform-exec/tfexec/internal/testutil" ) func TestParseWorkspaceList(t *testing.T) { @@ -55,3 +59,46 @@ func TestParseWorkspaceList(t *testing.T) { }) } } + +func TestWorkspaceListCmd(t *testing.T) { + td := testTempDir(t) + + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest014)) + if err != nil { + t.Fatal(err) + } + + // empty env, to avoid environ mismatch in testing + tf.SetEnv(map[string]string{ + // propagate for temp dirs + "TMPDIR": os.Getenv("TMPDIR"), + "TMP": os.Getenv("TMP"), + "TEMP": os.Getenv("TEMP"), + "USERPROFILE": os.Getenv("USERPROFILE"), + }) + + t.Run("defaults", func(t *testing.T) { + workspaceListCmd, err := tf.workspaceListCmd(context.Background()) + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "workspace", "list", + "-no-color", + }, nil, workspaceListCmd) + }) + + t.Run("chdir", func(t *testing.T) { + workspaceListCmd, err := tf.workspaceListCmd(context.Background(), Chdir("testpath")) + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "-chdir=testpath", + "workspace", "list", + "-no-color", + }, nil, workspaceListCmd) + }) +} diff --git a/tfexec/workspace_new.go b/tfexec/workspace_new.go index 2e05ffdb..7eea8140 100644 --- a/tfexec/workspace_new.go +++ b/tfexec/workspace_new.go @@ -8,9 +8,10 @@ import ( ) type workspaceNewConfig struct { + chdir string + copyState string lock bool lockTimeout string - copyState string } var defaultWorkspaceNewOptions = workspaceNewConfig{ @@ -23,6 +24,14 @@ type WorkspaceNewCmdOption interface { configureWorkspaceNew(*workspaceNewConfig) } +func (opt *ChdirOption) configureWorkspaceNew(conf *workspaceNewConfig) { + conf.chdir = opt.path +} + +func (opt *CopyStateOption) configureWorkspaceNew(conf *workspaceNewConfig) { + conf.copyState = opt.path +} + func (opt *LockOption) configureWorkspaceNew(conf *workspaceNewConfig) { conf.lock = opt.lock } @@ -31,10 +40,6 @@ func (opt *LockTimeoutOption) configureWorkspaceNew(conf *workspaceNewConfig) { conf.lockTimeout = opt.timeout } -func (opt *CopyStateOption) configureWorkspaceNew(conf *workspaceNewConfig) { - conf.copyState = opt.path -} - // WorkspaceNew represents the workspace new subcommand to the Terraform CLI. func (tf *Terraform) WorkspaceNew(ctx context.Context, workspace string, opts ...WorkspaceNewCmdOption) error { cmd, err := tf.workspaceNewCmd(ctx, workspace, opts...) @@ -51,6 +56,11 @@ func (tf *Terraform) workspaceNewCmd(ctx context.Context, workspace string, opts for _, o := range opts { switch o.(type) { + case *ChdirOption: + err := tf.compatible(ctx, tf0_14_0, nil) + if err != nil { + return nil, fmt.Errorf("-chdir was added in Terraform 0.14: %w", err) + } case *LockOption, *LockTimeoutOption: err := tf.compatible(ctx, tf0_12_0, nil) if err != nil { @@ -61,7 +71,14 @@ func (tf *Terraform) workspaceNewCmd(ctx context.Context, workspace string, opts o.configureWorkspaceNew(&c) } - args := []string{"workspace", "new", "-no-color"} + var args []string + + // global opts + if c.chdir != "" { + args = append(args, "-chdir="+c.chdir) + } + + args = append(args, []string{"workspace", "new", "-no-color"}...) if c.lockTimeout != "" && c.lockTimeout != defaultWorkspaceNewOptions.lockTimeout { // only pass if not default, so we don't need to worry about the 0.11 version check diff --git a/tfexec/workspace_new_test.go b/tfexec/workspace_new_test.go index eaf7c9db..7d1d438b 100644 --- a/tfexec/workspace_new_test.go +++ b/tfexec/workspace_new_test.go @@ -11,7 +11,7 @@ import ( func TestWorkspaceNewCmd(t *testing.T) { td := testTempDir(t) - tf, err := NewTerraform(td, tfVersion(t, testutil.Latest013)) + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest014)) if err != nil { t.Fatal(err) } @@ -53,4 +53,18 @@ func TestWorkspaceNewCmd(t *testing.T) { "workspace-name", }, nil, workspaceNewCmd) }) + + t.Run("chdir", func(t *testing.T) { + workspaceNewCmd, err := tf.workspaceNewCmd(context.Background(), "workspace-name", Chdir("testpath")) + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "-chdir=testpath", + "workspace", "new", + "-no-color", + "workspace-name", + }, nil, workspaceNewCmd) + }) } diff --git a/tfexec/workspace_select.go b/tfexec/workspace_select.go index 5a51330f..3be0db6d 100644 --- a/tfexec/workspace_select.go +++ b/tfexec/workspace_select.go @@ -1,10 +1,66 @@ package tfexec -import "context" +import ( + "context" + "fmt" + "os/exec" +) + +type workspaceSelectConfig struct { + chdir string +} + +var defaultWorkspaceSelectOptions = workspaceSelectConfig{} + +// WorkspaceSelectCmdOption represents options that are applicable to the WorkspaceSelect method. +type WorkspaceSelectCmdOption interface { + configureWorkspaceSelect(*workspaceSelectConfig) +} + +func (opt *ChdirOption) configureWorkspaceSelect(conf *workspaceSelectConfig) { + conf.chdir = opt.path +} // WorkspaceSelect represents the workspace select subcommand to the Terraform CLI. -func (tf *Terraform) WorkspaceSelect(ctx context.Context, workspace string) error { +func (tf *Terraform) WorkspaceSelect(ctx context.Context, workspace string, opts ...WorkspaceSelectCmdOption) error { + // TODO: [DIR] param option + + cmd, err := tf.workspaceSelectCmd(ctx, workspace, opts...) + + if err != nil { + return err + } + + return tf.runTerraformCmd(ctx, cmd) +} + +func (tf *Terraform) workspaceSelectCmd(ctx context.Context, workspace string, opts ...WorkspaceSelectCmdOption) (*exec.Cmd, error) { // TODO: [DIR] param option - return tf.runTerraformCmd(ctx, tf.buildTerraformCmd(ctx, nil, "workspace", "select", "-no-color", workspace)) + c := defaultWorkspaceSelectOptions + + for _, o := range opts { + switch o.(type) { + case *ChdirOption: + err := tf.compatible(ctx, tf0_14_0, nil) + if err != nil { + return nil, fmt.Errorf("-chdir was added in Terraform 0.14: %w", err) + } + } + + o.configureWorkspaceSelect(&c) + } + + var args []string + + // global opts + if c.chdir != "" { + args = append(args, "-chdir="+c.chdir) + } + + args = append(args, []string{"workspace", "select", "-no-color", workspace}...) + + cmd := tf.buildTerraformCmd(ctx, nil, args...) + + return cmd, nil } diff --git a/tfexec/workspace_select_test.go b/tfexec/workspace_select_test.go new file mode 100644 index 00000000..cbdd15a5 --- /dev/null +++ b/tfexec/workspace_select_test.go @@ -0,0 +1,54 @@ +package tfexec + +import ( + "context" + "os" + "testing" + + "github.com/hashicorp/terraform-exec/tfexec/internal/testutil" +) + +func TestWorkspaceSelectCmd(t *testing.T) { + td := testTempDir(t) + + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest014)) + if err != nil { + t.Fatal(err) + } + + // empty env, to avoid environ mismatch in testing + tf.SetEnv(map[string]string{ + // propagate for temp dirs + "TMPDIR": os.Getenv("TMPDIR"), + "TMP": os.Getenv("TMP"), + "TEMP": os.Getenv("TEMP"), + "USERPROFILE": os.Getenv("USERPROFILE"), + }) + + t.Run("defaults", func(t *testing.T) { + workspaceSelectCmd, err := tf.workspaceSelectCmd(context.Background(), "testworkspace") + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "workspace", "select", + "-no-color", + "testworkspace", + }, nil, workspaceSelectCmd) + }) + + t.Run("chdir", func(t *testing.T) { + workspaceSelectCmd, err := tf.workspaceSelectCmd(context.Background(), "testworkspace", Chdir("testpath")) + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "-chdir=testpath", + "workspace", "select", + "-no-color", + "testworkspace", + }, nil, workspaceSelectCmd) + }) +}