Merge pull request #28687 from hashicorp/jbardin/sensitive-changes

Decode change values with marks
This commit is contained in:
James Bardin 2021-05-13 12:44:03 -04:00 committed by GitHub
commit ef88c54604
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 152 additions and 26 deletions

View File

@ -129,14 +129,6 @@ func ResourceChange(
changeV.Change.Before = objchange.NormalizeObjectFromLegacySDK(changeV.Change.Before, 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)
if result.bodyWritten {
buf.WriteString("\n")

View File

@ -349,10 +349,15 @@ func (p *plan) marshalResourceChanges(changes *plans.Changes, schemas *terraform
if err != nil {
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 beforeSensitive, afterSensitive []byte
var afterUnknown cty.Value
if changeV.Before != cty.NilVal {
before, err = ctyjson.Marshal(changeV.Before, changeV.Before.Type())
if err != nil {
@ -475,6 +480,10 @@ func (p *plan) marshalOutputChanges(changes *plans.Changes) error {
if err != nil {
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
afterUnknown := cty.False

View File

@ -60,13 +60,15 @@ func marshalPlannedOutputs(changes *plans.Changes) (map[string]output, error) {
if err != nil {
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.IsWhollyKnown() {
after, err = ctyjson.Marshal(changeV.After, changeV.After.Type())
if err != nil {
return ret, err
}
if changeV.After != cty.NilVal && changeV.After.IsWhollyKnown() {
after, err = ctyjson.Marshal(changeV.After, changeV.After.Type())
if err != nil {
return ret, err
}
}
@ -193,6 +195,11 @@ func marshalPlanResources(changes *plans.Changes, ris []addrs.AbsResourceInstanc
if err != nil {
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.IsWhollyKnown() {

View File

@ -200,9 +200,10 @@ func (cs *ChangeSrc) Decode(ty cty.Type) (*Change, error) {
return nil, fmt.Errorf("error decoding 'after' value: %s", err)
}
}
return &Change{
Action: cs.Action,
Before: before,
After: after,
Before: before.MarkWithPaths(cs.BeforeValMarks),
After: after.MarkWithPaths(cs.AfterValMarks),
}, nil
}

41
plans/changes_test.go Normal file
View File

@ -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)
}
})
}
}

View File

@ -19,6 +19,12 @@ import (
// okay because type consistency is enforced when deserializing the value
// returned from the provider over the RPC wire protocol anyway.
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() {
// This should never happen in reasonable use, but we'll allow it
// 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 {
vals[name] = blockS.EmptyValue()
} else {
vals[name] = NormalizeObjectFromLegacySDK(lv, &blockS.Block)
vals[name] = normalizeObjectFromLegacySDK(lv, &blockS.Block)
}
} else {
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())
for it := lv.ElementIterator(); it.Next(); {
_, subVal := it.Element()
subVals = append(subVals, NormalizeObjectFromLegacySDK(subVal, &blockS.Block))
subVals = append(subVals, normalizeObjectFromLegacySDK(subVal, &blockS.Block))
}
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())
for it := lv.ElementIterator(); it.Next(); {
_, subVal := it.Element()
subVals = append(subVals, NormalizeObjectFromLegacySDK(subVal, &blockS.Block))
subVals = append(subVals, normalizeObjectFromLegacySDK(subVal, &blockS.Block))
}
vals[name] = cty.SetVal(subVals)
}

View File

@ -446,3 +446,64 @@ resource "test_resource" "b" {
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)
}
}
}

View File

@ -4793,7 +4793,7 @@ func TestContext2Plan_ignoreChangesSensitive(t *testing.T) {
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.StringVal("bar"),
"ami": cty.StringVal("ami-abcd1234"),
"ami": cty.StringVal("ami-abcd1234").Mark("sensitive"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
}
@ -5627,7 +5627,7 @@ func TestContext2Plan_variableSensitivity(t *testing.T) {
switch i := ric.Addr.String(); i {
case "aws_instance.foo":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"foo": cty.StringVal("foo"),
"foo": cty.StringVal("foo").Mark("sensitive"),
}), ric.After)
if len(res.ChangeSrc.BeforeValMarks) != 0 {
t.Errorf("unexpected BeforeValMarks: %#v", res.ChangeSrc.BeforeValMarks)
@ -5694,8 +5694,8 @@ func TestContext2Plan_variableSensitivityModule(t *testing.T) {
switch i := ric.Addr.String(); i {
case "module.child.aws_instance.foo":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"foo": cty.StringVal("foo"),
"value": cty.StringVal("boop"),
"foo": cty.StringVal("foo").Mark("sensitive"),
"value": cty.StringVal("boop").Mark("sensitive"),
}), ric.After)
if len(res.ChangeSrc.BeforeValMarks) != 0 {
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) {
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) {
t.Fatal(cmp.Diff(expected, got, valueTrans, equateEmpty))
}

View File

@ -459,7 +459,7 @@ func (d *evaluationStateData) GetModule(addr addrs.ModuleCall, rng tfdiags.Sourc
continue
}
instance[cfg.Name] = change.After.MarkWithPaths(changeSrc.AfterValMarks)
instance[cfg.Name] = change.After
if change.Sensitive && !change.After.HasMark("sensitive") {
instance[cfg.Name] = change.After.Mark("sensitive")

View File

@ -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
// was known
if changeRecorded {
var err error
val, err = n.Change.After.Decode(cty.DynamicPseudoType)
change, err := n.Change.Decode()
diags = diags.Append(err)
if err == nil {
val = change.After
}
}
// If there was no change recorded, or the recorded change was not wholly