diff --git a/terraformstate/terraform_state.go b/terraformstate/terraform_state.go
index 9f67da0..557bd9d 100644
--- a/terraformstate/terraform_state.go
+++ b/terraformstate/terraform_state.go
@@ -90,6 +90,17 @@ func importedResources(resources ResourceChanges) ResourceChanges {
return acc
}
+func movedResources(resources ResourceChanges) ResourceChanges {
+ acc := make(ResourceChanges, 0)
+ for _, r := range resources {
+ if r.PreviousAddress != "" && r.PreviousAddress != r.Address {
+ acc = append(acc, r)
+
+ }
+ }
+ return acc
+}
+
func FilterNoOpResources(ts *tfjson.Plan) {
acc := make(ResourceChanges, 0)
for _, r := range ts.ResourceChanges {
@@ -129,6 +140,15 @@ func GetAllResourceChanges(plan tfjson.Plan) map[string]ResourceChanges {
}
}
+// GetAllResourceMoves returns all resources that have moved.
+func GetAllResourceMoves(plan tfjson.Plan) map[string]ResourceChanges {
+ movedResources := movedResources(plan.ResourceChanges)
+
+ return map[string]ResourceChanges{
+ "moved": movedResources,
+ }
+}
+
func GetAllOutputChanges(plan tfjson.Plan) map[string][]string {
// create, update, and delete are the only available actions for outputChanges
// https://developer.hashicorp.com/terraform/internals/json-format
diff --git a/terraformstate/terraform_state_test.go b/terraformstate/terraform_state_test.go
index 00caaa7..dbea003 100644
--- a/terraformstate/terraform_state_test.go
+++ b/terraformstate/terraform_state_test.go
@@ -42,6 +42,7 @@ func TestGetAllResourceChanges(t *testing.T) {
&ResourceChange{Address: "update1", Change: &Change{Actions: Actions{ActionUpdate}}},
&ResourceChange{Address: "import2", Change: &Change{Importing: &Importing{ID: "id1"}}},
&ResourceChange{Address: "import1", Change: &Change{Importing: &Importing{ID: "id2"}}},
+ &ResourceChange{Address: "move1", PreviousAddress: "move", Change: &Change{Actions: Actions{}}},
&ResourceChange{Address: "recreate2", Change: &Change{Actions: Actions{ActionDelete, ActionCreate}}},
&ResourceChange{Address: "recreate1", Change: &Change{Actions: Actions{ActionDelete, ActionCreate}}},
}
@@ -75,6 +76,33 @@ func TestGetAllResourceChanges(t *testing.T) {
assert.Equal(t, expectedResourceChanges, result)
}
+func TestGetAllResourceMoves(t *testing.T) {
+ resourceChanges := ResourceChanges{
+ &ResourceChange{Address: "create2", Change: &Change{Actions: Actions{ActionCreate}}},
+ &ResourceChange{Address: "create1", Change: &Change{Actions: Actions{ActionCreate}}},
+ &ResourceChange{Address: "delete2", Change: &Change{Actions: Actions{ActionDelete}}},
+ &ResourceChange{Address: "delete1", Change: &Change{Actions: Actions{ActionDelete}}},
+ &ResourceChange{Address: "update2", Change: &Change{Actions: Actions{ActionUpdate}}},
+ &ResourceChange{Address: "update1", Change: &Change{Actions: Actions{ActionUpdate}}},
+ &ResourceChange{Address: "import2", Change: &Change{Importing: &Importing{ID: "id1"}}},
+ &ResourceChange{Address: "import1", Change: &Change{Importing: &Importing{ID: "id2"}}},
+ &ResourceChange{Address: "move1", PreviousAddress: "move", Change: &Change{Actions: Actions{}}},
+ &ResourceChange{Address: "recreate2", Change: &Change{Actions: Actions{ActionDelete, ActionCreate}}},
+ &ResourceChange{Address: "recreate1", Change: &Change{Actions: Actions{ActionDelete, ActionCreate}}},
+ }
+ plan := tfjson.Plan{ResourceChanges: resourceChanges}
+
+ result := GetAllResourceMoves(plan)
+
+ expectedResourceMoves := map[string]ResourceChanges{
+ "moved": {
+ &ResourceChange{Address: "move1", PreviousAddress: "move", Change: &Change{Actions: Actions{}}},
+ },
+ }
+
+ assert.Equal(t, expectedResourceMoves, result)
+}
+
func TestGetAllOutputChanges(t *testing.T) {
outputChanges := map[string]*Change{
diff --git a/writer/html.go b/writer/html.go
index c07c206..cc10fe3 100644
--- a/writer/html.go
+++ b/writer/html.go
@@ -11,16 +11,15 @@ import (
// HTMLWriter is a Writer that writes HTML.
type HTMLWriter struct {
ResourceChanges map[string]terraformstate.ResourceChanges
+ MovedResources map[string]terraformstate.ResourceChanges
OutputChanges map[string][]string
}
-var cfs = getFS()
-
// Write outputs the HTML summary to the io.Writer it's passed.
func (t HTMLWriter) Write(writer io.Writer) error {
templatesDir := "templates"
rcTmpl := "resourceChanges.html"
- tmpl, err := template.New(rcTmpl).ParseFS(cfs, path.Join(templatesDir, rcTmpl))
+ tmpl, err := template.New(rcTmpl).ParseFS(templates, path.Join(templatesDir, rcTmpl))
if err != nil {
return err
}
@@ -33,9 +32,8 @@ func (t HTMLWriter) Write(writer io.Writer) error {
if !hasOutputChanges(t.OutputChanges) {
return nil
}
-
ocTmpl := "outputChanges.html"
- outputTmpl, err := template.New(ocTmpl).ParseFS(cfs, path.Join(templatesDir, ocTmpl))
+ outputTmpl, err := template.New(ocTmpl).ParseFS(templates, path.Join(templatesDir, ocTmpl))
if err != nil {
return err
}
@@ -44,9 +42,10 @@ func (t HTMLWriter) Write(writer io.Writer) error {
}
// NewHTMLWriter returns a new HTMLWriter with the configuration it's passed.
-func NewHTMLWriter(changes map[string]terraformstate.ResourceChanges, outputChanges map[string][]string) Writer {
+func NewHTMLWriter(changes map[string]terraformstate.ResourceChanges, movedResources map[string]terraformstate.ResourceChanges, outputChanges map[string][]string) Writer {
return HTMLWriter{
ResourceChanges: changes,
+ MovedResources: movedResources,
OutputChanges: outputChanges,
}
}
diff --git a/writer/html_test.go b/writer/html_test.go
index 64d704e..9a7ea50 100644
--- a/writer/html_test.go
+++ b/writer/html_test.go
@@ -3,56 +3,14 @@ package writer
import (
"bytes"
"testing"
- "testing/fstest"
"github.com/dineshba/tf-summarize/terraformstate"
. "github.com/hashicorp/terraform-json"
)
-func TestHTMLWriterWithMockFileSystem(t *testing.T) {
- origFS := cfs
- cfs = fstest.MapFS{
- "templates/resourceChanges.html": &fstest.MapFile{
- Data: []byte(`
-
- | CHANGE |
- RESOURCE |
-
{{ range $change, $resources := .ResourceChanges }}{{ $length := len $resources }}{{ if gt $length 0 }}
-
- | {{ $change }} |
-
- {{ range $i, $r := $resources }}
- {{ $r.Address }} {{ end }}
-
- |
-
{{ end }}{{ end }}
-
-`),
- },
- "templates/outputChanges.html": &fstest.MapFile{
- Data: []byte(`
-
- | CHANGE |
- OUTPUT |
-
{{ range $change, $outputs := .OutputChanges }}{{ $length := len $outputs }}{{ if gt $length 0 }}
-
- | {{ $change }} |
-
- {{ range $i, $o := $outputs }}
- {{ $o }} {{ end }}
-
- |
-
{{ end }}{{ end }}
-
-`),
- },
- }
- t.Cleanup(func() {
- cfs = origFS
- })
-
+func TestHTMLWriter(t *testing.T) {
resourceChanges := map[string]terraformstate.ResourceChanges{
- "module.test": {
+ "update": {
{
Address: "aws_instance.example",
Name: "example",
@@ -64,11 +22,23 @@ func TestHTMLWriterWithMockFileSystem(t *testing.T) {
},
},
}
+ movedResources := map[string]terraformstate.ResourceChanges{
+ "moved": {
+ {
+ Address: "aws_instance.foo",
+ PreviousAddress: "aws_instance.bar",
+ Name: "foo",
+ Change: &Change{
+ Actions: Actions{},
+ },
+ },
+ },
+ }
outputChanges := map[string][]string{
"output_key": {"output_value"},
}
- htmlWriter := NewHTMLWriter(resourceChanges, outputChanges)
+ htmlWriter := NewHTMLWriter(resourceChanges, movedResources, outputChanges)
var buf bytes.Buffer
err := htmlWriter.Write(&buf)
@@ -82,13 +52,21 @@ func TestHTMLWriterWithMockFileSystem(t *testing.T) {
RESOURCE |
- | module.test |
+ update |
|
+
+ | moved |
+
+
+ aws_instance.bar to aws_instance.foo
+
+ |
+
@@ -106,7 +84,7 @@ func TestHTMLWriterWithMockFileSystem(t *testing.T) {
`
if buf.String() != expectedOutput {
- t.Errorf("expected %q, got %q", expectedOutput, buf.String())
+ t.Errorf("expected %s, got %s", expectedOutput, buf.String())
}
}
diff --git a/writer/table.go b/writer/table.go
index 8179a90..405577f 100644
--- a/writer/table.go
+++ b/writer/table.go
@@ -11,6 +11,7 @@ import (
type TableWriter struct {
mdEnabled bool
changes map[string]terraformstate.ResourceChanges
+ moves map[string]terraformstate.ResourceChanges
outputChanges map[string][]string
}
@@ -18,8 +19,10 @@ var tableOrder = []string{"import", "add", "update", "recreate", "delete"}
func (t TableWriter) Write(writer io.Writer) error {
tableString := make([][]string, 0, 4)
+
for _, change := range tableOrder {
changedResources := t.changes[change]
+
for _, changedResource := range changedResources {
if t.mdEnabled {
tableString = append(tableString, []string{change, fmt.Sprintf("`%s`", changedResource.Address)})
@@ -29,6 +32,16 @@ func (t TableWriter) Write(writer io.Writer) error {
}
}
+ for move, movedResources := range t.moves {
+ for _, movedResource := range movedResources {
+ if t.mdEnabled {
+ tableString = append(tableString, []string{move, fmt.Sprintf("`%s` to `%s`", movedResource.PreviousAddress, movedResource.Address)})
+ } else {
+ tableString = append(tableString, []string{move, fmt.Sprintf("%s to %s", movedResource.PreviousAddress, movedResource.Address)})
+ }
+ }
+ }
+
table := tablewriter.NewWriter(writer)
table.SetHeader([]string{"Change", "Resource"})
table.SetAutoMergeCells(true)
@@ -80,9 +93,10 @@ func (t TableWriter) Write(writer io.Writer) error {
return nil
}
-func NewTableWriter(changes map[string]terraformstate.ResourceChanges, outputChanges map[string][]string, mdEnabled bool) Writer {
+func NewTableWriter(changes map[string]terraformstate.ResourceChanges, moves map[string]terraformstate.ResourceChanges, outputChanges map[string][]string, mdEnabled bool) Writer {
return TableWriter{
changes: changes,
+ moves: moves,
mdEnabled: mdEnabled,
outputChanges: outputChanges,
}
diff --git a/writer/table_test.go b/writer/table_test.go
index 65043d7..26e3121 100644
--- a/writer/table_test.go
+++ b/writer/table_test.go
@@ -24,6 +24,16 @@ func TestTableWriter_Write_NoMarkdown(t *testing.T) {
},
}
+ movedResources := map[string]terraformstate.ResourceChanges{
+ "moved": {
+ {
+ Address: "aws_instance.new",
+ PreviousAddress: "aws_instance.old",
+ Change: &Change{Actions: Actions{}},
+ },
+ },
+ }
+
outputChanges := map[string][]string{
"update": {
"output.example",
@@ -31,7 +41,7 @@ func TestTableWriter_Write_NoMarkdown(t *testing.T) {
},
}
- tw := NewTableWriter(changes, outputChanges, false)
+ tw := NewTableWriter(changes, movedResources, outputChanges, false)
var output bytes.Buffer
err := tw.Write(&output)
assert.NoError(t, err)
@@ -47,6 +57,8 @@ func TestTableWriter_Write_NoMarkdown(t *testing.T) {
+--------+--------------------------------------------------+
| delete | aws_instance.example2 |
+--------+--------------------------------------------------+
+| moved | aws_instance.old to aws_instance.new |
++--------+--------------------------------------------------+
+--------+--------------------------------------------------------+
| CHANGE | OUTPUT |
+--------+--------------------------------------------------------+
@@ -62,6 +74,16 @@ func TestTableWriter_Write_NoMarkdown(t *testing.T) {
func TestTableWriter_Write_WithMarkdown(t *testing.T) {
changes := createMockChanges()
+ movedResources := map[string]terraformstate.ResourceChanges{
+ "moved": {
+ {
+ Address: "aws_instance.new",
+ PreviousAddress: "aws_instance.old",
+ Change: &Change{Actions: Actions{}},
+ },
+ },
+ }
+
outputChanges := map[string][]string{
"update": {
"output.example",
@@ -69,15 +91,16 @@ func TestTableWriter_Write_WithMarkdown(t *testing.T) {
},
}
- tw := NewTableWriter(changes, outputChanges, true)
+ tw := NewTableWriter(changes, movedResources, outputChanges, true)
var output bytes.Buffer
err := tw.Write(&output)
assert.NoError(t, err)
- expectedOutput := `| CHANGE | RESOURCE |
-|--------|-------------------------|
-| add | ` + "`aws_instance.example1`" + ` |
-| delete | ` + "`aws_instance.example2`" + ` |
+ expectedOutput := `| CHANGE | RESOURCE |
+|--------|------------------------------------------|
+| add | ` + "`aws_instance.example1`" + ` |
+| delete | ` + "`aws_instance.example2`" + ` |
+| moved | ` + "`aws_instance.old` to `aws_instance.new`" + ` |
| CHANGE | OUTPUT |
|--------|----------------------------------------------------------|
@@ -90,9 +113,10 @@ func TestTableWriter_Write_WithMarkdown(t *testing.T) {
func TestTableWriter_NoChanges(t *testing.T) {
changes := map[string]terraformstate.ResourceChanges{}
+ movedResources := map[string]terraformstate.ResourceChanges{}
outputChanges := map[string][]string{}
- tw := NewTableWriter(changes, outputChanges, false)
+ tw := NewTableWriter(changes, movedResources, outputChanges, false)
var output bytes.Buffer
err := tw.Write(&output)
assert.NoError(t, err)
diff --git a/writer/templates/resourceChanges.html b/writer/templates/resourceChanges.html
index 8f4f4ab..a05c422 100644
--- a/writer/templates/resourceChanges.html
+++ b/writer/templates/resourceChanges.html
@@ -3,11 +3,20 @@
CHANGE |
RESOURCE |
{{ range $change, $resources := .ResourceChanges }}{{ $length := len $resources }}{{ if gt $length 0 }}
+
+ | {{ $change }} |
+
+ {{ range $i, $r := $resources }}{{ if eq $change "moved" }}
+ {{ $r.PreviousAddress }} to {{ $r.Address }} {{ else }}
+ {{ $r.Address }} {{ end }}{{ end }}
+
+ |
+
{{ end }}{{ end }}{{ range $change, $resources := .MovedResources }}{{ $length := len $resources }}{{ if gt $length 0 }}
| {{ $change }} |
{{ range $i, $r := $resources }}
- {{ $r.Address }} {{ end }}
+ {{ $r.PreviousAddress }} to {{ $r.Address }} {{ end }}
|
{{ end }}{{ end }}
diff --git a/writer/util.go b/writer/util.go
index 1f59282..c75e0ff 100644
--- a/writer/util.go
+++ b/writer/util.go
@@ -2,7 +2,6 @@ package writer
import (
"embed"
- "io/fs"
"regexp"
)
@@ -11,10 +10,6 @@ import (
//go:embed templates
var templates embed.FS
-var getFS = func() fs.FS {
- return templates
-}
-
func hasOutputChanges(opChanges map[string][]string) bool {
hasChanges := false
diff --git a/writer/writer.go b/writer/writer.go
index d22fa51..4417302 100644
--- a/writer/writer.go
+++ b/writer/writer.go
@@ -22,11 +22,11 @@ func CreateWriter(tree, separateTree, drawable, mdEnabled, json, html bool, json
return NewJSONWriter(plan.ResourceChanges)
}
if html {
- return NewHTMLWriter(terraformstate.GetAllResourceChanges(plan), terraformstate.GetAllOutputChanges(plan))
+ return NewHTMLWriter(terraformstate.GetAllResourceChanges(plan), terraformstate.GetAllResourceMoves(plan), terraformstate.GetAllOutputChanges(plan))
}
if jsonSum {
return NewJsonSumWriter(terraformstate.GetAllResourceChanges(plan))
}
- return NewTableWriter(terraformstate.GetAllResourceChanges(plan), terraformstate.GetAllOutputChanges(plan), mdEnabled)
+ return NewTableWriter(terraformstate.GetAllResourceChanges(plan), terraformstate.GetAllResourceMoves(plan), terraformstate.GetAllOutputChanges(plan), mdEnabled)
}