mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
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:
parent
261b966562
commit
1ecb2dcae3
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user