diff --git a/tfexec/internal/e2etest/state_rm_test.go b/tfexec/internal/e2etest/state_rm_test.go new file mode 100644 index 00000000..5e59bb8f --- /dev/null +++ b/tfexec/internal/e2etest/state_rm_test.go @@ -0,0 +1,45 @@ +package e2etest + +import ( + "context" + "testing" + + "github.com/hashicorp/go-version" + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-exec/tfexec" +) + +func TestStateRm(t *testing.T) { + runTest(t, "basic_with_state", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) { + if tfv.LessThan(providerAddressMinVersion) { + t.Skip("state file provider FQNs not compatible with this Terraform version") + } + + err := tf.Init(context.Background()) + if err != nil { + t.Fatalf("error running Init in test directory: %s", err) + } + + err = tf.StateRm(context.Background(), "null_resource.foo") + if err != nil { + t.Fatalf("error running StateRm: %s", err) + } + + // test that the new state is as expected + expected := &tfjson.State{ + FormatVersion: "0.1", + // TerraformVersion is ignored to facilitate latest version testing + Values: nil, + } + + actual, err := tf.Show(context.Background()) + if err != nil { + t.Fatal(err) + } + + if diff := diffState(expected, actual); diff != "" { + t.Fatalf("mismatch (-want +got):\n%s", diff) + } + }) +} diff --git a/tfexec/state_rm.go b/tfexec/state_rm.go new file mode 100644 index 00000000..0c5dd666 --- /dev/null +++ b/tfexec/state_rm.go @@ -0,0 +1,104 @@ +package tfexec + +import ( + "context" + "os/exec" + "strconv" +) + +type stateRmConfig struct { + backup string + backupOut string + dryRun bool + lock bool + lockTimeout string + state string + stateOut string +} + +var defaultStateRmOptions = stateRmConfig{ + lock: true, + lockTimeout: "0s", +} + +// StateRmCmdOption represents options used in the Refresh method. +type StateRmCmdOption interface { + configureStateRm(*stateRmConfig) +} + +func (opt *BackupOption) configureStateRm(conf *stateRmConfig) { + conf.backup = opt.path +} + +func (opt *BackupOutOption) configureStateRm(conf *stateRmConfig) { + conf.backupOut = opt.path +} + +func (opt *DryRunOption) configureStateRm(conf *stateRmConfig) { + conf.dryRun = opt.dryRun +} + +func (opt *LockOption) configureStateRm(conf *stateRmConfig) { + conf.lock = opt.lock +} + +func (opt *LockTimeoutOption) configureStateRm(conf *stateRmConfig) { + conf.lockTimeout = opt.timeout +} + +func (opt *StateOption) configureStateRm(conf *stateRmConfig) { + conf.state = opt.path +} + +func (opt *StateOutOption) configureStateRm(conf *stateRmConfig) { + conf.stateOut = opt.path +} + +// StateRm represents the terraform state rm subcommand. +func (tf *Terraform) StateRm(ctx context.Context, address string, opts ...StateRmCmdOption) error { + cmd, err := tf.stateRmCmd(ctx, address, opts...) + if err != nil { + return err + } + return tf.runTerraformCmd(ctx, cmd) +} + +func (tf *Terraform) stateRmCmd(ctx context.Context, address string, opts ...StateRmCmdOption) (*exec.Cmd, error) { + c := defaultStateRmOptions + + for _, o := range opts { + o.configureStateRm(&c) + } + + args := []string{"state", "rm", "-no-color"} + + // string opts: only pass if set + if c.backup != "" { + args = append(args, "-backup="+c.backup) + } + if c.backupOut != "" { + args = append(args, "-backup-out="+c.backupOut) + } + if c.lockTimeout != "" { + args = append(args, "-lock-timeout="+c.lockTimeout) + } + if c.state != "" { + args = append(args, "-state="+c.state) + } + if c.stateOut != "" { + args = append(args, "-state-out="+c.stateOut) + } + + // boolean and numerical opts: always pass + args = append(args, "-lock="+strconv.FormatBool(c.lock)) + + // unary flags: pass if true + if c.dryRun { + args = append(args, "-dry-run") + } + + // positional arguments + args = append(args, address) + + return tf.buildTerraformCmd(ctx, nil, args...), nil +} diff --git a/tfexec/state_rm_test.go b/tfexec/state_rm_test.go new file mode 100644 index 00000000..e2794e4e --- /dev/null +++ b/tfexec/state_rm_test.go @@ -0,0 +1,56 @@ +package tfexec + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-exec/tfexec/internal/testutil" +) + +func TestStateRmCmd(t *testing.T) { + td := testTempDir(t) + + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest013)) + if err != nil { + t.Fatal(err) + } + + // empty env, to avoid environ mismatch in testing + tf.SetEnv(map[string]string{}) + + t.Run("defaults", func(t *testing.T) { + stateRmCmd, err := tf.stateRmCmd(context.Background(), "testAddress") + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "state", + "rm", + "-no-color", + "-lock-timeout=0s", + "-lock=true", + "testAddress", + }, nil, stateRmCmd) + }) + + t.Run("override all defaults", func(t *testing.T) { + stateRmCmd, err := tf.stateRmCmd(context.Background(), "testAddress", Backup("testbackup"), BackupOut("testbackupout"), LockTimeout("200s"), State("teststate"), StateOut("teststateout"), Lock(false)) + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "state", + "rm", + "-no-color", + "-backup=testbackup", + "-backup-out=testbackupout", + "-lock-timeout=200s", + "-state=teststate", + "-state-out=teststateout", + "-lock=false", + "testAddress", + }, nil, stateRmCmd) + }) +}