]>
Commit | Line | Data |
---|---|---|
107c1cdb ND |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "encoding/json" | |
5 | "fmt" | |
6 | "sync" | |
7 | ||
8 | "github.com/zclconf/go-cty/cty" | |
9 | ctyjson "github.com/zclconf/go-cty/cty/json" | |
10 | ||
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" | |
15 | ) | |
16 | ||
17 | var _ providers.Interface = (*MockProvider)(nil) | |
18 | ||
19 | // MockProvider implements providers.Interface but mocks out all the | |
20 | // calls for testing purposes. | |
21 | type MockProvider struct { | |
22 | sync.Mutex | |
23 | ||
24 | // Anything you want, in case you need to store extra data with the mock. | |
25 | Meta interface{} | |
26 | ||
27 | GetSchemaCalled bool | |
28 | GetSchemaReturn *ProviderSchema // This is using ProviderSchema directly rather than providers.GetSchemaResponse for compatibility with old tests | |
29 | ||
30 | PrepareProviderConfigCalled bool | |
31 | PrepareProviderConfigResponse providers.PrepareProviderConfigResponse | |
32 | PrepareProviderConfigRequest providers.PrepareProviderConfigRequest | |
33 | PrepareProviderConfigFn func(providers.PrepareProviderConfigRequest) providers.PrepareProviderConfigResponse | |
34 | ||
35 | ValidateResourceTypeConfigCalled bool | |
36 | ValidateResourceTypeConfigTypeName string | |
37 | ValidateResourceTypeConfigResponse providers.ValidateResourceTypeConfigResponse | |
38 | ValidateResourceTypeConfigRequest providers.ValidateResourceTypeConfigRequest | |
39 | ValidateResourceTypeConfigFn func(providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse | |
40 | ||
41 | ValidateDataSourceConfigCalled bool | |
42 | ValidateDataSourceConfigTypeName string | |
43 | ValidateDataSourceConfigResponse providers.ValidateDataSourceConfigResponse | |
44 | ValidateDataSourceConfigRequest providers.ValidateDataSourceConfigRequest | |
45 | ValidateDataSourceConfigFn func(providers.ValidateDataSourceConfigRequest) providers.ValidateDataSourceConfigResponse | |
46 | ||
47 | UpgradeResourceStateCalled bool | |
48 | UpgradeResourceStateTypeName string | |
49 | UpgradeResourceStateResponse providers.UpgradeResourceStateResponse | |
50 | UpgradeResourceStateRequest providers.UpgradeResourceStateRequest | |
51 | UpgradeResourceStateFn func(providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse | |
52 | ||
53 | ConfigureCalled bool | |
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 | |
57 | ||
58 | StopCalled bool | |
59 | StopFn func() error | |
60 | StopResponse error | |
61 | ||
62 | ReadResourceCalled bool | |
63 | ReadResourceResponse providers.ReadResourceResponse | |
64 | ReadResourceRequest providers.ReadResourceRequest | |
65 | ReadResourceFn func(providers.ReadResourceRequest) providers.ReadResourceResponse | |
66 | ||
67 | PlanResourceChangeCalled bool | |
68 | PlanResourceChangeResponse providers.PlanResourceChangeResponse | |
69 | PlanResourceChangeRequest providers.PlanResourceChangeRequest | |
70 | PlanResourceChangeFn func(providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse | |
71 | ||
72 | ApplyResourceChangeCalled bool | |
73 | ApplyResourceChangeResponse providers.ApplyResourceChangeResponse | |
74 | ApplyResourceChangeRequest providers.ApplyResourceChangeRequest | |
75 | ApplyResourceChangeFn func(providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse | |
76 | ||
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 | |
84 | ||
85 | ReadDataSourceCalled bool | |
86 | ReadDataSourceResponse providers.ReadDataSourceResponse | |
87 | ReadDataSourceRequest providers.ReadDataSourceRequest | |
88 | ReadDataSourceFn func(providers.ReadDataSourceRequest) providers.ReadDataSourceResponse | |
89 | ||
90 | CloseCalled bool | |
91 | CloseError error | |
92 | ||
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 | |
96 | // the old mock API. | |
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) | |
101 | } | |
102 | ||
103 | func (p *MockProvider) GetSchema() providers.GetSchemaResponse { | |
104 | p.Lock() | |
105 | defer p.Unlock() | |
106 | p.GetSchemaCalled = true | |
107 | return p.getSchema() | |
108 | } | |
109 | ||
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 | |
113 | // holding the lock. | |
114 | ||
115 | ret := providers.GetSchemaResponse{ | |
116 | Provider: providers.Schema{}, | |
117 | DataSources: map[string]providers.Schema{}, | |
118 | ResourceTypes: map[string]providers.Schema{}, | |
119 | } | |
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{ | |
124 | Block: s, | |
125 | } | |
126 | } | |
127 | for n, s := range p.GetSchemaReturn.ResourceTypes { | |
128 | ret.ResourceTypes[n] = providers.Schema{ | |
129 | Version: int64(p.GetSchemaReturn.ResourceTypeSchemaVersions[n]), | |
130 | Block: s, | |
131 | } | |
132 | } | |
133 | } | |
134 | ||
135 | return ret | |
136 | } | |
137 | ||
138 | func (p *MockProvider) PrepareProviderConfig(r providers.PrepareProviderConfigRequest) providers.PrepareProviderConfigResponse { | |
139 | p.Lock() | |
140 | defer p.Unlock() | |
141 | ||
142 | p.PrepareProviderConfigCalled = true | |
143 | p.PrepareProviderConfigRequest = r | |
144 | if p.PrepareProviderConfigFn != nil { | |
145 | return p.PrepareProviderConfigFn(r) | |
146 | } | |
147 | return p.PrepareProviderConfigResponse | |
148 | } | |
149 | ||
150 | func (p *MockProvider) ValidateResourceTypeConfig(r providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse { | |
151 | p.Lock() | |
152 | defer p.Unlock() | |
153 | ||
154 | p.ValidateResourceTypeConfigCalled = true | |
155 | p.ValidateResourceTypeConfigRequest = r | |
156 | ||
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)) | |
165 | } | |
166 | for _, err := range errs { | |
167 | ret.Diagnostics = ret.Diagnostics.Append(err) | |
168 | } | |
169 | } | |
170 | if p.ValidateResourceTypeConfigFn != nil { | |
171 | return p.ValidateResourceTypeConfigFn(r) | |
172 | } | |
173 | ||
174 | return p.ValidateResourceTypeConfigResponse | |
175 | } | |
176 | ||
177 | func (p *MockProvider) ValidateDataSourceConfig(r providers.ValidateDataSourceConfigRequest) providers.ValidateDataSourceConfigResponse { | |
178 | p.Lock() | |
179 | defer p.Unlock() | |
180 | ||
181 | p.ValidateDataSourceConfigCalled = true | |
182 | p.ValidateDataSourceConfigRequest = r | |
183 | ||
184 | if p.ValidateDataSourceConfigFn != nil { | |
185 | return p.ValidateDataSourceConfigFn(r) | |
186 | } | |
187 | ||
188 | return p.ValidateDataSourceConfigResponse | |
189 | } | |
190 | ||
191 | func (p *MockProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse { | |
192 | p.Lock() | |
193 | defer p.Unlock() | |
194 | ||
195 | schemas := p.getSchema() | |
196 | schema := schemas.ResourceTypes[r.TypeName] | |
197 | schemaType := schema.Block.ImpliedType() | |
198 | ||
199 | p.UpgradeResourceStateCalled = true | |
200 | p.UpgradeResourceStateRequest = r | |
201 | ||
202 | if p.UpgradeResourceStateFn != nil { | |
203 | return p.UpgradeResourceStateFn(r) | |
204 | } | |
205 | ||
206 | resp := p.UpgradeResourceStateResponse | |
207 | ||
208 | if resp.UpgradedState == cty.NilVal { | |
209 | switch { | |
210 | case r.RawStateFlatmap != nil: | |
211 | v, err := hcl2shim.HCL2ValueFromFlatmap(r.RawStateFlatmap, schemaType) | |
212 | if err != nil { | |
213 | resp.Diagnostics = resp.Diagnostics.Append(err) | |
214 | return resp | |
215 | } | |
216 | resp.UpgradedState = v | |
217 | case len(r.RawStateJSON) > 0: | |
218 | v, err := ctyjson.Unmarshal(r.RawStateJSON, schemaType) | |
219 | ||
220 | if err != nil { | |
221 | resp.Diagnostics = resp.Diagnostics.Append(err) | |
222 | return resp | |
223 | } | |
224 | resp.UpgradedState = v | |
225 | } | |
226 | } | |
227 | return resp | |
228 | } | |
229 | ||
230 | func (p *MockProvider) Configure(r providers.ConfigureRequest) providers.ConfigureResponse { | |
231 | p.Lock() | |
232 | defer p.Unlock() | |
233 | ||
234 | p.ConfigureCalled = true | |
235 | p.ConfigureRequest = r | |
236 | ||
237 | if p.ConfigureFn != nil { | |
238 | resp := p.getSchema() | |
239 | schema := resp.Provider.Block | |
240 | rc := NewResourceConfigShimmed(r.Config, schema) | |
241 | ret := providers.ConfigureResponse{} | |
242 | ||
243 | err := p.ConfigureFn(rc) | |
244 | if err != nil { | |
245 | ret.Diagnostics = ret.Diagnostics.Append(err) | |
246 | } | |
247 | return ret | |
248 | } | |
249 | if p.ConfigureNewFn != nil { | |
250 | return p.ConfigureNewFn(r) | |
251 | } | |
252 | ||
253 | return p.ConfigureResponse | |
254 | } | |
255 | ||
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. | |
261 | ||
262 | p.StopCalled = true | |
263 | if p.StopFn != nil { | |
264 | return p.StopFn() | |
265 | } | |
266 | ||
267 | return p.StopResponse | |
268 | } | |
269 | ||
270 | func (p *MockProvider) ReadResource(r providers.ReadResourceRequest) providers.ReadResourceResponse { | |
271 | p.Lock() | |
272 | defer p.Unlock() | |
273 | ||
274 | p.ReadResourceCalled = true | |
275 | p.ReadResourceRequest = r | |
276 | ||
277 | if p.ReadResourceFn != nil { | |
278 | return p.ReadResourceFn(r) | |
279 | } | |
280 | ||
281 | // make sure the NewState fits the schema | |
282 | newState, err := p.GetSchemaReturn.ResourceTypes[r.TypeName].CoerceValue(p.ReadResourceResponse.NewState) | |
283 | if err != nil { | |
284 | panic(err) | |
285 | } | |
286 | resp := p.ReadResourceResponse | |
287 | resp.NewState = newState | |
288 | ||
289 | return resp | |
290 | } | |
291 | ||
292 | func (p *MockProvider) PlanResourceChange(r providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { | |
293 | p.Lock() | |
294 | defer p.Unlock() | |
295 | ||
296 | p.PlanResourceChangeCalled = true | |
297 | p.PlanResourceChangeRequest = r | |
298 | ||
299 | if p.DiffFn != nil { | |
300 | ps := p.getSchema() | |
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)), | |
304 | } | |
305 | } | |
306 | schema := ps.ResourceTypes[r.TypeName].Block | |
307 | info := &InstanceInfo{ | |
308 | Type: r.TypeName, | |
309 | } | |
310 | priorState := NewInstanceStateShimmedFromValue(r.PriorState, 0) | |
311 | cfg := NewResourceConfigShimmed(r.Config, schema) | |
312 | ||
313 | legacyDiff, err := p.DiffFn(info, priorState, cfg) | |
314 | ||
315 | var res providers.PlanResourceChangeResponse | |
316 | res.PlannedState = r.ProposedNewState | |
317 | if err != nil { | |
318 | res.Diagnostics = res.Diagnostics.Append(err) | |
319 | } | |
320 | if legacyDiff != nil { | |
321 | newVal, err := legacyDiff.ApplyToValue(r.PriorState, schema) | |
322 | if err != nil { | |
323 | res.Diagnostics = res.Diagnostics.Append(err) | |
324 | } | |
325 | ||
326 | res.PlannedState = newVal | |
327 | ||
328 | var requiresNew []string | |
329 | for attr, d := range legacyDiff.Attributes { | |
330 | if d.RequiresNew { | |
331 | requiresNew = append(requiresNew, attr) | |
332 | } | |
333 | } | |
334 | requiresReplace, err := hcl2shim.RequiresReplace(requiresNew, schema.ImpliedType()) | |
335 | if err != nil { | |
336 | res.Diagnostics = res.Diagnostics.Append(err) | |
337 | } | |
338 | res.RequiresReplace = requiresReplace | |
339 | } | |
340 | return res | |
341 | } | |
342 | if p.PlanResourceChangeFn != nil { | |
343 | return p.PlanResourceChangeFn(r) | |
344 | } | |
345 | ||
346 | return p.PlanResourceChangeResponse | |
347 | } | |
348 | ||
349 | func (p *MockProvider) ApplyResourceChange(r providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { | |
350 | p.Lock() | |
351 | p.ApplyResourceChangeCalled = true | |
352 | p.ApplyResourceChangeRequest = r | |
353 | p.Unlock() | |
354 | ||
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] | |
364 | if !ok { | |
365 | return providers.ApplyResourceChangeResponse{ | |
366 | Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("no mocked schema available for resource type %s", r.TypeName)), | |
367 | } | |
368 | } | |
369 | ||
370 | info := &InstanceInfo{ | |
371 | Type: r.TypeName, | |
372 | } | |
373 | ||
374 | priorVal := r.PriorState | |
375 | plannedVal := r.PlannedState | |
376 | priorMap := hcl2shim.FlatmapValueFromHCL2(priorVal) | |
377 | plannedMap := hcl2shim.FlatmapValueFromHCL2(plannedVal) | |
378 | s := NewInstanceStateShimmedFromValue(priorVal, 0) | |
379 | d := &InstanceDiff{ | |
380 | Attributes: make(map[string]*ResourceAttrDiff), | |
381 | } | |
382 | if plannedMap == nil { // destroying, then | |
383 | d.Destroy = true | |
384 | // Destroy diffs don't have any attribute diffs | |
385 | } else { | |
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) | |
389 | } | |
390 | ||
391 | for k, new := range plannedMap { | |
392 | old := priorMap[k] | |
393 | newComputed := false | |
394 | if new == config.UnknownVariableValue { | |
395 | new = "" | |
396 | newComputed = true | |
397 | } | |
398 | d.Attributes[k] = &ResourceAttrDiff{ | |
399 | Old: old, | |
400 | New: new, | |
401 | NewComputed: newComputed, | |
402 | Type: DiffAttrInput, // not generally used in tests, so just hard-coded | |
403 | } | |
404 | } | |
405 | // Also need any attributes that were removed in "planned" | |
406 | for k, old := range priorMap { | |
407 | if _, ok := plannedMap[k]; ok { | |
408 | continue | |
409 | } | |
410 | d.Attributes[k] = &ResourceAttrDiff{ | |
411 | Old: old, | |
412 | NewRemoved: true, | |
413 | Type: DiffAttrInput, | |
414 | } | |
415 | } | |
416 | } | |
417 | newState, err := p.ApplyFn(info, s, d) | |
418 | resp := providers.ApplyResourceChangeResponse{} | |
419 | if err != nil { | |
420 | resp.Diagnostics = resp.Diagnostics.Append(err) | |
421 | } | |
422 | if newState != nil { | |
423 | var newVal cty.Value | |
424 | if newState != nil { | |
425 | var err error | |
426 | newVal, err = newState.AttrsAsObjectValue(schema.Block.ImpliedType()) | |
427 | if err != nil { | |
428 | resp.Diagnostics = resp.Diagnostics.Append(err) | |
429 | } | |
430 | } else { | |
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()) | |
435 | } | |
436 | resp.NewState = newVal | |
437 | } | |
438 | ||
439 | return resp | |
440 | } | |
441 | if p.ApplyResourceChangeFn != nil { | |
442 | return p.ApplyResourceChangeFn(r) | |
443 | } | |
444 | ||
445 | return p.ApplyResourceChangeResponse | |
446 | } | |
447 | ||
448 | func (p *MockProvider) ImportResourceState(r providers.ImportResourceStateRequest) providers.ImportResourceStateResponse { | |
449 | p.Lock() | |
450 | defer p.Unlock() | |
451 | ||
452 | if p.ImportStateReturn != nil { | |
453 | for _, is := range p.ImportStateReturn { | |
454 | if is.Attributes == nil { | |
455 | is.Attributes = make(map[string]string) | |
456 | } | |
457 | is.Attributes["id"] = is.ID | |
458 | ||
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. | |
462 | if typeName == "" { | |
463 | typeName = r.TypeName | |
464 | } | |
465 | ||
466 | schema := p.GetSchemaReturn.ResourceTypes[typeName] | |
467 | if schema == nil { | |
468 | panic("no schema found for " + typeName) | |
469 | } | |
470 | ||
471 | private, err := json.Marshal(is.Meta) | |
472 | if err != nil { | |
473 | panic(err) | |
474 | } | |
475 | ||
476 | state, err := hcl2shim.HCL2ValueFromFlatmap(is.Attributes, schema.ImpliedType()) | |
477 | if err != nil { | |
478 | panic(err) | |
479 | } | |
480 | ||
481 | state, err = schema.CoerceValue(state) | |
482 | if err != nil { | |
483 | panic(err) | |
484 | } | |
485 | ||
486 | p.ImportResourceStateResponse.ImportedResources = append( | |
487 | p.ImportResourceStateResponse.ImportedResources, | |
488 | providers.ImportedResource{ | |
489 | TypeName: is.Ephemeral.Type, | |
490 | State: state, | |
491 | Private: private, | |
492 | }) | |
493 | } | |
494 | } | |
495 | ||
496 | p.ImportResourceStateCalled = true | |
497 | p.ImportResourceStateRequest = r | |
498 | if p.ImportResourceStateFn != nil { | |
499 | return p.ImportResourceStateFn(r) | |
500 | } | |
501 | ||
502 | return p.ImportResourceStateResponse | |
503 | } | |
504 | ||
505 | func (p *MockProvider) ReadDataSource(r providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { | |
506 | p.Lock() | |
507 | defer p.Unlock() | |
508 | ||
509 | p.ReadDataSourceCalled = true | |
510 | p.ReadDataSourceRequest = r | |
511 | ||
512 | if p.ReadDataSourceFn != nil { | |
513 | return p.ReadDataSourceFn(r) | |
514 | } | |
515 | ||
516 | return p.ReadDataSourceResponse | |
517 | } | |
518 | ||
519 | func (p *MockProvider) Close() error { | |
520 | p.CloseCalled = true | |
521 | return p.CloseError | |
522 | } |