| package format |
| |
| import ( |
| "fmt" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| "github.com/hashicorp/terraform/internal/addrs" |
| "github.com/hashicorp/terraform/internal/configs/configschema" |
| "github.com/hashicorp/terraform/internal/lang/marks" |
| "github.com/hashicorp/terraform/internal/plans" |
| "github.com/hashicorp/terraform/internal/states" |
| "github.com/mitchellh/colorstring" |
| "github.com/zclconf/go-cty/cty" |
| ) |
| |
| func TestResourceChange_primitiveTypes(t *testing.T) { |
| testCases := map[string]testCase{ |
| "creation": { |
| Action: plans.Create, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.NullVal(cty.EmptyObject), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Computed: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be created |
| + resource "test_instance" "example" { |
| + id = (known after apply) |
| } |
| `, |
| }, |
| "creation (null string)": { |
| Action: plans.Create, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.NullVal(cty.EmptyObject), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "string": cty.StringVal("null"), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "string": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be created |
| + resource "test_instance" "example" { |
| + string = "null" |
| } |
| `, |
| }, |
| "creation (null string with extra whitespace)": { |
| Action: plans.Create, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.NullVal(cty.EmptyObject), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "string": cty.StringVal("null "), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "string": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be created |
| + resource "test_instance" "example" { |
| + string = "null " |
| } |
| `, |
| }, |
| "creation (object with quoted keys)": { |
| Action: plans.Create, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.NullVal(cty.EmptyObject), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "object": cty.ObjectVal(map[string]cty.Value{ |
| "unquoted": cty.StringVal("value"), |
| "quoted:key": cty.StringVal("some-value"), |
| }), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "object": {Type: cty.Object(map[string]cty.Type{ |
| "unquoted": cty.String, |
| "quoted:key": cty.String, |
| }), Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be created |
| + resource "test_instance" "example" { |
| + object = { |
| + "quoted:key" = "some-value" |
| + unquoted = "value" |
| } |
| } |
| `, |
| }, |
| "deletion": { |
| Action: plans.Delete, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| }), |
| After: cty.NullVal(cty.EmptyObject), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Computed: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be destroyed |
| - resource "test_instance" "example" { |
| - id = "i-02ae66f368e8518a9" -> null |
| } |
| `, |
| }, |
| "deletion of deposed object": { |
| Action: plans.Delete, |
| Mode: addrs.ManagedResourceMode, |
| DeposedKey: states.DeposedKey("byebye"), |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| }), |
| After: cty.NullVal(cty.EmptyObject), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Computed: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example (deposed object byebye) will be destroyed |
| # (left over from a partially-failed replacement of this instance) |
| - resource "test_instance" "example" { |
| - id = "i-02ae66f368e8518a9" -> null |
| } |
| `, |
| }, |
| "deletion (empty string)": { |
| Action: plans.Delete, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "intentionally_long": cty.StringVal(""), |
| }), |
| After: cty.NullVal(cty.EmptyObject), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Computed: true}, |
| "intentionally_long": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be destroyed |
| - resource "test_instance" "example" { |
| - id = "i-02ae66f368e8518a9" -> null |
| } |
| `, |
| }, |
| "string in-place update": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "ami": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| id = "i-02ae66f368e8518a9" |
| } |
| `, |
| }, |
| "update with quoted key": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "saml:aud": cty.StringVal("https://example.com/saml"), |
| "zeta": cty.StringVal("alpha"), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "saml:aud": cty.StringVal("https://saml.example.com"), |
| "zeta": cty.StringVal("alpha"), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "saml:aud": {Type: cty.String, Optional: true}, |
| "zeta": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| id = "i-02ae66f368e8518a9" |
| ~ "saml:aud" = "https://example.com/saml" -> "https://saml.example.com" |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "string force-new update": { |
| Action: plans.DeleteThenCreate, |
| ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "ami": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(cty.Path{ |
| cty.GetAttrStep{Name: "ami"}, |
| }), |
| ExpectedOutput: ` # test_instance.example must be replaced |
| -/+ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" # forces replacement |
| id = "i-02ae66f368e8518a9" |
| } |
| `, |
| }, |
| "string in-place update (null values)": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "unchanged": cty.NullVal(cty.String), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "unchanged": cty.NullVal(cty.String), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "ami": {Type: cty.String, Optional: true}, |
| "unchanged": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| id = "i-02ae66f368e8518a9" |
| } |
| `, |
| }, |
| "in-place update of multi-line string field": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "more_lines": cty.StringVal(`original |
| long |
| multi-line |
| string |
| field |
| `), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "more_lines": cty.StringVal(`original |
| extremely long |
| multi-line |
| string |
| field |
| `), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "more_lines": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ more_lines = <<-EOT |
| original |
| - long |
| + extremely long |
| multi-line |
| string |
| field |
| EOT |
| } |
| `, |
| }, |
| "addition of multi-line string field": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "more_lines": cty.NullVal(cty.String), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "more_lines": cty.StringVal(`original |
| new line |
| `), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "more_lines": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| + more_lines = <<-EOT |
| original |
| new line |
| EOT |
| } |
| `, |
| }, |
| "force-new update of multi-line string field": { |
| Action: plans.DeleteThenCreate, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "more_lines": cty.StringVal(`original |
| `), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "more_lines": cty.StringVal(`original |
| new line |
| `), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "more_lines": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(cty.Path{ |
| cty.GetAttrStep{Name: "more_lines"}, |
| }), |
| ExpectedOutput: ` # test_instance.example must be replaced |
| -/+ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ more_lines = <<-EOT # forces replacement |
| original |
| + new line |
| EOT |
| } |
| `, |
| }, |
| |
| // Sensitive |
| |
| "creation with sensitive field": { |
| Action: plans.Create, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.NullVal(cty.EmptyObject), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "password": cty.StringVal("top-secret"), |
| "conn_info": cty.ObjectVal(map[string]cty.Value{ |
| "user": cty.StringVal("not-secret"), |
| "password": cty.StringVal("top-secret"), |
| }), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Computed: true}, |
| "password": {Type: cty.String, Optional: true, Sensitive: true}, |
| "conn_info": { |
| NestedType: &configschema.Object{ |
| Nesting: configschema.NestingSingle, |
| Attributes: map[string]*configschema.Attribute{ |
| "user": {Type: cty.String, Optional: true}, |
| "password": {Type: cty.String, Optional: true, Sensitive: true}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be created |
| + resource "test_instance" "example" { |
| + conn_info = { |
| + password = (sensitive value) |
| + user = "not-secret" |
| } |
| + id = (known after apply) |
| + password = (sensitive value) |
| } |
| `, |
| }, |
| "update with equal sensitive field": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("blah"), |
| "str": cty.StringVal("before"), |
| "password": cty.StringVal("top-secret"), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "str": cty.StringVal("after"), |
| "password": cty.StringVal("top-secret"), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Computed: true}, |
| "str": {Type: cty.String, Optional: true}, |
| "password": {Type: cty.String, Optional: true, Sensitive: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ id = "blah" -> (known after apply) |
| ~ str = "before" -> "after" |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| |
| // tainted objects |
| "replace tainted resource": { |
| Action: plans.DeleteThenCreate, |
| ActionReason: plans.ResourceInstanceReplaceBecauseTainted, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "ami": cty.StringVal("ami-AFTER"), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "ami": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(cty.Path{ |
| cty.GetAttrStep{Name: "ami"}, |
| }), |
| ExpectedOutput: ` # test_instance.example is tainted, so must be replaced |
| -/+ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" # forces replacement |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| } |
| `, |
| }, |
| "force replacement with empty before value": { |
| Action: plans.DeleteThenCreate, |
| ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "name": cty.StringVal("name"), |
| "forced": cty.NullVal(cty.String), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "name": cty.StringVal("name"), |
| "forced": cty.StringVal("example"), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "name": {Type: cty.String, Optional: true}, |
| "forced": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(cty.Path{ |
| cty.GetAttrStep{Name: "forced"}, |
| }), |
| ExpectedOutput: ` # test_instance.example must be replaced |
| -/+ resource "test_instance" "example" { |
| + forced = "example" # forces replacement |
| name = "name" |
| } |
| `, |
| }, |
| "force replacement with empty before value legacy": { |
| Action: plans.DeleteThenCreate, |
| ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "name": cty.StringVal("name"), |
| "forced": cty.StringVal(""), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "name": cty.StringVal("name"), |
| "forced": cty.StringVal("example"), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "name": {Type: cty.String, Optional: true}, |
| "forced": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(cty.Path{ |
| cty.GetAttrStep{Name: "forced"}, |
| }), |
| ExpectedOutput: ` # test_instance.example must be replaced |
| -/+ resource "test_instance" "example" { |
| + forced = "example" # forces replacement |
| name = "name" |
| } |
| `, |
| }, |
| "read during apply because of unknown configuration": { |
| Action: plans.Read, |
| ActionReason: plans.ResourceInstanceReadBecauseConfigUnknown, |
| Mode: addrs.DataResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "name": cty.StringVal("name"), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "name": cty.StringVal("name"), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "name": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| ExpectedOutput: ` # data.test_instance.example will be read during apply |
| # (config refers to values not yet known) |
| <= data "test_instance" "example" { |
| name = "name" |
| } |
| `, |
| }, |
| "read during apply because of pending changes to upstream dependency": { |
| Action: plans.Read, |
| ActionReason: plans.ResourceInstanceReadBecauseDependencyPending, |
| Mode: addrs.DataResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "name": cty.StringVal("name"), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "name": cty.StringVal("name"), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "name": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| ExpectedOutput: ` # data.test_instance.example will be read during apply |
| # (depends on a resource or a module with changes pending) |
| <= data "test_instance" "example" { |
| name = "name" |
| } |
| `, |
| }, |
| "read during apply for unspecified reason": { |
| Action: plans.Read, |
| Mode: addrs.DataResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "name": cty.StringVal("name"), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "name": cty.StringVal("name"), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "name": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| ExpectedOutput: ` # data.test_instance.example will be read during apply |
| <= data "test_instance" "example" { |
| name = "name" |
| } |
| `, |
| }, |
| "show all identifying attributes even if unchanged": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "bar": cty.StringVal("bar"), |
| "foo": cty.StringVal("foo"), |
| "name": cty.StringVal("alice"), |
| "tags": cty.MapVal(map[string]cty.Value{ |
| "name": cty.StringVal("bob"), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "bar": cty.StringVal("bar"), |
| "foo": cty.StringVal("foo"), |
| "name": cty.StringVal("alice"), |
| "tags": cty.MapVal(map[string]cty.Value{ |
| "name": cty.StringVal("bob"), |
| }), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "ami": {Type: cty.String, Optional: true}, |
| "bar": {Type: cty.String, Optional: true}, |
| "foo": {Type: cty.String, Optional: true}, |
| "name": {Type: cty.String, Optional: true}, |
| "tags": {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-BEFORE" -> "ami-AFTER" |
| id = "i-02ae66f368e8518a9" |
| name = "alice" |
| tags = { |
| "name" = "bob" |
| } |
| # (2 unchanged attributes hidden) |
| } |
| `, |
| }, |
| } |
| |
| runTestCases(t, testCases) |
| } |
| |
| func TestResourceChange_JSON(t *testing.T) { |
| testCases := map[string]testCase{ |
| "creation": { |
| Action: plans.Create, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.NullVal(cty.EmptyObject), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "json_field": cty.StringVal(`{ |
| "str": "value", |
| "list":["a","b", 234, true], |
| "obj": {"key": "val"} |
| }`), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "json_field": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be created |
| + resource "test_instance" "example" { |
| + id = (known after apply) |
| + json_field = jsonencode( |
| { |
| + list = [ |
| + "a", |
| + "b", |
| + 234, |
| + true, |
| ] |
| + obj = { |
| + key = "val" |
| } |
| + str = "value" |
| } |
| ) |
| } |
| `, |
| }, |
| "in-place update of object": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "json_field": cty.StringVal(`{"aaa": "value","ccc": 5}`), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "json_field": cty.StringVal(`{"aaa": "value", "bbb": "new_value"}`), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "json_field": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ json_field = jsonencode( |
| ~ { |
| + bbb = "new_value" |
| - ccc = 5 -> null |
| # (1 unchanged element hidden) |
| } |
| ) |
| } |
| `, |
| }, |
| "in-place update of object with quoted keys": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "json_field": cty.StringVal(`{"aaa": "value", "c:c": "old_value"}`), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "json_field": cty.StringVal(`{"aaa": "value", "b:bb": "new_value"}`), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "json_field": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ json_field = jsonencode( |
| ~ { |
| + "b:bb" = "new_value" |
| - "c:c" = "old_value" -> null |
| # (1 unchanged element hidden) |
| } |
| ) |
| } |
| `, |
| }, |
| "in-place update (from empty tuple)": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "json_field": cty.StringVal(`{"aaa": []}`), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "json_field": cty.StringVal(`{"aaa": ["value"]}`), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "json_field": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ json_field = jsonencode( |
| ~ { |
| ~ aaa = [ |
| + "value", |
| ] |
| } |
| ) |
| } |
| `, |
| }, |
| "in-place update (to empty tuple)": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "json_field": cty.StringVal(`{"aaa": ["value"]}`), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "json_field": cty.StringVal(`{"aaa": []}`), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "json_field": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ json_field = jsonencode( |
| ~ { |
| ~ aaa = [ |
| - "value", |
| ] |
| } |
| ) |
| } |
| `, |
| }, |
| "in-place update (tuple of different types)": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "json_field": cty.StringVal(`{"aaa": [42, {"foo":"bar"}, "value"]}`), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "json_field": cty.StringVal(`{"aaa": [42, {"foo":"baz"}, "value"]}`), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "json_field": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ json_field = jsonencode( |
| ~ { |
| ~ aaa = [ |
| 42, |
| ~ { |
| ~ foo = "bar" -> "baz" |
| }, |
| "value", |
| ] |
| } |
| ) |
| } |
| `, |
| }, |
| "force-new update": { |
| Action: plans.DeleteThenCreate, |
| ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "json_field": cty.StringVal(`{"aaa": "value"}`), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "json_field": cty.StringVal(`{"aaa": "value", "bbb": "new_value"}`), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "json_field": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(cty.Path{ |
| cty.GetAttrStep{Name: "json_field"}, |
| }), |
| ExpectedOutput: ` # test_instance.example must be replaced |
| -/+ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ json_field = jsonencode( |
| ~ { |
| + bbb = "new_value" |
| # (1 unchanged element hidden) |
| } # forces replacement |
| ) |
| } |
| `, |
| }, |
| "in-place update (whitespace change)": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "json_field": cty.StringVal(`{"aaa": "value", "bbb": "another"}`), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "json_field": cty.StringVal(`{"aaa":"value", |
| "bbb":"another"}`), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "json_field": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ json_field = jsonencode( # whitespace changes |
| { |
| aaa = "value" |
| bbb = "another" |
| } |
| ) |
| } |
| `, |
| }, |
| "force-new update (whitespace change)": { |
| Action: plans.DeleteThenCreate, |
| ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "json_field": cty.StringVal(`{"aaa": "value", "bbb": "another"}`), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "json_field": cty.StringVal(`{"aaa":"value", |
| "bbb":"another"}`), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "json_field": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(cty.Path{ |
| cty.GetAttrStep{Name: "json_field"}, |
| }), |
| ExpectedOutput: ` # test_instance.example must be replaced |
| -/+ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ json_field = jsonencode( # whitespace changes force replacement |
| { |
| aaa = "value" |
| bbb = "another" |
| } |
| ) |
| } |
| `, |
| }, |
| "creation (empty)": { |
| Action: plans.Create, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.NullVal(cty.EmptyObject), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "json_field": cty.StringVal(`{}`), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "json_field": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be created |
| + resource "test_instance" "example" { |
| + id = (known after apply) |
| + json_field = jsonencode({}) |
| } |
| `, |
| }, |
| "JSON list item removal": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "json_field": cty.StringVal(`["first","second","third"]`), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "json_field": cty.StringVal(`["first","second"]`), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "json_field": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ json_field = jsonencode( |
| ~ [ |
| # (1 unchanged element hidden) |
| "second", |
| - "third", |
| ] |
| ) |
| } |
| `, |
| }, |
| "JSON list item addition": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "json_field": cty.StringVal(`["first","second"]`), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "json_field": cty.StringVal(`["first","second","third"]`), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "json_field": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ json_field = jsonencode( |
| ~ [ |
| # (1 unchanged element hidden) |
| "second", |
| + "third", |
| ] |
| ) |
| } |
| `, |
| }, |
| "JSON list object addition": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "json_field": cty.StringVal(`{"first":"111"}`), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "json_field": cty.StringVal(`{"first":"111","second":"222"}`), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "json_field": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ json_field = jsonencode( |
| ~ { |
| + second = "222" |
| # (1 unchanged element hidden) |
| } |
| ) |
| } |
| `, |
| }, |
| "JSON object with nested list": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "json_field": cty.StringVal(`{ |
| "Statement": ["first"] |
| }`), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "json_field": cty.StringVal(`{ |
| "Statement": ["first", "second"] |
| }`), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "json_field": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ json_field = jsonencode( |
| ~ { |
| ~ Statement = [ |
| "first", |
| + "second", |
| ] |
| } |
| ) |
| } |
| `, |
| }, |
| "JSON list of objects - adding item": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "json_field": cty.StringVal(`[{"one": "111"}]`), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "json_field": cty.StringVal(`[{"one": "111"}, {"two": "222"}]`), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "json_field": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ json_field = jsonencode( |
| ~ [ |
| { |
| one = "111" |
| }, |
| + { |
| + two = "222" |
| }, |
| ] |
| ) |
| } |
| `, |
| }, |
| "JSON list of objects - removing item": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "json_field": cty.StringVal(`[{"one": "111"}, {"two": "222"}, {"three": "333"}]`), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "json_field": cty.StringVal(`[{"one": "111"}, {"three": "333"}]`), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "json_field": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ json_field = jsonencode( |
| ~ [ |
| { |
| one = "111" |
| }, |
| - { |
| - two = "222" |
| }, |
| { |
| three = "333" |
| }, |
| ] |
| ) |
| } |
| `, |
| }, |
| "JSON object with list of objects": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "json_field": cty.StringVal(`{"parent":[{"one": "111"}]}`), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "json_field": cty.StringVal(`{"parent":[{"one": "111"}, {"two": "222"}]}`), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "json_field": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ json_field = jsonencode( |
| ~ { |
| ~ parent = [ |
| { |
| one = "111" |
| }, |
| + { |
| + two = "222" |
| }, |
| ] |
| } |
| ) |
| } |
| `, |
| }, |
| "JSON object double nested lists": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "json_field": cty.StringVal(`{"parent":[{"another_list": ["111"]}]}`), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "json_field": cty.StringVal(`{"parent":[{"another_list": ["111", "222"]}]}`), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "json_field": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ json_field = jsonencode( |
| ~ { |
| ~ parent = [ |
| ~ { |
| ~ another_list = [ |
| "111", |
| + "222", |
| ] |
| }, |
| ] |
| } |
| ) |
| } |
| `, |
| }, |
| "in-place update from object to tuple": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "json_field": cty.StringVal(`{"aaa": [42, {"foo":"bar"}, "value"]}`), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "json_field": cty.StringVal(`["aaa", 42, "something"]`), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "json_field": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ json_field = jsonencode( |
| ~ { |
| - aaa = [ |
| - 42, |
| - { |
| - foo = "bar" |
| }, |
| - "value", |
| ] |
| } -> [ |
| + "aaa", |
| + 42, |
| + "something", |
| ] |
| ) |
| } |
| `, |
| }, |
| } |
| runTestCases(t, testCases) |
| } |
| |
| func TestResourceChange_listObject(t *testing.T) { |
| testCases := map[string]testCase{ |
| // https://github.com/hashicorp/terraform/issues/30641 |
| "updating non-identifying attribute": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "accounts": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("1"), |
| "name": cty.StringVal("production"), |
| "status": cty.StringVal("ACTIVE"), |
| }), |
| cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("2"), |
| "name": cty.StringVal("staging"), |
| "status": cty.StringVal("ACTIVE"), |
| }), |
| cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("3"), |
| "name": cty.StringVal("disaster-recovery"), |
| "status": cty.StringVal("ACTIVE"), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "accounts": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("1"), |
| "name": cty.StringVal("production"), |
| "status": cty.StringVal("ACTIVE"), |
| }), |
| cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("2"), |
| "name": cty.StringVal("staging"), |
| "status": cty.StringVal("EXPLODED"), |
| }), |
| cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("3"), |
| "name": cty.StringVal("disaster-recovery"), |
| "status": cty.StringVal("ACTIVE"), |
| }), |
| }), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Optional: true, Computed: true}, |
| "accounts": { |
| Type: cty.List(cty.Object(map[string]cty.Type{ |
| "id": cty.String, |
| "name": cty.String, |
| "status": cty.String, |
| })), |
| }, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ accounts = [ |
| { |
| id = "1" |
| name = "production" |
| status = "ACTIVE" |
| }, |
| ~ { |
| id = "2" |
| name = "staging" |
| ~ status = "ACTIVE" -> "EXPLODED" |
| }, |
| { |
| id = "3" |
| name = "disaster-recovery" |
| status = "ACTIVE" |
| }, |
| ] |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| } |
| `, |
| }, |
| } |
| runTestCases(t, testCases) |
| } |
| |
| 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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| + list_field = [ |
| + "new-element", |
| ] |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "in-place update - first addition": { |
| 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.ListValEmpty(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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ list_field = [ |
| + "new-element", |
| ] |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "in-place update - insertion": { |
| 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("dddd"), |
| cty.StringVal("eeee"), |
| cty.StringVal("ffff"), |
| }), |
| }), |
| 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.StringVal("bbbb"), |
| cty.StringVal("cccc"), |
| cty.StringVal("dddd"), |
| cty.StringVal("eeee"), |
| cty.StringVal("ffff"), |
| }), |
| }), |
| 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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ list_field = [ |
| # (1 unchanged element hidden) |
| "bbbb", |
| + "cccc", |
| "dddd", |
| # (2 unchanged elements hidden) |
| ] |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "force-new update - insertion": { |
| Action: plans.DeleteThenCreate, |
| ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate, |
| 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("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.StringVal("bbbb"), |
| 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(cty.Path{ |
| cty.GetAttrStep{Name: "list_field"}, |
| }), |
| ExpectedOutput: ` # test_instance.example must be replaced |
| -/+ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ list_field = [ # forces replacement |
| "aaaa", |
| + "bbbb", |
| "cccc", |
| ] |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "in-place update - deletion": { |
| 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"), |
| cty.StringVal("dddd"), |
| cty.StringVal("eeee"), |
| }), |
| }), |
| 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("bbbb"), |
| cty.StringVal("dddd"), |
| cty.StringVal("eeee"), |
| }), |
| }), |
| 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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ list_field = [ |
| - "aaaa", |
| "bbbb", |
| - "cccc", |
| "dddd", |
| # (1 unchanged element hidden) |
| ] |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ list_field = [ |
| - "aaaa", |
| - "bbbb", |
| - "cccc", |
| ] |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| + list_field = [] |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ list_field = [ |
| "aaaa", |
| - "bbbb", |
| + (known after apply), |
| "cccc", |
| ] |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "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"), |
| cty.StringVal("dddd"), |
| cty.StringVal("eeee"), |
| }), |
| }), |
| 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"), |
| cty.StringVal("dddd"), |
| cty.StringVal("eeee"), |
| }), |
| }), |
| 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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ list_field = [ |
| "aaaa", |
| - "bbbb", |
| + (known after apply), |
| + (known after apply), |
| "cccc", |
| # (2 unchanged elements hidden) |
| ] |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| } |
| runTestCases(t, testCases) |
| } |
| |
| func TestResourceChange_primitiveTuple(t *testing.T) { |
| testCases := map[string]testCase{ |
| "in-place update": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "tuple_field": cty.TupleVal([]cty.Value{ |
| cty.StringVal("aaaa"), |
| cty.StringVal("bbbb"), |
| cty.StringVal("dddd"), |
| cty.StringVal("eeee"), |
| cty.StringVal("ffff"), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "tuple_field": cty.TupleVal([]cty.Value{ |
| cty.StringVal("aaaa"), |
| cty.StringVal("bbbb"), |
| cty.StringVal("cccc"), |
| cty.StringVal("eeee"), |
| cty.StringVal("ffff"), |
| }), |
| }), |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Required: true}, |
| "tuple_field": {Type: cty.Tuple([]cty.Type{cty.String, cty.String, cty.String, cty.String, cty.String}), Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| id = "i-02ae66f368e8518a9" |
| ~ tuple_field = [ |
| # (1 unchanged element hidden) |
| "bbbb", |
| - "dddd", |
| + "cccc", |
| "eeee", |
| # (1 unchanged element hidden) |
| ] |
| } |
| `, |
| }, |
| } |
| runTestCases(t, testCases) |
| } |
| |
| 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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| + set_field = [ |
| + "new-element", |
| ] |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "in-place update - first insertion": { |
| 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.SetValEmpty(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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ set_field = [ |
| + "new-element", |
| ] |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "in-place update - insertion": { |
| 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("cccc"), |
| }), |
| }), |
| 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.StringVal("bbbb"), |
| 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}, |
| "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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ set_field = [ |
| + "bbbb", |
| # (2 unchanged elements hidden) |
| ] |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "force-new update - insertion": { |
| Action: plans.DeleteThenCreate, |
| ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate, |
| 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("cccc"), |
| }), |
| }), |
| 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.StringVal("bbbb"), |
| 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}, |
| "set_field": {Type: cty.Set(cty.String), Optional: true}, |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(cty.Path{ |
| cty.GetAttrStep{Name: "set_field"}, |
| }), |
| ExpectedOutput: ` # test_instance.example must be replaced |
| -/+ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ set_field = [ # forces replacement |
| + "bbbb", |
| # (2 unchanged elements hidden) |
| ] |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "in-place update - deletion": { |
| 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"), |
| cty.StringVal("cccc"), |
| }), |
| }), |
| 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("bbbb"), |
| }), |
| }), |
| 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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ set_field = [ |
| - "aaaa", |
| - "cccc", |
| # (1 unchanged element hidden) |
| ] |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ set_field = [ |
| - "aaaa", |
| - "bbbb", |
| ] |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| + set_field = [] |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ set_field = [ |
| - "aaaa", |
| - "bbbb", |
| ] -> (known after apply) |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ set_field = [ |
| - "bbbb", |
| ~ (known after apply), |
| # (1 unchanged element hidden) |
| ] |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| } |
| runTestCases(t, testCases) |
| } |
| |
| func TestResourceChange_map(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"), |
| "map_field": cty.NullVal(cty.Map(cty.String)), |
| }), |
| 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{ |
| "new-key": cty.StringVal("new-element"), |
| "be:ep": cty.StringVal("boop"), |
| }), |
| }), |
| 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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| + map_field = { |
| + "be:ep" = "boop" |
| + "new-key" = "new-element" |
| } |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "in-place update - first insertion": { |
| 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.MapValEmpty(cty.String), |
| }), |
| 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{ |
| "new-key": cty.StringVal("new-element"), |
| "be:ep": cty.StringVal("boop"), |
| }), |
| }), |
| 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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ map_field = { |
| + "be:ep" = "boop" |
| + "new-key" = "new-element" |
| } |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "in-place update - insertion": { |
| 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"), |
| "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.StringVal("bbbb"), |
| "b:b": cty.StringVal("bbbb"), |
| "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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ map_field = { |
| + "b" = "bbbb" |
| + "b:b" = "bbbb" |
| # (2 unchanged elements hidden) |
| } |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "force-new update - insertion": { |
| Action: plans.DeleteThenCreate, |
| ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate, |
| 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"), |
| "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.StringVal("bbbb"), |
| "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(cty.Path{ |
| cty.GetAttrStep{Name: "map_field"}, |
| }), |
| ExpectedOutput: ` # test_instance.example must be replaced |
| -/+ resource "test_instance" "example" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ map_field = { # forces replacement |
| + "b" = "bbbb" |
| # (2 unchanged elements hidden) |
| } |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "in-place update - deletion": { |
| 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{ |
| "b": cty.StringVal("bbbb"), |
| }), |
| }), |
| 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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ map_field = { |
| - "a" = "aaaa" -> null |
| - "c" = "cccc" -> null |
| # (1 unchanged element hidden) |
| } |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| "creation - empty": { |
| 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"), |
| "map_field": cty.MapValEmpty(cty.String), |
| }), |
| 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 created |
| + resource "test_instance" "example" { |
| + ami = "ami-STATIC" |
| + 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" { |
| ~ id = "i-02ae66f368e8518a9" -> (known after apply) |
| ~ map_field = { |
| ~ "b" = "bbbb" -> (known after apply) |
| # (2 unchanged elements hidden) |
| } |
| # (1 unchanged attribute hidden) |
| } |
| `, |
| }, |
| } |
| runTestCases(t, testCases) |
| } |
| |
| func TestResourceChange_nestedList(t *testing.T) { |
| testCases := map[string]testCase{ |
| "in-place update - equal": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "root_block_device": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| }), |
| "disks": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "root_block_device": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| }), |
| "disks": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchema(configschema.NestingList), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| id = "i-02ae66f368e8518a9" |
| # (1 unchanged attribute hidden) |
| |
| # (1 unchanged block hidden) |
| } |
| `, |
| }, |
| "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-BEFORE"), |
| "root_block_device": cty.ListValEmpty(cty.Object(map[string]cty.Type{ |
| "volume_type": cty.String, |
| })), |
| "disks": cty.ListValEmpty(cty.Object(map[string]cty.Type{ |
| "mount_point": cty.String, |
| "size": cty.String, |
| })), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| })}), |
| "root_block_device": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.NullVal(cty.String), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchema(configschema.NestingList), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| ~ disks = [ |
| + { |
| + mount_point = "/var/diska" |
| + size = "50GB" |
| }, |
| ] |
| id = "i-02ae66f368e8518a9" |
| |
| + root_block_device {} |
| } |
| `, |
| }, |
| "in-place update - first insertion": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "root_block_device": cty.ListValEmpty(cty.Object(map[string]cty.Type{ |
| "volume_type": cty.String, |
| })), |
| "disks": cty.ListValEmpty(cty.Object(map[string]cty.Type{ |
| "mount_point": cty.String, |
| "size": cty.String, |
| })), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.NullVal(cty.String), |
| }), |
| }), |
| "root_block_device": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchema(configschema.NestingList), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| ~ disks = [ |
| + { |
| + mount_point = "/var/diska" |
| }, |
| ] |
| id = "i-02ae66f368e8518a9" |
| |
| + root_block_device { |
| + volume_type = "gp2" |
| } |
| } |
| `, |
| }, |
| "in-place update - insertion": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.NullVal(cty.String), |
| }), |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diskb"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.NullVal(cty.String), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diskb"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.StringVal("new_value"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchemaPlus(configschema.NestingList), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| ~ disks = [ |
| ~ { |
| + size = "50GB" |
| # (1 unchanged attribute hidden) |
| }, |
| # (1 unchanged element hidden) |
| ] |
| id = "i-02ae66f368e8518a9" |
| |
| ~ root_block_device { |
| + new_field = "new_value" |
| # (1 unchanged attribute hidden) |
| } |
| } |
| `, |
| }, |
| "force-new update (inside blocks)": { |
| Action: plans.DeleteThenCreate, |
| ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diskb"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("different"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet( |
| cty.Path{ |
| cty.GetAttrStep{Name: "root_block_device"}, |
| cty.IndexStep{Key: cty.NumberIntVal(0)}, |
| cty.GetAttrStep{Name: "volume_type"}, |
| }, |
| cty.Path{ |
| cty.GetAttrStep{Name: "disks"}, |
| cty.IndexStep{Key: cty.NumberIntVal(0)}, |
| cty.GetAttrStep{Name: "mount_point"}, |
| }, |
| ), |
| Schema: testSchema(configschema.NestingList), |
| ExpectedOutput: ` # test_instance.example must be replaced |
| -/+ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| ~ disks = [ |
| ~ { |
| ~ mount_point = "/var/diska" -> "/var/diskb" # forces replacement |
| # (1 unchanged attribute hidden) |
| }, |
| ] |
| id = "i-02ae66f368e8518a9" |
| |
| ~ root_block_device { |
| ~ volume_type = "gp2" -> "different" # forces replacement |
| } |
| } |
| `, |
| }, |
| "force-new update (whole block)": { |
| Action: plans.DeleteThenCreate, |
| ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diskb"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("different"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet( |
| cty.Path{cty.GetAttrStep{Name: "root_block_device"}}, |
| cty.Path{cty.GetAttrStep{Name: "disks"}}, |
| ), |
| Schema: testSchema(configschema.NestingList), |
| ExpectedOutput: ` # test_instance.example must be replaced |
| -/+ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| ~ disks = [ # forces replacement |
| ~ { |
| ~ mount_point = "/var/diska" -> "/var/diskb" |
| # (1 unchanged attribute hidden) |
| }, |
| ] |
| id = "i-02ae66f368e8518a9" |
| |
| ~ root_block_device { # forces replacement |
| ~ volume_type = "gp2" -> "different" |
| } |
| } |
| `, |
| }, |
| "in-place update - deletion": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.ListValEmpty(cty.Object(map[string]cty.Type{ |
| "mount_point": cty.String, |
| "size": cty.String, |
| })), |
| "root_block_device": cty.ListValEmpty(cty.Object(map[string]cty.Type{ |
| "volume_type": cty.String, |
| })), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchema(configschema.NestingList), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| ~ disks = [ |
| - { |
| - mount_point = "/var/diska" -> null |
| - size = "50GB" -> null |
| }, |
| ] |
| id = "i-02ae66f368e8518a9" |
| |
| - root_block_device { |
| - volume_type = "gp2" -> null |
| } |
| } |
| `, |
| }, |
| "with dynamically-typed attribute": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "block": cty.EmptyTupleVal, |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "block": cty.TupleVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "attr": cty.StringVal("foo"), |
| }), |
| cty.ObjectVal(map[string]cty.Value{ |
| "attr": cty.True, |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: &configschema.Block{ |
| BlockTypes: map[string]*configschema.NestedBlock{ |
| "block": { |
| Block: configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "attr": {Type: cty.DynamicPseudoType, Optional: true}, |
| }, |
| }, |
| Nesting: configschema.NestingList, |
| }, |
| }, |
| }, |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| + block { |
| + attr = "foo" |
| } |
| + block { |
| + attr = true |
| } |
| } |
| `, |
| }, |
| "in-place sequence update - deletion": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "list": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("x")}), |
| cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("y")}), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "list": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("y")}), |
| cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("z")}), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: &configschema.Block{ |
| BlockTypes: map[string]*configschema.NestedBlock{ |
| "list": { |
| Block: configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "attr": { |
| Type: cty.String, |
| Required: true, |
| }, |
| }, |
| }, |
| Nesting: configschema.NestingList, |
| }, |
| }, |
| }, |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ list { |
| ~ attr = "x" -> "y" |
| } |
| ~ list { |
| ~ attr = "y" -> "z" |
| } |
| } |
| `, |
| }, |
| "in-place update - unknown": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.StringVal("new_value"), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{ |
| "mount_point": cty.String, |
| "size": cty.String, |
| }))), |
| "root_block_device": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.StringVal("new_value"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchemaPlus(configschema.NestingList), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| ~ disks = [ |
| - { |
| - mount_point = "/var/diska" -> null |
| - size = "50GB" -> null |
| }, |
| ] -> (known after apply) |
| id = "i-02ae66f368e8518a9" |
| |
| # (1 unchanged block hidden) |
| } |
| `, |
| }, |
| "in-place update - modification": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diskb"), |
| "size": cty.StringVal("50GB"), |
| }), |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diskc"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.StringVal("new_value"), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diskb"), |
| "size": cty.StringVal("75GB"), |
| }), |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diskc"), |
| "size": cty.StringVal("25GB"), |
| }), |
| }), |
| "root_block_device": cty.ListVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.StringVal("new_value"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchemaPlus(configschema.NestingList), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| ~ disks = [ |
| ~ { |
| ~ size = "50GB" -> "75GB" |
| # (1 unchanged attribute hidden) |
| }, |
| ~ { |
| ~ size = "50GB" -> "25GB" |
| # (1 unchanged attribute hidden) |
| }, |
| # (1 unchanged element hidden) |
| ] |
| id = "i-02ae66f368e8518a9" |
| |
| # (1 unchanged block hidden) |
| } |
| `, |
| }, |
| } |
| runTestCases(t, testCases) |
| } |
| |
| func TestResourceChange_nestedSet(t *testing.T) { |
| testCases := map[string]testCase{ |
| "creation from null - sensitive set": { |
| Action: plans.Create, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.NullVal(cty.Object(map[string]cty.Type{ |
| "id": cty.String, |
| "ami": cty.String, |
| "disks": cty.Set(cty.Object(map[string]cty.Type{ |
| "mount_point": cty.String, |
| "size": cty.String, |
| })), |
| "root_block_device": cty.Set(cty.Object(map[string]cty.Type{ |
| "volume_type": cty.String, |
| })), |
| })), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.NullVal(cty.String), |
| }), |
| }), |
| "root_block_device": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| }), |
| }), |
| AfterValMarks: []cty.PathValueMarks{ |
| { |
| Path: cty.Path{cty.GetAttrStep{Name: "disks"}}, |
| Marks: cty.NewValueMarks(marks.Sensitive), |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchema(configschema.NestingSet), |
| ExpectedOutput: ` # test_instance.example will be created |
| + resource "test_instance" "example" { |
| + ami = "ami-AFTER" |
| + disks = (sensitive value) |
| + id = "i-02ae66f368e8518a9" |
| |
| + root_block_device { |
| + volume_type = "gp2" |
| } |
| } |
| `, |
| }, |
| "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-BEFORE"), |
| "disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{ |
| "mount_point": cty.String, |
| "size": cty.String, |
| })), |
| "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ |
| "volume_type": cty.String, |
| })), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.NullVal(cty.String), |
| }), |
| }), |
| "root_block_device": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchema(configschema.NestingSet), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| ~ disks = [ |
| + { |
| + mount_point = "/var/diska" |
| }, |
| ] |
| id = "i-02ae66f368e8518a9" |
| |
| + root_block_device { |
| + volume_type = "gp2" |
| } |
| } |
| `, |
| }, |
| "in-place update - creation - sensitive set": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{ |
| "mount_point": cty.String, |
| "size": cty.String, |
| })), |
| "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ |
| "volume_type": cty.String, |
| })), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.NullVal(cty.String), |
| }), |
| }), |
| "root_block_device": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| }), |
| }), |
| AfterValMarks: []cty.PathValueMarks{ |
| { |
| Path: cty.Path{cty.GetAttrStep{Name: "disks"}}, |
| Marks: cty.NewValueMarks(marks.Sensitive), |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchema(configschema.NestingSet), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| # Warning: this attribute value will be marked as sensitive and will not |
| # display in UI output after applying this change. |
| ~ disks = (sensitive value) |
| id = "i-02ae66f368e8518a9" |
| |
| + root_block_device { |
| + volume_type = "gp2" |
| } |
| } |
| `, |
| }, |
| "in-place update - marking set sensitive": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ |
| "volume_type": cty.String, |
| })), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ |
| "volume_type": cty.String, |
| })), |
| }), |
| AfterValMarks: []cty.PathValueMarks{ |
| { |
| Path: cty.Path{cty.GetAttrStep{Name: "disks"}}, |
| Marks: cty.NewValueMarks(marks.Sensitive), |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchema(configschema.NestingSet), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| # Warning: this attribute value will be marked as sensitive and will not |
| # display in UI output after applying this change. The value is unchanged. |
| ~ disks = (sensitive value) |
| id = "i-02ae66f368e8518a9" |
| } |
| `, |
| }, |
| "in-place update - insertion": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.NullVal(cty.String), |
| }), |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diskb"), |
| "size": cty.StringVal("100GB"), |
| }), |
| }), |
| "root_block_device": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.NullVal(cty.String), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diskb"), |
| "size": cty.StringVal("100GB"), |
| }), |
| }), |
| "root_block_device": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.StringVal("new_value"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchemaPlus(configschema.NestingSet), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| ~ disks = [ |
| + { |
| + mount_point = "/var/diska" |
| + size = "50GB" |
| }, |
| - { |
| - mount_point = "/var/diska" -> null |
| }, |
| # (1 unchanged element hidden) |
| ] |
| id = "i-02ae66f368e8518a9" |
| |
| + root_block_device { |
| + new_field = "new_value" |
| + volume_type = "gp2" |
| } |
| - root_block_device { |
| - volume_type = "gp2" -> null |
| } |
| } |
| `, |
| }, |
| "force-new update (whole block)": { |
| Action: plans.DeleteThenCreate, |
| ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "root_block_device": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| }), |
| "disks": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "root_block_device": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("different"), |
| }), |
| }), |
| "disks": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diskb"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet( |
| cty.Path{cty.GetAttrStep{Name: "root_block_device"}}, |
| cty.Path{cty.GetAttrStep{Name: "disks"}}, |
| ), |
| Schema: testSchema(configschema.NestingSet), |
| ExpectedOutput: ` # test_instance.example must be replaced |
| -/+ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| ~ disks = [ |
| - { # forces replacement |
| - mount_point = "/var/diska" -> null |
| - size = "50GB" -> null |
| }, |
| + { # forces replacement |
| + mount_point = "/var/diskb" |
| + size = "50GB" |
| }, |
| ] |
| id = "i-02ae66f368e8518a9" |
| |
| + root_block_device { # forces replacement |
| + volume_type = "different" |
| } |
| - root_block_device { # forces replacement |
| - volume_type = "gp2" -> null |
| } |
| } |
| `, |
| }, |
| "in-place update - deletion": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "root_block_device": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.StringVal("new_value"), |
| }), |
| }), |
| "disks": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ |
| "volume_type": cty.String, |
| "new_field": cty.String, |
| })), |
| "disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{ |
| "mount_point": cty.String, |
| "size": cty.String, |
| })), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchemaPlus(configschema.NestingSet), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| ~ disks = [ |
| - { |
| - mount_point = "/var/diska" -> null |
| - size = "50GB" -> null |
| }, |
| ] |
| id = "i-02ae66f368e8518a9" |
| |
| - root_block_device { |
| - new_field = "new_value" -> null |
| - volume_type = "gp2" -> null |
| } |
| } |
| `, |
| }, |
| "in-place update - empty nested sets": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ |
| "mount_point": cty.String, |
| "size": cty.String, |
| }))), |
| "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ |
| "volume_type": cty.String, |
| })), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{ |
| "mount_point": cty.String, |
| "size": cty.String, |
| })), |
| "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ |
| "volume_type": cty.String, |
| })), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchema(configschema.NestingSet), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| + disks = [ |
| ] |
| id = "i-02ae66f368e8518a9" |
| } |
| `, |
| }, |
| "in-place update - null insertion": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ |
| "mount_point": cty.String, |
| "size": cty.String, |
| }))), |
| "root_block_device": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.NullVal(cty.String), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.StringVal("new_value"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchemaPlus(configschema.NestingSet), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| + disks = [ |
| + { |
| + mount_point = "/var/diska" |
| + size = "50GB" |
| }, |
| ] |
| id = "i-02ae66f368e8518a9" |
| |
| + root_block_device { |
| + new_field = "new_value" |
| + volume_type = "gp2" |
| } |
| - root_block_device { |
| - volume_type = "gp2" -> null |
| } |
| } |
| `, |
| }, |
| "in-place update - unknown": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.StringVal("new_value"), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{ |
| "mount_point": cty.String, |
| "size": cty.String, |
| }))), |
| "root_block_device": cty.SetVal([]cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.StringVal("new_value"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchemaPlus(configschema.NestingSet), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| ~ disks = [ |
| - { |
| - mount_point = "/var/diska" -> null |
| - size = "50GB" -> null |
| }, |
| ] -> (known after apply) |
| id = "i-02ae66f368e8518a9" |
| |
| # (1 unchanged block hidden) |
| } |
| `, |
| }, |
| } |
| runTestCases(t, testCases) |
| } |
| |
| func TestResourceChange_nestedMap(t *testing.T) { |
| testCases := map[string]testCase{ |
| "creation from null": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.NullVal(cty.String), |
| "ami": cty.NullVal(cty.String), |
| "disks": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{ |
| "mount_point": cty.String, |
| "size": cty.String, |
| }))), |
| "root_block_device": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{ |
| "volume_type": cty.String, |
| }))), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.MapVal(map[string]cty.Value{ |
| "disk_a": cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.NullVal(cty.String), |
| }), |
| }), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
| "a": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchema(configschema.NestingMap), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| + ami = "ami-AFTER" |
| + disks = { |
| + "disk_a" = { |
| + mount_point = "/var/diska" |
| }, |
| } |
| + id = "i-02ae66f368e8518a9" |
| |
| + root_block_device "a" { |
| + volume_type = "gp2" |
| } |
| } |
| `, |
| }, |
| "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-BEFORE"), |
| "disks": cty.MapValEmpty(cty.Object(map[string]cty.Type{ |
| "mount_point": cty.String, |
| "size": cty.String, |
| })), |
| "root_block_device": cty.MapValEmpty(cty.Object(map[string]cty.Type{ |
| "volume_type": cty.String, |
| })), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.MapVal(map[string]cty.Value{ |
| "disk_a": cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.NullVal(cty.String), |
| }), |
| }), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
| "a": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchema(configschema.NestingMap), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| ~ disks = { |
| + "disk_a" = { |
| + mount_point = "/var/diska" |
| }, |
| } |
| id = "i-02ae66f368e8518a9" |
| |
| + root_block_device "a" { |
| + volume_type = "gp2" |
| } |
| } |
| `, |
| }, |
| "in-place update - change attr": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.MapVal(map[string]cty.Value{ |
| "disk_a": cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.NullVal(cty.String), |
| }), |
| }), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
| "a": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.NullVal(cty.String), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.MapVal(map[string]cty.Value{ |
| "disk_a": cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
| "a": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.StringVal("new_value"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchemaPlus(configschema.NestingMap), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| ~ disks = { |
| ~ "disk_a" = { |
| + size = "50GB" |
| # (1 unchanged attribute hidden) |
| }, |
| } |
| id = "i-02ae66f368e8518a9" |
| |
| ~ root_block_device "a" { |
| + new_field = "new_value" |
| # (1 unchanged attribute hidden) |
| } |
| } |
| `, |
| }, |
| "in-place update - insertion": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.MapVal(map[string]cty.Value{ |
| "disk_a": cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
| "a": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.NullVal(cty.String), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.MapVal(map[string]cty.Value{ |
| "disk_a": cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| "disk_2": cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/disk2"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
| "a": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.NullVal(cty.String), |
| }), |
| "b": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.StringVal("new_value"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchemaPlus(configschema.NestingMap), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| ~ disks = { |
| + "disk_2" = { |
| + mount_point = "/var/disk2" |
| + size = "50GB" |
| }, |
| # (1 unchanged element hidden) |
| } |
| id = "i-02ae66f368e8518a9" |
| |
| + root_block_device "b" { |
| + new_field = "new_value" |
| + volume_type = "gp2" |
| } |
| |
| # (1 unchanged block hidden) |
| } |
| `, |
| }, |
| "force-new update (whole block)": { |
| Action: plans.DeleteThenCreate, |
| ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.MapVal(map[string]cty.Value{ |
| "disk_a": cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
| "a": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| "b": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("standard"), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.MapVal(map[string]cty.Value{ |
| "disk_a": cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("100GB"), |
| }), |
| }), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
| "a": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("different"), |
| }), |
| "b": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("standard"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(cty.Path{ |
| cty.GetAttrStep{Name: "root_block_device"}, |
| cty.IndexStep{Key: cty.StringVal("a")}, |
| }, |
| cty.Path{cty.GetAttrStep{Name: "disks"}}, |
| ), |
| Schema: testSchema(configschema.NestingMap), |
| ExpectedOutput: ` # test_instance.example must be replaced |
| -/+ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| ~ disks = { |
| ~ "disk_a" = { # forces replacement |
| ~ size = "50GB" -> "100GB" |
| # (1 unchanged attribute hidden) |
| }, |
| } |
| id = "i-02ae66f368e8518a9" |
| |
| ~ root_block_device "a" { # forces replacement |
| ~ volume_type = "gp2" -> "different" |
| } |
| |
| # (1 unchanged block hidden) |
| } |
| `, |
| }, |
| "in-place update - deletion": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.MapVal(map[string]cty.Value{ |
| "disk_a": cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
| "a": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.StringVal("new_value"), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.MapValEmpty(cty.Object(map[string]cty.Type{ |
| "mount_point": cty.String, |
| "size": cty.String, |
| })), |
| "root_block_device": cty.MapValEmpty(cty.Object(map[string]cty.Type{ |
| "volume_type": cty.String, |
| "new_field": cty.String, |
| })), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchemaPlus(configschema.NestingMap), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| ~ disks = { |
| - "disk_a" = { |
| - mount_point = "/var/diska" -> null |
| - size = "50GB" -> null |
| }, |
| } |
| id = "i-02ae66f368e8518a9" |
| |
| - root_block_device "a" { |
| - new_field = "new_value" -> null |
| - volume_type = "gp2" -> null |
| } |
| } |
| `, |
| }, |
| "in-place update - unknown": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.MapVal(map[string]cty.Value{ |
| "disk_a": cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
| "a": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.StringVal("new_value"), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.UnknownVal(cty.Map(cty.Object(map[string]cty.Type{ |
| "mount_point": cty.String, |
| "size": cty.String, |
| }))), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
| "a": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.StringVal("new_value"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchemaPlus(configschema.NestingMap), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| ~ disks = { |
| - "disk_a" = { |
| - mount_point = "/var/diska" -> null |
| - size = "50GB" -> null |
| }, |
| } -> (known after apply) |
| id = "i-02ae66f368e8518a9" |
| |
| # (1 unchanged block hidden) |
| } |
| `, |
| }, |
| "in-place update - insertion sensitive": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.MapValEmpty(cty.Object(map[string]cty.Type{ |
| "mount_point": cty.String, |
| "size": cty.String, |
| })), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
| "a": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.StringVal("new_value"), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.MapVal(map[string]cty.Value{ |
| "disk_a": cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
| "a": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| "new_field": cty.StringVal("new_value"), |
| }), |
| }), |
| }), |
| AfterValMarks: []cty.PathValueMarks{ |
| { |
| Path: cty.Path{cty.GetAttrStep{Name: "disks"}, |
| cty.IndexStep{Key: cty.StringVal("disk_a")}, |
| cty.GetAttrStep{Name: "mount_point"}, |
| }, |
| Marks: cty.NewValueMarks(marks.Sensitive), |
| }, |
| }, |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchemaPlus(configschema.NestingMap), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| ~ disks = { |
| + "disk_a" = { |
| + mount_point = (sensitive value) |
| + size = "50GB" |
| }, |
| } |
| id = "i-02ae66f368e8518a9" |
| |
| # (1 unchanged block hidden) |
| } |
| `, |
| }, |
| "in-place update - multiple unchanged blocks": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.MapVal(map[string]cty.Value{ |
| "disk_a": cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
| "a": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| "b": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.MapVal(map[string]cty.Value{ |
| "disk_a": cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
| "a": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| "b": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchema(configschema.NestingMap), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| id = "i-02ae66f368e8518a9" |
| # (1 unchanged attribute hidden) |
| |
| # (2 unchanged blocks hidden) |
| } |
| `, |
| }, |
| "in-place update - multiple blocks first changed": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.MapVal(map[string]cty.Value{ |
| "disk_a": cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
| "a": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| "b": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.MapVal(map[string]cty.Value{ |
| "disk_a": cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
| "a": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| "b": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp3"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchema(configschema.NestingMap), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| id = "i-02ae66f368e8518a9" |
| # (1 unchanged attribute hidden) |
| |
| ~ root_block_device "b" { |
| ~ volume_type = "gp2" -> "gp3" |
| } |
| |
| # (1 unchanged block hidden) |
| } |
| `, |
| }, |
| "in-place update - multiple blocks second changed": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.MapVal(map[string]cty.Value{ |
| "disk_a": cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
| "a": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| "b": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.MapVal(map[string]cty.Value{ |
| "disk_a": cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
| "a": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp3"), |
| }), |
| "b": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| }), |
| }), |
| RequiredReplace: cty.NewPathSet(), |
| Schema: testSchema(configschema.NestingMap), |
| ExpectedOutput: ` # test_instance.example will be updated in-place |
| ~ resource "test_instance" "example" { |
| ~ ami = "ami-BEFORE" -> "ami-AFTER" |
| id = "i-02ae66f368e8518a9" |
| # (1 unchanged attribute hidden) |
| |
| ~ root_block_device "a" { |
| ~ volume_type = "gp2" -> "gp3" |
| } |
| |
| # (1 unchanged block hidden) |
| } |
| `, |
| }, |
| "in-place update - multiple blocks changed": { |
| Action: plans.Update, |
| Mode: addrs.ManagedResourceMode, |
| Before: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-BEFORE"), |
| "disks": cty.MapVal(map[string]cty.Value{ |
| "disk_a": cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
| "a": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| "b": cty.ObjectVal(map[string]cty.Value{ |
| "volume_type": cty.StringVal("gp2"), |
| }), |
| }), |
| }), |
| After: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("i-02ae66f368e8518a9"), |
| "ami": cty.StringVal("ami-AFTER"), |
| "disks": cty.MapVal(map[string]cty.Value{ |
| "disk_a": cty.ObjectVal(map[string]cty.Value{ |
| "mount_point": cty.StringVal("/var/diska"), |
| "size": cty.StringVal("50GB"), |
| }), |
| }), |
| "root_block_device": cty.MapVal(map[string]cty.Value{ |
<