fix: don't reveal nested attributes with sensitive schema

This commit is contained in:
Brandon Croft 2022-10-12 12:38:27 -06:00
parent 730756eca2
commit 076fccd8e7
No known key found for this signature in database
GPG Key ID: B01E32423322EB9D
2 changed files with 903 additions and 16 deletions

View File

@ -398,7 +398,7 @@ func (p *blockBodyDiffPrinter) writeAttrDiff(name string, attrS *configschema.At
}
if attrS.NestedType != nil {
p.writeNestedAttrDiff(name, attrS.NestedType, old, new, nameLen, indent, path, action, showJustNew)
p.writeNestedAttrDiff(name, attrS, old, new, nameLen, indent, path, action, showJustNew)
return false
}
@ -416,7 +416,7 @@ func (p *blockBodyDiffPrinter) writeAttrDiff(name string, attrS *configschema.At
p.buf.WriteString(" = ")
if attrS.Sensitive {
p.buf.WriteString("(sensitive value)")
p.buf.WriteString("(sensitive)")
if p.pathForcesNewResource(path) {
p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
}
@ -441,9 +441,11 @@ func (p *blockBodyDiffPrinter) writeAttrDiff(name string, attrS *configschema.At
// writeNestedAttrDiff is responsible for formatting Attributes with NestedTypes
// in the diff.
func (p *blockBodyDiffPrinter) writeNestedAttrDiff(
name string, objS *configschema.Object, old, new cty.Value,
name string, attrWithNestedS *configschema.Attribute, old, new cty.Value,
nameLen, indent int, path cty.Path, action plans.Action, showJustNew bool) {
objS := attrWithNestedS.NestedType
p.buf.WriteString("\n")
p.writeSensitivityWarning(old, new, indent, action, false)
p.buf.WriteString(strings.Repeat(" ", indent))
@ -454,8 +456,11 @@ func (p *blockBodyDiffPrinter) writeNestedAttrDiff(
p.buf.WriteString(p.color.Color("[reset]"))
p.buf.WriteString(strings.Repeat(" ", nameLen-len(name)))
if old.HasMark(marks.Sensitive) || new.HasMark(marks.Sensitive) {
p.buf.WriteString(" = (sensitive value)")
// Then schema of the attribute itself can be marked sensitive, or the values assigned
sensitive := attrWithNestedS.Sensitive || old.HasMark(marks.Sensitive) || new.HasMark(marks.Sensitive)
if sensitive {
p.buf.WriteString(" = (sensitive)")
if p.pathForcesNewResource(path) {
p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
}
@ -475,6 +480,12 @@ func (p *blockBodyDiffPrinter) writeNestedAttrDiff(
p.buf.WriteString(strings.Repeat(" ", indent+2))
p.buf.WriteString("}")
if !new.IsKnown() {
p.buf.WriteString(" -> (known after apply)")
} else if new.IsNull() {
p.buf.WriteString(p.color.Color("[dark_gray] -> null[reset]"))
}
case configschema.NestingList:
p.buf.WriteString(" = [")
if action != plans.NoOp && (p.pathForcesNewResource(path) || p.pathForcesNewResource(path[:len(path)-1])) {
@ -558,6 +569,8 @@ func (p *blockBodyDiffPrinter) writeNestedAttrDiff(
if !new.IsKnown() {
p.buf.WriteString(" -> (known after apply)")
} else if new.IsNull() {
p.buf.WriteString(p.color.Color("[dark_gray] -> null[reset]"))
}
case configschema.NestingSet:
@ -636,6 +649,8 @@ func (p *blockBodyDiffPrinter) writeNestedAttrDiff(
if !new.IsKnown() {
p.buf.WriteString(" -> (known after apply)")
} else if new.IsNull() {
p.buf.WriteString(p.color.Color("[dark_gray] -> null[reset]"))
}
case configschema.NestingMap:
@ -711,6 +726,8 @@ func (p *blockBodyDiffPrinter) writeNestedAttrDiff(
p.buf.WriteString("}")
if !new.IsKnown() {
p.buf.WriteString(" -> (known after apply)")
} else if new.IsNull() {
p.buf.WriteString(p.color.Color("[dark_gray] -> null[reset]"))
}
}
}

View File

@ -411,11 +411,11 @@ new line
ExpectedOutput: ` # test_instance.example will be created
+ resource "test_instance" "example" {
+ conn_info = {
+ password = (sensitive value)
+ password = (sensitive)
+ user = "not-secret"
}
+ id = (known after apply)
+ password = (sensitive value)
+ password = (sensitive)
}
`,
},
@ -3048,7 +3048,7 @@ func TestResourceChange_nestedSet(t *testing.T) {
ExpectedOutput: ` # test_instance.example will be created
+ resource "test_instance" "example" {
+ ami = "ami-AFTER"
+ disks = (sensitive value)
+ disks = (sensitive)
+ id = "i-02ae66f368e8518a9"
+ root_block_device {
@ -3146,7 +3146,7 @@ func TestResourceChange_nestedSet(t *testing.T) {
~ ami = "ami-BEFORE" -> "ami-AFTER"
# Warning: this attribute value will be marked as sensitive and will not
# display in UI output after applying this change.
~ disks = (sensitive value)
~ disks = (sensitive)
id = "i-02ae66f368e8518a9"
+ root_block_device {
@ -3197,7 +3197,7 @@ func TestResourceChange_nestedSet(t *testing.T) {
~ ami = "ami-BEFORE" -> "ami-AFTER"
# Warning: this attribute value will be marked as sensitive and will not
# display in UI output after applying this change. The value is unchanged.
~ disks = (sensitive value)
~ disks = (sensitive)
id = "i-02ae66f368e8518a9"
}
`,
@ -4572,6 +4572,845 @@ func TestResourceChange_nestedMap(t *testing.T) {
runTestCases(t, testCases)
}
func TestResourceChange_nestedSingle(t *testing.T) {
testCases := map[string]testCase{
"in-place update - equal": {
Action: plans.Update,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-BEFORE"),
"root_block_device": cty.ObjectVal(map[string]cty.Value{
"volume_type": cty.StringVal("gp2"),
}),
"disk": cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("50GB"),
}),
}),
After: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-AFTER"),
"root_block_device": cty.ObjectVal(map[string]cty.Value{
"volume_type": cty.StringVal("gp2"),
}),
"disk": cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("50GB"),
}),
}),
RequiredReplace: cty.NewPathSet(),
Schema: testSchema(configschema.NestingSingle),
ExpectedOutput: ` # test_instance.example will be updated in-place
~ resource "test_instance" "example" {
~ ami = "ami-BEFORE" -> "ami-AFTER"
id = "i-02ae66f368e8518a9"
# (1 unchanged attribute hidden)
# (1 unchanged block hidden)
}
`,
},
"in-place update - creation": {
Action: plans.Update,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-BEFORE"),
"root_block_device": cty.NullVal(cty.Object(map[string]cty.Type{
"volume_type": cty.String,
})),
"disk": cty.NullVal(cty.Object(map[string]cty.Type{
"mount_point": cty.String,
"size": cty.String,
})),
}),
After: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-AFTER"),
"disk": cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("50GB"),
}),
"root_block_device": cty.ObjectVal(map[string]cty.Value{
"volume_type": cty.NullVal(cty.String),
}),
}),
RequiredReplace: cty.NewPathSet(),
Schema: testSchema(configschema.NestingSingle),
ExpectedOutput: ` # test_instance.example will be updated in-place
~ resource "test_instance" "example" {
~ ami = "ami-BEFORE" -> "ami-AFTER"
+ disk = {
+ mount_point = "/var/diska"
+ size = "50GB"
}
id = "i-02ae66f368e8518a9"
+ root_block_device {}
}
`,
},
"force-new update (inside blocks)": {
Action: plans.DeleteThenCreate,
ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-BEFORE"),
"disk": cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("50GB"),
}),
"root_block_device": 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"),
"disk": cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diskb"),
"size": cty.StringVal("50GB"),
}),
"root_block_device": cty.ObjectVal(map[string]cty.Value{
"volume_type": cty.StringVal("different"),
}),
}),
RequiredReplace: cty.NewPathSet(
cty.Path{
cty.GetAttrStep{Name: "root_block_device"},
cty.GetAttrStep{Name: "volume_type"},
},
cty.Path{
cty.GetAttrStep{Name: "disk"},
cty.GetAttrStep{Name: "mount_point"},
},
),
Schema: testSchema(configschema.NestingSingle),
ExpectedOutput: ` # test_instance.example must be replaced
-/+ resource "test_instance" "example" {
~ ami = "ami-BEFORE" -> "ami-AFTER"
~ disk = {
~ mount_point = "/var/diska" -> "/var/diskb" # forces replacement
# (1 unchanged attribute hidden)
}
id = "i-02ae66f368e8518a9"
~ root_block_device {
~ volume_type = "gp2" -> "different" # forces replacement
}
}
`,
},
"force-new update (whole block)": {
Action: plans.DeleteThenCreate,
ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-BEFORE"),
"disk": cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("50GB"),
}),
"root_block_device": 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"),
"disk": cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diskb"),
"size": cty.StringVal("50GB"),
}),
"root_block_device": cty.ObjectVal(map[string]cty.Value{
"volume_type": cty.StringVal("different"),
}),
}),
RequiredReplace: cty.NewPathSet(
cty.Path{cty.GetAttrStep{Name: "root_block_device"}},
cty.Path{cty.GetAttrStep{Name: "disk"}},
),
Schema: testSchema(configschema.NestingSingle),
ExpectedOutput: ` # test_instance.example must be replaced
-/+ resource "test_instance" "example" {
~ ami = "ami-BEFORE" -> "ami-AFTER"
~ disk = { # forces replacement
~ mount_point = "/var/diska" -> "/var/diskb"
# (1 unchanged attribute hidden)
}
id = "i-02ae66f368e8518a9"
~ root_block_device { # forces replacement
~ volume_type = "gp2" -> "different"
}
}
`,
},
"in-place update - deletion": {
Action: plans.Update,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-BEFORE"),
"disk": cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("50GB"),
}),
"root_block_device": 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.NullVal(cty.Object(map[string]cty.Type{
"volume_type": cty.String,
})),
"disk": cty.NullVal(cty.Object(map[string]cty.Type{
"mount_point": cty.String,
"size": cty.String,
})),
}),
RequiredReplace: cty.NewPathSet(),
Schema: testSchema(configschema.NestingSingle),
ExpectedOutput: ` # test_instance.example will be updated in-place
~ resource "test_instance" "example" {
~ ami = "ami-BEFORE" -> "ami-AFTER"
- disk = {
- mount_point = "/var/diska" -> null
- size = "50GB" -> null
} -> null
id = "i-02ae66f368e8518a9"
- root_block_device {
- volume_type = "gp2" -> null
}
}
`,
},
"with dynamically-typed attribute": {
Action: plans.Update,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"block": cty.NullVal(cty.Object(map[string]cty.Type{
"attr": cty.String,
})),
}),
After: cty.ObjectVal(map[string]cty.Value{
"block": cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("foo"),
}),
}),
RequiredReplace: cty.NewPathSet(),
Schema: &configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"block": {
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"attr": {Type: cty.DynamicPseudoType, Optional: true},
},
},
Nesting: configschema.NestingSingle,
},
},
},
ExpectedOutput: ` # test_instance.example will be updated in-place
~ resource "test_instance" "example" {
+ block {
+ attr = "foo"
}
}
`,
},
"in-place update - unknown": {
Action: plans.Update,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-BEFORE"),
"disk": cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("50GB"),
}),
"root_block_device": 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"),
"disk": cty.UnknownVal(cty.Object(map[string]cty.Type{
"mount_point": cty.String,
"size": cty.String,
})),
"root_block_device": cty.ObjectVal(map[string]cty.Value{
"volume_type": cty.StringVal("gp2"),
"new_field": cty.StringVal("new_value"),
}),
}),
RequiredReplace: cty.NewPathSet(),
Schema: testSchemaPlus(configschema.NestingSingle),
ExpectedOutput: ` # test_instance.example will be updated in-place
~ resource "test_instance" "example" {
~ ami = "ami-BEFORE" -> "ami-AFTER"
~ disk = {
~ mount_point = "/var/diska" -> (known after apply)
~ size = "50GB" -> (known after apply)
} -> (known after apply)
id = "i-02ae66f368e8518a9"
# (1 unchanged block hidden)
}
`,
},
"in-place update - modification": {
Action: plans.Update,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-BEFORE"),
"disk": cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("50GB"),
}),
"root_block_device": 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"),
"disk": cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("25GB"),
}),
"root_block_device": cty.ObjectVal(map[string]cty.Value{
"volume_type": cty.StringVal("gp2"),
"new_field": cty.StringVal("new_value"),
}),
}),
RequiredReplace: cty.NewPathSet(),
Schema: testSchemaPlus(configschema.NestingSingle),
ExpectedOutput: ` # test_instance.example will be updated in-place
~ resource "test_instance" "example" {
~ ami = "ami-BEFORE" -> "ami-AFTER"
~ disk = {
~ size = "50GB" -> "25GB"
# (1 unchanged attribute hidden)
}
id = "i-02ae66f368e8518a9"
# (1 unchanged block hidden)
}
`,
},
}
runTestCases(t, testCases)
}
func TestResourceChange_nestedMapSensitiveSchema(t *testing.T) {
testCases := map[string]testCase{
"creation from null": {
Action: plans.Update,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.NullVal(cty.String),
"ami": cty.NullVal(cty.String),
"disks": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
"mount_point": cty.String,
"size": cty.String,
}))),
}),
After: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-AFTER"),
"disks": cty.MapVal(map[string]cty.Value{
"disk_a": cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.NullVal(cty.String),
}),
}),
}),
RequiredReplace: cty.NewPathSet(),
Schema: testSchemaSensitive(configschema.NestingMap),
ExpectedOutput: ` # test_instance.example will be updated in-place
~ resource "test_instance" "example" {
+ ami = "ami-AFTER"
+ disks = (sensitive value)
+ id = "i-02ae66f368e8518a9"
}
`,
},
"in-place update": {
Action: plans.Update,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-BEFORE"),
"disks": cty.MapValEmpty(cty.Object(map[string]cty.Type{
"mount_point": cty.String,
"size": cty.String,
})),
}),
After: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-AFTER"),
"disks": cty.MapVal(map[string]cty.Value{
"disk_a": cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.NullVal(cty.String),
}),
}),
}),
RequiredReplace: cty.NewPathSet(),
Schema: testSchemaSensitive(configschema.NestingMap),
ExpectedOutput: ` # test_instance.example will be updated in-place
~ resource "test_instance" "example" {
~ ami = "ami-BEFORE" -> "ami-AFTER"
~ disks = (sensitive value)
id = "i-02ae66f368e8518a9"
}
`,
},
"force-new update (whole block)": {
Action: plans.DeleteThenCreate,
ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-BEFORE"),
"disks": cty.MapVal(map[string]cty.Value{
"disk_a": cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("50GB"),
}),
}),
}),
After: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-AFTER"),
"disks": cty.MapVal(map[string]cty.Value{
"disk_a": cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("100GB"),
}),
}),
}),
RequiredReplace: cty.NewPathSet(
cty.Path{cty.GetAttrStep{Name: "disks"}},
),
Schema: testSchemaSensitive(configschema.NestingMap),
ExpectedOutput: ` # test_instance.example must be replaced
-/+ resource "test_instance" "example" {
~ ami = "ami-BEFORE" -> "ami-AFTER"
~ disks = (sensitive value) # forces replacement
id = "i-02ae66f368e8518a9"
}
`,
},
"in-place update - deletion": {
Action: plans.Update,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-BEFORE"),
"disks": cty.MapVal(map[string]cty.Value{
"disk_a": cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("50GB"),
}),
}),
}),
After: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-AFTER"),
"disks": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
"mount_point": cty.String,
"size": cty.String,
}))),
}),
RequiredReplace: cty.NewPathSet(),
Schema: testSchemaSensitive(configschema.NestingMap),
ExpectedOutput: ` # test_instance.example will be updated in-place
~ resource "test_instance" "example" {
~ ami = "ami-BEFORE" -> "ami-AFTER"
- disks = (sensitive value)
id = "i-02ae66f368e8518a9"
}
`,
},
"in-place update - unknown": {
Action: plans.Update,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-BEFORE"),
"disks": cty.MapVal(map[string]cty.Value{
"disk_a": cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("50GB"),
}),
}),
}),
After: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-AFTER"),
"disks": cty.UnknownVal(cty.Map(cty.Object(map[string]cty.Type{
"mount_point": cty.String,
"size": cty.String,
}))),
}),
RequiredReplace: cty.NewPathSet(),
Schema: testSchemaSensitive(configschema.NestingMap),
ExpectedOutput: ` # test_instance.example will be updated in-place
~ resource "test_instance" "example" {
~ ami = "ami-BEFORE" -> "ami-AFTER"
~ disks = (sensitive value)
id = "i-02ae66f368e8518a9"
}
`,
},
}
runTestCases(t, testCases)
}
func TestResourceChange_nestedListSensitiveSchema(t *testing.T) {
testCases := map[string]testCase{
"creation from null": {
Action: plans.Update,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.NullVal(cty.String),
"ami": cty.NullVal(cty.String),
"disks": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
"mount_point": cty.String,
"size": cty.String,
}))),
}),
After: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-AFTER"),
"disks": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.NullVal(cty.String),
}),
}),
}),
RequiredReplace: cty.NewPathSet(),
Schema: testSchemaSensitive(configschema.NestingList),
ExpectedOutput: ` # test_instance.example will be updated in-place
~ resource "test_instance" "example" {
+ ami = "ami-AFTER"
+ disks = (sensitive value)
+ id = "i-02ae66f368e8518a9"
}
`,
},
"in-place update": {
Action: plans.Update,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-BEFORE"),
"disks": cty.ListValEmpty(cty.Object(map[string]cty.Type{
"mount_point": cty.String,
"size": cty.String,
})),
}),
After: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-AFTER"),
"disks": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.NullVal(cty.String),
}),
}),
}),
RequiredReplace: cty.NewPathSet(),
Schema: testSchemaSensitive(configschema.NestingList),
ExpectedOutput: ` # test_instance.example will be updated in-place
~ resource "test_instance" "example" {
~ ami = "ami-BEFORE" -> "ami-AFTER"
~ disks = (sensitive value)
id = "i-02ae66f368e8518a9"
}
`,
},
"force-new update (whole block)": {
Action: plans.DeleteThenCreate,
ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-BEFORE"),
"disks": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("50GB"),
}),
}),
}),
After: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-AFTER"),
"disks": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("100GB"),
}),
}),
}),
RequiredReplace: cty.NewPathSet(
cty.Path{cty.GetAttrStep{Name: "disks"}},
),
Schema: testSchemaSensitive(configschema.NestingList),
ExpectedOutput: ` # test_instance.example must be replaced
-/+ resource "test_instance" "example" {
~ ami = "ami-BEFORE" -> "ami-AFTER"
~ disks = (sensitive value) # forces replacement
id = "i-02ae66f368e8518a9"
}
`,
},
"in-place update - deletion": {
Action: plans.Update,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-BEFORE"),
"disks": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("50GB"),
}),
}),
}),
After: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-AFTER"),
"disks": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
"mount_point": cty.String,
"size": cty.String,
}))),
}),
RequiredReplace: cty.NewPathSet(),
Schema: testSchemaSensitive(configschema.NestingList),
ExpectedOutput: ` # test_instance.example will be updated in-place
~ resource "test_instance" "example" {
~ ami = "ami-BEFORE" -> "ami-AFTER"
- disks = (sensitive value)
id = "i-02ae66f368e8518a9"
}
`,
},
"in-place update - unknown": {
Action: plans.Update,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-BEFORE"),
"disks": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("50GB"),
}),
}),
}),
After: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-AFTER"),
"disks": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
"mount_point": cty.String,
"size": cty.String,
}))),
}),
RequiredReplace: cty.NewPathSet(),
Schema: testSchemaSensitive(configschema.NestingList),
ExpectedOutput: ` # test_instance.example will be updated in-place
~ resource "test_instance" "example" {
~ ami = "ami-BEFORE" -> "ami-AFTER"
~ disks = (sensitive value)
id = "i-02ae66f368e8518a9"
}
`,
},
}
runTestCases(t, testCases)
}
func TestResourceChange_nestedSetSensitiveSchema(t *testing.T) {
testCases := map[string]testCase{
"creation from null": {
Action: plans.Update,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.NullVal(cty.String),
"ami": cty.NullVal(cty.String),
"disks": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
"mount_point": cty.String,
"size": cty.String,
}))),
}),
After: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-AFTER"),
"disks": cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.NullVal(cty.String),
}),
}),
}),
RequiredReplace: cty.NewPathSet(),
Schema: testSchemaSensitive(configschema.NestingSet),
ExpectedOutput: ` # test_instance.example will be updated in-place
~ resource "test_instance" "example" {
+ ami = "ami-AFTER"
+ disks = (sensitive value)
+ id = "i-02ae66f368e8518a9"
}
`,
},
"in-place update": {
Action: plans.Update,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-BEFORE"),
"disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{
"mount_point": cty.String,
"size": cty.String,
})),
}),
After: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-AFTER"),
"disks": cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.NullVal(cty.String),
}),
}),
}),
RequiredReplace: cty.NewPathSet(),
Schema: testSchemaSensitive(configschema.NestingSet),
ExpectedOutput: ` # test_instance.example will be updated in-place
~ resource "test_instance" "example" {
~ ami = "ami-BEFORE" -> "ami-AFTER"
~ disks = (sensitive value)
id = "i-02ae66f368e8518a9"
}
`,
},
"force-new update (whole block)": {
Action: plans.DeleteThenCreate,
ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-BEFORE"),
"disks": cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("50GB"),
}),
}),
}),
After: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-AFTER"),
"disks": cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("100GB"),
}),
}),
}),
RequiredReplace: cty.NewPathSet(
cty.Path{cty.GetAttrStep{Name: "disks"}},
),
Schema: testSchemaSensitive(configschema.NestingSet),
ExpectedOutput: ` # test_instance.example must be replaced
-/+ resource "test_instance" "example" {
~ ami = "ami-BEFORE" -> "ami-AFTER"
~ disks = (sensitive value) # forces replacement
id = "i-02ae66f368e8518a9"
}
`,
},
"in-place update - deletion": {
Action: plans.Update,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-BEFORE"),
"disks": cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("50GB"),
}),
}),
}),
After: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-AFTER"),
"disks": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
"mount_point": cty.String,
"size": cty.String,
}))),
}),
RequiredReplace: cty.NewPathSet(),
Schema: testSchemaSensitive(configschema.NestingSet),
ExpectedOutput: ` # test_instance.example will be updated in-place
~ resource "test_instance" "example" {
~ ami = "ami-BEFORE" -> "ami-AFTER"
- disks = (sensitive value)
id = "i-02ae66f368e8518a9"
}
`,
},
"in-place update - unknown": {
Action: plans.Update,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-BEFORE"),
"disks": cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.StringVal("50GB"),
}),
}),
}),
After: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-AFTER"),
"disks": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{
"mount_point": cty.String,
"size": cty.String,
}))),
}),
RequiredReplace: cty.NewPathSet(),
Schema: testSchemaSensitive(configschema.NestingSet),
ExpectedOutput: ` # test_instance.example will be updated in-place
~ resource "test_instance" "example" {
~ ami = "ami-BEFORE" -> "ami-AFTER"
~ disks = (sensitive value)
id = "i-02ae66f368e8518a9"
}
`,
},
}
runTestCases(t, testCases)
}
func TestResourceChange_actionReason(t *testing.T) {
emptySchema := &configschema.Block{}
nullVal := cty.NullVal(cty.EmptyObject)
@ -5301,7 +6140,7 @@ func TestResourceChange_sensitiveVariable(t *testing.T) {
},
ExpectedOutput: ` # test_instance.example will be updated in-place
~ resource "test_instance" "example" {
~ ami = (sensitive)
~ ami = (sensitive value)
id = "i-02ae66f368e8518a9"
~ list_field = [
- (sensitive),
@ -5685,7 +6524,7 @@ func TestResourceChange_sensitiveVariable(t *testing.T) {
),
ExpectedOutput: ` # test_instance.example must be replaced
-/+ resource "test_instance" "example" {
~ ami = (sensitive value) # forces replacement
~ ami = (sensitive) # forces replacement
id = "i-02ae66f368e8518a9"
}
`,
@ -5728,7 +6567,7 @@ func TestResourceChange_sensitiveVariable(t *testing.T) {
ExpectedOutput: ` # test_instance.example must be replaced
-/+ resource "test_instance" "example" {
~ conn_info = { # forces replacement
~ password = (sensitive value)
~ password = (sensitive)
# (1 unchanged attribute hidden)
}
id = "i-02ae66f368e8518a9"
@ -5985,7 +6824,7 @@ func TestOutputChanges(t *testing.T) {
},
`
~ a = 1 -> 2
~ b = (sensitive value)
~ b = (sensitive)
~ c = false -> true`,
},
}
@ -6023,11 +6862,16 @@ func outputChange(name string, before, after cty.Value, sensitive bool) *plans.O
// A basic test schema using a configurable NestingMode for one (NestedType) attribute and one block
func testSchema(nesting configschema.NestingMode) *configschema.Block {
var diskKey = "disks"
if nesting == configschema.NestingSingle {
diskKey = "disk"
}
return &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Optional: true, Computed: true},
"ami": {Type: cty.String, Optional: true},
"disks": {
diskKey: {
NestedType: &configschema.Object{
Attributes: map[string]*configschema.Attribute{
"mount_point": {Type: cty.String, Optional: true},
@ -6054,6 +6898,27 @@ func testSchema(nesting configschema.NestingMode) *configschema.Block {
}
}
// A basic test schema using a configurable NestingMode for one (NestedType)
// attribute marked sensitive.
func testSchemaSensitive(nesting configschema.NestingMode) *configschema.Block {
return &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Optional: true, Computed: true},
"ami": {Type: cty.String, Optional: true},
"disks": {
Sensitive: true,
NestedType: &configschema.Object{
Attributes: map[string]*configschema.Attribute{
"mount_point": {Type: cty.String, Optional: true},
"size": {Type: cty.String, Optional: true},
},
Nesting: nesting,
},
},
},
}
}
func testSchemaMultipleBlocks(nesting configschema.NestingMode) *configschema.Block {
return &configschema.Block{
Attributes: map[string]*configschema.Attribute{
@ -6100,11 +6965,16 @@ func testSchemaMultipleBlocks(nesting configschema.NestingMode) *configschema.Bl
// similar to testSchema with the addition of a "new_field" block
func testSchemaPlus(nesting configschema.NestingMode) *configschema.Block {
var diskKey = "disks"
if nesting == configschema.NestingSingle {
diskKey = "disk"
}
return &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Optional: true, Computed: true},
"ami": {Type: cty.String, Optional: true},
"disks": {
diskKey: {
NestedType: &configschema.Object{
Attributes: map[string]*configschema.Attribute{
"mount_point": {Type: cty.String, Optional: true},