2018-12-10 18:42:45 +01:00
|
|
|
package format
|
|
|
|
|
|
|
|
import (
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/addrs"
|
|
|
|
"github.com/hashicorp/terraform/configs/configschema"
|
|
|
|
"github.com/hashicorp/terraform/plans"
|
|
|
|
"github.com/mitchellh/colorstring"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
)
|
|
|
|
|
2018-12-11 12:49:17 +01:00
|
|
|
func TestResourceChange_primitiveTypes(t *testing.T) {
|
|
|
|
testCases := map[string]testCase{
|
2018-12-10 18:42:45 +01:00
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-10 18:42:45 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be created
|
|
|
|
+ resource "test_instance" "example" {
|
|
|
|
+ id = (known after apply)
|
|
|
|
}
|
2019-03-14 22:20:42 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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(),
|
|
|
|
Tainted: false,
|
|
|
|
ExpectedOutput: ` # test_instance.example will be created
|
|
|
|
+ resource "test_instance" "example" {
|
|
|
|
+ string = "null"
|
|
|
|
}
|
2018-12-10 18:42:45 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-10 18:42:45 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be destroyed
|
2019-01-13 23:51:05 +01:00
|
|
|
- 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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-13 23:51:05 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be destroyed
|
2018-12-10 18:42:45 +01:00
|
|
|
- resource "test_instance" "example" {
|
|
|
|
- id = "i-02ae66f368e8518a9" -> null
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
2018-12-11 12:49:17 +01:00
|
|
|
"string in-place update": {
|
2018-12-10 18:42:45 +01:00
|
|
|
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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-10 18:42:45 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
|
|
id = "i-02ae66f368e8518a9"
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
2018-12-11 12:49:17 +01:00
|
|
|
"string force-new update": {
|
2018-12-10 18:42:45 +01:00
|
|
|
Action: plans.DeleteThenCreate,
|
|
|
|
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{
|
2018-12-11 12:49:17 +01:00
|
|
|
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
2018-12-10 18:42:45 +01:00
|
|
|
"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"},
|
|
|
|
}),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-10 18:42:45 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example must be replaced
|
|
|
|
-/+ resource "test_instance" "example" {
|
2018-12-11 12:55:59 +01:00
|
|
|
~ ami = "ami-BEFORE" -> "ami-AFTER" # forces replacement
|
2018-12-11 12:49:17 +01:00
|
|
|
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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:49:17 +01:00
|
|
|
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
|
|
|
|
`),
|
|
|
|
}),
|
|
|
|
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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:49:17 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
2018-12-11 12:55:59 +01:00
|
|
|
"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"},
|
|
|
|
}),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:55:59 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
2018-12-11 12:49:17 +01:00
|
|
|
|
|
|
|
// 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"),
|
|
|
|
}),
|
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Computed: true},
|
|
|
|
"password": {Type: cty.String, Optional: true, Sensitive: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
RequiredReplace: cty.NewPathSet(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:49:17 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be created
|
|
|
|
+ resource "test_instance" "example" {
|
|
|
|
+ id = (known after apply)
|
|
|
|
+ password = (sensitive value)
|
2018-12-10 18:42:45 +01:00
|
|
|
}
|
2019-01-23 16:32:13 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-23 16:32:13 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
~ id = "blah" -> (known after apply)
|
|
|
|
password = (sensitive value)
|
|
|
|
~ str = "before" -> "after"
|
|
|
|
}
|
2019-03-06 01:18:55 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
|
|
|
|
// tainted resources
|
|
|
|
"replace tainted resource": {
|
|
|
|
Action: plans.DeleteThenCreate,
|
|
|
|
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"},
|
|
|
|
}),
|
|
|
|
Tainted: true,
|
|
|
|
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)
|
|
|
|
}
|
2019-03-29 22:52:13 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"force replacement with empty before value": {
|
|
|
|
Action: plans.DeleteThenCreate,
|
|
|
|
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"},
|
|
|
|
}),
|
|
|
|
Tainted: false,
|
|
|
|
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,
|
|
|
|
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"},
|
|
|
|
}),
|
|
|
|
Tainted: false,
|
|
|
|
ExpectedOutput: ` # test_instance.example must be replaced
|
|
|
|
-/+ resource "test_instance" "example" {
|
|
|
|
+ forced = "example" # forces replacement
|
|
|
|
name = "name"
|
|
|
|
}
|
2018-12-10 18:42:45 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2018-12-11 12:49:17 +01:00
|
|
|
runTestCases(t, testCases)
|
|
|
|
}
|
|
|
|
|
2018-12-11 12:50:08 +01:00
|
|
|
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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:50:08 +01:00
|
|
|
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"
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
2019-01-23 14:13:48 +01:00
|
|
|
"in-place update of object": {
|
2018-12-11 12:50:08 +01:00
|
|
|
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": "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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:50:08 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ json_field = jsonencode(
|
|
|
|
~ {
|
2019-01-15 16:37:03 +01:00
|
|
|
aaa = "value"
|
2018-12-11 12:50:08 +01:00
|
|
|
+ bbb = "new_value"
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
2019-01-15 16:37:03 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-15 16:37:03 +01:00
|
|
|
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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-15 16:37:03 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ json_field = jsonencode(
|
|
|
|
~ {
|
|
|
|
~ aaa = [
|
|
|
|
- "value",
|
|
|
|
]
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
2019-01-22 17:49:49 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-22 17:49:49 +01:00
|
|
|
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",
|
|
|
|
]
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
2018-12-11 12:55:59 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"force-new update": {
|
|
|
|
Action: plans.DeleteThenCreate,
|
|
|
|
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"},
|
|
|
|
}),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:55:59 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example must be replaced
|
|
|
|
-/+ resource "test_instance" "example" {
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ json_field = jsonencode(
|
|
|
|
~ {
|
2019-01-15 16:37:03 +01:00
|
|
|
aaa = "value"
|
2018-12-11 12:55:59 +01:00
|
|
|
+ bbb = "new_value"
|
|
|
|
} # forces replacement
|
|
|
|
)
|
|
|
|
}
|
2018-12-11 12:50:08 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:50:08 +01:00
|
|
|
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"
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
2018-12-11 12:55:59 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"force-new update (whitespace change)": {
|
|
|
|
Action: plans.DeleteThenCreate,
|
|
|
|
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"},
|
|
|
|
}),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:55:59 +01:00
|
|
|
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"
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
2019-01-14 16:29:36 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-14 16:29:36 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be created
|
|
|
|
+ resource "test_instance" "example" {
|
|
|
|
+ id = (known after apply)
|
|
|
|
+ json_field = jsonencode({})
|
|
|
|
}
|
2018-12-11 12:50:08 +01:00
|
|
|
`,
|
|
|
|
},
|
2019-01-15 16:37:03 +01:00
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-15 16:37:03 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ json_field = jsonencode(
|
|
|
|
~ [
|
|
|
|
"first",
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-15 16:37:03 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ json_field = jsonencode(
|
|
|
|
~ [
|
|
|
|
"first",
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-15 16:37:03 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ json_field = jsonencode(
|
|
|
|
~ {
|
|
|
|
first = "111"
|
|
|
|
+ second = "222"
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-15 16:37:03 +01:00
|
|
|
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",
|
|
|
|
]
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
2019-03-19 23:21:32 +01:00
|
|
|
"JSON list of objects - adding item": {
|
2019-01-15 16:37:03 +01:00
|
|
|
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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-15 16:37:03 +01:00
|
|
|
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"
|
|
|
|
},
|
|
|
|
]
|
|
|
|
)
|
|
|
|
}
|
2019-03-19 23:21:32 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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"}]`),
|
|
|
|
}),
|
|
|
|
After: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"json_field": cty.StringVal(`[{"one": "111"}]`),
|
|
|
|
}),
|
|
|
|
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(),
|
|
|
|
Tainted: false,
|
|
|
|
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"
|
|
|
|
},
|
|
|
|
]
|
|
|
|
)
|
|
|
|
}
|
2019-01-15 16:37:03 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-15 16:37:03 +01:00
|
|
|
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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-15 16:37:03 +01:00
|
|
|
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",
|
|
|
|
]
|
|
|
|
},
|
|
|
|
]
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
2019-01-23 14:13:48 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-23 14:13:48 +01:00
|
|
|
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",
|
|
|
|
]
|
|
|
|
)
|
|
|
|
}
|
2019-01-15 16:37:03 +01:00
|
|
|
`,
|
|
|
|
},
|
2018-12-11 12:50:08 +01:00
|
|
|
}
|
|
|
|
runTestCases(t, testCases)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestResourceChange_primitiveList(t *testing.T) {
|
|
|
|
testCases := map[string]testCase{
|
|
|
|
"in-place update - creation": {
|
2019-01-14 16:34:41 +01:00
|
|
|
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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-14 16:34:41 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
+ list_field = [
|
|
|
|
+ "new-element",
|
|
|
|
]
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
"in-place update - first addition": {
|
2018-12-11 12:50:08 +01:00
|
|
|
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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:50:08 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ list_field = [
|
|
|
|
+ "new-element",
|
|
|
|
]
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
"in-place update - 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("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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:50:08 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ list_field = [
|
|
|
|
"aaaa",
|
|
|
|
+ "bbbb",
|
|
|
|
"cccc",
|
|
|
|
]
|
|
|
|
}
|
2018-12-11 12:55:59 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"force-new update - insertion": {
|
|
|
|
Action: plans.DeleteThenCreate,
|
|
|
|
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"},
|
|
|
|
}),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:55:59 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example must be replaced
|
|
|
|
-/+ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ list_field = [ # forces replacement
|
|
|
|
"aaaa",
|
|
|
|
+ "bbbb",
|
|
|
|
"cccc",
|
|
|
|
]
|
|
|
|
}
|
2018-12-11 12:50:08 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
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"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:50:08 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ list_field = [
|
|
|
|
- "aaaa",
|
|
|
|
"bbbb",
|
|
|
|
- "cccc",
|
|
|
|
]
|
|
|
|
}
|
2019-01-14 16:34:41 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-14 16:34:41 +01:00
|
|
|
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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-14 16:34:41 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ list_field = [
|
|
|
|
- "aaaa",
|
|
|
|
- "bbbb",
|
|
|
|
- "cccc",
|
|
|
|
]
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
"in-place update - null to empty": {
|
|
|
|
Action: plans.Update,
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Before: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
|
|
|
"ami": cty.StringVal("ami-STATIC"),
|
|
|
|
"list_field": cty.NullVal(cty.List(cty.String)),
|
|
|
|
}),
|
|
|
|
After: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"ami": cty.StringVal("ami-STATIC"),
|
|
|
|
"list_field": cty.ListValEmpty(cty.String),
|
|
|
|
}),
|
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
"list_field": {Type: cty.List(cty.String), Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
RequiredReplace: cty.NewPathSet(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-14 16:34:41 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
+ list_field = []
|
|
|
|
}
|
2019-01-21 16:02:15 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-21 16:02:15 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ list_field = [
|
|
|
|
"aaaa",
|
|
|
|
- "bbbb",
|
|
|
|
+ (known after apply),
|
|
|
|
"cccc",
|
|
|
|
]
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
"update - two new unknown elements": {
|
|
|
|
Action: plans.Update,
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Before: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
|
|
|
"ami": cty.StringVal("ami-STATIC"),
|
|
|
|
"list_field": cty.ListVal([]cty.Value{
|
|
|
|
cty.StringVal("aaaa"),
|
|
|
|
cty.StringVal("bbbb"),
|
|
|
|
cty.StringVal("cccc"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
After: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"ami": cty.StringVal("ami-STATIC"),
|
|
|
|
"list_field": cty.ListVal([]cty.Value{
|
|
|
|
cty.StringVal("aaaa"),
|
|
|
|
cty.UnknownVal(cty.String),
|
|
|
|
cty.UnknownVal(cty.String),
|
|
|
|
cty.StringVal("cccc"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
"list_field": {Type: cty.List(cty.String), Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
RequiredReplace: cty.NewPathSet(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-21 16:02:15 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ list_field = [
|
|
|
|
"aaaa",
|
|
|
|
- "bbbb",
|
|
|
|
+ (known after apply),
|
|
|
|
+ (known after apply),
|
|
|
|
"cccc",
|
|
|
|
]
|
|
|
|
}
|
2018-12-11 12:50:08 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
runTestCases(t, testCases)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestResourceChange_primitiveSet(t *testing.T) {
|
|
|
|
testCases := map[string]testCase{
|
|
|
|
"in-place update - creation": {
|
2019-01-14 16:34:41 +01:00
|
|
|
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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-14 16:34:41 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
+ set_field = [
|
|
|
|
+ "new-element",
|
|
|
|
]
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
"in-place update - first insertion": {
|
2018-12-11 12:50:08 +01:00
|
|
|
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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:50:08 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ set_field = [
|
|
|
|
+ "new-element",
|
|
|
|
]
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
"in-place update - 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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:50:08 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ set_field = [
|
|
|
|
"aaaa",
|
|
|
|
+ "bbbb",
|
|
|
|
"cccc",
|
|
|
|
]
|
|
|
|
}
|
2018-12-11 12:55:59 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"force-new update - insertion": {
|
|
|
|
Action: plans.DeleteThenCreate,
|
|
|
|
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"},
|
|
|
|
}),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:55:59 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example must be replaced
|
|
|
|
-/+ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ set_field = [ # forces replacement
|
|
|
|
"aaaa",
|
|
|
|
+ "bbbb",
|
|
|
|
"cccc",
|
|
|
|
]
|
|
|
|
}
|
2018-12-11 12:50:08 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:50:08 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ set_field = [
|
|
|
|
- "aaaa",
|
|
|
|
"bbbb",
|
|
|
|
- "cccc",
|
|
|
|
]
|
|
|
|
}
|
2019-01-14 16:34:41 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-14 16:34:41 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be created
|
|
|
|
+ resource "test_instance" "example" {
|
|
|
|
+ ami = "ami-STATIC"
|
|
|
|
+ id = (known after apply)
|
|
|
|
+ set_field = []
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
"in-place update - full to empty set": {
|
|
|
|
Action: plans.Update,
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Before: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
|
|
|
"ami": cty.StringVal("ami-STATIC"),
|
|
|
|
"set_field": cty.SetVal([]cty.Value{
|
|
|
|
cty.StringVal("aaaa"),
|
|
|
|
cty.StringVal("bbbb"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
After: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"ami": cty.StringVal("ami-STATIC"),
|
|
|
|
"set_field": cty.SetValEmpty(cty.String),
|
|
|
|
}),
|
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
"set_field": {Type: cty.Set(cty.String), Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
RequiredReplace: cty.NewPathSet(),
|
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ set_field = [
|
|
|
|
- "aaaa",
|
|
|
|
- "bbbb",
|
|
|
|
]
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
"in-place update - null to empty set": {
|
|
|
|
Action: plans.Update,
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Before: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
|
|
|
"ami": cty.StringVal("ami-STATIC"),
|
|
|
|
"set_field": cty.NullVal(cty.Set(cty.String)),
|
|
|
|
}),
|
|
|
|
After: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"ami": cty.StringVal("ami-STATIC"),
|
|
|
|
"set_field": cty.SetValEmpty(cty.String),
|
|
|
|
}),
|
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
"set_field": {Type: cty.Set(cty.String), Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
RequiredReplace: cty.NewPathSet(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-14 16:34:41 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
+ set_field = []
|
|
|
|
}
|
2019-01-18 14:04:53 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-18 14:04:53 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ set_field = [
|
|
|
|
- "aaaa",
|
|
|
|
- "bbbb",
|
|
|
|
] -> (known after apply)
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
"in-place update to unknown element": {
|
|
|
|
Action: plans.Update,
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Before: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
|
|
|
"ami": cty.StringVal("ami-STATIC"),
|
|
|
|
"set_field": cty.SetVal([]cty.Value{
|
|
|
|
cty.StringVal("aaaa"),
|
|
|
|
cty.StringVal("bbbb"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
After: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"ami": cty.StringVal("ami-STATIC"),
|
|
|
|
"set_field": cty.SetVal([]cty.Value{
|
|
|
|
cty.StringVal("aaaa"),
|
|
|
|
cty.UnknownVal(cty.String),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
"set_field": {Type: cty.Set(cty.String), Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
RequiredReplace: cty.NewPathSet(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-18 14:04:53 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ set_field = [
|
|
|
|
"aaaa",
|
|
|
|
- "bbbb",
|
|
|
|
~ (known after apply),
|
|
|
|
]
|
|
|
|
}
|
2018-12-11 12:50:08 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
runTestCases(t, testCases)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestResourceChange_map(t *testing.T) {
|
|
|
|
testCases := map[string]testCase{
|
|
|
|
"in-place update - creation": {
|
2019-01-14 16:15:01 +01:00
|
|
|
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"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-14 16:15:01 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
+ map_field = {
|
|
|
|
+ "new-key" = "new-element"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
"in-place update - first insertion": {
|
2018-12-11 12:50:08 +01:00
|
|
|
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"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:50:08 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ map_field = {
|
|
|
|
+ "new-key" = "new-element"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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"),
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:50:08 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ map_field = {
|
|
|
|
"a" = "aaaa"
|
|
|
|
+ "b" = "bbbb"
|
|
|
|
"c" = "cccc"
|
|
|
|
}
|
|
|
|
}
|
2018-12-11 12:55:59 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"force-new update - insertion": {
|
|
|
|
Action: plans.DeleteThenCreate,
|
|
|
|
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"},
|
|
|
|
}),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:55:59 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example must be replaced
|
|
|
|
-/+ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ map_field = { # forces replacement
|
|
|
|
"a" = "aaaa"
|
|
|
|
+ "b" = "bbbb"
|
|
|
|
"c" = "cccc"
|
|
|
|
}
|
|
|
|
}
|
2018-12-11 12:50:08 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:50:08 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ map_field = {
|
|
|
|
- "a" = "aaaa" -> null
|
|
|
|
"b" = "bbbb"
|
|
|
|
- "c" = "cccc" -> null
|
|
|
|
}
|
|
|
|
}
|
2019-01-14 16:15:01 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-14 16:15:01 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be created
|
|
|
|
+ resource "test_instance" "example" {
|
|
|
|
+ ami = "ami-STATIC"
|
|
|
|
+ id = (known after apply)
|
|
|
|
+ map_field = {}
|
|
|
|
}
|
2019-01-21 16:02:51 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-21 16:02:51 +01:00
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
ami = "ami-STATIC"
|
|
|
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
|
|
~ map_field = {
|
|
|
|
"a" = "aaaa"
|
|
|
|
~ "b" = "bbbb" -> (known after apply)
|
|
|
|
"c" = "cccc"
|
|
|
|
}
|
|
|
|
}
|
2018-12-11 12:50:08 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
runTestCases(t, testCases)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestResourceChange_nestedList(t *testing.T) {
|
|
|
|
testCases := map[string]testCase{
|
2018-12-11 13:56:11 +01:00
|
|
|
"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"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
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"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
RequiredReplace: cty.NewPathSet(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 13:56:11 +01:00
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
|
|
"root_block_device": {
|
|
|
|
Block: configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"volume_type": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2018-12-12 15:28:12 +01:00
|
|
|
Nesting: configschema.NestingList,
|
2018-12-11 13:56:11 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
|
|
id = "i-02ae66f368e8518a9"
|
|
|
|
|
|
|
|
root_block_device {
|
|
|
|
volume_type = "gp2"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
2018-12-11 12:50:08 +01:00
|
|
|
"in-place update - creation": {
|
|
|
|
Action: plans.Update,
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Before: cty.ObjectVal(map[string]cty.Value{
|
2019-03-09 21:14:08 +01:00
|
|
|
"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,
|
|
|
|
})),
|
2018-12-11 12:50:08 +01:00
|
|
|
}),
|
|
|
|
After: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
|
|
|
"ami": cty.StringVal("ami-AFTER"),
|
|
|
|
"root_block_device": cty.ListVal([]cty.Value{
|
2019-01-14 17:07:04 +01:00
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"volume_type": cty.NullVal(cty.String),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
RequiredReplace: cty.NewPathSet(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2019-01-14 17:07:04 +01:00
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
|
|
"root_block_device": {
|
|
|
|
Block: configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"volume_type": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Nesting: configschema.NestingList,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
|
|
id = "i-02ae66f368e8518a9"
|
|
|
|
|
|
|
|
+ root_block_device {}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
"in-place update - first insertion": {
|
|
|
|
Action: plans.Update,
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Before: cty.ObjectVal(map[string]cty.Value{
|
2019-03-09 21:14:08 +01:00
|
|
|
"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,
|
|
|
|
})),
|
2019-01-14 17:07:04 +01:00
|
|
|
}),
|
|
|
|
After: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
|
|
|
"ami": cty.StringVal("ami-AFTER"),
|
|
|
|
"root_block_device": cty.ListVal([]cty.Value{
|
2018-12-11 12:50:08 +01:00
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"volume_type": cty.StringVal("gp2"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
RequiredReplace: cty.NewPathSet(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:50:08 +01:00
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
|
|
"root_block_device": {
|
|
|
|
Block: configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"volume_type": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2018-12-12 15:28:12 +01:00
|
|
|
Nesting: configschema.NestingList,
|
2018-12-11 12:50:08 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
|
|
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"),
|
|
|
|
"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"),
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:50:08 +01:00
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
|
|
"root_block_device": {
|
|
|
|
Block: configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"volume_type": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
"new_field": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2018-12-12 15:28:12 +01:00
|
|
|
Nesting: configschema.NestingList,
|
2018-12-11 12:50:08 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
|
|
id = "i-02ae66f368e8518a9"
|
|
|
|
|
|
|
|
~ root_block_device {
|
|
|
|
+ new_field = "new_value"
|
|
|
|
volume_type = "gp2"
|
|
|
|
}
|
|
|
|
}
|
2018-12-11 12:55:59 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"force-new update (inside block)": {
|
|
|
|
Action: plans.DeleteThenCreate,
|
|
|
|
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"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
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("different"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
RequiredReplace: cty.NewPathSet(cty.Path{
|
|
|
|
cty.GetAttrStep{Name: "root_block_device"},
|
|
|
|
cty.IndexStep{Key: cty.NumberIntVal(0)},
|
|
|
|
cty.GetAttrStep{Name: "volume_type"},
|
|
|
|
}),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:55:59 +01:00
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
|
|
"root_block_device": {
|
|
|
|
Block: configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"volume_type": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2018-12-12 15:28:12 +01:00
|
|
|
Nesting: configschema.NestingList,
|
2018-12-11 12:55:59 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ExpectedOutput: ` # test_instance.example must be replaced
|
|
|
|
-/+ resource "test_instance" "example" {
|
|
|
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
|
|
id = "i-02ae66f368e8518a9"
|
|
|
|
|
|
|
|
~ root_block_device {
|
|
|
|
~ volume_type = "gp2" -> "different" # forces replacement
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
"force-new update (whole block)": {
|
|
|
|
Action: plans.DeleteThenCreate,
|
|
|
|
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"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
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("different"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
RequiredReplace: cty.NewPathSet(cty.Path{
|
|
|
|
cty.GetAttrStep{Name: "root_block_device"},
|
|
|
|
}),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:55:59 +01:00
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
|
|
"root_block_device": {
|
|
|
|
Block: configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"volume_type": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2018-12-12 15:28:12 +01:00
|
|
|
Nesting: configschema.NestingList,
|
2018-12-11 12:55:59 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ExpectedOutput: ` # test_instance.example must be replaced
|
|
|
|
-/+ resource "test_instance" "example" {
|
|
|
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
|
|
id = "i-02ae66f368e8518a9"
|
|
|
|
|
|
|
|
~ root_block_device { # forces replacement
|
|
|
|
~ volume_type = "gp2" -> "different"
|
|
|
|
}
|
|
|
|
}
|
2018-12-11 12:50:08 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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.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{
|
2019-03-09 21:14:08 +01:00
|
|
|
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
|
|
|
"ami": cty.StringVal("ami-AFTER"),
|
|
|
|
"root_block_device": cty.ListValEmpty(cty.Object(map[string]cty.Type{
|
|
|
|
"volume_type": cty.String,
|
|
|
|
"new_field": cty.String,
|
|
|
|
})),
|
2018-12-11 12:50:08 +01:00
|
|
|
}),
|
|
|
|
RequiredReplace: cty.NewPathSet(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:50:08 +01:00
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
|
|
"root_block_device": {
|
|
|
|
Block: configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"volume_type": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
"new_field": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2018-12-12 15:28:12 +01:00
|
|
|
Nesting: configschema.NestingList,
|
2018-12-11 12:50:08 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
|
|
id = "i-02ae66f368e8518a9"
|
|
|
|
|
|
|
|
- root_block_device {
|
|
|
|
- new_field = "new_value" -> null
|
|
|
|
- volume_type = "gp2" -> null
|
|
|
|
}
|
|
|
|
}
|
2019-03-09 21:14:08 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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(),
|
|
|
|
Tainted: false,
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2018-12-11 12:50:08 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
runTestCases(t, testCases)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestResourceChange_nestedSet(t *testing.T) {
|
|
|
|
testCases := map[string]testCase{
|
|
|
|
"in-place update - creation": {
|
|
|
|
Action: plans.Update,
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Before: cty.ObjectVal(map[string]cty.Value{
|
2019-03-09 21:14:08 +01:00
|
|
|
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
|
|
|
"ami": cty.StringVal("ami-BEFORE"),
|
|
|
|
"root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{
|
|
|
|
"volume_type": cty.String,
|
|
|
|
})),
|
2018-12-11 12:50:08 +01:00
|
|
|
}),
|
|
|
|
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("gp2"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
RequiredReplace: cty.NewPathSet(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:50:08 +01:00
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
|
|
"root_block_device": {
|
|
|
|
Block: configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"volume_type": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2018-12-12 15:28:12 +01:00
|
|
|
Nesting: configschema.NestingSet,
|
2018-12-11 12:50:08 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
|
|
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"),
|
|
|
|
"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"),
|
|
|
|
"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(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:50:08 +01:00
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
|
|
"root_block_device": {
|
|
|
|
Block: configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"volume_type": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
"new_field": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2018-12-12 15:28:12 +01:00
|
|
|
Nesting: configschema.NestingSet,
|
2018-12-11 12:50:08 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
|
|
id = "i-02ae66f368e8518a9"
|
|
|
|
|
2018-12-12 15:28:12 +01:00
|
|
|
+ root_block_device {
|
|
|
|
+ new_field = "new_value"
|
|
|
|
+ volume_type = "gp2"
|
2018-12-11 12:55:59 +01:00
|
|
|
}
|
2019-05-01 00:29:47 +02:00
|
|
|
- root_block_device {
|
|
|
|
- volume_type = "gp2" -> null
|
|
|
|
}
|
2018-12-11 12:55:59 +01:00
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
"force-new update (whole block)": {
|
|
|
|
Action: plans.DeleteThenCreate,
|
|
|
|
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"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
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"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
RequiredReplace: cty.NewPathSet(cty.Path{
|
|
|
|
cty.GetAttrStep{Name: "root_block_device"},
|
|
|
|
}),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:55:59 +01:00
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
|
|
"root_block_device": {
|
|
|
|
Block: configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"volume_type": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2018-12-12 15:28:12 +01:00
|
|
|
Nesting: configschema.NestingSet,
|
2018-12-11 12:55:59 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ExpectedOutput: ` # test_instance.example must be replaced
|
|
|
|
-/+ resource "test_instance" "example" {
|
|
|
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
|
|
id = "i-02ae66f368e8518a9"
|
|
|
|
|
2018-12-12 15:28:12 +01:00
|
|
|
+ root_block_device { # forces replacement
|
|
|
|
+ volume_type = "different"
|
2018-12-11 12:55:59 +01:00
|
|
|
}
|
2019-05-01 00:29:47 +02:00
|
|
|
- root_block_device { # forces replacement
|
|
|
|
- volume_type = "gp2" -> null
|
|
|
|
}
|
2018-12-11 12:55:59 +01:00
|
|
|
}
|
2018-12-11 12:50:08 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
After: cty.ObjectVal(map[string]cty.Value{
|
2019-03-09 21:14:08 +01:00
|
|
|
"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,
|
|
|
|
})),
|
2018-12-11 12:50:08 +01:00
|
|
|
}),
|
|
|
|
RequiredReplace: cty.NewPathSet(),
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted: false,
|
2018-12-11 12:50:08 +01:00
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
|
|
"root_block_device": {
|
|
|
|
Block: configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"volume_type": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
"new_field": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2018-12-12 15:28:12 +01:00
|
|
|
Nesting: configschema.NestingSet,
|
2018-12-11 12:50:08 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
|
|
id = "i-02ae66f368e8518a9"
|
|
|
|
|
|
|
|
- root_block_device {
|
|
|
|
- new_field = "new_value" -> null
|
|
|
|
- volume_type = "gp2" -> null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
runTestCases(t, testCases)
|
|
|
|
}
|
|
|
|
|
2019-03-09 21:30:23 +01:00
|
|
|
func TestResourceChange_nestedMap(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-BEFORE"),
|
|
|
|
"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"),
|
|
|
|
"root_block_device": cty.MapVal(map[string]cty.Value{
|
|
|
|
"a": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"volume_type": cty.StringVal("gp2"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
RequiredReplace: cty.NewPathSet(),
|
|
|
|
Tainted: false,
|
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
|
|
"root_block_device": {
|
|
|
|
Block: configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"volume_type": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Nesting: configschema.NestingMap,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
|
|
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"),
|
|
|
|
"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"),
|
|
|
|
"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(),
|
|
|
|
Tainted: false,
|
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
|
|
"root_block_device": {
|
|
|
|
Block: configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"volume_type": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
"new_field": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Nesting: configschema.NestingMap,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
|
|
id = "i-02ae66f368e8518a9"
|
|
|
|
|
|
|
|
~ root_block_device "a" {
|
|
|
|
+ new_field = "new_value"
|
|
|
|
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"),
|
|
|
|
"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"),
|
|
|
|
"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(),
|
|
|
|
Tainted: false,
|
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
|
|
"root_block_device": {
|
|
|
|
Block: configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"volume_type": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
"new_field": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Nesting: configschema.NestingMap,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
|
|
id = "i-02ae66f368e8518a9"
|
|
|
|
|
|
|
|
root_block_device "a" {
|
|
|
|
volume_type = "gp2"
|
|
|
|
}
|
|
|
|
+ root_block_device "b" {
|
|
|
|
+ new_field = "new_value"
|
|
|
|
+ volume_type = "gp2"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
"force-new update (whole block)": {
|
|
|
|
Action: plans.DeleteThenCreate,
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Before: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
|
|
|
"ami": cty.StringVal("ami-BEFORE"),
|
|
|
|
"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"),
|
|
|
|
"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")},
|
|
|
|
}),
|
|
|
|
Tainted: false,
|
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
|
|
"root_block_device": {
|
|
|
|
Block: configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"volume_type": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Nesting: configschema.NestingMap,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ExpectedOutput: ` # test_instance.example must be replaced
|
|
|
|
-/+ resource "test_instance" "example" {
|
|
|
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
|
|
id = "i-02ae66f368e8518a9"
|
|
|
|
|
|
|
|
~ root_block_device "a" { # forces replacement
|
|
|
|
~ volume_type = "gp2" -> "different"
|
|
|
|
}
|
|
|
|
root_block_device "b" {
|
|
|
|
volume_type = "standard"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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.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"),
|
|
|
|
"root_block_device": cty.MapValEmpty(cty.Object(map[string]cty.Type{
|
|
|
|
"volume_type": cty.String,
|
|
|
|
"new_field": cty.String,
|
|
|
|
})),
|
|
|
|
}),
|
|
|
|
RequiredReplace: cty.NewPathSet(),
|
|
|
|
Tainted: false,
|
|
|
|
Schema: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
|
|
"root_block_device": {
|
|
|
|
Block: configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"volume_type": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
"new_field": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Nesting: configschema.NestingMap,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
|
|
|
~ resource "test_instance" "example" {
|
|
|
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
|
|
id = "i-02ae66f368e8518a9"
|
|
|
|
|
|
|
|
- root_block_device "a" {
|
|
|
|
- new_field = "new_value" -> null
|
|
|
|
- volume_type = "gp2" -> null
|
|
|
|
}
|
|
|
|
}
|
2019-04-27 17:23:37 +02:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"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(),
|
|
|
|
Tainted: false,
|
|
|
|
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"
|
|
|
|
}
|
|
|
|
}
|
2019-03-09 21:30:23 +01:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
runTestCases(t, testCases)
|
|
|
|
}
|
|
|
|
|
2018-12-11 12:49:17 +01:00
|
|
|
type testCase struct {
|
|
|
|
Action plans.Action
|
|
|
|
Mode addrs.ResourceMode
|
|
|
|
Before cty.Value
|
|
|
|
After cty.Value
|
|
|
|
Schema *configschema.Block
|
|
|
|
RequiredReplace cty.PathSet
|
2019-03-06 01:18:55 +01:00
|
|
|
Tainted bool
|
2018-12-11 12:49:17 +01:00
|
|
|
ExpectedOutput string
|
|
|
|
}
|
|
|
|
|
|
|
|
func runTestCases(t *testing.T, testCases map[string]testCase) {
|
2018-12-10 18:42:45 +01:00
|
|
|
color := &colorstring.Colorize{Colors: colorstring.DefaultColors, Disable: true}
|
|
|
|
|
|
|
|
for name, tc := range testCases {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
2019-03-09 21:14:08 +01:00
|
|
|
ty := tc.Schema.ImpliedType()
|
|
|
|
|
2018-12-10 18:42:45 +01:00
|
|
|
beforeVal := tc.Before
|
2019-03-09 21:14:08 +01:00
|
|
|
switch { // Some fixups to make the test cases a little easier to write
|
|
|
|
case beforeVal.IsNull():
|
|
|
|
beforeVal = cty.NullVal(ty) // allow mistyped nulls
|
|
|
|
case !beforeVal.IsKnown():
|
|
|
|
beforeVal = cty.UnknownVal(ty) // allow mistyped unknowns
|
|
|
|
}
|
|
|
|
before, err := plans.NewDynamicValue(beforeVal, ty)
|
2018-12-10 18:42:45 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
afterVal := tc.After
|
2019-03-09 21:14:08 +01:00
|
|
|
switch { // Some fixups to make the test cases a little easier to write
|
|
|
|
case afterVal.IsNull():
|
|
|
|
afterVal = cty.NullVal(ty) // allow mistyped nulls
|
|
|
|
case !afterVal.IsKnown():
|
|
|
|
afterVal = cty.UnknownVal(ty) // allow mistyped unknowns
|
|
|
|
}
|
|
|
|
after, err := plans.NewDynamicValue(afterVal, ty)
|
2018-12-10 18:42:45 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
change := &plans.ResourceInstanceChangeSrc{
|
|
|
|
Addr: addrs.Resource{
|
|
|
|
Mode: tc.Mode,
|
|
|
|
Type: "test_instance",
|
|
|
|
Name: "example",
|
|
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
|
|
|
ProviderAddr: addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
|
|
|
ChangeSrc: plans.ChangeSrc{
|
|
|
|
Action: tc.Action,
|
|
|
|
Before: before,
|
|
|
|
After: after,
|
|
|
|
},
|
|
|
|
RequiredReplace: tc.RequiredReplace,
|
|
|
|
}
|
|
|
|
|
2019-03-06 01:18:55 +01:00
|
|
|
output := ResourceChange(change, tc.Tainted, tc.Schema, color)
|
2018-12-10 18:42:45 +01:00
|
|
|
if output != tc.ExpectedOutput {
|
2019-03-09 21:30:23 +01:00
|
|
|
t.Fatalf("Unexpected diff.\ngot:\n%s\nwant:\n%s\n", output, tc.ExpectedOutput)
|
2018-12-10 18:42:45 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|