diff --git a/tfexec/state_pull.go b/tfexec/state_pull.go new file mode 100644 index 00000000..11b6b9c7 --- /dev/null +++ b/tfexec/state_pull.go @@ -0,0 +1,55 @@ +package tfexec + +import ( + "bytes" + "context" + "os/exec" +) + +type statePullConfig struct { + reattachInfo ReattachInfo +} + +var defaultStatePullConfig = statePullConfig{} + +type StatePullOption interface { + configureShow(*statePullConfig) +} + +func (opt *ReattachOption) configureStatePull(conf *statePullConfig) { + conf.reattachInfo = opt.info +} + +func (tf *Terraform) StatePull(ctx context.Context, opts ...StatePullOption) (string, error) { + c := defaultStatePullConfig + + for _, o := range opts { + o.configureShow(&c) + } + + mergeEnv := map[string]string{} + if c.reattachInfo != nil { + reattachStr, err := c.reattachInfo.marshalString() + if err != nil { + return "", err + } + mergeEnv[reattachEnvVar] = reattachStr + } + + cmd := tf.statePullCmd(ctx, mergeEnv) + + var ret bytes.Buffer + cmd.Stdout = &ret + err := tf.runTerraformCmd(ctx, cmd) + if err != nil { + return "", err + } + + return ret.String(), nil +} + +func (tf *Terraform) statePullCmd(ctx context.Context, mergeEnv map[string]string) *exec.Cmd { + args := []string{"state", "pull"} + + return tf.buildTerraformCmd(ctx, mergeEnv, args...) +} diff --git a/tfexec/state_pull_test.go b/tfexec/state_pull_test.go new file mode 100644 index 00000000..4ef6ef2e --- /dev/null +++ b/tfexec/state_pull_test.go @@ -0,0 +1,28 @@ +package tfexec + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-exec/tfexec/internal/testutil" +) + +func TestStatePull(t *testing.T) { + td := testTempDir(t) + + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest_v1)) + if err != nil { + t.Fatal(err) + } + + tf.SetEnv(map[string]string{}) + + t.Run("tfstate", func(t *testing.T) { + statePullCmd := tf.statePullCmd(context.Background(), nil) + + assertCmd(t, []string{ + "state", + "pull", + }, nil, statePullCmd) + }) +} diff --git a/tfexec/state_push.go b/tfexec/state_push.go new file mode 100644 index 00000000..98f50f15 --- /dev/null +++ b/tfexec/state_push.go @@ -0,0 +1,65 @@ +package tfexec + +import ( + "context" + "os/exec" + "strconv" +) + +type statePushConfig struct { + force bool + lock bool + lockTimeout string +} + +var defaultStatePushOptions = statePushConfig{ + lock: false, + lockTimeout: "0s", +} + +// StatePushCmdOption represents options used in the Refresh method. +type StatePushCmdOption interface { + configureStatePush(*statePushConfig) +} + +func (opt *ForceOption) configureStatePush(conf *statePushConfig) { + conf.force = opt.force +} + +func (opt *LockOption) configureStatePush(conf *statePushConfig) { + conf.lock = opt.lock +} + +func (opt *LockTimeoutOption) configureStatePush(conf *statePushConfig) { + conf.lockTimeout = opt.timeout +} + +func (tf *Terraform) StatePush(ctx context.Context, path string, opts ...StatePushCmdOption) error { + cmd, err := tf.statePushCmd(ctx, path, opts...) + if err != nil { + return err + } + return tf.runTerraformCmd(ctx, cmd) +} + +func (tf *Terraform) statePushCmd(ctx context.Context, path string, opts ...StatePushCmdOption) (*exec.Cmd, error) { + c := defaultStatePushOptions + + for _, o := range opts { + o.configureStatePush(&c) + } + + args := []string{"state", "push"} + + if c.force { + args = append(args, "-force") + } + + args = append(args, "-lock="+strconv.FormatBool(c.lock)) + + if c.lockTimeout != "" { + args = append(args, "-lock-timeout="+c.lockTimeout) + } + + return tf.buildTerraformCmd(ctx, nil, args...), nil +} diff --git a/tfexec/state_push_test.go b/tfexec/state_push_test.go new file mode 100644 index 00000000..79677185 --- /dev/null +++ b/tfexec/state_push_test.go @@ -0,0 +1,48 @@ +package tfexec + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-exec/tfexec/internal/testutil" +) + +func TestStatePushCmd(t *testing.T) { + td := testTempDir(t) + + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest_v1)) + if err != nil { + t.Fatal(err) + } + + tf.SetEnv(map[string]string{}) + + t.Run("defaults", func(t *testing.T) { + statePushCmd, err := tf.statePushCmd(context.Background(), "testpath") + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "state", + "push", + "-lock=false", + "-lock-timeout=0s", + }, nil, statePushCmd) + }) + + t.Run("override all defaults", func(t *testing.T) { + statePushCmd, err := tf.statePushCmd(context.Background(), "testpath", Force(true), Lock(true), LockTimeout("10s")) + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "state", + "push", + "-force", + "-lock=true", + "-lock-timeout=10s", + }, nil, statePushCmd) + }) +}