Skip to content

Commit

Permalink
add skip_plan option to state migrator
Browse files Browse the repository at this point in the history
This addresses issue #151 to add a `skip_plan` option to single state
migrations, similar to the `from_skip_plan` and `to_skip_plan` options
supported by multi state migrations.
  • Loading branch information
mdb committed Sep 14, 2023
1 parent 18eb9c4 commit ee511d5
Show file tree
Hide file tree
Showing 7 changed files with 35 additions and 26 deletions.
2 changes: 1 addition & 1 deletion tfmigrate/state_import_action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ resource "time_static" "baz" { triggers = {} }
NewStateImportAction("time_static.baz", "2006-01-02T15:04:05Z"),
}

m := NewStateMigrator(tf.Dir(), workspace, actions, &MigratorOption{}, false)
m := NewStateMigrator(tf.Dir(), workspace, actions, &MigratorOption{}, false, false)
err = m.Plan(ctx)
if err != nil {
t.Fatalf("failed to run migrator plan: %s", err)
Expand Down
37 changes: 23 additions & 14 deletions tfmigrate/state_migrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,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"`
// SkipPlan controls whether or not to run and analyze Terraform plan.
SkipPlan bool `hcl:"to_skip_plan,optional"`
// Workspace is the state workspace which the migration works with.
Workspace string `hcl:"workspace,optional"`
}
Expand Down Expand Up @@ -62,7 +64,7 @@ func (c *StateMigratorConfig) NewMigrator(o *MigratorOption) (Migrator, error) {
c.Workspace = "default"
}

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

