From 8dbe2342ba8ef845c0c4d4d41cf841eddaaab0be Mon Sep 17 00:00:00 2001 From: Karol Pasternak Date: Fri, 27 Aug 2021 15:43:35 -0700 Subject: [PATCH 1/2] Add support for state push/pull --- tfexec/state_pull.go | 49 +++++++++++++++++++++++++++++ tfexec/state_pull_test.go | 28 +++++++++++++++++ tfexec/state_push.go | 65 +++++++++++++++++++++++++++++++++++++++ tfexec/state_push_test.go | 48 +++++++++++++++++++++++++++++ 4 files changed, 190 insertions(+) create mode 100644 tfexec/state_pull.go create mode 100644 tfexec/state_pull_test.go create mode 100644 tfexec/state_push.go create mode 100644 tfexec/state_push_test.go diff --git a/tfexec/state_pull.go b/tfexec/state_pull.go new file mode 100644 index 00000000..48a97c1b --- /dev/null +++ b/tfexec/state_pull.go @@ -0,0 +1,49 @@ +package tfexec + +import ( + "context" + "os/exec" + + tfjson "github.com/hashicorp/terraform-json" +) + +type statePullConfig struct { + reattachInfo ReattachInfo +} + +var defaultStatePullConfig = statePullConfig{} + +func (tf *Terraform) StatePull(ctx context.Context) (*tfjson.State, error) { + c := defaultStatePullConfig + + mergeEnv := map[string]string{} + if c.reattachInfo != nil { + reattachStr, err := c.reattachInfo.marshalString() + if err != nil { + return nil, err + } + mergeEnv[reattachEnvVar] = reattachStr + } + + cmd := tf.statePullCmd(ctx) + + var ret tfjson.State + ret.UseJSONNumber(true) + err := tf.runTerraformCmdJSON(ctx, cmd, &ret) + if err != nil { + return nil, err + } + + err = ret.Validate() + if err != nil { + return nil, err + } + + return &ret, nil +} + +func (tf *Terraform) statePullCmd(ctx context.Context) *exec.Cmd { + args := []string{"state", "pull"} + + return tf.buildTerraformCmd(ctx, nil, args...) +} diff --git a/tfexec/state_pull_test.go b/tfexec/state_pull_test.go new file mode 100644 index 00000000..48c347a7 --- /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()) + + 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) + }) +} From ab4b69327e3d19cfa8dd0270830ade3d7f79d0ee Mon Sep 17 00:00:00 2001 From: Karol Pasternak Date: Tue, 31 Aug 2021 09:19:58 -0700 Subject: [PATCH 2/2] statePull: tfjson does not support state subcommand, output string instead --- tfexec/state_pull.go | 40 ++++++++++++++++++++++----------------- tfexec/state_pull_test.go | 2 +- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/tfexec/state_pull.go b/tfexec/state_pull.go index 48a97c1b..11b6b9c7 100644 --- a/tfexec/state_pull.go +++ b/tfexec/state_pull.go @@ -1,10 +1,9 @@ package tfexec import ( + "bytes" "context" "os/exec" - - tfjson "github.com/hashicorp/terraform-json" ) type statePullConfig struct { @@ -13,37 +12,44 @@ type statePullConfig struct { var defaultStatePullConfig = statePullConfig{} -func (tf *Terraform) StatePull(ctx context.Context) (*tfjson.State, error) { +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 nil, err + return "", err } mergeEnv[reattachEnvVar] = reattachStr } - cmd := tf.statePullCmd(ctx) - - var ret tfjson.State - ret.UseJSONNumber(true) - err := tf.runTerraformCmdJSON(ctx, cmd, &ret) - if err != nil { - return nil, err - } + cmd := tf.statePullCmd(ctx, mergeEnv) - err = ret.Validate() + var ret bytes.Buffer + cmd.Stdout = &ret + err := tf.runTerraformCmd(ctx, cmd) if err != nil { - return nil, err + return "", err } - return &ret, nil + return ret.String(), nil } -func (tf *Terraform) statePullCmd(ctx context.Context) *exec.Cmd { +func (tf *Terraform) statePullCmd(ctx context.Context, mergeEnv map[string]string) *exec.Cmd { args := []string{"state", "pull"} - return tf.buildTerraformCmd(ctx, nil, args...) + return tf.buildTerraformCmd(ctx, mergeEnv, args...) } diff --git a/tfexec/state_pull_test.go b/tfexec/state_pull_test.go index 48c347a7..4ef6ef2e 100644 --- a/tfexec/state_pull_test.go +++ b/tfexec/state_pull_test.go @@ -18,7 +18,7 @@ func TestStatePull(t *testing.T) { tf.SetEnv(map[string]string{}) t.Run("tfstate", func(t *testing.T) { - statePullCmd := tf.statePullCmd(context.Background()) + statePullCmd := tf.statePullCmd(context.Background(), nil) assertCmd(t, []string{ "state",