diff --git a/builtin/providers/test/resource.go b/builtin/providers/test/resource.go index 9aa384791..490e539f9 100644 --- a/builtin/providers/test/resource.go +++ b/builtin/providers/test/resource.go @@ -1,6 +1,7 @@ package test import ( + "errors" "fmt" "github.com/hashicorp/terraform/helper/schema" @@ -123,6 +124,11 @@ func testResource() *schema.Resource { }, }, }, + "apply_error": { + Type: schema.TypeString, + Optional: true, + Description: "return and error during apply", + }, }, } } @@ -130,6 +136,11 @@ func testResource() *schema.Resource { func testResourceCreate(d *schema.ResourceData, meta interface{}) error { d.SetId("testId") + errMsg, _ := d.Get("apply_error").(string) + if errMsg != "" { + return errors.New(errMsg) + } + // Required must make it through to Create if _, ok := d.GetOk("required"); !ok { return fmt.Errorf("Missing attribute 'required', but it's required!") @@ -156,6 +167,10 @@ func testResourceRead(d *schema.ResourceData, meta interface{}) error { } func testResourceUpdate(d *schema.ResourceData, meta interface{}) error { + errMsg, _ := d.Get("apply_error").(string) + if errMsg != "" { + return errors.New(errMsg) + } return nil } diff --git a/builtin/providers/test/resource_test.go b/builtin/providers/test/resource_test.go index 05fd5c0c5..2b210d00b 100644 --- a/builtin/providers/test/resource_test.go +++ b/builtin/providers/test/resource_test.go @@ -2,6 +2,7 @@ package test import ( "reflect" + "regexp" "strings" "testing" @@ -624,3 +625,55 @@ resource "test_resource" "foo" { }, }) } + +func TestResource_updateError(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + Providers: testAccProviders, + CheckDestroy: testAccCheckResourceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: strings.TrimSpace(` +resource "test_resource" "foo" { + required = "first" + required_map = { + a = "a" + } +} +`), + }, + resource.TestStep{ + Config: strings.TrimSpace(` +resource "test_resource" "foo" { + required = "second" + required_map = { + a = "a" + } + apply_error = "update_error" +} +`), + ExpectError: regexp.MustCompile("update_error"), + }, + }, + }) +} + +func TestResource_applyError(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + Providers: testAccProviders, + CheckDestroy: testAccCheckResourceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: strings.TrimSpace(` +resource "test_resource" "foo" { + required = "second" + required_map = { + a = "a" + } + apply_error = "apply_error" +} +`), + ExpectError: regexp.MustCompile("apply_error"), + }, + }, + }) +} diff --git a/helper/plugin/grpc_provider.go b/helper/plugin/grpc_provider.go index 709441b47..d83d484b6 100644 --- a/helper/plugin/grpc_provider.go +++ b/helper/plugin/grpc_provider.go @@ -656,7 +656,10 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl } func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.ApplyResourceChange_Request) (*proto.ApplyResourceChange_Response, error) { - resp := &proto.ApplyResourceChange_Response{} + resp := &proto.ApplyResourceChange_Response{ + // Start with the existing state as a fallback + NewState: req.PriorState, + } res := s.provider.ResourcesMap[req.TypeName] block := res.CoreConfigSchema() @@ -753,15 +756,17 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A } newInstanceState, err := s.provider.Apply(info, priorState, diff) + // we record the error here, but continue processing any returned state. if err != nil { resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) - return resp, nil } newStateVal := cty.NullVal(block.ImpliedType()) - // always return a nul value for destroy - if newInstanceState == nil || destroy { + // Always return a null value for destroy. + // While this is usually indicated by a nil state, check for missing ID or + // attributes in the case of a provider failure. + if destroy || newInstanceState == nil || newInstanceState.Attributes == nil || newInstanceState.ID == "" { newStateMP, err := msgpack.Marshal(newStateVal, block.ImpliedType()) if err != nil { resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)