From 5f14b7a7f2ca641b3949f8de35838a4a141666d9 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Mon, 14 Jan 2019 15:34:41 +0000 Subject: [PATCH] command/format: Render empty primitive list/set as [] --- command/format/diff.go | 11 +- command/format/diff_test.go | 246 ++++++++++++++++++++++++++++++++++++ 2 files changed, 254 insertions(+), 3 deletions(-) diff --git a/command/format/diff.go b/command/format/diff.go index 408663b32f..a3e7657a10 100644 --- a/command/format/diff.go +++ b/command/format/diff.go @@ -467,18 +467,23 @@ func (p *blockBodyDiffPrinter) writeValue(val cty.Value, action plans.Action, in fmt.Fprintf(p.buf, "%#v", val) } case ty.IsListType() || ty.IsSetType() || ty.IsTupleType(): - p.buf.WriteString("[\n") + p.buf.WriteString("[") it := val.ElementIterator() for it.Next() { _, val := it.Element() + + p.buf.WriteString("\n") p.buf.WriteString(strings.Repeat(" ", indent+2)) p.writeActionSymbol(action) p.writeValue(val, action, indent+4) - p.buf.WriteString(",\n") + p.buf.WriteString(",") } - p.buf.WriteString(strings.Repeat(" ", indent)) + if val.LengthInt() > 0 { + p.buf.WriteString("\n") + p.buf.WriteString(strings.Repeat(" ", indent)) + } p.buf.WriteString("]") case ty.IsMapType(): p.buf.WriteString("{") diff --git a/command/format/diff_test.go b/command/format/diff_test.go index b0b1f63c84..4e1d984814 100644 --- a/command/format/diff_test.go +++ b/command/format/diff_test.go @@ -445,6 +445,39 @@ func TestResourceChange_JSON(t *testing.T) { func TestResourceChange_primitiveList(t *testing.T) { testCases := map[string]testCase{ "in-place update - creation": { + Action: plans.Update, + Mode: addrs.ManagedResourceMode, + Before: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-STATIC"), + "list_field": cty.NullVal(cty.List(cty.String)), + }), + After: cty.ObjectVal(map[string]cty.Value{ + "id": cty.UnknownVal(cty.String), + "ami": cty.StringVal("ami-STATIC"), + "list_field": cty.ListVal([]cty.Value{ + cty.StringVal("new-element"), + }), + }), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Optional: true, Computed: true}, + "ami": {Type: cty.String, Optional: true}, + "list_field": {Type: cty.List(cty.String), Optional: true}, + }, + }, + RequiredReplace: cty.NewPathSet(), + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { + ami = "ami-STATIC" + ~ id = "i-02ae66f368e8518a9" -> (known after apply) + + list_field = [ + + "new-element", + ] + } +`, + }, + "in-place update - first addition": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ @@ -596,6 +629,97 @@ func TestResourceChange_primitiveList(t *testing.T) { - "cccc", ] } +`, + }, + "creation - empty list": { + Action: plans.Create, + Mode: addrs.ManagedResourceMode, + Before: cty.NullVal(cty.EmptyObject), + After: cty.ObjectVal(map[string]cty.Value{ + "id": cty.UnknownVal(cty.String), + "ami": cty.StringVal("ami-STATIC"), + "list_field": cty.ListValEmpty(cty.String), + }), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Optional: true, Computed: true}, + "ami": {Type: cty.String, Optional: true}, + "list_field": {Type: cty.List(cty.String), Optional: true}, + }, + }, + RequiredReplace: cty.NewPathSet(), + ExpectedOutput: ` # test_instance.example will be created + + resource "test_instance" "example" { + + ami = "ami-STATIC" + + id = (known after apply) + + list_field = [] + } +`, + }, + "in-place update - full to empty": { + Action: plans.Update, + Mode: addrs.ManagedResourceMode, + Before: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-STATIC"), + "list_field": cty.ListVal([]cty.Value{ + cty.StringVal("aaaa"), + cty.StringVal("bbbb"), + cty.StringVal("cccc"), + }), + }), + After: cty.ObjectVal(map[string]cty.Value{ + "id": cty.UnknownVal(cty.String), + "ami": cty.StringVal("ami-STATIC"), + "list_field": cty.ListValEmpty(cty.String), + }), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Optional: true, Computed: true}, + "ami": {Type: cty.String, Optional: true}, + "list_field": {Type: cty.List(cty.String), Optional: true}, + }, + }, + RequiredReplace: cty.NewPathSet(), + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { + ami = "ami-STATIC" + ~ id = "i-02ae66f368e8518a9" -> (known after apply) + ~ list_field = [ + - "aaaa", + - "bbbb", + - "cccc", + ] + } +`, + }, + "in-place update - null to empty": { + Action: plans.Update, + Mode: addrs.ManagedResourceMode, + Before: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-STATIC"), + "list_field": cty.NullVal(cty.List(cty.String)), + }), + After: cty.ObjectVal(map[string]cty.Value{ + "id": cty.UnknownVal(cty.String), + "ami": cty.StringVal("ami-STATIC"), + "list_field": cty.ListValEmpty(cty.String), + }), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Optional: true, Computed: true}, + "ami": {Type: cty.String, Optional: true}, + "list_field": {Type: cty.List(cty.String), Optional: true}, + }, + }, + RequiredReplace: cty.NewPathSet(), + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { + ami = "ami-STATIC" + ~ id = "i-02ae66f368e8518a9" -> (known after apply) + + list_field = [] + } `, }, } @@ -605,6 +729,39 @@ func TestResourceChange_primitiveList(t *testing.T) { func TestResourceChange_primitiveSet(t *testing.T) { testCases := map[string]testCase{ "in-place update - creation": { + Action: plans.Update, + Mode: addrs.ManagedResourceMode, + Before: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-STATIC"), + "set_field": cty.NullVal(cty.Set(cty.String)), + }), + After: cty.ObjectVal(map[string]cty.Value{ + "id": cty.UnknownVal(cty.String), + "ami": cty.StringVal("ami-STATIC"), + "set_field": cty.SetVal([]cty.Value{ + cty.StringVal("new-element"), + }), + }), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Optional: true, Computed: true}, + "ami": {Type: cty.String, Optional: true}, + "set_field": {Type: cty.Set(cty.String), Optional: true}, + }, + }, + RequiredReplace: cty.NewPathSet(), + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { + ami = "ami-STATIC" + ~ id = "i-02ae66f368e8518a9" -> (known after apply) + + set_field = [ + + "new-element", + ] + } +`, + }, + "in-place update - first insertion": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ @@ -756,6 +913,95 @@ func TestResourceChange_primitiveSet(t *testing.T) { - "cccc", ] } +`, + }, + "creation - empty set": { + Action: plans.Create, + Mode: addrs.ManagedResourceMode, + Before: cty.NullVal(cty.EmptyObject), + After: cty.ObjectVal(map[string]cty.Value{ + "id": cty.UnknownVal(cty.String), + "ami": cty.StringVal("ami-STATIC"), + "set_field": cty.SetValEmpty(cty.String), + }), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Optional: true, Computed: true}, + "ami": {Type: cty.String, Optional: true}, + "set_field": {Type: cty.Set(cty.String), Optional: true}, + }, + }, + RequiredReplace: cty.NewPathSet(), + ExpectedOutput: ` # test_instance.example will be created + + resource "test_instance" "example" { + + ami = "ami-STATIC" + + id = (known after apply) + + set_field = [] + } +`, + }, + "in-place update - full to empty set": { + Action: plans.Update, + Mode: addrs.ManagedResourceMode, + Before: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-STATIC"), + "set_field": cty.SetVal([]cty.Value{ + cty.StringVal("aaaa"), + cty.StringVal("bbbb"), + }), + }), + After: cty.ObjectVal(map[string]cty.Value{ + "id": cty.UnknownVal(cty.String), + "ami": cty.StringVal("ami-STATIC"), + "set_field": cty.SetValEmpty(cty.String), + }), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Optional: true, Computed: true}, + "ami": {Type: cty.String, Optional: true}, + "set_field": {Type: cty.Set(cty.String), Optional: true}, + }, + }, + RequiredReplace: cty.NewPathSet(), + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { + ami = "ami-STATIC" + ~ id = "i-02ae66f368e8518a9" -> (known after apply) + ~ set_field = [ + - "aaaa", + - "bbbb", + ] + } +`, + }, + "in-place update - null to empty set": { + Action: plans.Update, + Mode: addrs.ManagedResourceMode, + Before: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-STATIC"), + "set_field": cty.NullVal(cty.Set(cty.String)), + }), + After: cty.ObjectVal(map[string]cty.Value{ + "id": cty.UnknownVal(cty.String), + "ami": cty.StringVal("ami-STATIC"), + "set_field": cty.SetValEmpty(cty.String), + }), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Optional: true, Computed: true}, + "ami": {Type: cty.String, Optional: true}, + "set_field": {Type: cty.Set(cty.String), Optional: true}, + }, + }, + RequiredReplace: cty.NewPathSet(), + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { + ami = "ami-STATIC" + ~ id = "i-02ae66f368e8518a9" -> (known after apply) + + set_field = [] + } `, }, }