Skip to content
20 changes: 20 additions & 0 deletions terraformstate/terraform_state.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package terraformstate

Check warning on line 1 in terraformstate/terraform_state.go

View workflow job for this annotation

GitHub Actions / build

should have a package comment

import (
"encoding/json"
Expand Down Expand Up @@ -90,6 +90,17 @@
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 {
Expand Down Expand Up @@ -129,6 +140,15 @@
}
}

// 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
Expand Down
28 changes: 28 additions & 0 deletions terraformstate/terraform_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import (
"testing"

. "github.com/hashicorp/terraform-json"

Check warning on line 6 in terraformstate/terraform_state_test.go

View workflow job for this annotation

GitHub Actions / build

should not use dot imports
tfjson "github.com/hashicorp/terraform-json"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -42,6 +42,7 @@
&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}}},
}
Expand Down Expand Up @@ -75,6 +76,33 @@
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{
Expand Down
11 changes: 5 additions & 6 deletions writer/html.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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,
}
}
72 changes: 25 additions & 47 deletions writer/html_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(`<table>
<tr>
<th>CHANGE</th>
<th>RESOURCE</th>
</tr>{{ range $change, $resources := .ResourceChanges }}{{ $length := len $resources }}{{ if gt $length 0 }}
<tr>
<td>{{ $change }}</td>
<td>
<ul>{{ range $i, $r := $resources }}
<li><code>{{ $r.Address }}</code></li>{{ end }}
</ul>
</td>
</tr>{{ end }}{{ end }}
</table>
`),
},
"templates/outputChanges.html": &fstest.MapFile{
Data: []byte(`<table>
<tr>
<th>CHANGE</th>
<th>OUTPUT</th>
</tr>{{ range $change, $outputs := .OutputChanges }}{{ $length := len $outputs }}{{ if gt $length 0 }}
<tr>
<td>{{ $change }}</td>
<td>
<ul>{{ range $i, $o := $outputs }}
<li><code>{{ $o }}</code></li>{{ end }}
</ul>
</td>
</tr>{{ end }}{{ end }}
</table>
`),
},
}
t.Cleanup(func() {
cfs = origFS
})

func TestHTMLWriter(t *testing.T) {
resourceChanges := map[string]terraformstate.ResourceChanges{
"module.test": {
"update": {
{
Address: "aws_instance.example",
Name: "example",
Expand All @@ -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)
Expand All @@ -82,13 +52,21 @@ func TestHTMLWriterWithMockFileSystem(t *testing.T) {
<th>RESOURCE</th>
</tr>
<tr>
<td>module.test</td>
<td>update</td>
<td>
<ul>
<li><code>aws_instance.example</code></li>
</ul>
</td>
</tr>
<tr>
<td>moved</td>
<td>
<ul>
<li><code>aws_instance.bar</code> to <code>aws_instance.foo</code></li>
</ul>
</td>
</tr>
</table>
<table>
<tr>
Expand All @@ -106,7 +84,7 @@ func TestHTMLWriterWithMockFileSystem(t *testing.T) {
</table>
`
if buf.String() != expectedOutput {
t.Errorf("expected %q, got %q", expectedOutput, buf.String())
t.Errorf("expected %s, got %s", expectedOutput, buf.String())
}

}
16 changes: 15 additions & 1 deletion writer/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@ import (
type TableWriter struct {
mdEnabled bool
changes map[string]terraformstate.ResourceChanges
moves map[string]terraformstate.ResourceChanges
outputChanges map[string][]string
}

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)})
Expand All @@ -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)
Expand Down Expand Up @@ -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,
}
Expand Down
38 changes: 31 additions & 7 deletions writer/table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,24 @@ 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",
"output.long_resource_name.this[\"Custom/Resource Name\"]",
},
}

tw := NewTableWriter(changes, outputChanges, false)
tw := NewTableWriter(changes, movedResources, outputChanges, false)
var output bytes.Buffer
err := tw.Write(&output)
assert.NoError(t, err)
Expand All @@ -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 |
+--------+--------------------------------------------------------+
Expand All @@ -62,22 +74,33 @@ 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",
"output.long_resource_name.this[\"Custom/Resource Name\"]",
},
}

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 |
|--------|----------------------------------------------------------|
Expand All @@ -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)
Expand Down
11 changes: 10 additions & 1 deletion writer/templates/resourceChanges.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,20 @@
<th>CHANGE</th>
<th>RESOURCE</th>
</tr>{{ range $change, $resources := .ResourceChanges }}{{ $length := len $resources }}{{ if gt $length 0 }}
<tr>
<td>{{ $change }}</td>
<td>
<ul>{{ range $i, $r := $resources }}{{ if eq $change "moved" }}
<li><code>{{ $r.PreviousAddress }}</code> to <code>{{ $r.Address }}</code></li>{{ else }}
<li><code>{{ $r.Address }}</code></li>{{ end }}{{ end }}
</ul>
</td>
</tr>{{ end }}{{ end }}{{ range $change, $resources := .MovedResources }}{{ $length := len $resources }}{{ if gt $length 0 }}
<tr>
<td>{{ $change }}</td>
<td>
<ul>{{ range $i, $r := $resources }}
<li><code>{{ $r.Address }}</code></li>{{ end }}
<li><code>{{ $r.PreviousAddress }}</code> to <code>{{ $r.Address }}</code></li>{{ end }}
</ul>
</td>
</tr>{{ end }}{{ end }}
Expand Down
Loading
Loading