From 1db29d775ed9c61fe4532d0e90fdde46d1e2cba4 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 28 Apr 2021 13:47:41 -0400 Subject: [PATCH] unmark planned data source values --- terraform/context_plan2_test.go | 115 +++++++++++++++++++ terraform/node_resource_abstract_instance.go | 13 ++- 2 files changed, 122 insertions(+), 6 deletions(-) diff --git a/terraform/context_plan2_test.go b/terraform/context_plan2_test.go index 4fb293c91..312e971be 100644 --- a/terraform/context_plan2_test.go +++ b/terraform/context_plan2_test.go @@ -515,3 +515,118 @@ output "root" { t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) } } + +func TestContext2Plan_planDataSourceSensitiveNested(t *testing.T) { + m := testModuleInline(t, map[string]string{ + "main.tf": ` +resource "test_instance" "bar" { +} + +data "test_data_source" "foo" { + foo { + bar = test_instance.bar.sensitive + } +} +`, + }) + + p := new(MockProvider) + p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { + resp.PlannedState = cty.ObjectVal(map[string]cty.Value{ + "sensitive": cty.UnknownVal(cty.String), + }) + return resp + } + p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ + ResourceTypes: map[string]*configschema.Block{ + "test_instance": { + Attributes: map[string]*configschema.Attribute{ + "sensitive": { + Type: cty.String, + Computed: true, + Sensitive: true, + }, + }, + }, + }, + DataSources: map[string]*configschema.Block{ + "test_data_source": { + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, + }, + BlockTypes: map[string]*configschema.NestedBlock{ + "foo": { + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "bar": {Type: cty.String, Optional: true}, + }, + }, + Nesting: configschema.NestingSet, + }, + }, + }, + }, + }) + + state := states.NewState() + root := state.EnsureModule(addrs.RootModuleInstance) + root.SetResourceInstanceCurrent( + mustResourceInstanceAddr("data.test_data_source.foo").Resource, + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"string":"data_id", "foo":[{"bar":"old"}]}`), + AttrSensitivePaths: []cty.PathValueMarks{ + { + Path: cty.GetAttrPath("foo"), + Marks: cty.NewValueMarks("sensitive"), + }, + }, + }, + mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), + ) + root.SetResourceInstanceCurrent( + mustResourceInstanceAddr("test_instance.bar").Resource, + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"sensitive":"old"}`), + AttrSensitivePaths: []cty.PathValueMarks{ + { + Path: cty.GetAttrPath("sensitive"), + Marks: cty.NewValueMarks("sensitive"), + }, + }, + }, + mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), + ) + + ctx := testContext2(t, &ContextOpts{ + Config: m, + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + State: state, + }) + + plan, diags := ctx.Plan() + if diags.HasErrors() { + t.Fatal(diags.ErrWithWarnings()) + } + + for _, res := range plan.Changes.Resources { + switch res.Addr.String() { + case "test_instance.bar": + if res.Action != plans.Update { + t.Fatalf("unexpected %s change for %s", res.Action, res.Addr) + } + case "data.test_data_source.foo": + if res.Action != plans.Read { + t.Fatalf("unexpected %s change for %s", res.Action, res.Addr) + } + default: + t.Fatalf("unexpected %s change for %s", res.Action, res.Addr) + } + } +} diff --git a/terraform/node_resource_abstract_instance.go b/terraform/node_resource_abstract_instance.go index 2284d5402..59227b55a 100644 --- a/terraform/node_resource_abstract_instance.go +++ b/terraform/node_resource_abstract_instance.go @@ -1346,6 +1346,11 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, currentSt return nil, nil, diags } + unmarkedConfigVal, configMarkPaths := configVal.UnmarkDeepWithPaths() + // We drop marks on the values used here as the result is only + // temporarily used for validation. + unmarkedPriorVal, _ := priorVal.UnmarkDeep() + configKnown := configVal.IsWhollyKnown() // If our configuration contains any unknown values, or we depend on any // unknown values then we must defer the read to the apply phase by @@ -1358,7 +1363,7 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, currentSt log.Printf("[TRACE] planDataSource: %s configuration not fully known yet, so deferring to apply phase", n.Addr) } - proposedNewVal := objchange.PlannedDataResourceObject(schema, configVal) + proposedNewVal := objchange.PlannedDataResourceObject(schema, unmarkedConfigVal) diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) { return h.PreDiff(n.Addr, states.CurrentGen, priorVal, proposedNewVal) @@ -1366,6 +1371,7 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, currentSt if diags.HasErrors() { return nil, nil, diags } + proposedNewVal = proposedNewVal.MarkWithPaths(configMarkPaths) // Apply detects that the data source will need to be read by the After // value containing unknowns from PlanDataResourceObject. @@ -1408,11 +1414,6 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, currentSt // if we have a prior value, we can check for any irregularities in the response if !priorVal.IsNull() { - // We drop marks on the values used here as the result is only - // temporarily used for validation. - unmarkedConfigVal, _ := configVal.UnmarkDeep() - unmarkedPriorVal, _ := priorVal.UnmarkDeep() - // While we don't propose planned changes for data sources, we can // generate a proposed value for comparison to ensure the data source // is returning a result following the rules of the provider contract.