| package terraform |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "sync" |
| |
| "github.com/zclconf/go-cty/cty" |
| ctyjson "github.com/zclconf/go-cty/cty/json" |
| |
| "github.com/hashicorp/terraform/config" |
| "github.com/hashicorp/terraform/config/hcl2shim" |
| "github.com/hashicorp/terraform/providers" |
| "github.com/hashicorp/terraform/tfdiags" |
| ) |
| |
| var _ providers.Interface = (*MockProvider)(nil) |
| |
| // MockProvider implements providers.Interface but mocks out all the |
| // calls for testing purposes. |
| type MockProvider struct { |
| sync.Mutex |
| |
| // Anything you want, in case you need to store extra data with the mock. |
| Meta interface{} |
| |
| GetSchemaCalled bool |
| GetSchemaReturn *ProviderSchema // This is using ProviderSchema directly rather than providers.GetSchemaResponse for compatibility with old tests |
| |
| PrepareProviderConfigCalled bool |
| PrepareProviderConfigResponse providers.PrepareProviderConfigResponse |
| PrepareProviderConfigRequest providers.PrepareProviderConfigRequest |
| PrepareProviderConfigFn func(providers.PrepareProviderConfigRequest) providers.PrepareProviderConfigResponse |
| |
| ValidateResourceTypeConfigCalled bool |
| ValidateResourceTypeConfigTypeName string |
| ValidateResourceTypeConfigResponse providers.ValidateResourceTypeConfigResponse |
| ValidateResourceTypeConfigRequest providers.ValidateResourceTypeConfigRequest |
| ValidateResourceTypeConfigFn func(providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse |
| |
| ValidateDataSourceConfigCalled bool |
| ValidateDataSourceConfigTypeName string |
| ValidateDataSourceConfigResponse providers.ValidateDataSourceConfigResponse |
| ValidateDataSourceConfigRequest providers.ValidateDataSourceConfigRequest |
| ValidateDataSourceConfigFn func(providers.ValidateDataSourceConfigRequest) providers.ValidateDataSourceConfigResponse |
| |
| UpgradeResourceStateCalled bool |
| UpgradeResourceStateTypeName string |
| UpgradeResourceStateResponse providers.UpgradeResourceStateResponse |
| UpgradeResourceStateRequest providers.UpgradeResourceStateRequest |
| UpgradeResourceStateFn func(providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse |
| |
| ConfigureCalled bool |
| ConfigureResponse providers.ConfigureResponse |
| ConfigureRequest providers.ConfigureRequest |
| ConfigureNewFn func(providers.ConfigureRequest) providers.ConfigureResponse // Named ConfigureNewFn so we can still have the legacy ConfigureFn declared below |
| |
| StopCalled bool |
| StopFn func() error |
| StopResponse error |
| |
| ReadResourceCalled bool |
| ReadResourceResponse providers.ReadResourceResponse |
| ReadResourceRequest providers.ReadResourceRequest |
| ReadResourceFn func(providers.ReadResourceRequest) providers.ReadResourceResponse |
| |
| PlanResourceChangeCalled bool |
| PlanResourceChangeResponse providers.PlanResourceChangeResponse |
| PlanResourceChangeRequest providers.PlanResourceChangeRequest |
| PlanResourceChangeFn func(providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse |
| |
| ApplyResourceChangeCalled bool |
| ApplyResourceChangeResponse providers.ApplyResourceChangeResponse |
| ApplyResourceChangeRequest providers.ApplyResourceChangeRequest |
| ApplyResourceChangeFn func(providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse |
| |
| ImportResourceStateCalled bool |
| ImportResourceStateResponse providers.ImportResourceStateResponse |
| ImportResourceStateRequest providers.ImportResourceStateRequest |
| ImportResourceStateFn func(providers.ImportResourceStateRequest) providers.ImportResourceStateResponse |
| // Legacy return type for existing tests, which will be shimmed into an |
| // ImportResourceStateResponse if set |
| ImportStateReturn []*InstanceState |
| |
| ReadDataSourceCalled bool |
| ReadDataSourceResponse providers.ReadDataSourceResponse |
| ReadDataSourceRequest providers.ReadDataSourceRequest |
| ReadDataSourceFn func(providers.ReadDataSourceRequest) providers.ReadDataSourceResponse |
| |
| CloseCalled bool |
| CloseError error |
| |
| // Legacy callbacks: if these are set, we will shim incoming calls for |
| // new-style methods to these old-fashioned terraform.ResourceProvider |
| // mock callbacks, for the benefit of older tests that were written against |
| // the old mock API. |
| ValidateFn func(c *ResourceConfig) (ws []string, es []error) |
| ConfigureFn func(c *ResourceConfig) error |
| DiffFn func(info *InstanceInfo, s *InstanceState, c *ResourceConfig) (*InstanceDiff, error) |
| ApplyFn func(info *InstanceInfo, s *InstanceState, d *InstanceDiff) (*InstanceState, error) |
| } |
| |
| func (p *MockProvider) GetSchema() providers.GetSchemaResponse { |
| p.Lock() |
| defer p.Unlock() |
| p.GetSchemaCalled = true |
| return p.getSchema() |
| } |
| |
| func (p *MockProvider) getSchema() providers.GetSchemaResponse { |
| // This version of getSchema doesn't do any locking, so it's suitable to |
| // call from other methods of this mock as long as they are already |
| // holding the lock. |
| |
| ret := providers.GetSchemaResponse{ |
| Provider: providers.Schema{}, |
| DataSources: map[string]providers.Schema{}, |
| ResourceTypes: map[string]providers.Schema{}, |
| } |
| if p.GetSchemaReturn != nil { |
| ret.Provider.Block = p.GetSchemaReturn.Provider |
| for n, s := range p.GetSchemaReturn.DataSources { |
| ret.DataSources[n] = providers.Schema{ |
| Block: s, |
| } |
| } |
| for n, s := range p.GetSchemaReturn.ResourceTypes { |
| ret.ResourceTypes[n] = providers.Schema{ |
| Version: int64(p.GetSchemaReturn.ResourceTypeSchemaVersions[n]), |
| Block: s, |
| } |
| } |
| } |
| |
| return ret |
| } |
| |
| func (p *MockProvider) PrepareProviderConfig(r providers.PrepareProviderConfigRequest) providers.PrepareProviderConfigResponse { |
| p.Lock() |
| defer p.Unlock() |
| |
| p.PrepareProviderConfigCalled = true |
| p.PrepareProviderConfigRequest = r |
| if p.PrepareProviderConfigFn != nil { |
| return p.PrepareProviderConfigFn(r) |
| } |
| return p.PrepareProviderConfigResponse |
| } |
| |
| func (p *MockProvider) ValidateResourceTypeConfig(r providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse { |
| p.Lock() |
| defer p.Unlock() |
| |
| p.ValidateResourceTypeConfigCalled = true |
| p.ValidateResourceTypeConfigRequest = r |
| |
| if p.ValidateFn != nil { |
| resp := p.getSchema() |
| schema := resp.Provider.Block |
| rc := NewResourceConfigShimmed(r.Config, schema) |
| warns, errs := p.ValidateFn(rc) |
| ret := providers.ValidateResourceTypeConfigResponse{} |
| for _, warn := range warns { |
| ret.Diagnostics = ret.Diagnostics.Append(tfdiags.SimpleWarning(warn)) |
| } |
| for _, err := range errs { |
| ret.Diagnostics = ret.Diagnostics.Append(err) |
| } |
| } |
| if p.ValidateResourceTypeConfigFn != nil { |
| return p.ValidateResourceTypeConfigFn(r) |
| } |
| |
| return p.ValidateResourceTypeConfigResponse |
| } |
| |
| func (p *MockProvider) ValidateDataSourceConfig(r providers.ValidateDataSourceConfigRequest) providers.ValidateDataSourceConfigResponse { |
| p.Lock() |
| defer p.Unlock() |
| |
| p.ValidateDataSourceConfigCalled = true |
| p.ValidateDataSourceConfigRequest = r |
| |
| if p.ValidateDataSourceConfigFn != nil { |
| return p.ValidateDataSourceConfigFn(r) |
| } |
| |
| return p.ValidateDataSourceConfigResponse |
| } |
| |
| func (p *MockProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse { |
| p.Lock() |
| defer p.Unlock() |
| |
| schemas := p.getSchema() |
| schema := schemas.ResourceTypes[r.TypeName] |
| schemaType := schema.Block.ImpliedType() |
| |
| p.UpgradeResourceStateCalled = true |
| p.UpgradeResourceStateRequest = r |
| |
| if p.UpgradeResourceStateFn != nil { |
| return p.UpgradeResourceStateFn(r) |
| } |
| |
| resp := p.UpgradeResourceStateResponse |
| |
| if resp.UpgradedState == cty.NilVal { |
| switch { |
| case r.RawStateFlatmap != nil: |
| v, err := hcl2shim.HCL2ValueFromFlatmap(r.RawStateFlatmap, schemaType) |
| if err != nil { |
| resp.Diagnostics = resp.Diagnostics.Append(err) |
| return resp |
| } |
| resp.UpgradedState = v |
| case len(r.RawStateJSON) > 0: |
| v, err := ctyjson.Unmarshal(r.RawStateJSON, schemaType) |
| |
| if err != nil { |
| resp.Diagnostics = resp.Diagnostics.Append(err) |
| return resp |
| } |
| resp.UpgradedState = v |
| } |
| } |
| return resp |
| } |
| |
| func (p *MockProvider) Configure(r providers.ConfigureRequest) providers.ConfigureResponse { |
| p.Lock() |
| defer p.Unlock() |
| |
| p.ConfigureCalled = true |
| p.ConfigureRequest = r |
| |
| if p.ConfigureFn != nil { |
| resp := p.getSchema() |
| schema := resp.Provider.Block |
| rc := NewResourceConfigShimmed(r.Config, schema) |
| ret := providers.ConfigureResponse{} |
| |
| err := p.ConfigureFn(rc) |
| if err != nil { |
| ret.Diagnostics = ret.Diagnostics.Append(err) |
| } |
| return ret |
| } |
| if p.ConfigureNewFn != nil { |
| return p.ConfigureNewFn(r) |
| } |
| |
| return p.ConfigureResponse |
| } |
| |
| func (p *MockProvider) Stop() error { |
| // We intentionally don't lock in this one because the whole point of this |
| // method is to be called concurrently with another operation that can |
| // be cancelled. The provider itself is responsible for handling |
| // any concurrency concerns in this case. |
| |
| p.StopCalled = true |
| if p.StopFn != nil { |
| return p.StopFn() |
| } |
| |
| return p.StopResponse |
| } |
| |
| func (p *MockProvider) ReadResource(r providers.ReadResourceRequest) providers.ReadResourceResponse { |
| p.Lock() |
| defer p.Unlock() |
| |
| p.ReadResourceCalled = true |
| p.ReadResourceRequest = r |
| |
| if p.ReadResourceFn != nil { |
| return p.ReadResourceFn(r) |
| } |
| |
| // make sure the NewState fits the schema |
| newState, err := p.GetSchemaReturn.ResourceTypes[r.TypeName].CoerceValue(p.ReadResourceResponse.NewState) |
| if err != nil { |
| panic(err) |
| } |
| resp := p.ReadResourceResponse |
| resp.NewState = newState |
| |
| return resp |
| } |
| |
| func (p *MockProvider) PlanResourceChange(r providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { |
| p.Lock() |
| defer p.Unlock() |
| |
| p.PlanResourceChangeCalled = true |
| p.PlanResourceChangeRequest = r |
| |
| if p.DiffFn != nil { |
| ps := p.getSchema() |
| if ps.ResourceTypes == nil || ps.ResourceTypes[r.TypeName].Block == nil { |
| return providers.PlanResourceChangeResponse{ |
| Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Printf("mock provider has no schema for resource type %s", r.TypeName)), |
| } |
| } |
| schema := ps.ResourceTypes[r.TypeName].Block |
| info := &InstanceInfo{ |
| Type: r.TypeName, |
| } |
| priorState := NewInstanceStateShimmedFromValue(r.PriorState, 0) |
| cfg := NewResourceConfigShimmed(r.Config, schema) |
| |
| legacyDiff, err := p.DiffFn(info, priorState, cfg) |
| |
| var res providers.PlanResourceChangeResponse |
| res.PlannedState = r.ProposedNewState |
| if err != nil { |
| res.Diagnostics = res.Diagnostics.Append(err) |
| } |
| if legacyDiff != nil { |
| newVal, err := legacyDiff.ApplyToValue(r.PriorState, schema) |
| if err != nil { |
| res.Diagnostics = res.Diagnostics.Append(err) |
| } |
| |
| res.PlannedState = newVal |
| |
| var requiresNew []string |
| for attr, d := range legacyDiff.Attributes { |
| if d.RequiresNew { |
| requiresNew = append(requiresNew, attr) |
| } |
| } |
| requiresReplace, err := hcl2shim.RequiresReplace(requiresNew, schema.ImpliedType()) |
| if err != nil { |
| res.Diagnostics = res.Diagnostics.Append(err) |
| } |
| res.RequiresReplace = requiresReplace |
| } |
| return res |
| } |
| if p.PlanResourceChangeFn != nil { |
| return p.PlanResourceChangeFn(r) |
| } |
| |
| return p.PlanResourceChangeResponse |
| } |
| |
| func (p *MockProvider) ApplyResourceChange(r providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { |
| p.Lock() |
| p.ApplyResourceChangeCalled = true |
| p.ApplyResourceChangeRequest = r |
| p.Unlock() |
| |
| if p.ApplyFn != nil { |
| // ApplyFn is a special callback fashioned after our old provider |
| // interface, which expected to be given an actual diff rather than |
| // separate old/new values to apply. Therefore we need to approximate |
| // a diff here well enough that _most_ of our legacy ApplyFns in old |
| // tests still see the behavior they are expecting. New tests should |
| // not use this, and should instead use ApplyResourceChangeFn directly. |
| providerSchema := p.getSchema() |
| schema, ok := providerSchema.ResourceTypes[r.TypeName] |
| if !ok { |
| return providers.ApplyResourceChangeResponse{ |
| Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("no mocked schema available for resource type %s", r.TypeName)), |
| } |
| } |
| |
| info := &InstanceInfo{ |
| Type: r.TypeName, |
| } |
| |
| priorVal := r.PriorState |
| plannedVal := r.PlannedState |
| priorMap := hcl2shim.FlatmapValueFromHCL2(priorVal) |
| plannedMap := hcl2shim.FlatmapValueFromHCL2(plannedVal) |
| s := NewInstanceStateShimmedFromValue(priorVal, 0) |
| d := &InstanceDiff{ |
| Attributes: make(map[string]*ResourceAttrDiff), |
| } |
| if plannedMap == nil { // destroying, then |
| d.Destroy = true |
| // Destroy diffs don't have any attribute diffs |
| } else { |
| if priorMap == nil { // creating, then |
| // We'll just make an empty prior map to make things easier below. |
| priorMap = make(map[string]string) |
| } |
| |
| for k, new := range plannedMap { |
| old := priorMap[k] |
| newComputed := false |
| if new == config.UnknownVariableValue { |
| new = "" |
| newComputed = true |
| } |
| d.Attributes[k] = &ResourceAttrDiff{ |
| Old: old, |
| New: new, |
| NewComputed: newComputed, |
| Type: DiffAttrInput, // not generally used in tests, so just hard-coded |
| } |
| } |
| // Also need any attributes that were removed in "planned" |
| for k, old := range priorMap { |
| if _, ok := plannedMap[k]; ok { |
| continue |
| } |
| d.Attributes[k] = &ResourceAttrDiff{ |
| Old: old, |
| NewRemoved: true, |
| Type: DiffAttrInput, |
| } |
| } |
| } |
| newState, err := p.ApplyFn(info, s, d) |
| resp := providers.ApplyResourceChangeResponse{} |
| if err != nil { |
| resp.Diagnostics = resp.Diagnostics.Append(err) |
| } |
| if newState != nil { |
| var newVal cty.Value |
| if newState != nil { |
| var err error |
| newVal, err = newState.AttrsAsObjectValue(schema.Block.ImpliedType()) |
| if err != nil { |
| resp.Diagnostics = resp.Diagnostics.Append(err) |
| } |
| } else { |
| // If apply returned a nil new state then that's the old way to |
| // indicate that the object was destroyed. Our new interface calls |
| // for that to be signalled as a null value. |
| newVal = cty.NullVal(schema.Block.ImpliedType()) |
| } |
| resp.NewState = newVal |
| } |
| |
| return resp |
| } |
| if p.ApplyResourceChangeFn != nil { |
| return p.ApplyResourceChangeFn(r) |
| } |
| |
| return p.ApplyResourceChangeResponse |
| } |
| |
| func (p *MockProvider) ImportResourceState(r providers.ImportResourceStateRequest) providers.ImportResourceStateResponse { |
| p.Lock() |
| defer p.Unlock() |
| |
| if p.ImportStateReturn != nil { |
| for _, is := range p.ImportStateReturn { |
| if is.Attributes == nil { |
| is.Attributes = make(map[string]string) |
| } |
| is.Attributes["id"] = is.ID |
| |
| typeName := is.Ephemeral.Type |
| // Use the requested type if the resource has no type of it's own. |
| // We still return the empty type, which will error, but this prevents a panic. |
| if typeName == "" { |
| typeName = r.TypeName |
| } |
| |
| schema := p.GetSchemaReturn.ResourceTypes[typeName] |
| if schema == nil { |
| panic("no schema found for " + typeName) |
| } |
| |
| private, err := json.Marshal(is.Meta) |
| if err != nil { |
| panic(err) |
| } |
| |
| state, err := hcl2shim.HCL2ValueFromFlatmap(is.Attributes, schema.ImpliedType()) |
| if err != nil { |
| panic(err) |
| } |
| |
| state, err = schema.CoerceValue(state) |
| if err != nil { |
| panic(err) |
| } |
| |
| p.ImportResourceStateResponse.ImportedResources = append( |
| p.ImportResourceStateResponse.ImportedResources, |
| providers.ImportedResource{ |
| TypeName: is.Ephemeral.Type, |
| State: state, |
| Private: private, |
| }) |
| } |
| } |
| |
| p.ImportResourceStateCalled = true |
| p.ImportResourceStateRequest = r |
| if p.ImportResourceStateFn != nil { |
| return p.ImportResourceStateFn(r) |
| } |
| |
| return p.ImportResourceStateResponse |
| } |
| |
| func (p *MockProvider) ReadDataSource(r providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { |
| p.Lock() |
| defer p.Unlock() |
| |
| p.ReadDataSourceCalled = true |
| p.ReadDataSourceRequest = r |
| |
| if p.ReadDataSourceFn != nil { |
| return p.ReadDataSourceFn(r) |
| } |
| |
| return p.ReadDataSourceResponse |
| } |
| |
| func (p *MockProvider) Close() error { |
| p.CloseCalled = true |
| return p.CloseError |
| } |