diff --git a/terraform/shadow_resource_provider.go b/terraform/shadow_resource_provider.go index 63df15527..d3b74b29e 100644 --- a/terraform/shadow_resource_provider.go +++ b/terraform/shadow_resource_provider.go @@ -123,6 +123,21 @@ func (p *shadowResourceProviderReal) Apply( return result, err } +func (p *shadowResourceProviderReal) Diff( + info *InstanceInfo, + state *InstanceState, + desired *ResourceConfig) (*InstanceDiff, error) { + result, err := p.ResourceProvider.Diff(info, state, desired) + p.Shared.Diff.SetValue(info.HumanId(), &shadowResourceProviderDiff{ + State: state, + Desired: desired, + Result: result, + ResultErr: err, + }) + + return result, err +} + // 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. @@ -143,6 +158,7 @@ type shadowResourceProviderShared struct { Validate shadow.Value Configure shadow.Value Apply shadow.KeyedValue + Diff shadow.KeyedValue } func (p *shadowResourceProviderShadow) CloseShadow() error { @@ -296,6 +312,50 @@ func (p *shadowResourceProviderShadow) Apply( return result.Result, result.ResultErr } +func (p *shadowResourceProviderShadow) Diff( + info *InstanceInfo, + state *InstanceState, + desired *ResourceConfig) (*InstanceDiff, error) { + // Unique key + key := info.HumanId() + raw := p.Shared.Diff.Value(key) + if raw == nil { + p.ErrorLock.Lock() + defer p.ErrorLock.Unlock() + p.Error = multierror.Append(p.Error, fmt.Errorf( + "Unknown 'diff' call for %q:\n\n%#v\n\n%#v", + key, state, desired)) + return nil, nil + } + + result, ok := raw.(*shadowResourceProviderDiff) + if !ok { + p.ErrorLock.Lock() + defer p.ErrorLock.Unlock() + p.Error = multierror.Append(p.Error, fmt.Errorf( + "Unknown 'diff' shadow value: %#v", raw)) + return nil, nil + } + + // Compare the parameters, which should be identical + if !state.Equal(result.State) { + p.ErrorLock.Lock() + p.Error = multierror.Append(p.Error, fmt.Errorf( + "Diff %q had unequal states (real, then shadow):\n\n%#v\n\n%#v", + key, result.State, state)) + p.ErrorLock.Unlock() + } + if !desired.Equal(result.Desired) { + p.ErrorLock.Lock() + p.Error = multierror.Append(p.Error, fmt.Errorf( + "Diff %q had unequal states (real, then shadow):\n\n%#v\n\n%#v", + key, result.Desired, desired)) + p.ErrorLock.Unlock() + } + + return result.Result, result.ResultErr +} + // TODO // TODO // TODO @@ -306,13 +366,6 @@ func (p *shadowResourceProviderShadow) ValidateResource(t string, c *ResourceCon return nil, nil } -func (p *shadowResourceProviderShadow) Diff( - info *InstanceInfo, - state *InstanceState, - desired *ResourceConfig) (*InstanceDiff, error) { - return nil, nil -} - func (p *shadowResourceProviderShadow) Refresh( info *InstanceInfo, s *InstanceState) (*InstanceState, error) { @@ -365,3 +418,10 @@ type shadowResourceProviderApply struct { Result *InstanceState ResultErr error } + +type shadowResourceProviderDiff struct { + State *InstanceState + Desired *ResourceConfig + Result *InstanceDiff + ResultErr error +} diff --git a/terraform/shadow_resource_provider_test.go b/terraform/shadow_resource_provider_test.go index 4c185f3ce..622a92631 100644 --- a/terraform/shadow_resource_provider_test.go +++ b/terraform/shadow_resource_provider_test.go @@ -319,3 +319,57 @@ func TestShadowResourceProviderApply(t *testing.T) { t.Fatalf("bad: %s", err) } } + +func TestShadowResourceProviderDiff(t *testing.T) { + mock := new(MockResourceProvider) + real, shadow := newShadowResourceProvider(mock) + + // Test values + info := &InstanceInfo{Id: "foo"} + state := &InstanceState{ID: "foo"} + desired := testResourceConfig(t, map[string]interface{}{"foo": "bar"}) + mockResult := &InstanceDiff{Destroy: true} + + // Configure the mock + mock.DiffReturn = mockResult + + // Verify that it blocks until the real func is called + var result *InstanceDiff + var err error + doneCh := make(chan struct{}) + go func() { + defer close(doneCh) + result, err = shadow.Diff(info, state, desired) + }() + + select { + case <-doneCh: + t.Fatal("should block until finished") + case <-time.After(10 * time.Millisecond): + } + + // Call the real func + realResult, realErr := real.Diff(info, state, desired) + if !reflect.DeepEqual(realResult, mockResult) { + t.Fatalf("bad: %#v", realResult) + } + if realErr != nil { + t.Fatalf("bad: %#v", realErr) + } + + // The shadow should finish now + <-doneCh + + // Verify the shadow returned the same values + if !reflect.DeepEqual(result, mockResult) { + t.Fatalf("bad: %#v", result) + } + if err != nil { + t.Fatalf("bad: %#v", err) + } + + // Verify we have no errors + if err := shadow.CloseShadow(); err != nil { + t.Fatalf("bad: %s", err) + } +}