diff --git a/terraform/shadow_resource_provider.go b/terraform/shadow_resource_provider.go index d5ab83afc..9d7caafb6 100644 --- a/terraform/shadow_resource_provider.go +++ b/terraform/shadow_resource_provider.go @@ -87,6 +87,17 @@ func (p *shadowResourceProviderReal) Input( return result, err } +func (p *shadowResourceProviderReal) Validate(c *ResourceConfig) ([]string, []error) { + warns, errs := p.ResourceProvider.Validate(c) + p.Shared.Validate.SetValue(&shadowResourceProviderValidate{ + Config: c.DeepCopy(), + ResultWarn: warns, + ResultErr: errs, + }) + + return warns, errs +} + // shadowResourceProviderShadow is the shadow resource provider. Function // calls never affect real resources. This is paired with the "real" side // which must be called properly to enable recording. @@ -104,6 +115,7 @@ type shadowResourceProviderShadow struct { type shadowResourceProviderShared struct { CloseErr shadow.Value Input shadow.Value + Validate shadow.Value } func (p *shadowResourceProviderShadow) CloseShadow() error { @@ -160,16 +172,41 @@ func (p *shadowResourceProviderShadow) Input( return result.Result, result.ResultErr } -// TODO -// TODO -// TODO -// TODO -// TODO - func (p *shadowResourceProviderShadow) Validate(c *ResourceConfig) ([]string, []error) { - return nil, nil + // Get the result of the validate call + raw := p.Shared.Validate.Value() + if raw == nil { + return nil, nil + } + + result, ok := raw.(*shadowResourceProviderValidate) + if !ok { + p.ErrorLock.Lock() + defer p.ErrorLock.Unlock() + p.Error = multierror.Append(p.Error, fmt.Errorf( + "Unknown 'validate' shadow value: %#v", raw)) + return nil, nil + } + + // Compare the parameters, which should be identical + if !c.Equal(result.Config) { + p.ErrorLock.Lock() + p.Error = multierror.Append(p.Error, fmt.Errorf( + "Validate had unequal configurations (real, then shadow):\n\n%#v\n\n%#v", + result.Config, c)) + p.ErrorLock.Unlock() + } + + // Return the results + return result.ResultWarn, result.ResultErr } +// TODO +// TODO +// TODO +// TODO +// TODO + func (p *shadowResourceProviderShadow) ValidateResource(t string, c *ResourceConfig) ([]string, []error) { return nil, nil } @@ -226,3 +263,9 @@ type shadowResourceProviderInput struct { Result *ResourceConfig ResultErr error } + +type shadowResourceProviderValidate struct { + Config *ResourceConfig + ResultWarn []string + ResultErr []error +} diff --git a/terraform/shadow_resource_provider_test.go b/terraform/shadow_resource_provider_test.go index 2acb4181c..b2811aa9a 100644 --- a/terraform/shadow_resource_provider_test.go +++ b/terraform/shadow_resource_provider_test.go @@ -1,6 +1,7 @@ package terraform import ( + "fmt" "reflect" "testing" "time" @@ -113,3 +114,83 @@ func TestShadowResourceProviderInput_badInput(t *testing.T) { t.Fatal("should have error") } } + +func TestShadowResourceProviderValidate(t *testing.T) { + mock := new(MockResourceProvider) + real, shadow := newShadowResourceProvider(mock) + + // Test values + config := testResourceConfig(t, map[string]interface{}{ + "foo": "bar", + }) + returnWarns := []string{"foo"} + returnErrs := []error{fmt.Errorf("bar")} + + // Configure the mock + mock.ValidateReturnWarns = returnWarns + mock.ValidateReturnErrors = returnErrs + + // Verify that it blocks until the real func is called + var warns []string + var errs []error + doneCh := make(chan struct{}) + go func() { + defer close(doneCh) + warns, errs = shadow.Validate(config) + }() + + select { + case <-doneCh: + t.Fatal("should block until finished") + case <-time.After(10 * time.Millisecond): + } + + // Call the real func + realWarns, realErrs := real.Validate(config) + if !reflect.DeepEqual(realWarns, returnWarns) { + t.Fatalf("bad: %#v", realWarns) + } + if !reflect.DeepEqual(realErrs, returnErrs) { + t.Fatalf("bad: %#v", realWarns) + } + + // The shadow should finish now + <-doneCh + + // Verify the shadow returned the same values + if !reflect.DeepEqual(warns, returnWarns) { + t.Fatalf("bad: %#v", warns) + } + if !reflect.DeepEqual(errs, returnErrs) { + t.Fatalf("bad: %#v", errs) + } + + // Verify we have no errors + if err := shadow.CloseShadow(); err != nil { + t.Fatalf("bad: %s", err) + } +} + +func TestShadowResourceProviderValidate_badInput(t *testing.T) { + mock := new(MockResourceProvider) + real, shadow := newShadowResourceProvider(mock) + + // Test values + config := testResourceConfig(t, map[string]interface{}{ + "foo": "bar", + }) + configBad := testResourceConfig(t, map[string]interface{}{ + "foo": "nope", + }) + + // Call the real with one + real.Validate(config) + + // Call the shadow with another + shadow.Validate(configBad) + + // Verify we have an error + if err := shadow.CloseShadow(); err == nil { + t.Fatal("should have error") + } +}