github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/provider_mock.go (about) 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-plugin-sdk/internal/configs/hcl2shim" 12 "github.com/hashicorp/terraform-plugin-sdk/internal/providers" 13 "github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags" 14 ) 15 16 var _ providers.Interface = (*MockProvider)(nil) 17 18 // MockProvider implements providers.Interface but mocks out all the 19 // calls for testing purposes. 20 type MockProvider struct { 21 sync.Mutex 22 23 // Anything you want, in case you need to store extra data with the mock. 24 Meta interface{} 25 26 GetSchemaCalled bool 27 GetSchemaReturn *ProviderSchema // This is using ProviderSchema directly rather than providers.GetSchemaResponse for compatibility with old tests 28 29 PrepareProviderConfigCalled bool 30 PrepareProviderConfigResponse providers.PrepareProviderConfigResponse 31 PrepareProviderConfigRequest providers.PrepareProviderConfigRequest 32 PrepareProviderConfigFn func(providers.PrepareProviderConfigRequest) providers.PrepareProviderConfigResponse 33 34 ValidateResourceTypeConfigCalled bool 35 ValidateResourceTypeConfigTypeName string 36 ValidateResourceTypeConfigResponse providers.ValidateResourceTypeConfigResponse 37 ValidateResourceTypeConfigRequest providers.ValidateResourceTypeConfigRequest 38 ValidateResourceTypeConfigFn func(providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse 39 40 ValidateDataSourceConfigCalled bool 41 ValidateDataSourceConfigTypeName string 42 ValidateDataSourceConfigResponse providers.ValidateDataSourceConfigResponse 43 ValidateDataSourceConfigRequest providers.ValidateDataSourceConfigRequest 44 ValidateDataSourceConfigFn func(providers.ValidateDataSourceConfigRequest) providers.ValidateDataSourceConfigResponse 45 46 UpgradeResourceStateCalled bool 47 UpgradeResourceStateTypeName string 48 UpgradeResourceStateResponse providers.UpgradeResourceStateResponse 49 UpgradeResourceStateRequest providers.UpgradeResourceStateRequest 50 UpgradeResourceStateFn func(providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse 51 52 ConfigureCalled bool 53 ConfigureResponse providers.ConfigureResponse 54 ConfigureRequest providers.ConfigureRequest 55 ConfigureNewFn func(providers.ConfigureRequest) providers.ConfigureResponse // Named ConfigureNewFn so we can still have the legacy ConfigureFn declared below 56 57 StopCalled bool 58 StopFn func() error 59 StopResponse error 60 61 ReadResourceCalled bool 62 ReadResourceResponse providers.ReadResourceResponse 63 ReadResourceRequest providers.ReadResourceRequest 64 ReadResourceFn func(providers.ReadResourceRequest) providers.ReadResourceResponse 65 66 PlanResourceChangeCalled bool 67 PlanResourceChangeResponse providers.PlanResourceChangeResponse 68 PlanResourceChangeRequest providers.PlanResourceChangeRequest 69 PlanResourceChangeFn func(providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse 70 71 ApplyResourceChangeCalled bool 72 ApplyResourceChangeResponse providers.ApplyResourceChangeResponse 73 ApplyResourceChangeRequest providers.ApplyResourceChangeRequest 74 ApplyResourceChangeFn func(providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse 75 76 ImportResourceStateCalled bool 77 ImportResourceStateResponse providers.ImportResourceStateResponse 78 ImportResourceStateRequest providers.ImportResourceStateRequest 79 ImportResourceStateFn func(providers.ImportResourceStateRequest) providers.ImportResourceStateResponse 80 // Legacy return type for existing tests, which will be shimmed into an 81 // ImportResourceStateResponse if set 82 ImportStateReturn []*InstanceState 83 84 ReadDataSourceCalled bool 85 ReadDataSourceResponse providers.ReadDataSourceResponse 86 ReadDataSourceRequest providers.ReadDataSourceRequest 87 ReadDataSourceFn func(providers.ReadDataSourceRequest) providers.ReadDataSourceResponse 88 89 CloseCalled bool 90 CloseError error 91 92 // Legacy callbacks: if these are set, we will shim incoming calls for 93 // new-style methods to these old-fashioned terraform.ResourceProvider 94 // mock callbacks, for the benefit of older tests that were written against 95 // the old mock API. 96 ValidateFn func(c *ResourceConfig) (ws []string, es []error) 97 ConfigureFn func(c *ResourceConfig) error 98 DiffFn func(info *InstanceInfo, s *InstanceState, c *ResourceConfig) (*InstanceDiff, error) 99 ApplyFn func(info *InstanceInfo, s *InstanceState, d *InstanceDiff) (*InstanceState, error) 100 } 101 102 func (p *MockProvider) GetSchema() providers.GetSchemaResponse { 103 p.Lock() 104 defer p.Unlock() 105 p.GetSchemaCalled = true 106 return p.getSchema() 107 } 108 109 func (p *MockProvider) getSchema() providers.GetSchemaResponse { 110 // This version of getSchema doesn't do any locking, so it's suitable to 111 // call from other methods of this mock as long as they are already 112 // holding the lock. 113 114 ret := providers.GetSchemaResponse{ 115 Provider: providers.Schema{}, 116 DataSources: map[string]providers.Schema{}, 117 ResourceTypes: map[string]providers.Schema{}, 118 } 119 if p.GetSchemaReturn != nil { 120 ret.Provider.Block = p.GetSchemaReturn.Provider 121 for n, s := range p.GetSchemaReturn.DataSources { 122 ret.DataSources[n] = providers.Schema{ 123 Block: s, 124 } 125 } 126 for n, s := range p.GetSchemaReturn.ResourceTypes { 127 ret.ResourceTypes[n] = providers.Schema{ 128 Version: int64(p.GetSchemaReturn.ResourceTypeSchemaVersions[n]), 129 Block: s, 130 } 131 } 132 } 133 134 return ret 135 } 136 137 func (p *MockProvider) PrepareProviderConfig(r providers.PrepareProviderConfigRequest) providers.PrepareProviderConfigResponse { 138 p.Lock() 139 defer p.Unlock() 140 141 p.PrepareProviderConfigCalled = true 142 p.PrepareProviderConfigRequest = r 143 if p.PrepareProviderConfigFn != nil { 144 return p.PrepareProviderConfigFn(r) 145 } 146 return p.PrepareProviderConfigResponse 147 } 148 149 func (p *MockProvider) ValidateResourceTypeConfig(r providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse { 150 p.Lock() 151 defer p.Unlock() 152 153 p.ValidateResourceTypeConfigCalled = true 154 p.ValidateResourceTypeConfigRequest = r 155 156 if p.ValidateFn != nil { 157 resp := p.getSchema() 158 schema := resp.Provider.Block 159 rc := NewResourceConfigShimmed(r.Config, schema) 160 warns, errs := p.ValidateFn(rc) 161 ret := providers.ValidateResourceTypeConfigResponse{} 162 for _, warn := range warns { 163 ret.Diagnostics = ret.Diagnostics.Append(tfdiags.SimpleWarning(warn)) 164 } 165 for _, err := range errs { 166 ret.Diagnostics = ret.Diagnostics.Append(err) 167 } 168 } 169 if p.ValidateResourceTypeConfigFn != nil { 170 return p.ValidateResourceTypeConfigFn(r) 171 } 172 173 return p.ValidateResourceTypeConfigResponse 174 } 175 176 func (p *MockProvider) ValidateDataSourceConfig(r providers.ValidateDataSourceConfigRequest) providers.ValidateDataSourceConfigResponse { 177 p.Lock() 178 defer p.Unlock() 179 180 p.ValidateDataSourceConfigCalled = true 181 p.ValidateDataSourceConfigRequest = r 182 183 if p.ValidateDataSourceConfigFn != nil { 184 return p.ValidateDataSourceConfigFn(r) 185 } 186 187 return p.ValidateDataSourceConfigResponse 188 } 189 190 func (p *MockProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse { 191 p.Lock() 192 defer p.Unlock() 193 194 schemas := p.getSchema() 195 schema := schemas.ResourceTypes[r.TypeName] 196 schemaType := schema.Block.ImpliedType() 197 198 p.UpgradeResourceStateCalled = true 199 p.UpgradeResourceStateRequest = r 200 201 if p.UpgradeResourceStateFn != nil { 202 return p.UpgradeResourceStateFn(r) 203 } 204 205 resp := p.UpgradeResourceStateResponse 206 207 if resp.UpgradedState == cty.NilVal { 208 switch { 209 case r.RawStateFlatmap != nil: 210 v, err := hcl2shim.HCL2ValueFromFlatmap(r.RawStateFlatmap, schemaType) 211 if err != nil { 212 resp.Diagnostics = resp.Diagnostics.Append(err) 213 return resp 214 } 215 resp.UpgradedState = v 216 case len(r.RawStateJSON) > 0: 217 v, err := ctyjson.Unmarshal(r.RawStateJSON, schemaType) 218 219 if err != nil { 220 resp.Diagnostics = resp.Diagnostics.Append(err) 221 return resp 222 } 223 resp.UpgradedState = v 224 } 225 } 226 return resp 227 } 228 229 func (p *MockProvider) Configure(r providers.ConfigureRequest) providers.ConfigureResponse { 230 p.Lock() 231 defer p.Unlock() 232 233 p.ConfigureCalled = true 234 p.ConfigureRequest = r 235 236 if p.ConfigureFn != nil { 237 resp := p.getSchema() 238 schema := resp.Provider.Block 239 rc := NewResourceConfigShimmed(r.Config, schema) 240 ret := providers.ConfigureResponse{} 241 242 err := p.ConfigureFn(rc) 243 if err != nil { 244 ret.Diagnostics = ret.Diagnostics.Append(err) 245 } 246 return ret 247 } 248 if p.ConfigureNewFn != nil { 249 return p.ConfigureNewFn(r) 250 } 251 252 return p.ConfigureResponse 253 } 254 255 func (p *MockProvider) Stop() error { 256 // We intentionally don't lock in this one because the whole point of this 257 // method is to be called concurrently with another operation that can 258 // be cancelled. The provider itself is responsible for handling 259 // any concurrency concerns in this case. 260 261 p.StopCalled = true 262 if p.StopFn != nil { 263 return p.StopFn() 264 } 265 266 return p.StopResponse 267 } 268 269 func (p *MockProvider) ReadResource(r providers.ReadResourceRequest) providers.ReadResourceResponse { 270 p.Lock() 271 defer p.Unlock() 272 273 p.ReadResourceCalled = true 274 p.ReadResourceRequest = r 275 276 if p.ReadResourceFn != nil { 277 return p.ReadResourceFn(r) 278 } 279 280 // make sure the NewState fits the schema 281 newState, err := p.GetSchemaReturn.ResourceTypes[r.TypeName].CoerceValue(p.ReadResourceResponse.NewState) 282 if err != nil { 283 panic(err) 284 } 285 resp := p.ReadResourceResponse 286 resp.NewState = newState 287 288 return resp 289 } 290 291 func (p *MockProvider) PlanResourceChange(r providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 292 p.Lock() 293 defer p.Unlock() 294 295 p.PlanResourceChangeCalled = true 296 p.PlanResourceChangeRequest = r 297 298 if p.DiffFn != nil { 299 ps := p.getSchema() 300 if ps.ResourceTypes == nil || ps.ResourceTypes[r.TypeName].Block == nil { 301 return providers.PlanResourceChangeResponse{ 302 Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Printf("mock provider has no schema for resource type %s", r.TypeName)), 303 } 304 } 305 schema := ps.ResourceTypes[r.TypeName].Block 306 info := &InstanceInfo{ 307 Type: r.TypeName, 308 } 309 priorState := NewInstanceStateShimmedFromValue(r.PriorState, 0) 310 cfg := NewResourceConfigShimmed(r.Config, schema) 311 312 legacyDiff, err := p.DiffFn(info, priorState, cfg) 313 314 var res providers.PlanResourceChangeResponse 315 res.PlannedState = r.ProposedNewState 316 if err != nil { 317 res.Diagnostics = res.Diagnostics.Append(err) 318 } 319 if legacyDiff != nil { 320 newVal, err := legacyDiff.ApplyToValue(r.PriorState, schema) 321 if err != nil { 322 res.Diagnostics = res.Diagnostics.Append(err) 323 } 324 325 res.PlannedState = newVal 326 327 var requiresNew []string 328 for attr, d := range legacyDiff.Attributes { 329 if d.RequiresNew { 330 requiresNew = append(requiresNew, attr) 331 } 332 } 333 requiresReplace, err := hcl2shim.RequiresReplace(requiresNew, schema.ImpliedType()) 334 if err != nil { 335 res.Diagnostics = res.Diagnostics.Append(err) 336 } 337 res.RequiresReplace = requiresReplace 338 } 339 return res 340 } 341 if p.PlanResourceChangeFn != nil { 342 return p.PlanResourceChangeFn(r) 343 } 344 345 return p.PlanResourceChangeResponse 346 } 347 348 func (p *MockProvider) ApplyResourceChange(r providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 349 p.Lock() 350 p.ApplyResourceChangeCalled = true 351 p.ApplyResourceChangeRequest = r 352 p.Unlock() 353 354 if p.ApplyFn != nil { 355 // ApplyFn is a special callback fashioned after our old provider 356 // interface, which expected to be given an actual diff rather than 357 // separate old/new values to apply. Therefore we need to approximate 358 // a diff here well enough that _most_ of our legacy ApplyFns in old 359 // tests still see the behavior they are expecting. New tests should 360 // not use this, and should instead use ApplyResourceChangeFn directly. 361 providerSchema := p.getSchema() 362 schema, ok := providerSchema.ResourceTypes[r.TypeName] 363 if !ok { 364 return providers.ApplyResourceChangeResponse{ 365 Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("no mocked schema available for resource type %s", r.TypeName)), 366 } 367 } 368 369 info := &InstanceInfo{ 370 Type: r.TypeName, 371 } 372 373 priorVal := r.PriorState 374 plannedVal := r.PlannedState 375 priorMap := hcl2shim.FlatmapValueFromHCL2(priorVal) 376 plannedMap := hcl2shim.FlatmapValueFromHCL2(plannedVal) 377 s := NewInstanceStateShimmedFromValue(priorVal, 0) 378 d := &InstanceDiff{ 379 Attributes: make(map[string]*ResourceAttrDiff), 380 } 381 if plannedMap == nil { // destroying, then 382 d.Destroy = true 383 // Destroy diffs don't have any attribute diffs 384 } else { 385 if priorMap == nil { // creating, then 386 // We'll just make an empty prior map to make things easier below. 387 priorMap = make(map[string]string) 388 } 389 390 for k, new := range plannedMap { 391 old := priorMap[k] 392 newComputed := false 393 if new == hcl2shim.UnknownVariableValue { 394 new = "" 395 newComputed = true 396 } 397 d.Attributes[k] = &ResourceAttrDiff{ 398 Old: old, 399 New: new, 400 NewComputed: newComputed, 401 Type: DiffAttrInput, // not generally used in tests, so just hard-coded 402 } 403 } 404 // Also need any attributes that were removed in "planned" 405 for k, old := range priorMap { 406 if _, ok := plannedMap[k]; ok { 407 continue 408 } 409 d.Attributes[k] = &ResourceAttrDiff{ 410 Old: old, 411 NewRemoved: true, 412 Type: DiffAttrInput, 413 } 414 } 415 } 416 newState, err := p.ApplyFn(info, s, d) 417 resp := providers.ApplyResourceChangeResponse{} 418 if err != nil { 419 resp.Diagnostics = resp.Diagnostics.Append(err) 420 } 421 if newState != nil { 422 var newVal cty.Value 423 if newState != nil { 424 var err error 425 newVal, err = newState.AttrsAsObjectValue(schema.Block.ImpliedType()) 426 if err != nil { 427 resp.Diagnostics = resp.Diagnostics.Append(err) 428 } 429 } else { 430 // If apply returned a nil new state then that's the old way to 431 // indicate that the object was destroyed. Our new interface calls 432 // for that to be signalled as a null value. 433 newVal = cty.NullVal(schema.Block.ImpliedType()) 434 } 435 resp.NewState = newVal 436 } 437 438 return resp 439 } 440 if p.ApplyResourceChangeFn != nil { 441 return p.ApplyResourceChangeFn(r) 442 } 443 444 return p.ApplyResourceChangeResponse 445 } 446 447 func (p *MockProvider) ImportResourceState(r providers.ImportResourceStateRequest) providers.ImportResourceStateResponse { 448 p.Lock() 449 defer p.Unlock() 450 451 if p.ImportStateReturn != nil { 452 for _, is := range p.ImportStateReturn { 453 if is.Attributes == nil { 454 is.Attributes = make(map[string]string) 455 } 456 is.Attributes["id"] = is.ID 457 458 typeName := is.Ephemeral.Type 459 // Use the requested type if the resource has no type of it's own. 460 // We still return the empty type, which will error, but this prevents a panic. 461 if typeName == "" { 462 typeName = r.TypeName 463 } 464 465 schema := p.GetSchemaReturn.ResourceTypes[typeName] 466 if schema == nil { 467 panic("no schema found for " + typeName) 468 } 469 470 private, err := json.Marshal(is.Meta) 471 if err != nil { 472 panic(err) 473 } 474 475 state, err := hcl2shim.HCL2ValueFromFlatmap(is.Attributes, schema.ImpliedType()) 476 if err != nil { 477 panic(err) 478 } 479 480 state, err = schema.CoerceValue(state) 481 if err != nil { 482 panic(err) 483 } 484 485 p.ImportResourceStateResponse.ImportedResources = append( 486 p.ImportResourceStateResponse.ImportedResources, 487 providers.ImportedResource{ 488 TypeName: is.Ephemeral.Type, 489 State: state, 490 Private: private, 491 }) 492 } 493 } 494 495 p.ImportResourceStateCalled = true 496 p.ImportResourceStateRequest = r 497 if p.ImportResourceStateFn != nil { 498 return p.ImportResourceStateFn(r) 499 } 500 501 return p.ImportResourceStateResponse 502 } 503 504 func (p *MockProvider) ReadDataSource(r providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 505 p.Lock() 506 defer p.Unlock() 507 508 p.ReadDataSourceCalled = true 509 p.ReadDataSourceRequest = r 510 511 if p.ReadDataSourceFn != nil { 512 return p.ReadDataSourceFn(r) 513 } 514 515 return p.ReadDataSourceResponse 516 } 517 518 func (p *MockProvider) Close() error { 519 p.CloseCalled = true 520 return p.CloseError 521 }