Fixes: show insensitive plan details when resource in set with sensitive value (#1313)

Signed-off-by: Zejun Chen <tibazq@gmail.com>
Signed-off-by: chenzj <tibazq@gmail.com>
Co-authored-by: Oleksandr Levchenkov <ollevche@gmail.com>
This commit is contained in:
chenzj 2024-06-21 02:06:33 +08:00 committed by GitHub
parent 261b966562
commit 1ecb2dcae3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 327 additions and 62 deletions

View File

@ -19,7 +19,8 @@ BUG FIXES:
* Fixed inmem backend crash due to missing struct field. ([#1619](https://github.com/opentofu/opentofu/pull/1619))
* Added a check in the `tofu test` to validate that the names of test run blocks do not contain spaces. ([#1489](https://github.com/opentofu/opentofu/pull/1489))
* `tofu test` now supports accessing module outputs when the module has no resources. ([#1409](https://github.com/opentofu/opentofu/pull/1409))
* Fixed support for provider functions in tests. ([#1603](https://github.com/opentofu/opentofu/pull/1603))
* Fixed support for provider functions in tests ([#1603](https://github.com/opentofu/opentofu/pull/1603))
* Only hide sensitive attributes in plan detail when plan on a set of resources ([#1313](https://github.com/opentofu/opentofu/pull/1313))
* Added a better error message on `for_each` block with sensitive value of unsuitable type. ([#1485](https://github.com/opentofu/opentofu/pull/1485))
* Fix race condition on locking in gcs backend ([#1342](https://github.com/opentofu/opentofu/pull/1342))
* Fix bug where provider functions were unusable in variables and outputs ([#1689](https://github.com/opentofu/opentofu/pull/1689))

View File

@ -435,7 +435,7 @@ func MarshalResourceChanges(resources []*plans.ResourceInstanceChangeSrc, schema
if schema.ContainsSensitive() {
marks = append(marks, schema.ValueMarks(changeV.Before, nil)...)
}
bs := jsonstate.SensitiveAsBool(changeV.Before.MarkWithPaths(marks))
bs := jsonstate.SensitiveAsBoolWithPathValueMarks(changeV.Before, marks)
beforeSensitive, err = ctyjson.Marshal(bs, bs.Type())
if err != nil {
return nil, err
@ -464,7 +464,7 @@ func MarshalResourceChanges(resources []*plans.ResourceInstanceChangeSrc, schema
if schema.ContainsSensitive() {
marks = append(marks, schema.ValueMarks(changeV.After, nil)...)
}
as := jsonstate.SensitiveAsBool(changeV.After.MarkWithPaths(marks))
as := jsonstate.SensitiveAsBoolWithPathValueMarks(changeV.After, marks)
afterSensitive, err = ctyjson.Marshal(as, as.Type())
if err != nil {
return nil, err

View File

@ -417,7 +417,7 @@ func marshalResources(resources map[string]*states.Resource, module addrs.Module
if schema.ContainsSensitive() {
marks = append(marks, schema.ValueMarks(value, nil)...)
}
s := SensitiveAsBool(value.MarkWithPaths(marks))
s := SensitiveAsBoolWithPathValueMarks(value, marks)
v, err := ctyjson.Marshal(s, s.Type())
if err != nil {
return nil, err
@ -496,73 +496,130 @@ func marshalResources(resources map[string]*states.Resource, module addrs.Module
}
func SensitiveAsBool(val cty.Value) cty.Value {
return SensitiveAsBoolWithPathValueMarks(val, nil)
}
func SensitiveAsBoolWithPathValueMarks(val cty.Value, pvms []cty.PathValueMarks) cty.Value {
var sensitiveMarks []cty.PathValueMarks
for _, pvm := range pvms {
if _, ok := pvm.Marks[marks.Sensitive]; ok {
sensitiveMarks = append(sensitiveMarks, pvm)
}
}
return sensitiveAsBoolWithPathValueMarks(val, cty.Path{}, sensitiveMarks)
}
func sensitiveAsBoolWithPathValueMarks(val cty.Value, path cty.Path, pvms []cty.PathValueMarks) cty.Value {
if val.HasMark(marks.Sensitive) {
return cty.True
}
for _, pvm := range pvms {
if path.Equals(pvm.Path) {
return cty.True
}
}
ty := val.Type()
switch {
case val.IsNull(), ty.IsPrimitiveType(), ty.Equals(cty.DynamicPseudoType):
return cty.False
case ty.IsListType() || ty.IsTupleType() || ty.IsSetType():
if !val.IsKnown() {
// If the collection is unknown we can't say anything about the
// sensitivity of its contents
return cty.EmptyTupleVal
}
length := val.LengthInt()
if length == 0 {
// If there are no elements then we can't have sensitive values
return cty.EmptyTupleVal
}
vals := make([]cty.Value, 0, length)
it := val.ElementIterator()
for it.Next() {
_, v := it.Element()
vals = append(vals, SensitiveAsBool(v))
}
// The above transform may have changed the types of some of the
// elements, so we'll always use a tuple here in case we've now made
// different elements have different types. Our ultimate goal is to
// marshal to JSON anyway, and all of these sequence types are
// indistinguishable in JSON.
return cty.TupleVal(vals)
case ty.IsMapType() || ty.IsObjectType():
if !val.IsKnown() {
// If the map/object is unknown we can't say anything about the
// sensitivity of its attributes
return cty.EmptyObjectVal
}
var length int
switch {
case ty.IsMapType():
length = val.LengthInt()
default:
length = len(val.Type().AttributeTypes())
}
if length == 0 {
// If there are no elements then we can't have sensitive values
return cty.EmptyObjectVal
}
vals := make(map[string]cty.Value)
it := val.ElementIterator()
for it.Next() {
k, v := it.Element()
s := SensitiveAsBool(v)
// Omit all of the "false"s for non-sensitive values for more
// compact serialization
if !s.RawEquals(cty.False) {
vals[k.AsString()] = s
}
}
// The above transform may have changed the types of some of the
// elements, so we'll always use an object here in case we've now made
// different elements have different types. Our ultimate goal is to
// marshal to JSON anyway, and all of these mapping types are
// indistinguishable in JSON.
return cty.ObjectVal(vals)
return sensitiveCollectionAsBool(val, path, pvms)
case ty.IsMapType():
return sensitiveMapAsBool(val, path, pvms)
case ty.IsObjectType():
return sensitiveObjectAsBool(val, path, pvms)
default:
// Should never happen, since the above should cover all types
panic(fmt.Sprintf("sensitiveAsBool cannot handle %#v", val))
panic(fmt.Sprintf("sensitiveAsBoolWithPathValueMarks cannot handle %#v", val))
}
}
func sensitiveCollectionAsBool(val cty.Value, path []cty.PathStep, pvms []cty.PathValueMarks) cty.Value {
if !val.IsKnown() {
// If the collection is unknown we can't say anything about the
// sensitivity of its contents
return cty.EmptyTupleVal
}
length := val.LengthInt()
if length == 0 {
// If there are no elements then we can't have sensitive values
return cty.EmptyTupleVal
}
vals := make([]cty.Value, 0, length)
it := val.ElementIterator()
for it.Next() {
kv, ev := it.Element()
path = append(path, cty.IndexStep{
Key: kv,
})
vals = append(vals, sensitiveAsBoolWithPathValueMarks(ev, path, pvms))
path = path[0 : len(path)-1]
}
// The above transform may have changed the types of some of the
// elements, so we'll always use a tuple here in case we've now made
// different elements have different types. Our ultimate goal is to
// marshal to JSON anyway, and all of these sequence types are
// indistinguishable in JSON.
return cty.TupleVal(vals)
}
func sensitiveMapAsBool(val cty.Value, path []cty.PathStep, pvms []cty.PathValueMarks) cty.Value {
if !val.IsKnown() {
// If the map/object is unknown we can't say anything about the
// sensitivity of its attributes
return cty.EmptyObjectVal
}
length := val.LengthInt()
if length == 0 {
// If there are no elements then we can't have sensitive values
return cty.EmptyObjectVal
}
vals := make(map[string]cty.Value)
it := val.ElementIterator()
for it.Next() {
kv, ev := it.Element()
path = append(path, cty.IndexStep{
Key: kv,
})
s := sensitiveAsBoolWithPathValueMarks(ev, path, pvms)
path = path[0 : len(path)-1]
// Omit all of the "false"s for non-sensitive values for more
// compact serialization
if !s.RawEquals(cty.False) {
vals[kv.AsString()] = s
}
}
// The above transform may have changed the types of some of the
// elements, so we'll always use an object here in case we've now made
// different elements have different types. Our ultimate goal is to
// marshal to JSON anyway, and all of these mapping types are
// indistinguishable in JSON.
return cty.ObjectVal(vals)
}
func sensitiveObjectAsBool(val cty.Value, path []cty.PathStep, pvms []cty.PathValueMarks) cty.Value {
if !val.IsKnown() {
// If the map/object is unknown we can't say anything about the
// sensitivity of its attributes
return cty.EmptyObjectVal
}
ty := val.Type()
if len(ty.AttributeTypes()) == 0 {
// If there are no elements then we can't have sensitive values
return cty.EmptyObjectVal
}
vals := make(map[string]cty.Value)
for name := range ty.AttributeTypes() {
av := val.GetAttr(name)
path = append(path, cty.GetAttrStep{
Name: name,
})
s := sensitiveAsBoolWithPathValueMarks(av, path, pvms)
path = path[0 : len(path)-1]
if !s.RawEquals(cty.False) {
vals[name] = s
}
}
return cty.ObjectVal(vals)
}

View File

@ -1054,3 +1054,210 @@ func TestSensitiveAsBool(t *testing.T) {
}
}
}
func TestSensitiveAsBoolWithPathValueMarks(t *testing.T) {
tests := []struct {
Input cty.Value
Pvms []cty.PathValueMarks
Want cty.Value
}{
{
cty.ListVal([]cty.Value{
cty.StringVal("hello"),
cty.StringVal("friend"),
}),
[]cty.PathValueMarks{{
Path: cty.Path{cty.IndexStep{Key: cty.NumberIntVal(1)}},
Marks: cty.NewValueMarks(marks.Sensitive)},
},
cty.TupleVal([]cty.Value{
cty.False,
cty.True,
}),
},
{
cty.ListVal([]cty.Value{
cty.StringVal("hello").Mark(marks.Sensitive),
cty.StringVal("friend"),
}),
[]cty.PathValueMarks{{
Path: cty.Path{cty.IndexStep{Key: cty.NumberIntVal(1)}},
Marks: cty.NewValueMarks(marks.Sensitive)},
},
cty.TupleVal([]cty.Value{
cty.True,
cty.True,
}),
},
{
cty.TupleVal([]cty.Value{
cty.StringVal("hello"),
cty.StringVal("friend"),
}),
[]cty.PathValueMarks{{
Path: cty.Path{cty.IndexStep{Key: cty.NumberIntVal(1)}},
Marks: cty.NewValueMarks(marks.Sensitive)},
},
cty.TupleVal([]cty.Value{
cty.False,
cty.True,
}),
},
{
cty.TupleVal([]cty.Value{
cty.StringVal("hello").Mark(marks.Sensitive),
cty.StringVal("friend"),
}),
[]cty.PathValueMarks{{
Path: cty.Path{cty.IndexStep{Key: cty.NumberIntVal(1)}},
Marks: cty.NewValueMarks(marks.Sensitive)},
},
cty.TupleVal([]cty.Value{
cty.True,
cty.True,
}),
},
{
cty.SetVal([]cty.Value{
cty.StringVal("hello"),
cty.StringVal("friend"),
}),
[]cty.PathValueMarks{{
Path: cty.Path{cty.IndexStep{Key: cty.StringVal("hello")}},
Marks: cty.NewValueMarks(marks.Sensitive)},
},
cty.TupleVal([]cty.Value{
cty.False,
cty.True,
}),
},
{
cty.MapVal(map[string]cty.Value{
"greeting": cty.StringVal("hello"),
"animal": cty.StringVal("horse"),
}),
[]cty.PathValueMarks{{
Path: cty.Path{cty.IndexStep{Key: cty.StringVal("animal")}},
Marks: cty.NewValueMarks(marks.Sensitive)},
},
cty.ObjectVal(map[string]cty.Value{
"animal": cty.True,
}),
},
{
cty.ObjectVal(map[string]cty.Value{
"greeting": cty.StringVal("hello"),
"animal": cty.StringVal("horse"),
}),
[]cty.PathValueMarks{{
Path: cty.Path{cty.GetAttrStep{Name: "animal"}},
Marks: cty.NewValueMarks(marks.Sensitive)},
},
cty.ObjectVal(map[string]cty.Value{
"animal": cty.True,
}),
},
{
cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"a": cty.UnknownVal(cty.String),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal("known"),
}),
}),
[]cty.PathValueMarks{{
Path: cty.Path{cty.IndexStep{Key: cty.NumberIntVal(1)}, cty.GetAttrStep{Name: "a"}},
Marks: cty.NewValueMarks(marks.Sensitive)},
},
cty.TupleVal([]cty.Value{
cty.EmptyObjectVal,
cty.ObjectVal(map[string]cty.Value{
"a": cty.True,
}),
}),
},
{
cty.TupleVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"a": cty.UnknownVal(cty.String),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal("known"),
}),
}),
[]cty.PathValueMarks{{
Path: cty.Path{cty.IndexStep{Key: cty.NumberIntVal(1)}, cty.GetAttrStep{Name: "a"}},
Marks: cty.NewValueMarks(marks.Sensitive)},
},
cty.TupleVal([]cty.Value{
cty.EmptyObjectVal,
cty.ObjectVal(map[string]cty.Value{
"a": cty.True,
}),
}),
},
{
cty.ListVal([]cty.Value{
cty.MapValEmpty(cty.String),
cty.MapVal(map[string]cty.Value{
"a": cty.StringVal("known"),
}),
cty.MapVal(map[string]cty.Value{
"a": cty.UnknownVal(cty.String),
}),
}),
[]cty.PathValueMarks{{
Path: cty.Path{cty.IndexStep{Key: cty.NumberIntVal(1)}, cty.IndexStep{Key: cty.StringVal("a")}},
Marks: cty.NewValueMarks(marks.Sensitive)},
},
cty.TupleVal([]cty.Value{
cty.EmptyObjectVal,
cty.ObjectVal(map[string]cty.Value{
"a": cty.True,
}),
cty.EmptyObjectVal,
}),
},
{
cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"greeting": cty.StringVal("hello"),
"animal": cty.StringVal("cat"),
}),
cty.ObjectVal(map[string]cty.Value{
"greeting": cty.StringVal("hello"),
"animal": cty.StringVal("horse"),
}),
}),
[]cty.PathValueMarks{{
Path: cty.Path{
cty.IndexStep{Key: cty.ObjectVal(map[string]cty.Value{
"greeting": cty.StringVal("hello"),
"animal": cty.StringVal("cat"),
})},
cty.GetAttrStep{Name: "animal"}},
Marks: cty.NewValueMarks(marks.Sensitive)},
},
cty.TupleVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"animal": cty.True,
}),
cty.EmptyObjectVal,
}),
},
}
for _, test := range tests {
got := SensitiveAsBoolWithPathValueMarks(test.Input, test.Pvms)
if !reflect.DeepEqual(got, test.Want) {
t.Errorf(
"wrong result\ninput: %#v\ngot: %#v\nwant: %#v",
test.Input, got, test.Want,
)
}
}
}