diff --git a/terraform/shadow_resource_provider.go b/terraform/shadow_resource_provider.go index a9a1b2dc7..63df15527 100644 --- a/terraform/shadow_resource_provider.go +++ b/terraform/shadow_resource_provider.go @@ -108,6 +108,21 @@ func (p *shadowResourceProviderReal) Configure(c *ResourceConfig) error { return err } +func (p *shadowResourceProviderReal) Apply( + info *InstanceInfo, + state *InstanceState, + diff *InstanceDiff) (*InstanceState, error) { + result, err := p.ResourceProvider.Apply(info, state, diff) + p.Shared.Apply.SetValue(info.HumanId(), &shadowResourceProviderApply{ + State: state, + Diff: diff, + 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. @@ -127,6 +142,7 @@ type shadowResourceProviderShared struct { Input shadow.Value Validate shadow.Value Configure shadow.Value + Apply shadow.KeyedValue } func (p *shadowResourceProviderShadow) CloseShadow() error { @@ -241,6 +257,45 @@ func (p *shadowResourceProviderShadow) Configure(c *ResourceConfig) error { return result.Result } +func (p *shadowResourceProviderShadow) Apply( + info *InstanceInfo, + state *InstanceState, + diff *InstanceDiff) (*InstanceState, error) { + // Unique key + key := info.HumanId() + raw := p.Shared.Apply.Value(key) + if raw == nil { + p.ErrorLock.Lock() + defer p.ErrorLock.Unlock() + p.Error = multierror.Append(p.Error, fmt.Errorf( + "Unknown 'apply' call for %q:\n\n%#v\n\n%#v", + key, state, diff)) + return nil, nil + } + + result, ok := raw.(*shadowResourceProviderApply) + if !ok { + p.ErrorLock.Lock() + defer p.ErrorLock.Unlock() + p.Error = multierror.Append(p.Error, fmt.Errorf( + "Unknown 'apply' 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( + "State had unequal states (real, then shadow):\n\n%#v\n\n%#v", + result.State, state)) + p.ErrorLock.Unlock() + } + + // TODO: compare diffs + + return result.Result, result.ResultErr +} + // TODO // TODO // TODO @@ -251,13 +306,6 @@ func (p *shadowResourceProviderShadow) ValidateResource(t string, c *ResourceCon return nil, nil } -func (p *shadowResourceProviderShadow) Apply( - info *InstanceInfo, - state *InstanceState, - diff *InstanceDiff) (*InstanceState, error) { - return nil, nil -} - func (p *shadowResourceProviderShadow) Diff( info *InstanceInfo, state *InstanceState, @@ -310,3 +358,10 @@ type shadowResourceProviderConfigure struct { Config *ResourceConfig Result error } + +type shadowResourceProviderApply struct { + State *InstanceState + Diff *InstanceDiff + Result *InstanceState + ResultErr error +} diff --git a/terraform/shadow_resource_provider_test.go b/terraform/shadow_resource_provider_test.go index 9c36fd546..4c185f3ce 100644 --- a/terraform/shadow_resource_provider_test.go +++ b/terraform/shadow_resource_provider_test.go @@ -265,3 +265,57 @@ func TestShadowResourceProviderConfigure_badInput(t *testing.T) { t.Fatal("should have error") } } + +func TestShadowResourceProviderApply(t *testing.T) { + mock := new(MockResourceProvider) + real, shadow := newShadowResourceProvider(mock) + + // Test values + info := &InstanceInfo{Id: "foo"} + state := &InstanceState{ID: "foo"} + diff := &InstanceDiff{Destroy: true} + mockResult := &InstanceState{ID: "bar"} + + // Configure the mock + mock.ApplyReturn = mockResult + + // Verify that it blocks until the real func is called + var result *InstanceState + var err error + doneCh := make(chan struct{}) + go func() { + defer close(doneCh) + result, err = shadow.Apply(info, state, diff) + }() + + select { + case <-doneCh: + t.Fatal("should block until finished") + case <-time.After(10 * time.Millisecond): + } + + // Call the real func + realResult, realErr := real.Apply(info, state, diff) + if !realResult.Equal(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 !result.Equal(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) + } +}