diff --git a/command/format/diff.go b/command/format/diff.go index d816e17f1..d88a80147 100644 --- a/command/format/diff.go +++ b/command/format/diff.go @@ -377,6 +377,9 @@ func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *config var action plans.Action var oldValue, newValue cty.Value switch { + case !val.IsKnown(): + action = plans.Update + newValue = val case !old.HasElement(val).True(): action = plans.Create oldValue = cty.NullVal(val.Type()) @@ -697,7 +700,7 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa for it := new.ElementIterator(); it.Next(); { _, val := it.Element() allVals = append(allVals, val) - if old.HasElement(val).False() { + if val.IsKnown() && old.HasElement(val).False() { addedVals = append(addedVals, val) } } @@ -726,6 +729,8 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa var action plans.Action switch { + case !val.IsKnown(): + action = plans.Update case added.HasElement(val).True(): action = plans.Create case removed.HasElement(val).True(): @@ -1062,7 +1067,7 @@ func ctyObjectSimilarity(old, new cty.Value) float64 { } func ctyEqualWithUnknown(old, new cty.Value) bool { - if !old.IsKnown() || !new.IsKnown() { + if !old.IsWhollyKnown() || !new.IsWhollyKnown() { return false } return old.Equals(new).True() diff --git a/command/format/diff_test.go b/command/format/diff_test.go index 5fc4637dc..bc0a3ad82 100644 --- a/command/format/diff_test.go +++ b/command/format/diff_test.go @@ -720,6 +720,92 @@ func TestResourceChange_primitiveList(t *testing.T) { ~ id = "i-02ae66f368e8518a9" -> (known after apply) + list_field = [] } +`, + }, + "update to unknown element": { + 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.ListVal([]cty.Value{ + cty.StringVal("aaaa"), + cty.UnknownVal(cty.String), + cty.StringVal("cccc"), + }), + }), + 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", + + (known after apply), + "cccc", + ] + } +`, + }, + "update - two new unknown elements": { + 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.ListVal([]cty.Value{ + cty.StringVal("aaaa"), + cty.UnknownVal(cty.String), + cty.UnknownVal(cty.String), + cty.StringVal("cccc"), + }), + }), + 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", + + (known after apply), + + (known after apply), + "cccc", + ] + } `, }, } @@ -1002,6 +1088,80 @@ func TestResourceChange_primitiveSet(t *testing.T) { ~ id = "i-02ae66f368e8518a9" -> (known after apply) + set_field = [] } +`, + }, + "in-place update to unknown": { + 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.UnknownVal(cty.Set(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", + ] -> (known after apply) + } +`, + }, + "in-place update to unknown element": { + 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.SetVal([]cty.Value{ + cty.StringVal("aaaa"), + cty.UnknownVal(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", + ~ (known after apply), + ] + } `, }, } @@ -1220,6 +1380,47 @@ func TestResourceChange_map(t *testing.T) { + id = (known after apply) + map_field = {} } +`, + }, + "update to unknown element": { + Action: plans.Update, + Mode: addrs.ManagedResourceMode, + Before: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-STATIC"), + "map_field": cty.MapVal(map[string]cty.Value{ + "a": cty.StringVal("aaaa"), + "b": cty.StringVal("bbbb"), + "c": cty.StringVal("cccc"), + }), + }), + After: cty.ObjectVal(map[string]cty.Value{ + "id": cty.UnknownVal(cty.String), + "ami": cty.StringVal("ami-STATIC"), + "map_field": cty.MapVal(map[string]cty.Value{ + "a": cty.StringVal("aaaa"), + "b": cty.UnknownVal(cty.String), + "c": cty.StringVal("cccc"), + }), + }), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Optional: true, Computed: true}, + "ami": {Type: cty.String, Optional: true}, + "map_field": {Type: cty.Map(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) + ~ map_field = { + "a" = "aaaa" + ~ "b" = "bbbb" -> (known after apply) + "c" = "cccc" + } + } `, }, }