validate the configuration before ignore_changes

The ignore_changes option `all` can cause computed attributes to show up
in the validation configuration, which will be rejected by the provider
SDK. Validate the config before applying the ignore_changes options.

In the future it we should probably have a way for processIgnoreChanges
to skip computed values based on the schema. Since we also want a way to
more easily query the schema for "computed-ness" to validate the
ignore_changes arguments for computed values, we can fix these at the
same time with a future change to configschema. This will most likely
require some sort of method to retrieve info from the configschema.Block
via cty.Path, which we cannot easily do right now.
This commit is contained in:
James Bardin 2020-12-03 17:58:46 -05:00
parent 02e7efab9e
commit 4f4e8c17e0
2 changed files with 78 additions and 15 deletions

View File

@ -6539,3 +6539,63 @@ resource "test_instance" "a" {
} }
} }
} }
func TestContext2Plan_validateIgnoreAll(t *testing.T) {
m := testModuleInline(t, map[string]string{
"main.tf": `
resource "test_instance" "a" {
lifecycle {
ignore_changes = all
}
}
`,
})
p := testProvider("test")
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"test_instance": {
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Computed: true},
"data": {Type: cty.String, Optional: true},
},
},
},
}
p.PlanResourceChangeFn = testDiffFn
p.ValidateResourceTypeConfigFn = func(req providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse {
var diags tfdiags.Diagnostics
if req.TypeName == "test_instance" {
if !req.Config.GetAttr("id").IsNull() {
diags = diags.Append(errors.New("id cannot be set in config"))
}
}
return providers.ValidateResourceTypeConfigResponse{
Diagnostics: diags,
}
}
state := states.NewState()
root := state.EnsureModule(addrs.RootModuleInstance)
root.SetResourceInstanceCurrent(
mustResourceInstanceAddr("test_instance.a").Resource,
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"id":"a","data":"foo"}`),
Dependencies: []addrs.ConfigResource{},
},
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,
})
_, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatal(diags.Err())
}
}

View File

@ -212,6 +212,24 @@ func (n *EvalDiff) Eval(ctx EvalContext) tfdiags.Diagnostics {
unmarkedConfigVal, unmarkedPaths := origConfigVal.UnmarkDeepWithPaths() unmarkedConfigVal, unmarkedPaths := origConfigVal.UnmarkDeepWithPaths()
unmarkedPriorVal, priorPaths := priorVal.UnmarkDeepWithPaths() unmarkedPriorVal, priorPaths := priorVal.UnmarkDeepWithPaths()
log.Printf("[TRACE] Re-validating config for %q", n.Addr.Absolute(ctx.Path()))
// Allow the provider to validate the final set of values.
// The config was statically validated early on, but there may have been
// unknown values which the provider could not validate at the time.
// TODO: It would be more correct to validate the config after
// ignore_changes has been applied, but the current implementation cannot
// exclude computed-only attributes when given the `all` option.
validateResp := provider.ValidateResourceTypeConfig(
providers.ValidateResourceTypeConfigRequest{
TypeName: n.Addr.Resource.Type,
Config: unmarkedConfigVal,
},
)
if validateResp.Diagnostics.HasErrors() {
diags = diags.Append(validateResp.Diagnostics.InConfigBody(config.Config))
return diags
}
// ignore_changes is meant to only apply to the configuration, so it must // ignore_changes is meant to only apply to the configuration, so it must
// be applied before we generate a plan. This ensures the config used for // be applied before we generate a plan. This ensures the config used for
// the proposed value, the proposed value itself, and the config presented // the proposed value, the proposed value itself, and the config presented
@ -235,21 +253,6 @@ func (n *EvalDiff) Eval(ctx EvalContext) tfdiags.Diagnostics {
} }
} }
log.Printf("[TRACE] Re-validating config for %q", n.Addr.Absolute(ctx.Path()))
// Allow the provider to validate the final set of values.
// The config was statically validated early on, but there may have been
// unknown values which the provider could not validate at the time.
validateResp := provider.ValidateResourceTypeConfig(
providers.ValidateResourceTypeConfigRequest{
TypeName: n.Addr.Resource.Type,
Config: configValIgnored,
},
)
if validateResp.Diagnostics.HasErrors() {
diags = diags.Append(validateResp.Diagnostics.InConfigBody(config.Config))
return diags
}
resp := provider.PlanResourceChange(providers.PlanResourceChangeRequest{ resp := provider.PlanResourceChange(providers.PlanResourceChangeRequest{
TypeName: n.Addr.Resource.Type, TypeName: n.Addr.Resource.Type,
Config: configValIgnored, Config: configValIgnored,