diff --git a/terraform/shadow_resource_provider.go b/terraform/shadow_resource_provider.go index 84dc349be..d5ab83afc 100644 --- a/terraform/shadow_resource_provider.go +++ b/terraform/shadow_resource_provider.go @@ -48,6 +48,9 @@ func newShadowResourceProvider(p ResourceProvider) (ResourceProvider, shadowReso // Create the shadow that watches the real value shadow := &shadowResourceProviderShadow{ Shared: &shared, + + resources: p.Resources(), + dataSources: p.DataSources(), } return real, shadow @@ -62,18 +65,6 @@ type shadowResourceProviderReal struct { Shared *shadowResourceProviderShared } -func (p *shadowResourceProviderReal) Resources() []ResourceType { - result := p.ResourceProvider.Resources() - p.Shared.Resources.SetValue(result) - return result -} - -func (p *shadowResourceProviderReal) DataSources() []DataSource { - result := p.ResourceProvider.DataSources() - p.Shared.DataSources.SetValue(result) - return result -} - func (p *shadowResourceProviderReal) Close() error { var result error if c, ok := p.ResourceProvider.(ResourceProviderCloser); ok { @@ -88,8 +79,8 @@ func (p *shadowResourceProviderReal) Input( input UIInput, c *ResourceConfig) (*ResourceConfig, error) { result, err := p.ResourceProvider.Input(input, c) p.Shared.Input.SetValue(&shadowResourceProviderInput{ - Config: c, - Result: result, + Config: c.DeepCopy(), + Result: result.DeepCopy(), ResultErr: err, }) @@ -102,35 +93,32 @@ func (p *shadowResourceProviderReal) Input( type shadowResourceProviderShadow struct { Shared *shadowResourceProviderShared + // Cached values that are expected to not change + resources []ResourceType + dataSources []DataSource + Error error // Error is the list of errors from the shadow ErrorLock sync.Mutex } type shadowResourceProviderShared struct { - CloseErr shadow.Value - Input shadow.Value - Resources shadow.Value - DataSources shadow.Value + CloseErr shadow.Value + Input shadow.Value } -func (p *shadowResourceProviderShadow) CloseShadow() error { return nil } +func (p *shadowResourceProviderShadow) CloseShadow() error { + // For now, just return the error. What we need to do in the future + // is marked the provider as "closed" so that any subsequent calls + // will fail out. + return p.Error +} func (p *shadowResourceProviderShadow) Resources() []ResourceType { - v := p.Shared.Resources.Value() - if v == nil { - return nil - } - - return v.([]ResourceType) + return p.resources } func (p *shadowResourceProviderShadow) DataSources() []DataSource { - v := p.Shared.DataSources.Value() - if v == nil { - return nil - } - - return v.([]DataSource) + return p.dataSources } func (p *shadowResourceProviderShadow) Close() error { diff --git a/terraform/shadow_resource_provider_test.go b/terraform/shadow_resource_provider_test.go new file mode 100644 index 000000000..2acb4181c --- /dev/null +++ b/terraform/shadow_resource_provider_test.go @@ -0,0 +1,115 @@ +package terraform + +import ( + "reflect" + "testing" + "time" +) + +func TestShadowResourceProvider_cachedValues(t *testing.T) { + mock := new(MockResourceProvider) + real, shadow := newShadowResourceProvider(mock) + + // Resources + { + actual := shadow.Resources() + expected := real.Resources() + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad:\n\n%#v\n\n%#v", actual, expected) + } + } + + // DataSources + { + actual := shadow.DataSources() + expected := real.DataSources() + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad:\n\n%#v\n\n%#v", actual, expected) + } + } +} + +func TestShadowResourceProviderInput(t *testing.T) { + mock := new(MockResourceProvider) + real, shadow := newShadowResourceProvider(mock) + + // Test values + ui := new(MockUIInput) + config := testResourceConfig(t, map[string]interface{}{ + "foo": "bar", + }) + returnConfig := testResourceConfig(t, map[string]interface{}{ + "bar": "baz", + }) + + // Configure the mock + mock.InputReturnConfig = returnConfig + + // Verify that it blocks until the real input is called + var actual *ResourceConfig + var err error + doneCh := make(chan struct{}) + go func() { + defer close(doneCh) + actual, err = shadow.Input(ui, config) + }() + + select { + case <-doneCh: + t.Fatal("should block until finished") + case <-time.After(10 * time.Millisecond): + } + + // Call the real input + realResult, realErr := real.Input(ui, config) + if !realResult.Equal(returnConfig) { + t.Fatalf("bad: %#v", realResult) + } + if realErr != nil { + t.Fatalf("bad: %s", realErr) + } + + // The shadow should finish now + <-doneCh + + // Verify the shadow returned the same values + if !actual.Equal(returnConfig) { + t.Fatalf("bad: %#v", actual) + } + if err != nil { + t.Fatalf("bad: %s", err) + } + + // Verify we have no errors + if err := shadow.CloseShadow(); err != nil { + t.Fatalf("bad: %s", err) + } +} + +func TestShadowResourceProviderInput_badInput(t *testing.T) { + mock := new(MockResourceProvider) + real, shadow := newShadowResourceProvider(mock) + + // Test values + ui := new(MockUIInput) + config := testResourceConfig(t, map[string]interface{}{ + "foo": "bar", + }) + configBad := testResourceConfig(t, map[string]interface{}{ + "foo": "nope", + }) + + // Call the real with one + real.Input(ui, config) + + // Call the shadow with another + _, err := shadow.Input(ui, configBad) + if err != nil { + t.Fatalf("bad: %s", err) + } + + // Verify we have an error + if err := shadow.CloseShadow(); err == nil { + t.Fatal("should have error") + } +}