-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3d90b10
commit 47ebc3e
Showing
8 changed files
with
364 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
package config | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/hashicorp/hcl/v2" | ||
"github.com/hashicorp/hcl/v2/gohcl" | ||
"github.com/hashicorp/hcl/v2/hclsimple" | ||
"github.com/minamijoyo/tfmigrate/tfmigrate" | ||
) | ||
|
||
// MigrationFile represents a config for migration written in HCL. | ||
type MigrationFile struct { | ||
// Migration is a migration block. | ||
// It must contain only one block, and multiple blocks are not allowed, | ||
// because it's hard to re-run the file if partially failed. | ||
Migration MigrationBlock `hcl:"migration,block"` | ||
} | ||
|
||
// MigrationBlock represents a migration block in HCL. | ||
type MigrationBlock struct { | ||
// Type is a type for migration. | ||
// Valid values are `state` and `multi_state`. | ||
Type string `hcl:"type,label"` | ||
// Name is an arbitrary name for migration. | ||
Name string `hcl:"name,label"` | ||
// Remain is a body of migration block. | ||
// We first decode only a block header and then decode schema depending on | ||
// its type label. | ||
Remain hcl.Body `hcl:",remain"` | ||
} | ||
|
||
// StateMigratorConfig is a config for StateMigrator. | ||
type StateMigratorConfig struct { | ||
// Dir is a working directory for executing terraform command. | ||
// Default to `.` (current directory). | ||
Dir string `hcl:"dir,optional"` | ||
// Actions is a list of state action. | ||
// action is a plain text for state operation. | ||
// Valid formats are the following. | ||
// "mv <source> <destination>" | ||
// "rm <addresses>... | ||
// "import <address> <id>" | ||
// We could define strict block schema for action, but intentionally use a | ||
// schema-less string to allow us to easily copy terraform state command to | ||
// action. | ||
Actions []string `hcl:"actions"` | ||
} | ||
|
||
// MultiStateMigratorConfig is a config for MultiStateMigrator. | ||
type MultiStateMigratorConfig struct { | ||
// FromDir is a working directory where states of resources move from. | ||
FromDir string `hcl:"from_dir"` | ||
// ToDir is a working directory where states of rsources move to. | ||
ToDir string `hcl:"to_dir"` | ||
// Actions is a list of multi state action. | ||
// action is a plain text for state operation. | ||
// Valid formats are the following. | ||
// "mv <source> <destination>" | ||
Actions []string `hcl:"actions"` | ||
} | ||
|
||
// MigratorConfig is an interface of factory method for Migrator. | ||
type MigratorConfig interface { | ||
// NewMigrator returns a new instance of Migrator. | ||
NewMigrator(o *tfmigrate.MigratorOption) (tfmigrate.Migrator, error) | ||
} | ||
|
||
// StateMigratorConfig implements a MigratorConfig. | ||
var _ MigratorConfig = (*StateMigratorConfig)(nil) | ||
|
||
// MultiStateMigratorConfig implements a MigratorConfig. | ||
var _ MigratorConfig = (*MultiStateMigratorConfig)(nil) | ||
|
||
// NewMigrator returns a new instance of StateMigrator. | ||
func (c *StateMigratorConfig) NewMigrator(o *tfmigrate.MigratorOption) (tfmigrate.Migrator, error) { | ||
// default working directory | ||
dir := "." | ||
if len(c.Dir) > 0 { | ||
dir = c.Dir | ||
} | ||
|
||
// build actions from config. | ||
actions := []tfmigrate.StateAction{} | ||
for _, cmdStr := range c.Actions { | ||
action, err := tfmigrate.NewStateActionFromString(cmdStr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
actions = append(actions, action) | ||
} | ||
|
||
return tfmigrate.NewStateMigrator(dir, actions, o), nil | ||
} | ||
|
||
// NewMigrator returns a new instance of MultiStateMigrator. | ||
func (c *MultiStateMigratorConfig) NewMigrator(o *tfmigrate.MigratorOption) (tfmigrate.Migrator, error) { | ||
// build actions from config. | ||
actions := []tfmigrate.MultiStateAction{} | ||
for _, cmdStr := range c.Actions { | ||
action, err := tfmigrate.NewMultiStateActionFromString(cmdStr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
actions = append(actions, action) | ||
} | ||
|
||
return tfmigrate.NewMultiStateMigrator(c.FromDir, c.ToDir, actions, o), nil | ||
} | ||
|
||
// ParseMigrationFile parses a given source of migration file and returns a MigratorConfig. | ||
// Note that this method does not read a file and you should pass source of config in bytes. | ||
// The filename is used for error message and selecting HCL syntax (.hcl and .hcl.json). | ||
func ParseMigrationFile(filename string, source []byte) (MigratorConfig, error) { | ||
// Decode migration block header. | ||
var f MigrationFile | ||
err := hclsimple.Decode(filename, source, nil, &f) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to decode migration file: %s, err: %s", filename, err) | ||
} | ||
|
||
// switch schema by migration type. | ||
var config MigratorConfig | ||
m := f.Migration | ||
switch m.Type { | ||
case "state": | ||
var state StateMigratorConfig | ||
diags := gohcl.DecodeBody(m.Remain, nil, &state) | ||
if diags.HasErrors() { | ||
return nil, diags | ||
} | ||
config = &state | ||
case "multi_state": | ||
var multiState MultiStateMigratorConfig | ||
diags := gohcl.DecodeBody(m.Remain, nil, &multiState) | ||
if diags.HasErrors() { | ||
return nil, diags | ||
} | ||
config = &multiState | ||
default: | ||
return nil, fmt.Errorf("unknown migration type in %s: %s", filename, m.Type) | ||
} | ||
return config, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package config | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/davecgh/go-spew/spew" | ||
"github.com/minamijoyo/tfmigrate/tfmigrate" | ||
) | ||
|
||
func TestParseMigrationFileWithState(t *testing.T) { | ||
const source = ` | ||
migration "state" "test" { | ||
dir = "dir1" | ||
actions = [ | ||
"mv aws_security_group.foo aws_security_group.foo2", | ||
"mv aws_security_group.bar aws_security_group.bar2", | ||
"rm aws_security_group.bar", | ||
"import aws_security_group.piyo piyo", | ||
] | ||
} | ||
` | ||
|
||
config, err := ParseMigrationFile("test.hcl", []byte(source)) | ||
if err != nil { | ||
t.Fatalf("failed to ParseMigration: %s", err) | ||
} | ||
spew.Dump(config) | ||
|
||
o := &tfmigrate.MigratorOption{ | ||
ExecPath: "direnv exec . terraform", | ||
} | ||
m, err := config.NewMigrator(o) | ||
if err != nil { | ||
t.Fatalf("failed to NewMigrator: %s", err) | ||
} | ||
spew.Dump(m) | ||
} | ||
|
||
func TestParseMigrationFileWithMultiState(t *testing.T) { | ||
const source = ` | ||
migration "multi_state" "mv_dir1_dir2" { | ||
from_dir = "dir1" | ||
to_dir = "dir2" | ||
actions = [ | ||
"mv aws_security_group.foo aws_security_group.foo2", | ||
"mv aws_security_group.bar aws_security_group.bar2", | ||
] | ||
} | ||
` | ||
|
||
config, err := ParseMigrationFile("test.hcl", []byte(source)) | ||
if err != nil { | ||
t.Fatalf("failed to ParseMigration: %s", err) | ||
} | ||
spew.Dump(config) | ||
|
||
o := &tfmigrate.MigratorOption{ | ||
ExecPath: "direnv exec . terraform", | ||
} | ||
m, err := config.NewMigrator(o) | ||
if err != nil { | ||
t.Fatalf("failed to NewMigrator: %s", err) | ||
} | ||
spew.Dump(m) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,62 @@ | ||
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= | ||
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= | ||
github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= | ||
github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= | ||
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= | ||
github.com/apparentlymart/go-textseg/v12 v12.0.0 h1:bNEQyAGak9tojivJNkoqWErVCQbjdL7GzRt3F8NvfJ0= | ||
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= | ||
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= | ||
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= | ||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | ||
github.com/hashicorp/hcl/v2 v2.6.0 h1:3krZOfGY6SziUXa6H9PJU6TyohHn7I+ARYnhbeNBz+o= | ||
github.com/hashicorp/hcl/v2 v2.6.0/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY= | ||
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= | ||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= | ||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= | ||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | ||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= | ||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= | ||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= | ||
github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw= | ||
github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= | ||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= | ||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= | ||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= | ||
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= | ||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= | ||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= | ||
github.com/zclconf/go-cty v1.2.0 h1:sPHsy7ADcIZQP3vILvTjrh74ZA175TFP5vqiNK1UmlI= | ||
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= | ||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= | ||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= | ||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= | ||
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= | ||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | ||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 h1:EBZoQjiKKPaLbPrbpssUfuHtwM6KV/vb4U85g/cigFY= | ||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= | ||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package tfmigrate | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/minamijoyo/tfmigrate/tfexec" | ||
) | ||
|
||
// MultiStateAction abstracts multi state migration operations. | ||
// It's used for moving resources from a state to another. | ||
type MultiStateAction interface { | ||
// MultiStateUpdate updates given two states and returns new two states. | ||
MultiStateUpdate(ctx context.Context, fromTf tfexec.TerraformCLI, toTf tfexec.TerraformCLI, fromState *tfexec.State, toState *tfexec.State) (*tfexec.State, *tfexec.State, error) | ||
} | ||
|
||
// NewMultiStateActionFromString is a factory method which returns a new | ||
// MultiStateAction from a given string. | ||
// cmdStr is a plain text for state operation. | ||
// This method is useful to build an action from terraform state command. | ||
// Valid formats are the following. | ||
// "mv <source> <destination>" | ||
func NewMultiStateActionFromString(cmdStr string) (MultiStateAction, error) { | ||
// split cmdStr using Fields instead of Split to allow cmdStr to have duplicated white spaces. | ||
args := strings.Fields(cmdStr) | ||
if len(args) == 0 { | ||
return nil, fmt.Errorf("multi state action is empty: %s", cmdStr) | ||
} | ||
actionType := args[0] | ||
|
||
// switch by action type and parse arguments and build an action. | ||
var action MultiStateAction | ||
switch actionType { | ||
case "mv": | ||
if len(args) != 3 { | ||
return nil, fmt.Errorf("multi state mv action is invalid: %s", cmdStr) | ||
} | ||
src := args[1] | ||
dst := args[2] | ||
action = NewMultiStateMvAction(src, dst) | ||
|
||
default: | ||
return nil, fmt.Errorf("unknown multi state action type: %s", cmdStr) | ||
} | ||
|
||
return action, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.