handle marks within ignore_changes

Up until now marks were not considered by `ignore_changes`, that however
means changes to sensitivity within a configuration cannot ignored, even
though they are planned as changes.

Rather than separating the marks and tracking their paths, we can easily
update the processIgnoreChanges routine to handle the marked values
directly. Moving the `processIgnoreChanges` call also cleans up some of
the variable naming, making it more consistent through the body of the
function.
This commit is contained in:
James Bardin 2021-07-19 14:54:48 -04:00
parent 6d65c19134
commit ea077cbd90
2 changed files with 56 additions and 29 deletions

View File

@ -687,26 +687,24 @@ func (n *NodeAbstractResourceInstance) plan(
priorVal = cty.NullVal(schema.ImpliedType()) priorVal = cty.NullVal(schema.ImpliedType())
} }
// Create an unmarked version of our config val and our prior val.
// Store the paths for the config val to re-markafter
// we've sent things over the wire.
unmarkedConfigVal, unmarkedPaths := origConfigVal.UnmarkDeepWithPaths()
unmarkedPriorVal, priorPaths := priorVal.UnmarkDeepWithPaths()
log.Printf("[TRACE] Re-validating config for %q", n.Addr) log.Printf("[TRACE] Re-validating config for %q", n.Addr)
// Allow the provider to validate the final set of values. // Allow the provider to validate the final set of values. The config was
// The config was statically validated early on, but there may have been // statically validated early on, but there may have been unknown values
// unknown values which the provider could not validate at the time. // which the provider could not validate at the time.
//
// TODO: It would be more correct to validate the config after // TODO: It would be more correct to validate the config after
// ignore_changes has been applied, but the current implementation cannot // ignore_changes has been applied, but the current implementation cannot
// exclude computed-only attributes when given the `all` option. // exclude computed-only attributes when given the `all` option.
// we must unmark and use the original config, since the ignore_changes
// handling below needs access to the marks.
unmarkedConfigVal, _ := origConfigVal.UnmarkDeep()
validateResp := provider.ValidateResourceConfig( validateResp := provider.ValidateResourceConfig(
providers.ValidateResourceConfigRequest{ providers.ValidateResourceConfigRequest{
TypeName: n.Addr.Resource.Resource.Type, TypeName: n.Addr.Resource.Resource.Type,
Config: unmarkedConfigVal, Config: unmarkedConfigVal,
}, },
) )
diags = diags.Append(validateResp.Diagnostics.InConfigBody(config.Config, n.Addr.String())) diags = diags.Append(validateResp.Diagnostics.InConfigBody(config.Config, n.Addr.String()))
if diags.HasErrors() { if diags.HasErrors() {
return plan, state, diags return plan, state, diags
@ -717,13 +715,21 @@ func (n *NodeAbstractResourceInstance) plan(
// the proposed value, the proposed value itself, and the config presented // the proposed value, the proposed value itself, and the config presented
// to the provider in the PlanResourceChange request all agree on the // to the provider in the PlanResourceChange request all agree on the
// starting values. // starting values.
configValIgnored, ignoreChangeDiags := n.processIgnoreChanges(unmarkedPriorVal, unmarkedConfigVal) // Here we operate on the marked values, so as to revert any changes to the
// marks as well as the value.
configValIgnored, ignoreChangeDiags := n.processIgnoreChanges(priorVal, origConfigVal)
diags = diags.Append(ignoreChangeDiags) diags = diags.Append(ignoreChangeDiags)
if ignoreChangeDiags.HasErrors() { if ignoreChangeDiags.HasErrors() {
return plan, state, diags return plan, state, diags
} }
proposedNewVal := objchange.ProposedNew(schema, unmarkedPriorVal, configValIgnored) // Create an unmarked version of our config val and our prior val.
// Store the paths for the config val to re-mark after we've sent things
// over the wire.
unmarkedConfigVal, unmarkedPaths := configValIgnored.UnmarkDeepWithPaths()
unmarkedPriorVal, priorPaths := priorVal.UnmarkDeepWithPaths()
proposedNewVal := objchange.ProposedNew(schema, unmarkedPriorVal, unmarkedConfigVal)
// Call pre-diff hook // Call pre-diff hook
diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) { diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) {
@ -735,7 +741,7 @@ func (n *NodeAbstractResourceInstance) plan(
resp := provider.PlanResourceChange(providers.PlanResourceChangeRequest{ resp := provider.PlanResourceChange(providers.PlanResourceChangeRequest{
TypeName: n.Addr.Resource.Resource.Type, TypeName: n.Addr.Resource.Resource.Type,
Config: configValIgnored, Config: unmarkedConfigVal,
PriorState: unmarkedPriorVal, PriorState: unmarkedPriorVal,
ProposedNewState: proposedNewVal, ProposedNewState: proposedNewVal,
PriorPrivate: priorPrivate, PriorPrivate: priorPrivate,
@ -774,7 +780,7 @@ func (n *NodeAbstractResourceInstance) plan(
return plan, state, diags return plan, state, diags
} }
if errs := objchange.AssertPlanValid(schema, unmarkedPriorVal, configValIgnored, plannedNewVal); len(errs) > 0 { if errs := objchange.AssertPlanValid(schema, unmarkedPriorVal, unmarkedConfigVal, plannedNewVal); len(errs) > 0 {
if resp.LegacyTypeSystem { if resp.LegacyTypeSystem {
// The shimming of the old type system in the legacy SDK is not precise // The shimming of the old type system in the legacy SDK is not precise
// enough to pass this consistency check, so we'll give it a pass here, // enough to pass this consistency check, so we'll give it a pass here,
@ -1085,7 +1091,7 @@ func (n *NodeAbstractResource) processIgnoreChanges(prior, config cty.Value) (ct
return config, nil return config, nil
} }
ignoreChanges := n.Config.Managed.IgnoreChanges ignoreChanges := traversalsToPaths(n.Config.Managed.IgnoreChanges)
ignoreAll := n.Config.Managed.IgnoreAllChanges ignoreAll := n.Config.Managed.IgnoreAllChanges
if len(ignoreChanges) == 0 && !ignoreAll { if len(ignoreChanges) == 0 && !ignoreAll {
@ -1100,14 +1106,16 @@ func (n *NodeAbstractResource) processIgnoreChanges(prior, config cty.Value) (ct
return config, nil return config, nil
} }
return processIgnoreChangesIndividual(prior, config, ignoreChanges) ret, diags := processIgnoreChangesIndividual(prior, config, ignoreChanges)
return ret, diags
} }
func processIgnoreChangesIndividual(prior, config cty.Value, ignoreChanges []hcl.Traversal) (cty.Value, tfdiags.Diagnostics) { // Convert the hcl.Traversal values we get form the configuration to the
// When we walk below we will be using cty.Path values for comparison, so // cty.Path values we need to operate on the cty.Values
// we'll convert our traversals here so we can compare more easily. func traversalsToPaths(traversals []hcl.Traversal) []cty.Path {
ignoreChangesPath := make([]cty.Path, len(ignoreChanges)) paths := make([]cty.Path, len(traversals))
for i, traversal := range ignoreChanges { for i, traversal := range traversals {
path := make(cty.Path, len(traversal)) path := make(cty.Path, len(traversal))
for si, step := range traversal { for si, step := range traversal {
switch ts := step.(type) { switch ts := step.(type) {
@ -1127,9 +1135,12 @@ func processIgnoreChangesIndividual(prior, config cty.Value, ignoreChanges []hcl
panic(fmt.Sprintf("unsupported traversal step %#v", step)) panic(fmt.Sprintf("unsupported traversal step %#v", step))
} }
} }
ignoreChangesPath[i] = path paths[i] = path
} }
return paths
}
func processIgnoreChangesIndividual(prior, config cty.Value, ignoreChangesPath []cty.Path) (cty.Value, tfdiags.Diagnostics) {
type ignoreChange struct { type ignoreChange struct {
// Path is the full path, minus any trailing map index // Path is the full path, minus any trailing map index
path cty.Path path cty.Path
@ -1175,8 +1186,7 @@ func processIgnoreChangesIndividual(prior, config cty.Value, ignoreChanges []hcl
// here even if our ignored key doesn't change. That is OK since it // here even if our ignored key doesn't change. That is OK since it
// won't cause any changes in the transformation, but allows us to skip // won't cause any changes in the transformation, but allows us to skip
// breaking up the maps and checking for key existence here too. // breaking up the maps and checking for key existence here too.
eq := p.Equals(c) if !p.RawEquals(c) {
if !eq.IsKnown() || eq.False() {
// there a change to ignore at this path, store the prior value // there a change to ignore at this path, store the prior value
ignoredValues = append(ignoredValues, ignoreChange{icPath, p, key}) ignoredValues = append(ignoredValues, ignoreChange{icPath, p, key})
} }
@ -1205,6 +1215,10 @@ func processIgnoreChangesIndividual(prior, config cty.Value, ignoreChanges []hcl
return v, nil return v, nil
} }
// The map values will remain as cty values, so we only need to store
// the marks from the outer map itself
v, vMarks := v.Unmark()
// The configMap is the current configuration value, which we will // The configMap is the current configuration value, which we will
// mutate based on the ignored paths and the prior map value. // mutate based on the ignored paths and the prior map value.
var configMap map[string]cty.Value var configMap map[string]cty.Value
@ -1234,11 +1248,17 @@ func processIgnoreChangesIndividual(prior, config cty.Value, ignoreChanges []hcl
// return null for a key with a null value and for a non-existent // return null for a key with a null value and for a non-existent
// key. // key.
var priorMap map[string]cty.Value var priorMap map[string]cty.Value
// We need to drop the marks from the ignored map for handling. We
// don't need to store these, as we now know the ignored value is
// only within the map, not the map itself.
ignoredVal, _ := ignored.value.Unmark()
switch { switch {
case ignored.value.IsNull() || ignored.value.LengthInt() == 0: case ignored.value.IsNull() || ignoredVal.LengthInt() == 0:
priorMap = map[string]cty.Value{} priorMap = map[string]cty.Value{}
default: default:
priorMap = ignored.value.AsValueMap() priorMap = ignoredVal.AsValueMap()
} }
key := ignored.key.AsString() key := ignored.key.AsString()
@ -1254,11 +1274,18 @@ func processIgnoreChangesIndividual(prior, config cty.Value, ignoreChanges []hcl
} }
} }
var newVal cty.Value
if len(configMap) == 0 { if len(configMap) == 0 {
return cty.MapValEmpty(v.Type().ElementType()), nil newVal = cty.MapValEmpty(v.Type().ElementType())
} else {
newVal = cty.MapVal(configMap)
} }
return cty.MapVal(configMap), nil if len(vMarks) > 0 {
newVal = v.WithMarks(vMarks)
}
return newVal, nil
}) })
return ret, nil return ret, nil
} }

View File

@ -382,7 +382,7 @@ func TestProcessIgnoreChangesIndividual(t *testing.T) {
ignore[i] = trav ignore[i] = trav
} }
ret, diags := processIgnoreChangesIndividual(test.Old, test.New, ignore) ret, diags := processIgnoreChangesIndividual(test.Old, test.New, traversalsToPaths(ignore))
if diags.HasErrors() { if diags.HasErrors() {
t.Fatal(diags.Err()) t.Fatal(diags.Err())
} }