github.com/opentofu/opentofu@v1.7.1/internal/tofu/provider_mock.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package tofu 7 8 import ( 9 "fmt" 10 "sync" 11 12 "github.com/zclconf/go-cty/cty" 13 ctyjson "github.com/zclconf/go-cty/cty/json" 14 "github.com/zclconf/go-cty/cty/msgpack" 15 16 "github.com/opentofu/opentofu/internal/configs/hcl2shim" 17 "github.com/opentofu/opentofu/internal/providers" 18 ) 19 20 var _ providers.Interface = (*MockProvider)(nil) 21 22 // MockProvider implements providers.Interface but mocks out all the 23 // calls for testing purposes. 24 type MockProvider struct { 25 sync.Mutex 26 27 // Anything you want, in case you need to store extra data with the mock. 28 Meta interface{} 29 30 GetProviderSchemaCalled bool 31 GetProviderSchemaResponse *providers.GetProviderSchemaResponse 32 33 ValidateProviderConfigCalled bool 34 ValidateProviderConfigResponse *providers.ValidateProviderConfigResponse 35 ValidateProviderConfigRequest providers.ValidateProviderConfigRequest 36 ValidateProviderConfigFn func(providers.ValidateProviderConfigRequest) providers.ValidateProviderConfigResponse 37 38 ValidateResourceConfigCalled bool 39 ValidateResourceConfigTypeName string 40 ValidateResourceConfigResponse *providers.ValidateResourceConfigResponse 41 ValidateResourceConfigRequest providers.ValidateResourceConfigRequest 42 ValidateResourceConfigFn func(providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse 43 44 ValidateDataResourceConfigCalled bool 45 ValidateDataResourceConfigTypeName string 46 ValidateDataResourceConfigResponse *providers.ValidateDataResourceConfigResponse 47 ValidateDataResourceConfigRequest providers.ValidateDataResourceConfigRequest 48 ValidateDataResourceConfigFn func(providers.ValidateDataResourceConfigRequest) providers.ValidateDataResourceConfigResponse 49 50 UpgradeResourceStateCalled bool 51 UpgradeResourceStateTypeName string 52 UpgradeResourceStateResponse *providers.UpgradeResourceStateResponse 53 UpgradeResourceStateRequest providers.UpgradeResourceStateRequest 54 UpgradeResourceStateFn func(providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse 55 56 ConfigureProviderCalled bool 57 ConfigureProviderResponse *providers.ConfigureProviderResponse 58 ConfigureProviderRequest providers.ConfigureProviderRequest 59 ConfigureProviderFn func(providers.ConfigureProviderRequest) providers.ConfigureProviderResponse 60 61 StopCalled bool 62 StopFn func() error 63 StopResponse error 64 65 ReadResourceCalled bool 66 ReadResourceResponse *providers.ReadResourceResponse 67 ReadResourceRequest providers.ReadResourceRequest 68 ReadResourceFn func(providers.ReadResourceRequest) providers.ReadResourceResponse 69 70 PlanResourceChangeCalled bool 71 PlanResourceChangeResponse *providers.PlanResourceChangeResponse 72 PlanResourceChangeRequest providers.PlanResourceChangeRequest 73 PlanResourceChangeFn func(providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse 74 75 ApplyResourceChangeCalled bool 76 ApplyResourceChangeResponse *providers.ApplyResourceChangeResponse 77 ApplyResourceChangeRequest providers.ApplyResourceChangeRequest 78 ApplyResourceChangeFn func(providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse 79 80 ImportResourceStateCalled bool 81 ImportResourceStateResponse *providers.ImportResourceStateResponse 82 ImportResourceStateRequest providers.ImportResourceStateRequest 83 ImportResourceStateFn func(providers.ImportResourceStateRequest) providers.ImportResourceStateResponse 84 85 ReadDataSourceCalled bool 86 ReadDataSourceResponse *providers.ReadDataSourceResponse 87 ReadDataSourceRequest providers.ReadDataSourceRequest 88 ReadDataSourceFn func(providers.ReadDataSourceRequest) providers.ReadDataSourceResponse 89 90 GetFunctionsCalled bool 91 GetFunctionsResponse *providers.GetFunctionsResponse 92 GetFunctionsFn func() providers.GetFunctionsResponse 93 94 CallFunctionCalled bool 95 CallFunctionResponse *providers.CallFunctionResponse 96 CallFunctionRequest providers.CallFunctionRequest 97 CallFunctionFn func(providers.CallFunctionRequest) providers.CallFunctionResponse 98 99 CloseCalled bool 100 CloseError error 101 } 102 103 func (p *MockProvider) GetProviderSchema() providers.GetProviderSchemaResponse { 104 p.Lock() 105 defer p.Unlock() 106 p.GetProviderSchemaCalled = true 107 return p.getProviderSchema() 108 } 109 110 func (p *MockProvider) getProviderSchema() providers.GetProviderSchemaResponse { 111 // This version of getProviderSchema 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 if p.GetProviderSchemaResponse != nil { 115 return *p.GetProviderSchemaResponse 116 } 117 118 return providers.GetProviderSchemaResponse{ 119 Provider: providers.Schema{}, 120 DataSources: map[string]providers.Schema{}, 121 ResourceTypes: map[string]providers.Schema{}, 122 } 123 } 124 125 func (p *MockProvider) ValidateProviderConfig(r providers.ValidateProviderConfigRequest) (resp providers.ValidateProviderConfigResponse) { 126 p.Lock() 127 defer p.Unlock() 128 129 p.ValidateProviderConfigCalled = true 130 p.ValidateProviderConfigRequest = r 131 if p.ValidateProviderConfigFn != nil { 132 return p.ValidateProviderConfigFn(r) 133 } 134 135 if p.ValidateProviderConfigResponse != nil { 136 return *p.ValidateProviderConfigResponse 137 } 138 139 resp.PreparedConfig = r.Config 140 return resp 141 } 142 143 func (p *MockProvider) ValidateResourceConfig(r providers.ValidateResourceConfigRequest) (resp providers.ValidateResourceConfigResponse) { 144 p.Lock() 145 defer p.Unlock() 146 147 p.ValidateResourceConfigCalled = true 148 p.ValidateResourceConfigRequest = r 149 150 // Marshall the value to replicate behavior by the GRPC protocol, 151 // and return any relevant errors 152 resourceSchema, ok := p.getProviderSchema().ResourceTypes[r.TypeName] 153 if !ok { 154 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("no schema found for %q", r.TypeName)) 155 return resp 156 } 157 158 _, err := msgpack.Marshal(r.Config, resourceSchema.Block.ImpliedType()) 159 if err != nil { 160 resp.Diagnostics = resp.Diagnostics.Append(err) 161 return resp 162 } 163 164 if p.ValidateResourceConfigFn != nil { 165 return p.ValidateResourceConfigFn(r) 166 } 167 168 if p.ValidateResourceConfigResponse != nil { 169 return *p.ValidateResourceConfigResponse 170 } 171 172 return resp 173 } 174 175 func (p *MockProvider) ValidateDataResourceConfig(r providers.ValidateDataResourceConfigRequest) (resp providers.ValidateDataResourceConfigResponse) { 176 p.Lock() 177 defer p.Unlock() 178 179 p.ValidateDataResourceConfigCalled = true 180 p.ValidateDataResourceConfigRequest = r 181 182 // Marshall the value to replicate behavior by the GRPC protocol 183 dataSchema, ok := p.getProviderSchema().DataSources[r.TypeName] 184 if !ok { 185 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("no schema found for %q", r.TypeName)) 186 return resp 187 } 188 _, err := msgpack.Marshal(r.Config, dataSchema.Block.ImpliedType()) 189 if err != nil { 190 resp.Diagnostics = resp.Diagnostics.Append(err) 191 return resp 192 } 193 194 if p.ValidateDataResourceConfigFn != nil { 195 return p.ValidateDataResourceConfigFn(r) 196 } 197 198 if p.ValidateDataResourceConfigResponse != nil { 199 return *p.ValidateDataResourceConfigResponse 200 } 201 202 return resp 203 } 204 205 func (p *MockProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) { 206 p.Lock() 207 defer p.Unlock() 208 209 if !p.ConfigureProviderCalled { 210 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("Configure not called before UpgradeResourceState %q", r.TypeName)) 211 return resp 212 } 213 214 schema, ok := p.getProviderSchema().ResourceTypes[r.TypeName] 215 if !ok { 216 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("no schema found for %q", r.TypeName)) 217 return resp 218 } 219 220 schemaType := schema.Block.ImpliedType() 221 222 p.UpgradeResourceStateCalled = true 223 p.UpgradeResourceStateRequest = r 224 225 if p.UpgradeResourceStateFn != nil { 226 return p.UpgradeResourceStateFn(r) 227 } 228 229 if p.UpgradeResourceStateResponse != nil { 230 return *p.UpgradeResourceStateResponse 231 } 232 233 switch { 234 case r.RawStateFlatmap != nil: 235 v, err := hcl2shim.HCL2ValueFromFlatmap(r.RawStateFlatmap, schemaType) 236 if err != nil { 237 resp.Diagnostics = resp.Diagnostics.Append(err) 238 return resp 239 } 240 resp.UpgradedState = v 241 case len(r.RawStateJSON) > 0: 242 v, err := ctyjson.Unmarshal(r.RawStateJSON, schemaType) 243 244 if err != nil { 245 resp.Diagnostics = resp.Diagnostics.Append(err) 246 return resp 247 } 248 resp.UpgradedState = v 249 } 250 251 return resp 252 } 253 254 func (p *MockProvider) ConfigureProvider(r providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 255 p.Lock() 256 defer p.Unlock() 257 258 p.ConfigureProviderCalled = true 259 p.ConfigureProviderRequest = r 260 261 if p.ConfigureProviderFn != nil { 262 return p.ConfigureProviderFn(r) 263 } 264 265 if p.ConfigureProviderResponse != nil { 266 return *p.ConfigureProviderResponse 267 } 268 269 return resp 270 } 271 272 func (p *MockProvider) Stop() error { 273 // We intentionally don't lock in this one because the whole point of this 274 // method is to be called concurrently with another operation that can 275 // be cancelled. The provider itself is responsible for handling 276 // any concurrency concerns in this case. 277 278 p.StopCalled = true 279 if p.StopFn != nil { 280 return p.StopFn() 281 } 282 283 return p.StopResponse 284 } 285 286 func (p *MockProvider) ReadResource(r providers.ReadResourceRequest) (resp providers.ReadResourceResponse) { 287 p.Lock() 288 defer p.Unlock() 289 290 p.ReadResourceCalled = true 291 p.ReadResourceRequest = r 292 293 if !p.ConfigureProviderCalled { 294 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("Configure not called before ReadResource %q", r.TypeName)) 295 return resp 296 } 297 298 if p.ReadResourceFn != nil { 299 return p.ReadResourceFn(r) 300 } 301 302 if p.ReadResourceResponse != nil { 303 resp = *p.ReadResourceResponse 304 305 // Make sure the NewState conforms to the schema. 306 // This isn't always the case for the existing tests. 307 schema, ok := p.getProviderSchema().ResourceTypes[r.TypeName] 308 if !ok { 309 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("no schema found for %q", r.TypeName)) 310 return resp 311 } 312 313 newState, err := schema.Block.CoerceValue(resp.NewState) 314 if err != nil { 315 resp.Diagnostics = resp.Diagnostics.Append(err) 316 } 317 resp.NewState = newState 318 return resp 319 } 320 321 // otherwise just return the same state we received 322 resp.NewState = r.PriorState 323 resp.Private = r.Private 324 return resp 325 } 326 327 func (p *MockProvider) PlanResourceChange(r providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 328 p.Lock() 329 defer p.Unlock() 330 331 if !p.ConfigureProviderCalled { 332 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("Configure not called before PlanResourceChange %q", r.TypeName)) 333 return resp 334 } 335 336 p.PlanResourceChangeCalled = true 337 p.PlanResourceChangeRequest = r 338 339 if p.PlanResourceChangeFn != nil { 340 return p.PlanResourceChangeFn(r) 341 } 342 343 if p.PlanResourceChangeResponse != nil { 344 return *p.PlanResourceChangeResponse 345 } 346 347 // this is a destroy plan, 348 if r.ProposedNewState.IsNull() { 349 resp.PlannedState = r.ProposedNewState 350 resp.PlannedPrivate = r.PriorPrivate 351 return resp 352 } 353 354 schema, ok := p.getProviderSchema().ResourceTypes[r.TypeName] 355 if !ok { 356 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("no schema found for %q", r.TypeName)) 357 return resp 358 } 359 360 // The default plan behavior is to accept the proposed value, and mark all 361 // nil computed attributes as unknown. 362 val, err := cty.Transform(r.ProposedNewState, func(path cty.Path, v cty.Value) (cty.Value, error) { 363 // We're only concerned with known null values, which can be computed 364 // by the provider. 365 if !v.IsKnown() { 366 return v, nil 367 } 368 369 attrSchema := schema.Block.AttributeByPath(path) 370 if attrSchema == nil { 371 // this is an intermediate path which does not represent an attribute 372 return v, nil 373 } 374 375 // get the current configuration value, to detect when a 376 // computed+optional attributes has become unset 377 configVal, err := path.Apply(r.Config) 378 if err != nil { 379 return v, err 380 } 381 382 switch { 383 case attrSchema.Computed && !attrSchema.Optional && v.IsNull(): 384 // this is the easy path, this value is not yet set, and _must_ be computed 385 return cty.UnknownVal(v.Type()), nil 386 387 case attrSchema.Computed && attrSchema.Optional && !v.IsNull() && configVal.IsNull(): 388 // If an optional+computed value has gone from set to unset, it 389 // becomes computed. (this was not possible to do with legacy 390 // providers) 391 return cty.UnknownVal(v.Type()), nil 392 } 393 394 return v, nil 395 }) 396 if err != nil { 397 resp.Diagnostics = resp.Diagnostics.Append(err) 398 return resp 399 } 400 401 resp.PlannedPrivate = r.PriorPrivate 402 resp.PlannedState = val 403 404 return resp 405 } 406 407 func (p *MockProvider) ApplyResourceChange(r providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 408 p.Lock() 409 defer p.Unlock() 410 p.ApplyResourceChangeCalled = true 411 p.ApplyResourceChangeRequest = r 412 413 if !p.ConfigureProviderCalled { 414 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("Configure not called before ApplyResourceChange %q", r.TypeName)) 415 return resp 416 } 417 418 if p.ApplyResourceChangeFn != nil { 419 return p.ApplyResourceChangeFn(r) 420 } 421 422 if p.ApplyResourceChangeResponse != nil { 423 return *p.ApplyResourceChangeResponse 424 } 425 426 // if the value is nil, we return that directly to correspond to a delete 427 if r.PlannedState.IsNull() { 428 resp.NewState = r.PlannedState 429 return resp 430 } 431 432 // the default behavior will be to create the minimal valid apply value by 433 // setting unknowns (which correspond to computed attributes) to a zero 434 // value. 435 val, _ := cty.Transform(r.PlannedState, func(path cty.Path, v cty.Value) (cty.Value, error) { 436 if !v.IsKnown() { 437 ty := v.Type() 438 switch { 439 case ty == cty.String: 440 return cty.StringVal(""), nil 441 case ty == cty.Number: 442 return cty.NumberIntVal(0), nil 443 case ty == cty.Bool: 444 return cty.False, nil 445 case ty.IsMapType(): 446 return cty.MapValEmpty(ty.ElementType()), nil 447 case ty.IsListType(): 448 return cty.ListValEmpty(ty.ElementType()), nil 449 default: 450 return cty.NullVal(ty), nil 451 } 452 } 453 return v, nil 454 }) 455 456 resp.NewState = val 457 resp.Private = r.PlannedPrivate 458 459 return resp 460 } 461 462 func (p *MockProvider) ImportResourceState(r providers.ImportResourceStateRequest) (resp providers.ImportResourceStateResponse) { 463 p.Lock() 464 defer p.Unlock() 465 466 if !p.ConfigureProviderCalled { 467 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("Configure not called before ImportResourceState %q", r.TypeName)) 468 return resp 469 } 470 471 p.ImportResourceStateCalled = true 472 p.ImportResourceStateRequest = r 473 if p.ImportResourceStateFn != nil { 474 return p.ImportResourceStateFn(r) 475 } 476 477 if p.ImportResourceStateResponse != nil { 478 // There's no guarantee that the imported resources slice isn't being read somewhere else 479 // As such, any changes we make on it (including through pointers) would lead to data races. 480 // To avoid that, copy and make changes on the copy 481 resp.ImportedResources = make([]providers.ImportedResource, len(p.ImportResourceStateResponse.ImportedResources)) 482 483 // fixup the cty value to match the schema 484 for i, res := range p.ImportResourceStateResponse.ImportedResources { 485 schema, ok := p.getProviderSchema().ResourceTypes[res.TypeName] 486 if !ok { 487 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("no schema found for %q", res.TypeName)) 488 return resp 489 } 490 491 var err error 492 res.State, err = schema.Block.CoerceValue(res.State) 493 if err != nil { 494 resp.Diagnostics = resp.Diagnostics.Append(err) 495 return resp 496 } 497 498 resp.ImportedResources[i] = res 499 } 500 } 501 502 return resp 503 } 504 505 func (p *MockProvider) ReadDataSource(r providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) { 506 p.Lock() 507 defer p.Unlock() 508 509 if !p.ConfigureProviderCalled { 510 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("Configure not called before ReadDataSource %q", r.TypeName)) 511 return resp 512 } 513 514 p.ReadDataSourceCalled = true 515 p.ReadDataSourceRequest = r 516 517 if p.ReadDataSourceFn != nil { 518 return p.ReadDataSourceFn(r) 519 } 520 521 if p.ReadDataSourceResponse != nil { 522 resp = *p.ReadDataSourceResponse 523 } 524 525 return resp 526 } 527 528 func (p *MockProvider) GetFunctions() (resp providers.GetFunctionsResponse) { 529 p.Lock() 530 defer p.Unlock() 531 532 p.GetFunctionsCalled = true 533 534 if p.GetFunctionsFn != nil { 535 return p.GetFunctionsFn() 536 } 537 538 if p.GetFunctionsResponse != nil { 539 resp = *p.GetFunctionsResponse 540 } 541 return resp 542 } 543 544 func (p *MockProvider) CallFunction(r providers.CallFunctionRequest) (resp providers.CallFunctionResponse) { 545 p.Lock() 546 defer p.Unlock() 547 548 p.CallFunctionCalled = true 549 p.CallFunctionRequest = r 550 551 if p.CallFunctionFn != nil { 552 return p.CallFunctionFn(r) 553 } 554 555 if p.CallFunctionResponse != nil { 556 resp = *p.CallFunctionResponse 557 } 558 return resp 559 } 560 561 func (p *MockProvider) Close() error { 562 p.Lock() 563 defer p.Unlock() 564 565 p.CloseCalled = true 566 return p.CloseError 567 }