Skip to content

Commit

Permalink
add state mv
Browse files Browse the repository at this point in the history
  • Loading branch information
kmoe committed Dec 7, 2020
1 parent b76393f commit eb6cde0
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 6 deletions.
6 changes: 0 additions & 6 deletions tfexec/internal/e2etest/show_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,6 @@ import (
"github.com/hashicorp/terraform-exec/tfexec/internal/testutil"
)

var (
showMinVersion = version.Must(version.NewVersion("0.12.0"))

providerAddressMinVersion = version.Must(version.NewVersion("0.13.0"))
)

func TestShow(t *testing.T) {
runTest(t, "basic_with_state", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
if tfv.LessThan(showMinVersion) {
Expand Down
64 changes: 64 additions & 0 deletions tfexec/internal/e2etest/state_mv_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package e2etest

import (
"context"
"testing"

"github.com/hashicorp/go-version"
tfjson "github.com/hashicorp/terraform-json"

"github.com/hashicorp/terraform-exec/tfexec"
)

func TestStateMv(t *testing.T) {
runTest(t, "basic_with_state", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
if tfv.LessThan(showMinVersion) {
t.Skip("terraform show was added in Terraform 0.12, so test is not valid")
}

providerName := "registry.terraform.io/-/null"
if tfv.LessThan(providerAddressMinVersion) {
providerName = "null"
}

err := tf.Init(context.Background())
if err != nil {
t.Fatalf("error running Init in test directory: %s", err)
}

err = tf.StateMv(context.Background(), "null_resource.foo", "null_resource.bar")
if err != nil {
t.Fatalf("error running StateMv: %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: &tfjson.StateValues{
RootModule: &tfjson.StateModule{
Resources: []*tfjson.StateResource{{
Address: "null_resource.bar",
AttributeValues: map[string]interface{}{
"id": "5510719323588825107",
"triggers": nil,
},
Mode: tfjson.ManagedResourceMode,
Type: "null_resource",
Name: "bar",
ProviderName: providerName,
}},
},
},
}

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)
}
})
}
6 changes: 6 additions & 0 deletions tfexec/internal/e2etest/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ import (
const testFixtureDir = "testdata"
const masterRef = "refs/heads/master"

var (
showMinVersion = version.Must(version.NewVersion("0.12.0"))

providerAddressMinVersion = version.Must(version.NewVersion("0.13.0"))
)

func runTest(t *testing.T, fixtureName string, cb func(t *testing.T, tfVersion *version.Version, tf *tfexec.Terraform)) {
t.Helper()

Expand Down
18 changes: 18 additions & 0 deletions tfexec/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ func BackendConfig(backendConfig string) *BackendConfigOption {
return &BackendConfigOption{backendConfig}
}

type BackupOutOption struct {
path string
}

// BackupOutOption represents the -backup-out flag.
func BackupOut(path string) *BackupOutOption {
return &BackupOutOption{path}
}

// BackupOption represents the -backup flag.
type BackupOption struct {
path string
Expand Down Expand Up @@ -99,6 +108,15 @@ func Destroy(destroy bool) *DestroyFlagOption {
return &DestroyFlagOption{destroy}
}

type DryRunOption struct {
dryRun bool
}

// DryRun represents the -dry-run flag.
func DryRun(dryRun bool) *DryRunOption {
return &DryRunOption{dryRun}
}

type ForceOption struct {
force bool
}
Expand Down
105 changes: 105 additions & 0 deletions tfexec/state_mv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package tfexec

import (
"context"
"os/exec"
"strconv"
)

type stateMvConfig struct {
backup string
backupOut string
dryRun bool
lock bool
lockTimeout string
state string
stateOut string
}

var defaultStateMvOptions = stateMvConfig{
lock: true,
lockTimeout: "0s",
}

// StateMvCmdOption represents options used in the Refresh method.
type StateMvCmdOption interface {
configureStateMv(*stateMvConfig)
}

func (opt *BackupOption) configureStateMv(conf *stateMvConfig) {
conf.backup = opt.path
}

func (opt *BackupOutOption) configureStateMv(conf *stateMvConfig) {
conf.backupOut = opt.path
}

func (opt *DryRunOption) configureStateMv(conf *stateMvConfig) {
conf.dryRun = opt.dryRun
}

func (opt *LockOption) configureStateMv(conf *stateMvConfig) {
conf.lock = opt.lock
}

func (opt *LockTimeoutOption) configureStateMv(conf *stateMvConfig) {
conf.lockTimeout = opt.timeout
}

func (opt *StateOption) configureStateMv(conf *stateMvConfig) {
conf.state = opt.path
}

func (opt *StateOutOption) configureStateMv(conf *stateMvConfig) {
conf.stateOut = opt.path
}

// StateMv represents the terraform state mv subcommand.
func (tf *Terraform) StateMv(ctx context.Context, source string, destination string, opts ...StateMvCmdOption) error {
cmd, err := tf.stateMvCmd(ctx, source, destination, opts...)
if err != nil {
return err
}
return tf.runTerraformCmd(cmd)
}

func (tf *Terraform) stateMvCmd(ctx context.Context, source string, destination string, opts ...StateMvCmdOption) (*exec.Cmd, error) {
c := defaultStateMvOptions

for _, o := range opts {
o.configureStateMv(&c)
}

args := []string{"state", "mv", "-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, source)
args = append(args, destination)

return tf.buildTerraformCmd(ctx, nil, args...), nil
}
58 changes: 58 additions & 0 deletions tfexec/state_mv_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package tfexec

import (
"context"
"testing"

"github.com/hashicorp/terraform-exec/tfexec/internal/testutil"
)

func TestStateMvCmd(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) {
stateMvCmd, err := tf.stateMvCmd(context.Background(), "testsource", "testdestination")
if err != nil {
t.Fatal(err)
}

assertCmd(t, []string{
"state",
"mv",
"-no-color",
"-lock-timeout=0s",
"-lock=true",
"testsource",
"testdestination",
}, nil, stateMvCmd)
})

t.Run("override all defaults", func(t *testing.T) {
stateMvCmd, err := tf.stateMvCmd(context.Background(), "testsrc", "testdest", Backup("testbackup"), BackupOut("testbackupout"), LockTimeout("200s"), State("teststate"), StateOut("teststateout"), Lock(false))
if err != nil {
t.Fatal(err)
}

assertCmd(t, []string{
"state",
"mv",
"-no-color",
"-backup=testbackup",
"-backup-out=testbackupout",
"-lock-timeout=200s",
"-state=teststate",
"-state-out=teststateout",
"-lock=false",
"testsrc",
"testdest",
}, nil, stateMvCmd)
})
}

0 comments on commit eb6cde0

Please sign in to comment.