Merge pull request #28687 from hashicorp/jbardin/sensitive-changes
Decode change values with marks
This commit is contained in:
commit
ef88c54604
|
@ -129,14 +129,6 @@ func ResourceChange(
|
||||||
changeV.Change.Before = objchange.NormalizeObjectFromLegacySDK(changeV.Change.Before, schema)
|
changeV.Change.Before = objchange.NormalizeObjectFromLegacySDK(changeV.Change.Before, schema)
|
||||||
changeV.Change.After = objchange.NormalizeObjectFromLegacySDK(changeV.Change.After, schema)
|
changeV.Change.After = objchange.NormalizeObjectFromLegacySDK(changeV.Change.After, schema)
|
||||||
|
|
||||||
// Now that the change is decoded, add back the marks at the defined paths
|
|
||||||
if len(change.BeforeValMarks) > 0 {
|
|
||||||
changeV.Change.Before = changeV.Change.Before.MarkWithPaths(change.BeforeValMarks)
|
|
||||||
}
|
|
||||||
if len(change.AfterValMarks) > 0 {
|
|
||||||
changeV.Change.After = changeV.Change.After.MarkWithPaths(change.AfterValMarks)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := p.writeBlockBodyDiff(schema, changeV.Before, changeV.After, 6, path)
|
result := p.writeBlockBodyDiff(schema, changeV.Before, changeV.After, 6, path)
|
||||||
if result.bodyWritten {
|
if result.bodyWritten {
|
||||||
buf.WriteString("\n")
|
buf.WriteString("\n")
|
||||||
|
|
|
@ -349,10 +349,15 @@ func (p *plan) marshalResourceChanges(changes *plans.Changes, schemas *terraform
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// We drop the marks from the change, as decoding is only an
|
||||||
|
// intermediate step to re-encode the values as json
|
||||||
|
changeV.Before, _ = changeV.Before.UnmarkDeep()
|
||||||
|
changeV.After, _ = changeV.After.UnmarkDeep()
|
||||||
|
|
||||||
var before, after []byte
|
var before, after []byte
|
||||||
var beforeSensitive, afterSensitive []byte
|
var beforeSensitive, afterSensitive []byte
|
||||||
var afterUnknown cty.Value
|
var afterUnknown cty.Value
|
||||||
|
|
||||||
if changeV.Before != cty.NilVal {
|
if changeV.Before != cty.NilVal {
|
||||||
before, err = ctyjson.Marshal(changeV.Before, changeV.Before.Type())
|
before, err = ctyjson.Marshal(changeV.Before, changeV.Before.Type())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -475,6 +480,10 @@ func (p *plan) marshalOutputChanges(changes *plans.Changes) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// We drop the marks from the change, as decoding is only an
|
||||||
|
// intermediate step to re-encode the values as json
|
||||||
|
changeV.Before, _ = changeV.Before.UnmarkDeep()
|
||||||
|
changeV.After, _ = changeV.After.UnmarkDeep()
|
||||||
|
|
||||||
var before, after []byte
|
var before, after []byte
|
||||||
afterUnknown := cty.False
|
afterUnknown := cty.False
|
||||||
|
|
|
@ -60,13 +60,15 @@ func marshalPlannedOutputs(changes *plans.Changes) (map[string]output, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ret, err
|
return ret, err
|
||||||
}
|
}
|
||||||
|
// The values may be marked, but we must rely on the Sensitive flag
|
||||||
|
// as the decoded value is only an intermediate step in transcoding
|
||||||
|
// this to a json format.
|
||||||
|
changeV.After, _ = changeV.After.UnmarkDeep()
|
||||||
|
|
||||||
if changeV.After != cty.NilVal {
|
if changeV.After != cty.NilVal && changeV.After.IsWhollyKnown() {
|
||||||
if changeV.After.IsWhollyKnown() {
|
after, err = ctyjson.Marshal(changeV.After, changeV.After.Type())
|
||||||
after, err = ctyjson.Marshal(changeV.After, changeV.After.Type())
|
if err != nil {
|
||||||
if err != nil {
|
return ret, err
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,6 +195,11 @@ func marshalPlanResources(changes *plans.Changes, ris []addrs.AbsResourceInstanc
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// The values may be marked, but we must rely on the Sensitive flag
|
||||||
|
// as the decoded value is only an intermediate step in transcoding
|
||||||
|
// this to a json format.
|
||||||
|
changeV.Before, _ = changeV.Before.UnmarkDeep()
|
||||||
|
changeV.After, _ = changeV.After.UnmarkDeep()
|
||||||
|
|
||||||
if changeV.After != cty.NilVal {
|
if changeV.After != cty.NilVal {
|
||||||
if changeV.After.IsWhollyKnown() {
|
if changeV.After.IsWhollyKnown() {
|
||||||
|
|
|
@ -200,9 +200,10 @@ func (cs *ChangeSrc) Decode(ty cty.Type) (*Change, error) {
|
||||||
return nil, fmt.Errorf("error decoding 'after' value: %s", err)
|
return nil, fmt.Errorf("error decoding 'after' value: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Change{
|
return &Change{
|
||||||
Action: cs.Action,
|
Action: cs.Action,
|
||||||
Before: before,
|
Before: before.MarkWithPaths(cs.BeforeValMarks),
|
||||||
After: after,
|
After: after.MarkWithPaths(cs.AfterValMarks),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
package plans
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestChangeEncodeSensitive(t *testing.T) {
|
||||||
|
testVals := []cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"ding": cty.StringVal("dong").Mark("sensitive"),
|
||||||
|
}),
|
||||||
|
cty.StringVal("bleep").Mark("bloop"),
|
||||||
|
cty.ListVal([]cty.Value{cty.UnknownVal(cty.String).Mark("sup?")}),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testVals {
|
||||||
|
t.Run(fmt.Sprintf("%#v", v), func(t *testing.T) {
|
||||||
|
change := Change{
|
||||||
|
Before: cty.NullVal(v.Type()),
|
||||||
|
After: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded, err := change.Encode(v.Type())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded, err := encoded.Decode(v.Type())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !v.RawEquals(decoded.After) {
|
||||||
|
t.Fatalf("%#v != %#v\n", decoded.After, v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,12 @@ import (
|
||||||
// okay because type consistency is enforced when deserializing the value
|
// okay because type consistency is enforced when deserializing the value
|
||||||
// returned from the provider over the RPC wire protocol anyway.
|
// returned from the provider over the RPC wire protocol anyway.
|
||||||
func NormalizeObjectFromLegacySDK(val cty.Value, schema *configschema.Block) cty.Value {
|
func NormalizeObjectFromLegacySDK(val cty.Value, schema *configschema.Block) cty.Value {
|
||||||
|
val, valMarks := val.UnmarkDeepWithPaths()
|
||||||
|
val = normalizeObjectFromLegacySDK(val, schema)
|
||||||
|
return val.MarkWithPaths(valMarks)
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeObjectFromLegacySDK(val cty.Value, schema *configschema.Block) cty.Value {
|
||||||
if val == cty.NilVal || val.IsNull() {
|
if val == cty.NilVal || val.IsNull() {
|
||||||
// This should never happen in reasonable use, but we'll allow it
|
// This should never happen in reasonable use, but we'll allow it
|
||||||
// and normalize to a null of the expected type rather than panicking
|
// and normalize to a null of the expected type rather than panicking
|
||||||
|
@ -50,7 +56,7 @@ func NormalizeObjectFromLegacySDK(val cty.Value, schema *configschema.Block) cty
|
||||||
if lv.IsNull() && blockS.Nesting == configschema.NestingGroup {
|
if lv.IsNull() && blockS.Nesting == configschema.NestingGroup {
|
||||||
vals[name] = blockS.EmptyValue()
|
vals[name] = blockS.EmptyValue()
|
||||||
} else {
|
} else {
|
||||||
vals[name] = NormalizeObjectFromLegacySDK(lv, &blockS.Block)
|
vals[name] = normalizeObjectFromLegacySDK(lv, &blockS.Block)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
vals[name] = unknownBlockStub(&blockS.Block)
|
vals[name] = unknownBlockStub(&blockS.Block)
|
||||||
|
@ -65,7 +71,7 @@ func NormalizeObjectFromLegacySDK(val cty.Value, schema *configschema.Block) cty
|
||||||
subVals := make([]cty.Value, 0, lv.LengthInt())
|
subVals := make([]cty.Value, 0, lv.LengthInt())
|
||||||
for it := lv.ElementIterator(); it.Next(); {
|
for it := lv.ElementIterator(); it.Next(); {
|
||||||
_, subVal := it.Element()
|
_, subVal := it.Element()
|
||||||
subVals = append(subVals, NormalizeObjectFromLegacySDK(subVal, &blockS.Block))
|
subVals = append(subVals, normalizeObjectFromLegacySDK(subVal, &blockS.Block))
|
||||||
}
|
}
|
||||||
vals[name] = cty.ListVal(subVals)
|
vals[name] = cty.ListVal(subVals)
|
||||||
}
|
}
|
||||||
|
@ -79,7 +85,7 @@ func NormalizeObjectFromLegacySDK(val cty.Value, schema *configschema.Block) cty
|
||||||
subVals := make([]cty.Value, 0, lv.LengthInt())
|
subVals := make([]cty.Value, 0, lv.LengthInt())
|
||||||
for it := lv.ElementIterator(); it.Next(); {
|
for it := lv.ElementIterator(); it.Next(); {
|
||||||
_, subVal := it.Element()
|
_, subVal := it.Element()
|
||||||
subVals = append(subVals, NormalizeObjectFromLegacySDK(subVal, &blockS.Block))
|
subVals = append(subVals, normalizeObjectFromLegacySDK(subVal, &blockS.Block))
|
||||||
}
|
}
|
||||||
vals[name] = cty.SetVal(subVals)
|
vals[name] = cty.SetVal(subVals)
|
||||||
}
|
}
|
||||||
|
|
|
@ -446,3 +446,64 @@ resource "test_resource" "b" {
|
||||||
t.Fatal(diags.ErrWithWarnings())
|
t.Fatal(diags.ErrWithWarnings())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContext2Apply_sensitiveOutputPassthrough(t *testing.T) {
|
||||||
|
// Ensure we're not trying to double-mark values decoded from state
|
||||||
|
m := testModuleInline(t, map[string]string{
|
||||||
|
"main.tf": `
|
||||||
|
module "mod" {
|
||||||
|
source = "./mod"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "test_object" "a" {
|
||||||
|
test_string = module.mod.out
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
|
||||||
|
"mod/main.tf": `
|
||||||
|
variable "in" {
|
||||||
|
sensitive = true
|
||||||
|
default = "foo"
|
||||||
|
}
|
||||||
|
output "out" {
|
||||||
|
value = var.in
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
|
||||||
|
p := simpleMockProvider()
|
||||||
|
|
||||||
|
ctx := testContext2(t, &ContextOpts{
|
||||||
|
Config: m,
|
||||||
|
Providers: map[addrs.Provider]providers.Factory{
|
||||||
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
_, diags := ctx.Plan()
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.ErrWithWarnings())
|
||||||
|
}
|
||||||
|
|
||||||
|
state, diags := ctx.Apply()
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.ErrWithWarnings())
|
||||||
|
}
|
||||||
|
|
||||||
|
obj := state.ResourceInstance(mustResourceInstanceAddr("test_object.a"))
|
||||||
|
if len(obj.Current.AttrSensitivePaths) != 1 {
|
||||||
|
t.Fatalf("Expected 1 sensitive mark for test_object.a, got %#v\n", obj.Current.AttrSensitivePaths)
|
||||||
|
}
|
||||||
|
|
||||||
|
plan, diags := ctx.Plan()
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.ErrWithWarnings())
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the same marks are compared in the next plan as well
|
||||||
|
for _, c := range plan.Changes.Resources {
|
||||||
|
if c.Action != plans.NoOp {
|
||||||
|
t.Errorf("Unexpcetd %s change for %s", c.Action, c.Addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4793,7 +4793,7 @@ func TestContext2Plan_ignoreChangesSensitive(t *testing.T) {
|
||||||
|
|
||||||
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
||||||
"id": cty.StringVal("bar"),
|
"id": cty.StringVal("bar"),
|
||||||
"ami": cty.StringVal("ami-abcd1234"),
|
"ami": cty.StringVal("ami-abcd1234").Mark("sensitive"),
|
||||||
"type": cty.StringVal("aws_instance"),
|
"type": cty.StringVal("aws_instance"),
|
||||||
}), ric.After)
|
}), ric.After)
|
||||||
}
|
}
|
||||||
|
@ -5627,7 +5627,7 @@ func TestContext2Plan_variableSensitivity(t *testing.T) {
|
||||||
switch i := ric.Addr.String(); i {
|
switch i := ric.Addr.String(); i {
|
||||||
case "aws_instance.foo":
|
case "aws_instance.foo":
|
||||||
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
||||||
"foo": cty.StringVal("foo"),
|
"foo": cty.StringVal("foo").Mark("sensitive"),
|
||||||
}), ric.After)
|
}), ric.After)
|
||||||
if len(res.ChangeSrc.BeforeValMarks) != 0 {
|
if len(res.ChangeSrc.BeforeValMarks) != 0 {
|
||||||
t.Errorf("unexpected BeforeValMarks: %#v", res.ChangeSrc.BeforeValMarks)
|
t.Errorf("unexpected BeforeValMarks: %#v", res.ChangeSrc.BeforeValMarks)
|
||||||
|
@ -5694,8 +5694,8 @@ func TestContext2Plan_variableSensitivityModule(t *testing.T) {
|
||||||
switch i := ric.Addr.String(); i {
|
switch i := ric.Addr.String(); i {
|
||||||
case "module.child.aws_instance.foo":
|
case "module.child.aws_instance.foo":
|
||||||
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
||||||
"foo": cty.StringVal("foo"),
|
"foo": cty.StringVal("foo").Mark("sensitive"),
|
||||||
"value": cty.StringVal("boop"),
|
"value": cty.StringVal("boop").Mark("sensitive"),
|
||||||
}), ric.After)
|
}), ric.After)
|
||||||
if len(res.ChangeSrc.BeforeValMarks) != 0 {
|
if len(res.ChangeSrc.BeforeValMarks) != 0 {
|
||||||
t.Errorf("unexpected BeforeValMarks: %#v", res.ChangeSrc.BeforeValMarks)
|
t.Errorf("unexpected BeforeValMarks: %#v", res.ChangeSrc.BeforeValMarks)
|
||||||
|
@ -5729,6 +5729,13 @@ func TestContext2Plan_variableSensitivityModule(t *testing.T) {
|
||||||
|
|
||||||
func checkVals(t *testing.T, expected, got cty.Value) {
|
func checkVals(t *testing.T, expected, got cty.Value) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
// The GoStringer format seems to result in the closest thing to a useful
|
||||||
|
// diff for values with marks.
|
||||||
|
// TODO: if we want to continue using cmp.Diff on cty.Values, we should
|
||||||
|
// make a transformer that creates a more comparable structure.
|
||||||
|
valueTrans := cmp.Transformer("gostring", func(v cty.Value) string {
|
||||||
|
return fmt.Sprintf("%#v\n", v)
|
||||||
|
})
|
||||||
if !cmp.Equal(expected, got, valueComparer, typeComparer, equateEmpty) {
|
if !cmp.Equal(expected, got, valueComparer, typeComparer, equateEmpty) {
|
||||||
t.Fatal(cmp.Diff(expected, got, valueTrans, equateEmpty))
|
t.Fatal(cmp.Diff(expected, got, valueTrans, equateEmpty))
|
||||||
}
|
}
|
||||||
|
|
|
@ -459,7 +459,7 @@ func (d *evaluationStateData) GetModule(addr addrs.ModuleCall, rng tfdiags.Sourc
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
instance[cfg.Name] = change.After.MarkWithPaths(changeSrc.AfterValMarks)
|
instance[cfg.Name] = change.After
|
||||||
|
|
||||||
if change.Sensitive && !change.After.HasMark("sensitive") {
|
if change.Sensitive && !change.After.HasMark("sensitive") {
|
||||||
instance[cfg.Name] = change.After.Mark("sensitive")
|
instance[cfg.Name] = change.After.Mark("sensitive")
|
||||||
|
|
|
@ -259,9 +259,11 @@ func (n *NodeApplyableOutput) Execute(ctx EvalContext, op walkOperation) (diags
|
||||||
// we we have a change recorded, we don't need to re-evaluate if the value
|
// we we have a change recorded, we don't need to re-evaluate if the value
|
||||||
// was known
|
// was known
|
||||||
if changeRecorded {
|
if changeRecorded {
|
||||||
var err error
|
change, err := n.Change.Decode()
|
||||||
val, err = n.Change.After.Decode(cty.DynamicPseudoType)
|
|
||||||
diags = diags.Append(err)
|
diags = diags.Append(err)
|
||||||
|
if err == nil {
|
||||||
|
val = change.After
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there was no change recorded, or the recorded change was not wholly
|
// If there was no change recorded, or the recorded change was not wholly
|
||||||
|
|
Loading…
Reference in New Issue