core: EvalApply must run to completion even if provider produces errors

Although we have a special case where a result of the wrong type will bail
early, we must keep that set of diagnostics separate so that we can still
run to completion when there are _already_ diagnostics present (from the
provider's response) but the return value _is_ type-conforming.

This fix is verified by TestContext2Apply_provisionerCreateFail.
This commit is contained in:
Martin Atkins 2018-09-14 15:25:59 -07:00
parent 9eb32c4536
commit ccd1b1df53
2 changed files with 19 additions and 11 deletions

View File

@ -4378,10 +4378,7 @@ func TestContext2Apply_provisionerCreateFail(t *testing.T) {
pr := testProvisioner()
p.DiffFn = testDiffFn
p.ApplyFn = func(
info *InstanceInfo,
is *InstanceState,
id *InstanceDiff) (*InstanceState, error) {
p.ApplyFn = func(info *InstanceInfo, is *InstanceState, id *InstanceDiff) (*InstanceState, error) {
is.ID = "foo"
return is, fmt.Errorf("error")
}
@ -4407,10 +4404,10 @@ func TestContext2Apply_provisionerCreateFail(t *testing.T) {
t.Fatal("should error")
}
actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyProvisionerFailCreateStr)
if actual != expected {
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
got := strings.TrimSpace(state.String())
want := strings.TrimSpace(testTerraformApplyProvisionerFailCreateStr)
if got != want {
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", got, want)
}
}

View File

@ -95,20 +95,31 @@ func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) {
newVal = cty.NullVal(schema.ImpliedType())
}
var conformDiags tfdiags.Diagnostics
for _, err := range newVal.Type().TestConformance(schema.ImpliedType()) {
diags = diags.Append(tfdiags.Sourceless(
conformDiags = conformDiags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Provider produced invalid object",
fmt.Sprintf(
"Provider %q planned an invalid value after apply for %s. The result could not be saved.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
"Provider %q planned an invalid value after apply for %s. The result cannot not be saved in the Terraform state.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
n.ProviderAddr.ProviderConfig.Type, tfdiags.FormatErrorPrefixed(err, absAddr.String()),
),
))
}
if diags.HasErrors() {
diags = diags.Append(conformDiags)
if conformDiags.HasErrors() {
// Bail early in this particular case, because an object that doesn't
// conform to the schema can't be saved in the state anyway -- the
// serializer will reject it.
return nil, diags.Err()
}
// After this point we have a type-conforming result object and so we
// must always run to completion to ensure it can be saved. If n.Error
// is set then we must not return a non-nil error, in order to allow
// evaluation to continue to a later point where our state object will
// be saved.
// By this point there must not be any unknown values remaining in our
// object, because we've applied the change and we can't save unknowns
// in our persistent state. If any are present then we will indicate an