From c6be76e53c7f5304316d5d19c5794e287b813c5b Mon Sep 17 00:00:00 2001 From: Pam Selle <204372+pselle@users.noreply.github.com> Date: Tue, 20 Oct 2020 16:29:05 -0400 Subject: [PATCH] Return marked After values in GetResource If a change exists for a resource instance, the After value is returned, however, this value will not have its marks as it as been encoded. This Marks the return value so the marks follow that resource reference. --- terraform/evaluate.go | 2 +- terraform/evaluate_test.go | 128 +++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/terraform/evaluate.go b/terraform/evaluate.go index add559cbcc..b403afa083 100644 --- a/terraform/evaluate.go +++ b/terraform/evaluate.go @@ -756,7 +756,7 @@ func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.Sourc if schema.ContainsSensitive() { val = markProviderSensitiveAttributes(schema, val) } - instances[key] = val + instances[key] = val.MarkWithPaths(change.AfterValMarks) continue } diff --git a/terraform/evaluate_test.go b/terraform/evaluate_test.go index eb07af314f..5e838cd0de 100644 --- a/terraform/evaluate_test.go +++ b/terraform/evaluate_test.go @@ -297,6 +297,134 @@ func TestEvaluatorGetResource(t *testing.T) { } } +// GetResource will return a planned object's After value +// if there is a change for that resource instance. +func TestEvaluatorGetResource_changes(t *testing.T) { + // Set up existing state + stateSync := states.BuildState(func(ss *states.SyncState) { + ss.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_resource", + Name: "foo", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectPlanned, + AttrsJSON: []byte(`{"id":"foo", "to_mark_val":"tacos", "sensitive_value":"abc"}`), + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + }).SyncWrapper() + + // Create a change for the existing state resource, + // to exercise retrieving the After value of the change + changesSync := plans.NewChanges().SyncWrapper() + change := &plans.ResourceInstanceChange{ + Addr: mustResourceInstanceAddr("test_resource.foo"), + ProviderAddr: addrs.AbsProviderConfig{ + Module: addrs.RootModule, + Provider: addrs.NewDefaultProvider("test"), + }, + Change: plans.Change{ + Action: plans.Update, + // Provide an After value that contains a marked value + After: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("foo"), + "to_mark_val": cty.StringVal("pizza").Mark("sensitive"), + "sensitive_value": cty.StringVal("abc"), + }), + }, + } + + // Set up our schemas + schemas := &Schemas{ + Providers: map[addrs.Provider]*ProviderSchema{ + addrs.NewDefaultProvider("test"): { + Provider: &configschema.Block{}, + ResourceTypes: map[string]*configschema.Block{ + "test_resource": { + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, + "to_mark_val": { + Type: cty.String, + Computed: true, + }, + "sensitive_value": { + Type: cty.String, + Computed: true, + Sensitive: true, + }, + }, + }, + }, + }, + }, + } + + // The resource we'll inspect + addr := addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_resource", + Name: "foo", + } + schema, _ := schemas.ResourceTypeConfig(addrs.NewDefaultProvider("test"), addr.Mode, addr.Type) + // This encoding separates out the After's marks into its AfterValMarks + csrc, _ := change.Encode(schema.ImpliedType()) + changesSync.AppendResourceInstanceChange(csrc) + + evaluator := &Evaluator{ + Meta: &ContextMeta{ + Env: "foo", + }, + Changes: changesSync, + Config: &configs.Config{ + Module: &configs.Module{ + ManagedResources: map[string]*configs.Resource{ + "test_resource.foo": &configs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_resource", + Name: "foo", + Provider: addrs.Provider{ + Hostname: addrs.DefaultRegistryHost, + Namespace: "hashicorp", + Type: "test", + }, + }, + }, + }, + }, + State: stateSync, + Schemas: schemas, + } + + data := &evaluationStateData{ + Evaluator: evaluator, + } + scope := evaluator.Scope(data, nil) + + want := cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("foo"), + "to_mark_val": cty.StringVal("pizza").Mark("sensitive"), + "sensitive_value": cty.StringVal("abc").Mark("sensitive"), + }) + + got, diags := scope.Data.GetResource(addr, tfdiags.SourceRange{}) + + if len(diags) != 0 { + t.Errorf("unexpected diagnostics %s", spew.Sdump(diags)) + } + + if !got.RawEquals(want) { + t.Errorf("wrong result:\ngot: %#v\nwant: %#v", got, want) + } +} + func TestEvaluatorGetModule(t *testing.T) { // Create a new evaluator with an existing state stateSync := states.BuildState(func(ss *states.SyncState) {