Merge pull request #20295 from hashicorp/jbardin/apply-error

process state even after provider.Apply errors
This commit is contained in:
James Bardin 2019-02-11 16:51:06 -05:00 committed by GitHub
commit d871ce63fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 77 additions and 4 deletions

View File

@ -1,6 +1,7 @@
package test package test
import ( import (
"errors"
"fmt" "fmt"
"github.com/hashicorp/terraform/helper/schema" "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 { func testResourceCreate(d *schema.ResourceData, meta interface{}) error {
d.SetId("testId") d.SetId("testId")
errMsg, _ := d.Get("apply_error").(string)
if errMsg != "" {
return errors.New(errMsg)
}
// Required must make it through to Create // Required must make it through to Create
if _, ok := d.GetOk("required"); !ok { if _, ok := d.GetOk("required"); !ok {
return fmt.Errorf("Missing attribute 'required', but it's required!") 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 { func testResourceUpdate(d *schema.ResourceData, meta interface{}) error {
errMsg, _ := d.Get("apply_error").(string)
if errMsg != "" {
return errors.New(errMsg)
}
return nil return nil
} }

View File

@ -2,6 +2,7 @@ package test
import ( import (
"reflect" "reflect"
"regexp"
"strings" "strings"
"testing" "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"),
},
},
})
}

View File

@ -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) { 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] res := s.provider.ResourcesMap[req.TypeName]
block := res.CoreConfigSchema() block := res.CoreConfigSchema()
@ -753,15 +756,17 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A
} }
newInstanceState, err := s.provider.Apply(info, priorState, diff) newInstanceState, err := s.provider.Apply(info, priorState, diff)
// we record the error here, but continue processing any returned state.
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
} }
newStateVal := cty.NullVal(block.ImpliedType()) newStateVal := cty.NullVal(block.ImpliedType())
// always return a nul value for destroy // Always return a null value for destroy.
if newInstanceState == nil || 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()) newStateMP, err := msgpack.Marshal(newStateVal, block.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)