620 lines
14 KiB
Go
620 lines
14 KiB
Go
package format
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/hashicorp/terraform/terraform"
|
|
"github.com/mitchellh/colorstring"
|
|
)
|
|
|
|
var disabledColorize = &colorstring.Colorize{
|
|
Colors: colorstring.DefaultColors,
|
|
Disable: true,
|
|
}
|
|
|
|
func TestNewPlan(t *testing.T) {
|
|
tests := map[string]struct {
|
|
Input *terraform.Plan
|
|
Want *Plan
|
|
}{
|
|
"nil input": {
|
|
Input: nil,
|
|
Want: &Plan{
|
|
Resources: nil,
|
|
},
|
|
},
|
|
"nil diff": {
|
|
Input: &terraform.Plan{},
|
|
Want: &Plan{
|
|
Resources: nil,
|
|
},
|
|
},
|
|
"empty diff": {
|
|
Input: &terraform.Plan{
|
|
Diff: &terraform.Diff{
|
|
Modules: []*terraform.ModuleDiff{
|
|
{
|
|
Path: []string{"root"},
|
|
Resources: map[string]*terraform.InstanceDiff{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Want: &Plan{
|
|
Resources: nil,
|
|
},
|
|
},
|
|
"create managed resource": {
|
|
Input: &terraform.Plan{
|
|
Diff: &terraform.Diff{
|
|
Modules: []*terraform.ModuleDiff{
|
|
{
|
|
Path: []string{"root"},
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
"test_resource.foo": {
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
"id": {
|
|
NewComputed: true,
|
|
RequiresNew: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Want: &Plan{
|
|
Resources: []*InstanceDiff{
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.foo"),
|
|
Action: terraform.DiffCreate,
|
|
Attributes: []*AttributeDiff{
|
|
{
|
|
Path: "id",
|
|
Action: terraform.DiffCreate,
|
|
NewComputed: true,
|
|
ForcesNew: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"create managed resource in child module": {
|
|
Input: &terraform.Plan{
|
|
Diff: &terraform.Diff{
|
|
Modules: []*terraform.ModuleDiff{
|
|
{
|
|
Path: []string{"root"},
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
"test_resource.foo": {
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
"id": {
|
|
NewComputed: true,
|
|
RequiresNew: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Path: []string{"root", "foo"},
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
"test_resource.foo": {
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
"id": {
|
|
NewComputed: true,
|
|
RequiresNew: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Want: &Plan{
|
|
Resources: []*InstanceDiff{
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.foo"),
|
|
Action: terraform.DiffCreate,
|
|
Attributes: []*AttributeDiff{
|
|
{
|
|
Path: "id",
|
|
Action: terraform.DiffCreate,
|
|
NewComputed: true,
|
|
ForcesNew: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Addr: mustParseResourceAddress("module.foo.test_resource.foo"),
|
|
Action: terraform.DiffCreate,
|
|
Attributes: []*AttributeDiff{
|
|
{
|
|
Path: "id",
|
|
Action: terraform.DiffCreate,
|
|
NewComputed: true,
|
|
ForcesNew: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"create data resource": {
|
|
Input: &terraform.Plan{
|
|
Diff: &terraform.Diff{
|
|
Modules: []*terraform.ModuleDiff{
|
|
{
|
|
Path: []string{"root"},
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
"data.test_data_source.foo": {
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
"id": {
|
|
NewComputed: true,
|
|
RequiresNew: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Want: &Plan{
|
|
Resources: []*InstanceDiff{
|
|
{
|
|
Addr: mustParseResourceAddress("data.test_data_source.foo"),
|
|
Action: terraform.DiffRefresh,
|
|
Attributes: []*AttributeDiff{
|
|
{
|
|
Path: "id",
|
|
Action: terraform.DiffUpdate,
|
|
NewComputed: true,
|
|
ForcesNew: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"destroy managed resource": {
|
|
Input: &terraform.Plan{
|
|
Diff: &terraform.Diff{
|
|
Modules: []*terraform.ModuleDiff{
|
|
{
|
|
Path: []string{"root"},
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
"test_resource.foo": {
|
|
Destroy: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Want: &Plan{
|
|
Resources: []*InstanceDiff{
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.foo"),
|
|
Action: terraform.DiffDestroy,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"destroy data resource": {
|
|
Input: &terraform.Plan{
|
|
Diff: &terraform.Diff{
|
|
Modules: []*terraform.ModuleDiff{
|
|
{
|
|
Path: []string{"root"},
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
"data.test_data_source.foo": {
|
|
Destroy: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Want: &Plan{
|
|
// Data source destroys are not shown
|
|
Resources: nil,
|
|
},
|
|
},
|
|
"destroy many instances of a resource": {
|
|
Input: &terraform.Plan{
|
|
Diff: &terraform.Diff{
|
|
Modules: []*terraform.ModuleDiff{
|
|
{
|
|
Path: []string{"root"},
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
"test_resource.foo.0": {
|
|
Destroy: true,
|
|
},
|
|
"test_resource.foo.1": {
|
|
Destroy: true,
|
|
},
|
|
"test_resource.foo.10": {
|
|
Destroy: true,
|
|
},
|
|
"test_resource.foo.2": {
|
|
Destroy: true,
|
|
},
|
|
"test_resource.foo.3": {
|
|
Destroy: true,
|
|
},
|
|
"test_resource.foo.4": {
|
|
Destroy: true,
|
|
},
|
|
"test_resource.foo.5": {
|
|
Destroy: true,
|
|
},
|
|
"test_resource.foo.6": {
|
|
Destroy: true,
|
|
},
|
|
"test_resource.foo.7": {
|
|
Destroy: true,
|
|
},
|
|
"test_resource.foo.8": {
|
|
Destroy: true,
|
|
},
|
|
"test_resource.foo.9": {
|
|
Destroy: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Want: &Plan{
|
|
Resources: []*InstanceDiff{
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.foo[0]"),
|
|
Action: terraform.DiffDestroy,
|
|
},
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.foo[1]"),
|
|
Action: terraform.DiffDestroy,
|
|
},
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.foo[2]"),
|
|
Action: terraform.DiffDestroy,
|
|
},
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.foo[3]"),
|
|
Action: terraform.DiffDestroy,
|
|
},
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.foo[4]"),
|
|
Action: terraform.DiffDestroy,
|
|
},
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.foo[5]"),
|
|
Action: terraform.DiffDestroy,
|
|
},
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.foo[6]"),
|
|
Action: terraform.DiffDestroy,
|
|
},
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.foo[7]"),
|
|
Action: terraform.DiffDestroy,
|
|
},
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.foo[8]"),
|
|
Action: terraform.DiffDestroy,
|
|
},
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.foo[9]"),
|
|
Action: terraform.DiffDestroy,
|
|
},
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.foo[10]"),
|
|
Action: terraform.DiffDestroy,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
got := NewPlan(test.Input)
|
|
if !reflect.DeepEqual(got, test.Want) {
|
|
t.Errorf(
|
|
"wrong result\ninput: %sgot: %swant:%s",
|
|
spew.Sdump(test.Input),
|
|
spew.Sdump(got),
|
|
spew.Sdump(test.Want),
|
|
)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPlanStats(t *testing.T) {
|
|
tests := map[string]struct {
|
|
Input *Plan
|
|
Want PlanStats
|
|
}{
|
|
"empty": {
|
|
&Plan{},
|
|
PlanStats{},
|
|
},
|
|
"destroy": {
|
|
&Plan{
|
|
Resources: []*InstanceDiff{
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.foo"),
|
|
Action: terraform.DiffDestroy,
|
|
},
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.bar"),
|
|
Action: terraform.DiffDestroy,
|
|
},
|
|
},
|
|
},
|
|
PlanStats{
|
|
ToDestroy: 2,
|
|
},
|
|
},
|
|
"create": {
|
|
&Plan{
|
|
Resources: []*InstanceDiff{
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.foo"),
|
|
Action: terraform.DiffCreate,
|
|
},
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.bar"),
|
|
Action: terraform.DiffCreate,
|
|
},
|
|
},
|
|
},
|
|
PlanStats{
|
|
ToAdd: 2,
|
|
},
|
|
},
|
|
"update": {
|
|
&Plan{
|
|
Resources: []*InstanceDiff{
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.foo"),
|
|
Action: terraform.DiffUpdate,
|
|
},
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.bar"),
|
|
Action: terraform.DiffUpdate,
|
|
},
|
|
},
|
|
},
|
|
PlanStats{
|
|
ToChange: 2,
|
|
},
|
|
},
|
|
"data source refresh": {
|
|
&Plan{
|
|
Resources: []*InstanceDiff{
|
|
{
|
|
Addr: mustParseResourceAddress("data.test.foo"),
|
|
Action: terraform.DiffRefresh,
|
|
},
|
|
},
|
|
},
|
|
PlanStats{
|
|
// data resource refreshes are not counted in our stats
|
|
},
|
|
},
|
|
"replace": {
|
|
&Plan{
|
|
Resources: []*InstanceDiff{
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.foo"),
|
|
Action: terraform.DiffDestroyCreate,
|
|
},
|
|
{
|
|
Addr: mustParseResourceAddress("test_resource.bar"),
|
|
Action: terraform.DiffDestroyCreate,
|
|
},
|
|
},
|
|
},
|
|
PlanStats{
|
|
ToDestroy: 2,
|
|
ToAdd: 2,
|
|
},
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
got := test.Input.Stats()
|
|
if !reflect.DeepEqual(got, test.Want) {
|
|
t.Errorf(
|
|
"wrong result\ninput: %sgot: %swant:%s",
|
|
spew.Sdump(test.Input),
|
|
spew.Sdump(got),
|
|
spew.Sdump(test.Want),
|
|
)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test that deposed instances are marked as such
|
|
func TestPlan_destroyDeposed(t *testing.T) {
|
|
plan := &terraform.Plan{
|
|
Diff: &terraform.Diff{
|
|
Modules: []*terraform.ModuleDiff{
|
|
&terraform.ModuleDiff{
|
|
Path: []string{"root"},
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
"aws_instance.foo": &terraform.InstanceDiff{
|
|
DestroyDeposed: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
dispPlan := NewPlan(plan)
|
|
actual := dispPlan.Format(disabledColorize)
|
|
|
|
expected := strings.TrimSpace(`
|
|
- aws_instance.foo (deposed)
|
|
`)
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
// Test that computed fields with an interpolation string get displayed
|
|
func TestPlan_displayInterpolations(t *testing.T) {
|
|
plan := &terraform.Plan{
|
|
Diff: &terraform.Diff{
|
|
Modules: []*terraform.ModuleDiff{
|
|
&terraform.ModuleDiff{
|
|
Path: []string{"root"},
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
"aws_instance.foo": &terraform.InstanceDiff{
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
"computed_field": &terraform.ResourceAttrDiff{
|
|
New: "${aws_instance.other.id}",
|
|
NewComputed: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
dispPlan := NewPlan(plan)
|
|
out := dispPlan.Format(disabledColorize)
|
|
lines := strings.Split(out, "\n")
|
|
if len(lines) != 2 {
|
|
t.Fatal("expected 2 lines of output, got:\n", out)
|
|
}
|
|
|
|
actual := strings.TrimSpace(lines[1])
|
|
expected := `computed_field: "" => "${aws_instance.other.id}"`
|
|
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
// Ensure that (forces new resource) text is included
|
|
// https://github.com/hashicorp/terraform/issues/16035
|
|
func TestPlan_forcesNewResource(t *testing.T) {
|
|
plan := &terraform.Plan{
|
|
Diff: &terraform.Diff{
|
|
Modules: []*terraform.ModuleDiff{
|
|
&terraform.ModuleDiff{
|
|
Path: []string{"root"},
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
"test_resource.foo": &terraform.InstanceDiff{
|
|
Destroy: true,
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
"A": &terraform.ResourceAttrDiff{
|
|
New: "B",
|
|
RequiresNew: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
dispPlan := NewPlan(plan)
|
|
actual := dispPlan.Format(disabledColorize)
|
|
|
|
expected := strings.TrimSpace(`
|
|
-/+ test_resource.foo (new resource required)
|
|
A: "" => "B" (forces new resource)
|
|
`)
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
// Test that a root level data source gets a special plan output on create
|
|
func TestPlan_rootDataSource(t *testing.T) {
|
|
plan := &terraform.Plan{
|
|
Diff: &terraform.Diff{
|
|
Modules: []*terraform.ModuleDiff{
|
|
&terraform.ModuleDiff{
|
|
Path: []string{"root"},
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
"data.type.name": &terraform.InstanceDiff{
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
"A": &terraform.ResourceAttrDiff{
|
|
New: "B",
|
|
RequiresNew: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
dispPlan := NewPlan(plan)
|
|
actual := dispPlan.Format(disabledColorize)
|
|
|
|
expected := strings.TrimSpace(`
|
|
<= data.type.name
|
|
A: "B"
|
|
`)
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
// Test that data sources nested in modules get the same plan output
|
|
func TestPlan_nestedDataSource(t *testing.T) {
|
|
plan := &terraform.Plan{
|
|
Diff: &terraform.Diff{
|
|
Modules: []*terraform.ModuleDiff{
|
|
&terraform.ModuleDiff{
|
|
Path: []string{"root", "nested"},
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
"data.type.name": &terraform.InstanceDiff{
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
"A": &terraform.ResourceAttrDiff{
|
|
New: "B",
|
|
RequiresNew: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
dispPlan := NewPlan(plan)
|
|
actual := dispPlan.Format(disabledColorize)
|
|
|
|
expected := strings.TrimSpace(`
|
|
<= module.nested.data.type.name
|
|
A: "B"
|
|
`)
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func mustParseResourceAddress(s string) *terraform.ResourceAddress {
|
|
addr, err := terraform.ParseResourceAddress(s)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return addr
|
|
}
|