diff --git a/tfexec/add.go b/tfexec/add.go new file mode 100644 index 00000000..0e60034e --- /dev/null +++ b/tfexec/add.go @@ -0,0 +1,101 @@ +package tfexec + +import ( + "context" + "fmt" + "os/exec" + "strconv" + "strings" +) + +type addConfig struct { + fromState bool + out string + includeOptional bool + provider string + reattachInfo ReattachInfo +} + +var defaultAddOptions = addConfig{} + +type AddOption interface { + configureAdd(*addConfig) +} + +func (opt *FromStateOption) configureAdd(conf *addConfig) { + conf.fromState = opt.fromState +} + +func (opt *OutOption) configureAdd(conf *addConfig) { + conf.out = opt.path +} + +func (opt *IncludeOptionalOption) configureAdd(conf *addConfig) { + conf.includeOptional = opt.includeOptional +} + +func (opt *ProviderOption) configureAdd(conf *addConfig) { + conf.provider = opt.provider +} + +func (opt *ReattachOption) configureAdd(conf *addConfig) { + conf.reattachInfo = opt.info +} + +// Add represents the `terraform add` subcommand (added in 1.1.0). +// +// Note that this function signature and behaviour is subject +// to breaking changes including removal of that function +// until final 1.1.0 Terraform version (with this command) is released. +func (tf *Terraform) Add(ctx context.Context, address string, opts ...AddOption) (string, error) { + cmd, err := tf.addCmd(ctx, address, opts...) + if err != nil { + return "", err + } + + var outBuf strings.Builder + cmd.Stdout = mergeWriters(cmd.Stdout, &outBuf) + + if err := tf.runTerraformCmd(ctx, cmd); err != nil { + return "", err + } + + return outBuf.String(), nil +} + +func (tf *Terraform) addCmd(ctx context.Context, address string, opts ...AddOption) (*exec.Cmd, error) { + err := tf.compatible(ctx, tf1_1_0, nil) + if err != nil { + return nil, fmt.Errorf("terraform add was added in 1.1.0: %w", err) + } + + c := defaultAddOptions + + for _, o := range opts { + o.configureAdd(&c) + } + + args := []string{"add"} + + args = append(args, "-from-state="+strconv.FormatBool(c.fromState)) + if c.out != "" { + args = append(args, "-out="+c.out) + } + args = append(args, "-optional="+strconv.FormatBool(c.includeOptional)) + if c.provider != "" { + args = append(args, "-provider="+c.provider) + } + + args = append(args, address) + + mergeEnv := map[string]string{} + if c.reattachInfo != nil { + reattachStr, err := c.reattachInfo.marshalString() + if err != nil { + return nil, err + } + mergeEnv[reattachEnvVar] = reattachStr + } + + return tf.buildTerraformCmd(ctx, mergeEnv, args...), nil +} diff --git a/tfexec/add_test.go b/tfexec/add_test.go new file mode 100644 index 00000000..1af63cc7 --- /dev/null +++ b/tfexec/add_test.go @@ -0,0 +1,56 @@ +package tfexec + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-exec/tfexec/internal/testutil" +) + +func TestAddCmd(t *testing.T) { + td := testTempDir(t) + + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest_v1_1)) + if err != nil { + t.Fatal(err) + } + + // empty env, to avoid environ mismatch in testing + tf.SetEnv(map[string]string{}) + + t.Run("default", func(t *testing.T) { + addCmd, err := tf.addCmd(context.Background(), "my-addr") + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "add", + "-from-state=false", + "-optional=false", + "my-addr", + }, nil, addCmd) + }) + + t.Run("override-default", func(t *testing.T) { + addCmd, err := tf.addCmd(context.Background(), + "my-addr", + FromState(true), + Out("out"), + IncludeOptional(true), + Provider("my-provider"), + ) + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "add", + "-from-state=true", + "-out=out", + "-optional=true", + "-provider=my-provider", + "my-addr", + }, nil, addCmd) + }) +} diff --git a/tfexec/internal/testutil/tfcache.go b/tfexec/internal/testutil/tfcache.go index 4f3bcd3a..4fac9317 100644 --- a/tfexec/internal/testutil/tfcache.go +++ b/tfexec/internal/testutil/tfcache.go @@ -9,12 +9,13 @@ import ( ) const ( - Latest011 = "0.11.15" - Latest012 = "0.12.31" - Latest013 = "0.13.7" - Latest014 = "0.14.11" - Latest015 = "0.15.5" - Latest_v1 = "1.0.0" + Latest011 = "0.11.15" + Latest012 = "0.12.31" + Latest013 = "0.13.7" + Latest014 = "0.14.11" + Latest015 = "0.15.5" + Latest_v1 = "1.0.0" + Latest_v1_1 = "1.1.0-alpha20210811" ) const appendUserAgent = "tfexec-testutil" diff --git a/tfexec/options.go b/tfexec/options.go index 74fd96a0..4d573efd 100644 --- a/tfexec/options.go +++ b/tfexec/options.go @@ -365,3 +365,21 @@ type VerifyPluginsOption struct { func VerifyPlugins(verifyPlugins bool) *VerifyPluginsOption { return &VerifyPluginsOption{verifyPlugins} } + +// FromStateOption represents the -from-state option of the "terraform add" command. +type FromStateOption struct { + fromState bool +} + +func FromState(fromState bool) *FromStateOption { + return &FromStateOption{fromState} +} + +// IncludeOptionalOption represents the -optional option of the "terraform add" command. +type IncludeOptionalOption struct { + includeOptional bool +} + +func IncludeOptional(includeOptional bool) *IncludeOptionalOption { + return &IncludeOptionalOption{includeOptional} +} diff --git a/tfexec/version.go b/tfexec/version.go index 2e842a87..0bdcbcef 100644 --- a/tfexec/version.go +++ b/tfexec/version.go @@ -19,6 +19,7 @@ var ( 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")) + tf1_1_0 = version.Must(version.NewVersion("1.1.0")) ) // Version returns structured output from the terraform version command including both the Terraform CLI version