Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support workspace for state migrator #61

Merged
merged 1 commit into from
Dec 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tfmigrate/state_import_action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ resource "aws_iam_user" "baz" {
NewStateImportAction("aws_iam_user.baz", "baz"),
}

m := NewStateMigrator(tf.Dir(), actions, &MigratorOption{}, false)
m := NewStateMigrator(tf.Dir(), "default", actions, &MigratorOption{}, false)
err = m.Plan(ctx)
if err != nil {
t.Fatalf("failed to run migrator plan: %s", err)
Expand Down
24 changes: 17 additions & 7 deletions tfmigrate/state_migrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ type StateMigratorConfig struct {
// Force option controls behaviour in case of unexpected diff in plan.
// When set forces applying even if plan shows diff.
Force bool `hcl:"force,optional"`
// Workspace is the state workspace which the migration works with.
Workspace string `hcl:"workspace,optional"`
}

// StateMigratorConfig implements a MigratorConfig.
Expand Down Expand Up @@ -54,7 +56,12 @@ func (c *StateMigratorConfig) NewMigrator(o *MigratorOption) (Migrator, error) {
actions = append(actions, action)
}

return NewStateMigrator(dir, actions, o, c.Force), nil
//use default workspace if not specified by user
if len(c.Workspace) == 0 {
c.Workspace = "default"
}

return NewStateMigrator(dir, c.Workspace, actions, o, c.Force), nil
}

// StateMigrator implements the Migrator interface.
Expand All @@ -68,23 +75,26 @@ type StateMigrator struct {
o *MigratorOption
// force operation in case of unexpected diff
force bool
// workspace is the state workspace which the migration works with.
workspace string
}

var _ Migrator = (*StateMigrator)(nil)

