Skip to content

Commit

Permalink
support conditionally disabling TF plan
Browse files Browse the repository at this point in the history
This seeks to address issue #142 by adding two fields to
`MultiStateMigratorConfig`:

* `disable_from_dir_diff_check` - when `true`, `tfmigrator` skips running `terraform plan`
  in the `from_dir`. Default: `false`.
* `disable_from_to_diff_check` - when `true`, `tfmigrator` skips running `terraform plan`
  in the `to_dir`. Default: `false`.

Example:

```hcl
migration "multi_state" "test" {
  from_dir                    = "."
  disable_from_dir_diff_check = true

  to_dir                    = "dir2"
  disable_to_dir_diff_check = true

  actions = [
    "mv aws_security_group.baz aws_security_group.baz2",
  ]
}
```

Signed-off-by: Mike Ball <mikedball@gmail.com>
  • Loading branch information
mdb committed Aug 4, 2023
1 parent 1fb61da commit 87dd6ac
Show file tree
Hide file tree
Showing 5 changed files with 441 additions and 36 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -638,8 +638,10 @@ migration "state" "test" {
The `multi_state` migration updates states in two different directories. It is intended for moving resources across states. It has the following attributes.

- `from_dir` (required): A working directory where states of resources move from.
- `disable_from_dir_diff_check` (optional): If true, `tfmigrate` will not perform and analyze a `terraform plan` in the `from_dir`.
- `from_workspace` (optional): A terraform workspace in the FROM directory. Defaults to "default".
- `to_dir` (required): A working directory where states of resources move to.
- `disable_to_dir_diff_check` (optional): If true, `tfmigrate` will not perform and analyze a `terraform plan` in the `to_dir`.
- `to_workspace` (optional): A terraform workspace in the TO directory. Defaults to "default".
- `actions` (required): Actions is a list of multi state action. An action is a plain text for state operation. Valid formats are the following.
- `"mv <source> <destination>"`
Expand Down
82 changes: 51 additions & 31 deletions tfmigrate/multi_state_migrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@ import (
type MultiStateMigratorConfig struct {
// FromDir is a working directory where states of resources move from.
FromDir string `hcl:"from_dir"`
// DisableFromDirDiffCheck controls whether or not to run and analyze Terraform
// plan within the from_dir.
DisableFromDirDiffCheck bool `hcl:"disable_from_dir_diff_check,optional"`
// ToDir is a working directory where states of resources move to.
ToDir string `hcl:"to_dir"`
// DisableToDirDiffCheck controls whether or not to and analyze Terraform plan
// within the to_dir.
DisableToDirDiffCheck bool `hcl:"disable_to_dir_diff_check,optional"`
// FromWorkspace is a workspace within FromDir
FromWorkspace string `hcl:"from_workspace,optional"`
// ToWorkspace is a workspace within ToDir
Expand Down Expand Up @@ -56,15 +62,19 @@ func (c *MultiStateMigratorConfig) NewMigrator(o *MigratorOption) (Migrator, err
c.ToWorkspace = "default"
}

return NewMultiStateMigrator(c.FromDir, c.ToDir, c.FromWorkspace, c.ToWorkspace, actions, o, c.Force), nil
return NewMultiStateMigrator(c.FromDir, c.ToDir, c.FromWorkspace, c.ToWorkspace, actions, o, c.Force, c.DisableFromDirDiffCheck, c.DisableToDirDiffCheck), nil
}

// MultiStateMigrator implements the Migrator interface.
type MultiStateMigrator struct {
// fromTf is an instance of TerraformCLI which executes terraform command in a fromDir.
fromTf tfexec.TerraformCLI
// disableFromDirDiffCheck disables the running of Terraform plan in fromDir.
disableFromDirDiffCheck bool
// fromTf is an instance of TerraformCLI which executes terraform command in a toDir.
toTf tfexec.TerraformCLI
// disableToDirDiffCheck disables the running of Terraform plan in toDir.
disableToDirDiffCheck bool
//fromWorkspace is the workspace from which the resource will be migrated
fromWorkspace string
//toWorkspace is the workspace to which the resource will be migrated
Expand All @@ -82,7 +92,7 @@ var _ Migrator = (*MultiStateMigrator)(nil)

// NewMultiStateMigrator returns a new MultiStateMigrator instance.
func NewMultiStateMigrator(fromDir string, toDir string, fromWorkspace string, toWorkspace string,
actions []MultiStateAction, o *MigratorOption, force bool) *MultiStateMigrator {
actions []MultiStateAction, o *MigratorOption, force bool, disableFromDirDiffCheck bool, disableToDirDiffCheck bool) *MultiStateMigrator {
fromTf := tfexec.NewTerraformCLI(tfexec.NewExecutor(fromDir, os.Environ()))
toTf := tfexec.NewTerraformCLI(tfexec.NewExecutor(toDir, os.Environ()))
if o != nil && len(o.ExecPath) > 0 {
Expand All @@ -91,13 +101,15 @@ func NewMultiStateMigrator(fromDir string, toDir string, fromWorkspace string, t
}

return &MultiStateMigrator{
fromTf: fromTf,
toTf: toTf,
fromWorkspace: fromWorkspace,
toWorkspace: toWorkspace,
actions: actions,
o: o,
force: force,
fromTf: fromTf,
disableFromDirDiffCheck: disableFromDirDiffCheck,
toTf: toTf,
disableToDirDiffCheck: disableToDirDiffCheck,
fromWorkspace: fromWorkspace,
toWorkspace: toWorkspace,
actions: actions,
o: o,
force: force,
}
}

Expand Down Expand Up @@ -139,34 +151,42 @@ func (m *MultiStateMigrator) plan(ctx context.Context) (*tfexec.State, *tfexec.S
planOpts = append(planOpts, "-out="+m.o.PlanOut)
}

// check if a plan in fromDir has no changes.
log.Printf("[INFO] [migrator@%s] check diffs\n", m.fromTf.Dir())
_, err = m.fromTf.Plan(ctx, fromCurrentState, 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.fromTf.Dir())
return nil, nil, fmt.Errorf("terraform plan command returns unexpected diffs: %s", err)
if m.disableFromDirDiffCheck {
// check if a plan in fromDir has no changes.
log.Printf("[INFO] [migrator@%s] check diffs\n", m.fromTf.Dir())
_, err = m.fromTf.Plan(ctx, fromCurrentState, 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.fromTf.Dir())
return nil, 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.fromTf.Dir(), err)
} else {
return nil, nil, err
}
log.Printf("[INFO] [migrator@%s] unexpected diffs, ignoring as force option is true: %s", m.fromTf.Dir(), err)
} else {
return nil, nil, err
}
} else {
log.Printf("[INFO] [migrator@%s] skipping check diffs\n", m.fromTf.Dir())
}

// check if a plan in toDir has no changes.
log.Printf("[INFO] [migrator@%s] check diffs\n", m.toTf.Dir())
_, err = m.toTf.Plan(ctx, toCurrentState, 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.toTf.Dir())
return nil, nil, fmt.Errorf("terraform plan command returns unexpected diffs: %s", err)
if m.disableToDirDiffCheck {
// check if a plan in toDir has no changes.
log.Printf("[INFO] [migrator@%s] check diffs\n", m.toTf.Dir())
_, err = m.toTf.Plan(ctx, toCurrentState, 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.toTf.Dir())
return nil, 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.toTf.Dir(), err)
} else {
return nil, nil, err
}
log.Printf("[INFO] [migrator@%s] unexpected diffs, ignoring as force option is true: %s", m.toTf.Dir(), err)
} else {
return nil, nil, err
}
} else {
log.Printf("[INFO] [migrator@%s] skipping check diffs\n", m.toTf.Dir())
}

return fromCurrentState, toCurrentState, nil
Expand Down
Loading

0 comments on commit 87dd6ac

Please sign in to comment.