Skip to content

Commit 31f278b

Browse files
authored
Merge pull request #32892 from hashicorp/jbardin/update-missing-sensitive
don't compare plan marks for missing values
2 parents a4e92f3 + 81b74cd commit 31f278b

File tree

3 files changed

+88
-2
lines changed

3 files changed

+88
-2
lines changed

internal/terraform/context_plan2_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4035,3 +4035,66 @@ func TestContext2Plan_dataSourceReadPlanError(t *testing.T) {
40354035
t.Fatalf("failed to round-trip through planfile: %s", err)
40364036
}
40374037
}
4038+
4039+
func TestContext2Plan_ignoredMarkedValue(t *testing.T) {
4040+
m := testModuleInline(t, map[string]string{
4041+
"main.tf": `
4042+
resource "test_object" "a" {
4043+
map = {
4044+
prior = "value"
4045+
new = sensitive("ignored")
4046+
}
4047+
}
4048+
`})
4049+
4050+
testProvider := &MockProvider{
4051+
GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
4052+
ResourceTypes: map[string]providers.Schema{
4053+
"test_object": providers.Schema{
4054+
Block: &configschema.Block{
4055+
Attributes: map[string]*configschema.Attribute{
4056+
"map": {
4057+
Type: cty.Map(cty.String),
4058+
Optional: true,
4059+
},
4060+
},
4061+
},
4062+
},
4063+
},
4064+
},
4065+
}
4066+
4067+
testProvider.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
4068+
// We're going to ignore any changes here and return the prior state.
4069+
resp.PlannedState = req.PriorState
4070+
return resp
4071+
}
4072+
4073+
state := states.NewState()
4074+
root := state.RootModule()
4075+
root.SetResourceInstanceCurrent(
4076+
mustResourceInstanceAddr("test_object.a").Resource,
4077+
&states.ResourceInstanceObjectSrc{
4078+
Status: states.ObjectReady,
4079+
AttrsJSON: []byte(`{"map":{"prior":"value"}}`),
4080+
Dependencies: []addrs.ConfigResource{},
4081+
},
4082+
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
4083+
)
4084+
ctx := testContext2(t, &ContextOpts{
4085+
Providers: map[addrs.Provider]providers.Factory{
4086+
addrs.NewDefaultProvider("test"): testProviderFuncFixed(testProvider),
4087+
},
4088+
})
4089+
4090+
// plan+apply to create the initial state
4091+
opts := SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))
4092+
plan, diags := ctx.Plan(m, state, opts)
4093+
assertNoErrors(t, diags)
4094+
4095+
for _, c := range plan.Changes.Resources {
4096+
if c.Action != plans.NoOp {
4097+
t.Errorf("unexpected %s change for %s", c.Action, c.Addr)
4098+
}
4099+
}
4100+
}

internal/terraform/marks.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@ import (
77
"github.com/zclconf/go-cty/cty"
88
)
99

10+
// filterMarks removes any PathValueMarks from marks which cannot be applied to
11+
// the given value. When comparing existing marks to those from a map or other
12+
// dynamic value, we may not have values at the same paths and need to strip
13+
// out irrelevant marks.
14+
func filterMarks(val cty.Value, marks []cty.PathValueMarks) []cty.PathValueMarks {
15+
var res []cty.PathValueMarks
16+
for _, mark := range marks {
17+
// any error here just means the path cannot apply to this value, so we
18+
// don't need this mark for comparison.
19+
if _, err := mark.Path.Apply(val); err == nil {
20+
res = append(res, mark)
21+
}
22+
}
23+
return res
24+
}
25+
1026
// marksEqual compares 2 unordered sets of PathValue marks for equality, with
1127
// the comparison using the cty.PathValueMarks.Equal method.
1228
func marksEqual(a, b []cty.PathValueMarks) bool {

internal/terraform/node_resource_abstract_instance.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,8 +1074,15 @@ func (n *NodeAbstractResourceInstance) plan(
10741074
}
10751075

10761076
// If we plan to write or delete sensitive paths from state,
1077-
// this is an Update action
1078-
if action == plans.NoOp && !marksEqual(unmarkedPaths, priorPaths) {
1077+
// this is an Update action.
1078+
//
1079+
// We need to filter out any marks which may not apply to the new planned
1080+
// value before comparison. The one case where a provider is allowed to
1081+
// return a different value from the configuration is when a config change
1082+
// is not functionally significant and the prior state can be returned. If a
1083+
// new mark was also discarded from that config change, it needs to be
1084+
// ignored here to prevent an errant update action.
1085+
if action == plans.NoOp && !marksEqual(filterMarks(plannedNewVal, unmarkedPaths), priorPaths) {
10791086
action = plans.Update
10801087
}
10811088

0 commit comments

Comments
 (0)