// StateMigrator implements the Migrator interface.
Expand All @@ -74,6 +76,8 @@ type StateMigrator struct {
// o is an option for migrator.
// It is used for shared settings across Migrator instances.
o *MigratorOption
// skipPlan controls whether or not to run and analyze Terraform plan.
skipPlan bool
// force operation in case of unexpected diff
force bool
// workspace is the state workspace which the migration works with.
Expand All @@ -84,7 +88,7 @@ var _ Migrator = (*StateMigrator)(nil)

// NewStateMigrator returns a new StateMigrator instance.
func NewStateMigrator(dir string, workspace string, actions []StateAction,
o *MigratorOption, force bool) *StateMigrator {
o *MigratorOption, force bool, skipPlan bool) *StateMigrator {
e := tfexec.NewExecutor(dir, os.Environ())
tf := tfexec.NewTerraformCLI(e)
if o != nil && len(o.ExecPath) > 0 {
Expand All @@ -96,6 +100,7 @@ func NewStateMigrator(dir string, workspace string, actions []StateAction,
actions: actions,
o: o,
force: force,
skipPlan: skipPlan,
workspace: workspace,
}
}
Expand Down Expand Up @@ -145,19 +150,23 @@ func (m *StateMigrator) plan(ctx context.Context) (currentState *tfexec.State, e
planOpts = append(planOpts, "-out="+m.o.PlanOut)
}

log.Printf("[INFO] [migrator@%s] check diffs\n", m.tf.Dir())
_, err = m.tf.Plan(ctx, currentState, planOpts...)
if err != nil {
if exitErr, ok := err.(tfexec.ExitError); ok && exitErr.ExitCode() == 2 {
if !m.force {
log.Printf("[ERROR] [migrator@%s] unexpected diffs\n", m.tf.Dir())
return nil, fmt.Errorf("terraform plan command returns unexpected diffs: %s", err)
if m.skipPlan {
log.Printf("[INFO] [migrator@%s] skipping check diffs\n", m.tf.Dir())
} else {
log.Printf("[INFO] [migrator@%s] check diffs\n", m.tf.Dir())
_, err = m.tf.Plan(ctx, currentState, planOpts...)
if err != nil {
if exitErr, ok := err.(tfexec.ExitError); ok && exitErr.ExitCode() == 2 {
if !m.force {
log.Printf("[ERROR] [migrator@%s] unexpected diffs\n", m.tf.Dir())
return nil, fmt.Errorf("terraform plan command returns unexpected diffs: %s", err)
}
log.Printf("[INFO] [migrator@%s] unexpected diffs, ignoring as force option is true: %s", m.tf.Dir(), err)
// reset err to nil to intentionally ignore unexpected diffs.
err = nil
} else {
return nil, err
}
log.Printf("[INFO] [migrator@%s] unexpected diffs, ignoring as force option is true: %s", m.tf.Dir(), err)
// reset err to nil to intentionally ignore unexpected diffs.
err = nil
} else {
return nil, err
}
}

Expand Down
12 changes: 6 additions & 6 deletions tfmigrate/state_migrator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ resource "time_static" "qux" { triggers = {} }
}

force := false
m := NewStateMigrator(tf.Dir(), workspace, actions, &MigratorOption{}, force)
m := NewStateMigrator(tf.Dir(), workspace, actions, &MigratorOption{}, force, false)
err = m.Plan(ctx)
if err != nil {
t.Fatalf("failed to run migrator plan: %s", err)
Expand Down Expand Up @@ -236,7 +236,7 @@ resource "null_resource" "bar" {}
}

force := false
m := NewStateMigrator(tf.Dir(), workspace, actions, &MigratorOption{}, force)
m := NewStateMigrator(tf.Dir(), workspace, actions, &MigratorOption{}, force, false)
err = m.Plan(ctx)
if err != nil {
t.Fatalf("failed to run migrator plan: %s", err)
Expand Down Expand Up @@ -308,7 +308,7 @@ resource "null_resource" "baz" {}
o := &MigratorOption{}
o.PlanOut = "foo.tfplan"
force := true
m := NewStateMigrator(tf.Dir(), workspace, actions, o, force)
m := NewStateMigrator(tf.Dir(), workspace, actions, o, force, false)
err = m.Plan(ctx)
if err != nil {
t.Fatalf("failed to run migrator plan: %s", err)
Expand Down Expand Up @@ -440,7 +440,7 @@ resource "null_resource" "bar" {}
}

force := false
m := NewStateMigrator(tf.Dir(), workspace, actions, &MigratorOption{}, force)
m := NewStateMigrator(tf.Dir(), workspace, actions, &MigratorOption{}, force, false)

err := m.Plan(ctx)
if err == nil {
Expand Down Expand Up @@ -479,7 +479,7 @@ resource "null_resource" "bar" {}
}

force := false
m := NewStateMigrator(tf.Dir(), workspace, actions, &MigratorOption{}, force)
m := NewStateMigrator(tf.Dir(), workspace, actions, &MigratorOption{}, force, false)

err := m.Plan(ctx)
if err == nil {
Expand Down Expand Up @@ -524,7 +524,7 @@ resource "null_resource" "bar" {}
}

force := false
m := NewStateMigrator(tf.Dir(), workspace, actions, &MigratorOption{}, force)
m := NewStateMigrator(tf.Dir(), workspace, actions, &MigratorOption{}, force, false)

err := m.Plan(ctx)
if err == nil {
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 @@ -43,7 +43,7 @@ resource "null_resource" "baz" {}
NewStateMvAction("null_resource.bar", "null_resource.bar2"),
}

m := NewStateMigrator(tf.Dir(), workspace, actions, &MigratorOption{}, false)
m := NewStateMigrator(tf.Dir(), workspace, actions, &MigratorOption{}, false, false)
err = m.Plan(ctx)
if err != nil {
t.Fatalf("failed to run migrator plan: %s", err)
Expand Down
4 changes: 2 additions & 2 deletions tfmigrate/state_replace_provider_action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ resource "null_resource" "foo" {}
}

expected := "replace-provider action requires Terraform version >= 0.13.0"
m := NewStateMigrator(tf.Dir(), workspace, actions, &MigratorOption{}, false)
m := NewStateMigrator(tf.Dir(), workspace, actions, &MigratorOption{}, false, false)
err := m.Plan(ctx)
if err == nil || strings.Contains(err.Error(), expected) {
t.Fatalf("expected to receive '%s' error using legacy Terraform; got: %s", expected, err)
Expand Down Expand Up @@ -96,7 +96,7 @@ func TestAccStateReplaceProviderAction(t *testing.T) {
NewStateReplaceProviderAction("registry.terraform.io/-/null", "registry.terraform.io/hashicorp/null"),
}

m := NewStateMigrator(tf.Dir(), workspace, actions, &MigratorOption{}, false)
m := NewStateMigrator(tf.Dir(), workspace, actions, &MigratorOption{}, false, 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 @@ -42,7 +42,7 @@ resource "null_resource" "baz" {}
NewStateRmAction([]string{"null_resource.qux"}),
}

m := NewStateMigrator(tf.Dir(), workspace, actions, &MigratorOption{}, false)
m := NewStateMigrator(tf.Dir(), workspace, actions, &MigratorOption{}, false, 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_xmv_action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ resource "null_resource" "bar2" {}
NewStateXmvAction("null_resource.*", "null_resource.${1}2"),
}

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

0 comments on commit ee511d5

Please sign in to comment.