// NewStateMigrator returns a new StateMigrator instance.
func NewStateMigrator(dir string, actions []StateAction, o *MigratorOption, force bool) *StateMigrator {
func NewStateMigrator(dir string, workspace string, actions []StateAction, o *MigratorOption, force bool) *StateMigrator {
e := tfexec.NewExecutor(dir, os.Environ())
tf := tfexec.NewTerraformCLI(e)
if o != nil && len(o.ExecPath) > 0 {
tf.SetExecPath(o.ExecPath)
}

return &StateMigrator{
tf: tf,
actions: actions,
o: o,
force: force,
tf: tf,
actions: actions,
o: o,
force: force,
workspace: workspace,
}
}

Expand All @@ -94,7 +104,7 @@ func NewStateMigrator(dir string, actions []StateAction, o *MigratorOption, forc
// the Migrator interface between a single and multi state migrator.
func (m *StateMigrator) plan(ctx context.Context) (*tfexec.State, error) {
// setup work dir.
currentState, switchBackToRemotekFunc, err := setupWorkDir(ctx, m.tf, "default")
currentState, switchBackToRemotekFunc, err := setupWorkDir(ctx, m.tf, m.workspace)
if err != nil {
return nil, err
}
Expand Down
139 changes: 87 additions & 52 deletions tfmigrate/state_migrator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,23 @@ func TestStateMigratorConfigNewMigrator(t *testing.T) {
},
ok: true,
},
{
desc: "valid in non-default workspace",
config: &StateMigratorConfig{
Dir: "dir1",
Actions: []string{
"mv aws_security_group.foo aws_security_group.foo2",
"mv aws_security_group.bar aws_security_group.bar2",
"rm aws_security_group.baz",
"import aws_security_group.qux qux",
},
Workspace: "workspace1",
},
o: &MigratorOption{
ExecPath: "direnv exec . terraform",
},
ok: true,
},
{
desc: "invalid action",
config: &StateMigratorConfig{
Expand Down Expand Up @@ -106,81 +123,99 @@ func TestStateMigratorConfigNewMigrator(t *testing.T) {
func TestAccStateMigratorApply(t *testing.T) {
tfexec.SkipUnlessAcceptanceTestEnabled(t)

backend := tfexec.GetTestAccBackendS3Config(t.Name())
cases := []struct {
desc string
workspace string
}{
{
desc: "default workspace",
workspace: "default",
},
{
desc: "non-default workspace",
workspace: "workspace1",
},
}

source := `
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
backend := tfexec.GetTestAccBackendS3Config(t.Name())

source := `
resource "aws_security_group" "foo" {}
resource "aws_security_group" "bar" {}
resource "aws_security_group" "baz" {}
resource "aws_iam_user" "qux" {
name = "qux"
}
`
tf := tfexec.SetupTestAccWithApply(t, "default", backend+source)
ctx := context.Background()
tf := tfexec.SetupTestAccWithApply(t, tc.workspace, backend+source)
ctx := context.Background()

updatedSource := `
updatedSource := `
resource "aws_security_group" "foo2" {}
resource "aws_security_group" "baz" {}
resource "aws_iam_user" "qux" {
name = "qux"
}
`

tfexec.UpdateTestAccSource(t, tf, backend+updatedSource)
tfexec.UpdateTestAccSource(t, tf, backend+updatedSource)

_, err := tf.StateRm(ctx, nil, []string{"aws_iam_user.qux"})
if err != nil {
t.Fatalf("failed to run terraform state rm: %s", err)
}
_, err := tf.StateRm(ctx, nil, []string{"aws_iam_user.qux"})
if err != nil {
t.Fatalf("failed to run terraform state rm: %s", err)
}

changed, err := tf.PlanHasChange(ctx, nil)
if err != nil {
t.Fatalf("failed to run PlanHasChange: %s", err)
}
if !changed {
t.Fatalf("expect to have changes")
}
changed, err := tf.PlanHasChange(ctx, nil)
if err != nil {
t.Fatalf("failed to run PlanHasChange: %s", err)
}
if !changed {
t.Fatalf("expect to have changes")
}

actions := []StateAction{
NewStateMvAction("aws_security_group.foo", "aws_security_group.foo2"),
NewStateRmAction([]string{"aws_security_group.bar"}),
NewStateImportAction("aws_iam_user.qux", "qux"),
}
actions := []StateAction{
NewStateMvAction("aws_security_group.foo", "aws_security_group.foo2"),
NewStateRmAction([]string{"aws_security_group.bar"}),
NewStateImportAction("aws_iam_user.qux", "qux"),
}

m := NewStateMigrator(tf.Dir(), actions, &MigratorOption{}, false)
err = m.Plan(ctx)
if err != nil {
t.Fatalf("failed to run migrator plan: %s", err)
}
m := NewStateMigrator(tf.Dir(), tc.workspace, actions, &MigratorOption{}, false)
err = m.Plan(ctx)
if err != nil {
t.Fatalf("failed to run migrator plan: %s", err)
}

err = m.Apply(ctx)
if err != nil {
t.Fatalf("failed to run migrator apply: %s", err)
}
err = m.Apply(ctx)
if err != nil {
t.Fatalf("failed to run migrator apply: %s", err)
}

got, err := tf.StateList(ctx, nil, nil)
if err != nil {
t.Fatalf("failed to run terraform state list: %s", err)
}
got, err := tf.StateList(ctx, nil, nil)
if err != nil {
t.Fatalf("failed to run terraform state list: %s", err)
}

want := []string{
"aws_security_group.foo2",
"aws_security_group.baz",
"aws_iam_user.qux",
}
sort.Strings(got)
sort.Strings(want)
if !reflect.DeepEqual(got, want) {
t.Errorf("got state: %v, want state: %v", got, want)
}
want := []string{
"aws_security_group.foo2",
"aws_security_group.baz",
"aws_iam_user.qux",
}
sort.Strings(got)
sort.Strings(want)
if !reflect.DeepEqual(got, want) {
t.Errorf("got state: %v, want state: %v", got, want)
}

changed, err = tf.PlanHasChange(ctx, nil)
if err != nil {
t.Fatalf("failed to run PlanHasChange: %s", err)
}
if changed {
t.Fatalf("expect not to have changes")
changed, err = tf.PlanHasChange(ctx, nil)
if err != nil {
t.Fatalf("failed to run PlanHasChange: %s", err)
}
if changed {
t.Fatalf("expect not to have changes")
}
})
}
}

Expand Down Expand Up @@ -219,7 +254,7 @@ resource "aws_security_group" "baz" {}
o := &MigratorOption{}
o.PlanOut = "foo.tfplan"

m := NewStateMigrator(tf.Dir(), actions, o, true)
m := NewStateMigrator(tf.Dir(), "default", actions, o, true)
err = m.Plan(ctx)
if err != nil {
t.Fatalf("failed to run migrator plan: %s", err)
Expand Down
2 changes: 1 addition & 1 deletion tfmigrate/state_mv_action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ resource "aws_security_group" "baz" {}
NewStateMvAction("aws_security_group.bar", "aws_security_group.bar2"),
}

m := NewStateMigrator(tf.Dir(), actions, &MigratorOption{}, false)
m := NewStateMigrator(tf.Dir(), "default", actions, &MigratorOption{}, false)
err = m.Plan(ctx)
if err != nil {
t.Fatalf("failed to run migrator plan: %s", err)
Expand Down
2 changes: 1 addition & 1 deletion tfmigrate/state_rm_action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ resource "aws_security_group" "baz" {}
NewStateRmAction([]string{"aws_security_group.qux"}),
}

m := NewStateMigrator(tf.Dir(), actions, &MigratorOption{}, false)
m := NewStateMigrator(tf.Dir(), "default", actions, &MigratorOption{}, false)
err = m.Plan(ctx)
if err != nil {
t.Fatalf("failed to run migrator plan: %s", err)
Expand Down