8 "github.com/zclconf/go-cty/cty"
9 ctyjson "github.com/zclconf/go-cty/cty/json"
11 "github.com/hashicorp/terraform/config"
12 "github.com/hashicorp/terraform/config/hcl2shim"
13 "github.com/hashicorp/terraform/providers"
14 "github.com/hashicorp/terraform/tfdiags"
17 var _ providers.Interface = (*MockProvider)(nil)
19 // MockProvider implements providers.Interface but mocks out all the
20 // calls for testing purposes.
21 type MockProvider struct {
24 // Anything you want, in case you need to store extra data with the mock.
28 GetSchemaReturn *ProviderSchema // This is using ProviderSchema directly rather than providers.GetSchemaResponse for compatibility with old tests
30 PrepareProviderConfigCalled bool
31 PrepareProviderConfigResponse providers.PrepareProviderConfigResponse
32 PrepareProviderConfigRequest providers.PrepareProviderConfigRequest
33 PrepareProviderConfigFn func(providers.PrepareProviderConfigRequest) providers.PrepareProviderConfigResponse
35 ValidateResourceTypeConfigCalled bool
36 ValidateResourceTypeConfigTypeName string
37 ValidateResourceTypeConfigResponse providers.ValidateResourceTypeConfigResponse
38 ValidateResourceTypeConfigRequest providers.ValidateResourceTypeConfigRequest
39 ValidateResourceTypeConfigFn func(providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse
41 ValidateDataSourceConfigCalled bool
42 ValidateDataSourceConfigTypeName string
43 ValidateDataSourceConfigResponse providers.ValidateDataSourceConfigResponse
44 ValidateDataSourceConfigRequest providers.ValidateDataSourceConfigRequest
45 ValidateDataSourceConfigFn func(providers.ValidateDataSourceConfigRequest) providers.ValidateDataSourceConfigResponse
47 UpgradeResourceStateCalled bool
48 UpgradeResourceStateTypeName string
49 UpgradeResourceStateResponse providers.UpgradeResourceStateResponse
50 UpgradeResourceStateRequest providers.UpgradeResourceStateRequest
51 UpgradeResourceStateFn func(providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse
54 ConfigureResponse providers.ConfigureResponse
55 ConfigureRequest providers.ConfigureRequest
56 ConfigureNewFn func(providers.ConfigureRequest) providers.ConfigureResponse // Named ConfigureNewFn so we can still have the legacy ConfigureFn declared below
62 ReadResourceCalled bool
63 ReadResourceResponse providers.ReadResourceResponse
64 ReadResourceRequest providers.ReadResourceRequest
65 ReadResourceFn func(providers.ReadResourceRequest) providers.ReadResourceResponse
67 PlanResourceChangeCalled bool
68 PlanResourceChangeResponse providers.PlanResourceChangeResponse
69 PlanResourceChangeRequest providers.PlanResourceChangeRequest
70 PlanResourceChangeFn func(providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse
72 ApplyResourceChangeCalled bool
73 ApplyResourceChangeResponse providers.ApplyResourceChangeResponse
74 ApplyResourceChangeRequest providers.ApplyResourceChangeRequest
75 ApplyResourceChangeFn func(providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse
77 ImportResourceStateCalled bool
78 ImportResourceStateResponse providers.ImportResourceStateResponse
79 ImportResourceStateRequest providers.ImportResourceStateRequest
80 ImportResourceStateFn func(providers.ImportResourceStateRequest) providers.ImportResourceStateResponse
81 // Legacy return type for existing tests, which will be shimmed into an
82 // ImportResourceStateResponse if set
83 ImportStateReturn []*InstanceState
85 ReadDataSourceCalled bool
86 ReadDataSourceResponse providers.ReadDataSourceResponse
87 ReadDataSourceRequest providers.ReadDataSourceRequest
88 ReadDataSourceFn func(providers.ReadDataSourceRequest) providers.ReadDataSourceResponse
93 // Legacy callbacks: if these are set, we will shim incoming calls for
94 // new-style methods to these old-fashioned terraform.ResourceProvider
95 // mock callbacks, for the benefit of older tests that were written against
97 ValidateFn func(c *ResourceConfig) (ws []string, es []error)
98 ConfigureFn func(c *ResourceConfig) error
99 DiffFn func(info *InstanceInfo, s *InstanceState, c *ResourceConfig) (*InstanceDiff, error)
100 ApplyFn func(info *InstanceInfo, s *InstanceState, d *InstanceDiff) (*InstanceState, error)
103 func (p *MockProvider) GetSchema() providers.GetSchemaResponse {
106 p.GetSchemaCalled = true
110 func (p *MockProvider) getSchema() providers.GetSchemaResponse {
111 // This version of getSchema doesn't do any locking, so it's suitable to
112 // call from other methods of this mock as long as they are already
115 ret := providers.GetSchemaResponse{
116 Provider: providers.Schema{},
117 DataSources: map[string]providers.Schema{},
118 ResourceTypes: map[string]providers.Schema{},
120 if p.GetSchemaReturn != nil {
121 ret.Provider.Block = p.GetSchemaReturn.Provider
122 for n, s := range p.GetSchemaReturn.DataSources {
123 ret.DataSources[n] = providers.Schema{
127 for n, s := range p.GetSchemaReturn.ResourceTypes {
128 ret.ResourceTypes[n] = providers.Schema{
129 Version: int64(p.GetSchemaReturn.ResourceTypeSchemaVersions[n]),
138 func (p *MockProvider) PrepareProviderConfig(r providers.PrepareProviderConfigRequest) providers.PrepareProviderConfigResponse {
142 p.PrepareProviderConfigCalled = true
143 p.PrepareProviderConfigRequest = r
144 if p.PrepareProviderConfigFn != nil {
145 return p.PrepareProviderConfigFn(r)
147 return p.PrepareProviderConfigResponse
150 func (p *MockProvider) ValidateResourceTypeConfig(r providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse {
154 p.ValidateResourceTypeConfigCalled = true
155 p.ValidateResourceTypeConfigRequest = r
157 if p.ValidateFn != nil {
158 resp := p.getSchema()
159 schema := resp.Provider.Block
160 rc := NewResourceConfigShimmed(r.Config, schema)
161 warns, errs := p.ValidateFn(rc)
162 ret := providers.ValidateResourceTypeConfigResponse{}
163 for _, warn := range warns {
164 ret.Diagnostics = ret.Diagnostics.Append(tfdiags.SimpleWarning(warn))
166 for _, err := range errs {
167 ret.Diagnostics = ret.Diagnostics.Append(err)
170 if p.ValidateResourceTypeConfigFn != nil {
171 return p.ValidateResourceTypeConfigFn(r)
174 return p.ValidateResourceTypeConfigResponse
177 func (p *MockProvider) ValidateDataSourceConfig(r providers.ValidateDataSourceConfigRequest) providers.ValidateDataSourceConfigResponse {
181 p.ValidateDataSourceConfigCalled = true
182 p.ValidateDataSourceConfigRequest = r
184 if p.ValidateDataSourceConfigFn != nil {
185 return p.ValidateDataSourceConfigFn(r)
188 return p.ValidateDataSourceConfigResponse
191 func (p *MockProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse {
195 schemas := p.getSchema()
196 schema := schemas.ResourceTypes[r.TypeName]
197 schemaType := schema.Block.ImpliedType()
199 p.UpgradeResourceStateCalled = true
200 p.UpgradeResourceStateRequest = r
202 if p.UpgradeResourceStateFn != nil {
203 return p.UpgradeResourceStateFn(r)
206 resp := p.UpgradeResourceStateResponse
208 if resp.UpgradedState == cty.NilVal {
210 case r.RawStateFlatmap != nil:
211 v, err := hcl2shim.HCL2ValueFromFlatmap(r.RawStateFlatmap, schemaType)
213 resp.Diagnostics = resp.Diagnostics.Append(err)
216 resp.UpgradedState = v
217 case len(r.RawStateJSON) > 0:
218 v, err := ctyjson.Unmarshal(r.RawStateJSON, schemaType)
221 resp.Diagnostics = resp.Diagnostics.Append(err)
224 resp.UpgradedState = v
230 func (p *MockProvider) Configure(r providers.ConfigureRequest) providers.ConfigureResponse {
234 p.ConfigureCalled = true
235 p.ConfigureRequest = r
237 if p.ConfigureFn != nil {
238 resp := p.getSchema()
239 schema := resp.Provider.Block
240 rc := NewResourceConfigShimmed(r.Config, schema)
241 ret := providers.ConfigureResponse{}
243 err := p.ConfigureFn(rc)
245 ret.Diagnostics = ret.Diagnostics.Append(err)
249 if p.ConfigureNewFn != nil {
250 return p.ConfigureNewFn(r)
253 return p.ConfigureResponse
256 func (p *MockProvider) Stop() error {
257 // We intentionally don't lock in this one because the whole point of this
258 // method is to be called concurrently with another operation that can
259 // be cancelled. The provider itself is responsible for handling
260 // any concurrency concerns in this case.
267 return p.StopResponse
270 func (p *MockProvider) ReadResource(r providers.ReadResourceRequest) providers.ReadResourceResponse {
274 p.ReadResourceCalled = true
275 p.ReadResourceRequest = r
277 if p.ReadResourceFn != nil {
278 return p.ReadResourceFn(r)
281 // make sure the NewState fits the schema
282 newState, err := p.GetSchemaReturn.ResourceTypes[r.TypeName].CoerceValue(p.ReadResourceResponse.NewState)
286 resp := p.ReadResourceResponse
287 resp.NewState = newState
292 func (p *MockProvider) PlanResourceChange(r providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
296 p.PlanResourceChangeCalled = true
297 p.PlanResourceChangeRequest = r
301 if ps.ResourceTypes == nil || ps.ResourceTypes[r.TypeName].Block == nil {
302 return providers.PlanResourceChangeResponse{
303 Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Printf("mock provider has no schema for resource type %s", r.TypeName)),
306 schema := ps.ResourceTypes[r.TypeName].Block
307 info := &InstanceInfo{
310 priorState := NewInstanceStateShimmedFromValue(r.PriorState, 0)
311 cfg := NewResourceConfigShimmed(r.Config, schema)
313 legacyDiff, err := p.DiffFn(info, priorState, cfg)
315 var res providers.PlanResourceChangeResponse
316 res.PlannedState = r.ProposedNewState
318 res.Diagnostics = res.Diagnostics.Append(err)
320 if legacyDiff != nil {
321 newVal, err := legacyDiff.ApplyToValue(r.PriorState, schema)
323 res.Diagnostics = res.Diagnostics.Append(err)
326 res.PlannedState = newVal
328 var requiresNew []string
329 for attr, d := range legacyDiff.Attributes {
331 requiresNew = append(requiresNew, attr)
334 requiresReplace, err := hcl2shim.RequiresReplace(requiresNew, schema.ImpliedType())
336 res.Diagnostics = res.Diagnostics.Append(err)
338 res.RequiresReplace = requiresReplace
342 if p.PlanResourceChangeFn != nil {
343 return p.PlanResourceChangeFn(r)
346 return p.PlanResourceChangeResponse
349 func (p *MockProvider) ApplyResourceChange(r providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
351 p.ApplyResourceChangeCalled = true
352 p.ApplyResourceChangeRequest = r
355 if p.ApplyFn != nil {
356 // ApplyFn is a special callback fashioned after our old provider
357 // interface, which expected to be given an actual diff rather than
358 // separate old/new values to apply. Therefore we need to approximate
359 // a diff here well enough that _most_ of our legacy ApplyFns in old
360 // tests still see the behavior they are expecting. New tests should
361 // not use this, and should instead use ApplyResourceChangeFn directly.
362 providerSchema := p.getSchema()
363 schema, ok := providerSchema.ResourceTypes[r.TypeName]
365 return providers.ApplyResourceChangeResponse{
366 Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("no mocked schema available for resource type %s", r.TypeName)),
370 info := &InstanceInfo{
374 priorVal := r.PriorState
375 plannedVal := r.PlannedState
376 priorMap := hcl2shim.FlatmapValueFromHCL2(priorVal)
377 plannedMap := hcl2shim.FlatmapValueFromHCL2(plannedVal)
378 s := NewInstanceStateShimmedFromValue(priorVal, 0)
380 Attributes: make(map[string]*ResourceAttrDiff),
382 if plannedMap == nil { // destroying, then
384 // Destroy diffs don't have any attribute diffs
386 if priorMap == nil { // creating, then
387 // We'll just make an empty prior map to make things easier below.
388 priorMap = make(map[string]string)
391 for k, new := range plannedMap {
394 if new == config.UnknownVariableValue {
398 d.Attributes[k] = &ResourceAttrDiff{
401 NewComputed: newComputed,
402 Type: DiffAttrInput, // not generally used in tests, so just hard-coded
405 // Also need any attributes that were removed in "planned"
406 for k, old := range priorMap {
407 if _, ok := plannedMap[k]; ok {
410 d.Attributes[k] = &ResourceAttrDiff{
417 newState, err := p.ApplyFn(info, s, d)
418 resp := providers.ApplyResourceChangeResponse{}
420 resp.Diagnostics = resp.Diagnostics.Append(err)
426 newVal, err = newState.AttrsAsObjectValue(schema.Block.ImpliedType())
428 resp.Diagnostics = resp.Diagnostics.Append(err)
431 // If apply returned a nil new state then that's the old way to
432 // indicate that the object was destroyed. Our new interface calls
433 // for that to be signalled as a null value.
434 newVal = cty.NullVal(schema.Block.ImpliedType())
436 resp.NewState = newVal
441 if p.ApplyResourceChangeFn != nil {
442 return p.ApplyResourceChangeFn(r)
445 return p.ApplyResourceChangeResponse
448 func (p *MockProvider) ImportResourceState(r providers.ImportResourceStateRequest) providers.ImportResourceStateResponse {
452 if p.ImportStateReturn != nil {
453 for _, is := range p.ImportStateReturn {
454 if is.Attributes == nil {
455 is.Attributes = make(map[string]string)
457 is.Attributes["id"] = is.ID
459 typeName := is.Ephemeral.Type
460 // Use the requested type if the resource has no type of it's own.
461 // We still return the empty type, which will error, but this prevents a panic.
463 typeName = r.TypeName
466 schema := p.GetSchemaReturn.ResourceTypes[typeName]
468 panic("no schema found for " + typeName)
471 private, err := json.Marshal(is.Meta)
476 state, err := hcl2shim.HCL2ValueFromFlatmap(is.Attributes, schema.ImpliedType())
481 state, err = schema.CoerceValue(state)
486 p.ImportResourceStateResponse.ImportedResources = append(
487 p.ImportResourceStateResponse.ImportedResources,
488 providers.ImportedResource{
489 TypeName: is.Ephemeral.Type,
496 p.ImportResourceStateCalled = true
497 p.ImportResourceStateRequest = r
498 if p.ImportResourceStateFn != nil {
499 return p.ImportResourceStateFn(r)
502 return p.ImportResourceStateResponse
505 func (p *MockProvider) ReadDataSource(r providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
509 p.ReadDataSourceCalled = true
510 p.ReadDataSourceRequest = r
512 if p.ReadDataSourceFn != nil {
513 return p.ReadDataSourceFn(r)
516 return p.ReadDataSourceResponse
519 func (p *MockProvider) Close() error {