Skip to content

Commit

Permalink
Analyze a given plan and resolve conflicts and generate a migration
Browse files Browse the repository at this point in the history
  • Loading branch information
minamijoyo committed Apr 20, 2022
1 parent 13def48 commit 0f0a961
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 68 deletions.
30 changes: 30 additions & 0 deletions migration/analyzer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package migration

type Analyzer interface {
Analyze(plan *Plan) *StateMigration
}

type defaultAnalyzer struct {
resolvers []Resolver
}

var _ Analyzer = (*defaultAnalyzer)(nil)

func NewDefaultAnalyzer() Analyzer {
return &defaultAnalyzer{
resolvers: []Resolver{
&StateImportResolver{},
},
}
}

func (a *defaultAnalyzer) Analyze(plan *Plan) *StateMigration {
var migration StateMigration

for _, r := range a.resolvers {
actions := r.Resolve(plan)
migration.AppendActions(actions...)
}

return &migration
}
13 changes: 13 additions & 0 deletions migration/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package migration

func Generate(planJSON []byte) ([]byte, error) {
plan, err := NewPlan(planJSON)
if err != nil {
return nil, err
}

analyzer := NewDefaultAnalyzer()
migration := analyzer.Analyze(plan)

return migration.Render()
}
59 changes: 42 additions & 17 deletions migration/migration.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,51 @@
package migration

import (
"github.com/minamijoyo/tfedit/migration/schema"
_ "github.com/minamijoyo/tfedit/migration/schema/aws" // Register schema for aws
"bytes"
"fmt"
"text/template"
)

func Generate(planJSON []byte) ([]byte, error) {
plan, err := NewPlan(planJSON)
if err != nil {
return nil, err
}
// StateMigration is a type which is equivalent to tfmigrate.StateMigratorConfig of
// minamijoyo/tfmigrate.
// The current implementation doesn't encode migration actions to a file
// directly with gohcl, so we avoid to depend on tfmigrate's type and we define
// only what we need here.
type StateMigration struct {
// Dir is a working directory for executing terraform command.
Dir string
// Actions is a list of state action.
Actions []Action
}

var migrationTemplate = `migration "state" "awsv4upgrade" {
actions = [
{{- range .Actions }}
"{{ .MigrationAction }}",
{{- end }}
]
}
`

var compiledMigrationTemplate = template.Must(template.New("migration").Parse(migrationTemplate))

// AppendActions appends a list of actions to migration.
func (m *StateMigration) AppendActions(actions ...Action) {
m.Actions = append(m.Actions, actions...)
}

var migration StateMigration
for _, rc := range plan.ResourceChanges() {
if rc.Change.Actions.Create() {
address := rc.Address
after := rc.Change.After.(map[string]interface{})
importID := schema.ImportID(rc.Type, after)
action := NewStateImportAction(address, importID)
migration.AppendAction(action)
}
// Render converts a state migration config to bytes
// Encoding StateMigratorConfig directly with gohcl has some problems.
// An array contains multiple elements is output as one line. It's not readable
// for multiple actions. In additon, the default value is set explicitly, it's
// not only redundant but also increases cognitive load for user who isn't
// familiar with tfmigrate.
// So we use text/template to render a migration file.
func (m *StateMigration) Render() ([]byte, error) {
var output bytes.Buffer
if err := compiledMigrationTemplate.Execute(&output, m); err != nil {
return nil, fmt.Errorf("failed to render migration file: %s", err)
}

return migration.Render()
return output.Bytes(), nil
}
30 changes: 30 additions & 0 deletions migration/resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package migration

import (
"github.com/minamijoyo/tfedit/migration/schema"
_ "github.com/minamijoyo/tfedit/migration/schema/aws" // Register schema for aws
)

type Resolver interface {
Resolve(plan *Plan) []Action
}

type StateImportResolver struct {
}

var _ Resolver = (*StateImportResolver)(nil)

func (r *StateImportResolver) Resolve(plan *Plan) []Action {
actions := []Action{}
for _, rc := range plan.ResourceChanges() {
if rc.Change.Actions.Create() {
address := rc.Address
after := rc.Change.After.(map[string]interface{})
importID := schema.ImportID(rc.Type, after)
action := NewStateImportAction(address, importID)
actions = append(actions, action)
}
}

return actions
}
51 changes: 0 additions & 51 deletions migration/state_migration.go

This file was deleted.

0 comments on commit 0f0a961

Please sign in to comment.