github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/provider_test.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 schema 7 8 import ( 9 "fmt" 10 "reflect" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/google/go-cmp/cmp" 16 "github.com/zclconf/go-cty/cty" 17 18 "github.com/opentofu/opentofu/internal/configs/configschema" 19 "github.com/opentofu/opentofu/internal/legacy/tofu" 20 ) 21 22 func TestProvider_impl(t *testing.T) { 23 var _ tofu.ResourceProvider = new(Provider) 24 } 25 26 func TestProviderGetSchema(t *testing.T) { 27 // This functionality is already broadly tested in core_schema_test.go, 28 // so this is just to ensure that the call passes through correctly. 29 p := &Provider{ 30 Schema: map[string]*Schema{ 31 "bar": { 32 Type: TypeString, 33 Required: true, 34 }, 35 }, 36 ResourcesMap: map[string]*Resource{ 37 "foo": &Resource{ 38 Schema: map[string]*Schema{ 39 "bar": { 40 Type: TypeString, 41 Required: true, 42 }, 43 }, 44 }, 45 }, 46 DataSourcesMap: map[string]*Resource{ 47 "baz": &Resource{ 48 Schema: map[string]*Schema{ 49 "bur": { 50 Type: TypeString, 51 Required: true, 52 }, 53 }, 54 }, 55 }, 56 } 57 58 want := &tofu.ProviderSchema{ 59 Provider: &configschema.Block{ 60 Attributes: map[string]*configschema.Attribute{ 61 "bar": &configschema.Attribute{ 62 Type: cty.String, 63 Required: true, 64 }, 65 }, 66 BlockTypes: map[string]*configschema.NestedBlock{}, 67 }, 68 ResourceTypes: map[string]*configschema.Block{ 69 "foo": testResource(&configschema.Block{ 70 Attributes: map[string]*configschema.Attribute{ 71 "bar": &configschema.Attribute{ 72 Type: cty.String, 73 Required: true, 74 }, 75 }, 76 BlockTypes: map[string]*configschema.NestedBlock{}, 77 }), 78 }, 79 DataSources: map[string]*configschema.Block{ 80 "baz": testResource(&configschema.Block{ 81 Attributes: map[string]*configschema.Attribute{ 82 "bur": &configschema.Attribute{ 83 Type: cty.String, 84 Required: true, 85 }, 86 }, 87 BlockTypes: map[string]*configschema.NestedBlock{}, 88 }), 89 }, 90 } 91 got, err := p.GetSchema(&tofu.ProviderSchemaRequest{ 92 ResourceTypes: []string{"foo", "bar"}, 93 DataSources: []string{"baz", "bar"}, 94 }) 95 if err != nil { 96 t.Fatalf("unexpected error %s", err) 97 } 98 99 if !cmp.Equal(got, want, equateEmpty, typeComparer) { 100 t.Error("wrong result:\n", cmp.Diff(got, want, equateEmpty, typeComparer)) 101 } 102 } 103 104 func TestProviderConfigure(t *testing.T) { 105 cases := []struct { 106 P *Provider 107 Config map[string]interface{} 108 Err bool 109 }{ 110 { 111 P: &Provider{}, 112 Config: nil, 113 Err: false, 114 }, 115 116 { 117 P: &Provider{ 118 Schema: map[string]*Schema{ 119 "foo": &Schema{ 120 Type: TypeInt, 121 Optional: true, 122 }, 123 }, 124 125 ConfigureFunc: func(d *ResourceData) (interface{}, error) { 126 if d.Get("foo").(int) == 42 { 127 return nil, nil 128 } 129 130 return nil, fmt.Errorf("nope") 131 }, 132 }, 133 Config: map[string]interface{}{ 134 "foo": 42, 135 }, 136 Err: false, 137 }, 138 139 { 140 P: &Provider{ 141 Schema: map[string]*Schema{ 142 "foo": &Schema{ 143 Type: TypeInt, 144 Optional: true, 145 }, 146 }, 147 148 ConfigureFunc: func(d *ResourceData) (interface{}, error) { 149 if d.Get("foo").(int) == 42 { 150 return nil, nil 151 } 152 153 return nil, fmt.Errorf("nope") 154 }, 155 }, 156 Config: map[string]interface{}{ 157 "foo": 52, 158 }, 159 Err: true, 160 }, 161 } 162 163 for i, tc := range cases { 164 c := tofu.NewResourceConfigRaw(tc.Config) 165 err := tc.P.Configure(c) 166 if err != nil != tc.Err { 167 t.Fatalf("%d: %s", i, err) 168 } 169 } 170 } 171 172 func TestProviderResources(t *testing.T) { 173 cases := []struct { 174 P *Provider 175 Result []tofu.ResourceType 176 }{ 177 { 178 P: &Provider{}, 179 Result: []tofu.ResourceType{}, 180 }, 181 182 { 183 P: &Provider{ 184 ResourcesMap: map[string]*Resource{ 185 "foo": nil, 186 "bar": nil, 187 }, 188 }, 189 Result: []tofu.ResourceType{ 190 tofu.ResourceType{Name: "bar", SchemaAvailable: true}, 191 tofu.ResourceType{Name: "foo", SchemaAvailable: true}, 192 }, 193 }, 194 195 { 196 P: &Provider{ 197 ResourcesMap: map[string]*Resource{ 198 "foo": nil, 199 "bar": &Resource{Importer: &ResourceImporter{}}, 200 "baz": nil, 201 }, 202 }, 203 Result: []tofu.ResourceType{ 204 tofu.ResourceType{Name: "bar", Importable: true, SchemaAvailable: true}, 205 tofu.ResourceType{Name: "baz", SchemaAvailable: true}, 206 tofu.ResourceType{Name: "foo", SchemaAvailable: true}, 207 }, 208 }, 209 } 210 211 for i, tc := range cases { 212 actual := tc.P.Resources() 213 if !reflect.DeepEqual(actual, tc.Result) { 214 t.Fatalf("%d: %#v", i, actual) 215 } 216 } 217 } 218 219 func TestProviderDataSources(t *testing.T) { 220 cases := []struct { 221 P *Provider 222 Result []tofu.DataSource 223 }{ 224 { 225 P: &Provider{}, 226 Result: []tofu.DataSource{}, 227 }, 228 229 { 230 P: &Provider{ 231 DataSourcesMap: map[string]*Resource{ 232 "foo": nil, 233 "bar": nil, 234 }, 235 }, 236 Result: []tofu.DataSource{ 237 tofu.DataSource{Name: "bar", SchemaAvailable: true}, 238 tofu.DataSource{Name: "foo", SchemaAvailable: true}, 239 }, 240 }, 241 } 242 243 for i, tc := range cases { 244 actual := tc.P.DataSources() 245 if !reflect.DeepEqual(actual, tc.Result) { 246 t.Fatalf("%d: got %#v; want %#v", i, actual, tc.Result) 247 } 248 } 249 } 250 251 func TestProviderValidate(t *testing.T) { 252 cases := []struct { 253 P *Provider 254 Config map[string]interface{} 255 Err bool 256 }{ 257 { 258 P: &Provider{ 259 Schema: map[string]*Schema{ 260 "foo": &Schema{}, 261 }, 262 }, 263 Config: nil, 264 Err: true, 265 }, 266 } 267 268 for i, tc := range cases { 269 c := tofu.NewResourceConfigRaw(tc.Config) 270 _, es := tc.P.Validate(c) 271 if len(es) > 0 != tc.Err { 272 t.Fatalf("%d: %#v", i, es) 273 } 274 } 275 } 276 277 func TestProviderDiff_legacyTimeoutType(t *testing.T) { 278 p := &Provider{ 279 ResourcesMap: map[string]*Resource{ 280 "blah": &Resource{ 281 Schema: map[string]*Schema{ 282 "foo": { 283 Type: TypeInt, 284 Optional: true, 285 }, 286 }, 287 Timeouts: &ResourceTimeout{ 288 Create: DefaultTimeout(10 * time.Minute), 289 }, 290 }, 291 }, 292 } 293 294 invalidCfg := map[string]interface{}{ 295 "foo": 42, 296 "timeouts": []interface{}{ 297 map[string]interface{}{ 298 "create": "40m", 299 }, 300 }, 301 } 302 ic := tofu.NewResourceConfigRaw(invalidCfg) 303 _, err := p.Diff( 304 &tofu.InstanceInfo{ 305 Type: "blah", 306 }, 307 nil, 308 ic, 309 ) 310 if err != nil { 311 t.Fatal(err) 312 } 313 } 314 315 func TestProviderDiff_timeoutInvalidValue(t *testing.T) { 316 p := &Provider{ 317 ResourcesMap: map[string]*Resource{ 318 "blah": &Resource{ 319 Schema: map[string]*Schema{ 320 "foo": { 321 Type: TypeInt, 322 Optional: true, 323 }, 324 }, 325 Timeouts: &ResourceTimeout{ 326 Create: DefaultTimeout(10 * time.Minute), 327 }, 328 }, 329 }, 330 } 331 332 invalidCfg := map[string]interface{}{ 333 "foo": 42, 334 "timeouts": map[string]interface{}{ 335 "create": "invalid", 336 }, 337 } 338 ic := tofu.NewResourceConfigRaw(invalidCfg) 339 _, err := p.Diff( 340 &tofu.InstanceInfo{ 341 Type: "blah", 342 }, 343 nil, 344 ic, 345 ) 346 if err == nil { 347 t.Fatal("Expected provider.Diff to fail with invalid timeout value") 348 } 349 expectedErrMsg := `time: invalid duration "invalid"` 350 if !strings.Contains(err.Error(), expectedErrMsg) { 351 t.Fatalf("Unexpected error message: %q\nExpected message to contain %q", 352 err.Error(), 353 expectedErrMsg) 354 } 355 } 356 357 func TestProviderValidateResource(t *testing.T) { 358 cases := []struct { 359 P *Provider 360 Type string 361 Config map[string]interface{} 362 Err bool 363 }{ 364 { 365 P: &Provider{}, 366 Type: "foo", 367 Config: nil, 368 Err: true, 369 }, 370 371 { 372 P: &Provider{ 373 ResourcesMap: map[string]*Resource{ 374 "foo": &Resource{}, 375 }, 376 }, 377 Type: "foo", 378 Config: nil, 379 Err: false, 380 }, 381 } 382 383 for i, tc := range cases { 384 c := tofu.NewResourceConfigRaw(tc.Config) 385 _, es := tc.P.ValidateResource(tc.Type, c) 386 if len(es) > 0 != tc.Err { 387 t.Fatalf("%d: %#v", i, es) 388 } 389 } 390 } 391 392 func TestProviderImportState_default(t *testing.T) { 393 p := &Provider{ 394 ResourcesMap: map[string]*Resource{ 395 "foo": &Resource{ 396 Importer: &ResourceImporter{}, 397 }, 398 }, 399 } 400 401 states, err := p.ImportState(&tofu.InstanceInfo{ 402 Type: "foo", 403 }, "bar") 404 if err != nil { 405 t.Fatalf("err: %s", err) 406 } 407 408 if len(states) != 1 { 409 t.Fatalf("bad: %#v", states) 410 } 411 if states[0].ID != "bar" { 412 t.Fatalf("bad: %#v", states) 413 } 414 } 415 416 func TestProviderImportState_setsId(t *testing.T) { 417 var val string 418 stateFunc := func(d *ResourceData, meta interface{}) ([]*ResourceData, error) { 419 val = d.Id() 420 return []*ResourceData{d}, nil 421 } 422 423 p := &Provider{ 424 ResourcesMap: map[string]*Resource{ 425 "foo": &Resource{ 426 Importer: &ResourceImporter{ 427 State: stateFunc, 428 }, 429 }, 430 }, 431 } 432 433 _, err := p.ImportState(&tofu.InstanceInfo{ 434 Type: "foo", 435 }, "bar") 436 if err != nil { 437 t.Fatalf("err: %s", err) 438 } 439 440 if val != "bar" { 441 t.Fatal("should set id") 442 } 443 } 444 445 func TestProviderImportState_setsType(t *testing.T) { 446 var tVal string 447 stateFunc := func(d *ResourceData, meta interface{}) ([]*ResourceData, error) { 448 d.SetId("foo") 449 tVal = d.State().Ephemeral.Type 450 return []*ResourceData{d}, nil 451 } 452 453 p := &Provider{ 454 ResourcesMap: map[string]*Resource{ 455 "foo": &Resource{ 456 Importer: &ResourceImporter{ 457 State: stateFunc, 458 }, 459 }, 460 }, 461 } 462 463 _, err := p.ImportState(&tofu.InstanceInfo{ 464 Type: "foo", 465 }, "bar") 466 if err != nil { 467 t.Fatalf("err: %s", err) 468 } 469 470 if tVal != "foo" { 471 t.Fatal("should set type") 472 } 473 } 474 475 func TestProviderMeta(t *testing.T) { 476 p := new(Provider) 477 if v := p.Meta(); v != nil { 478 t.Fatalf("bad: %#v", v) 479 } 480 481 expected := 42 482 p.SetMeta(42) 483 if v := p.Meta(); !reflect.DeepEqual(v, expected) { 484 t.Fatalf("bad: %#v", v) 485 } 486 } 487 488 func TestProviderStop(t *testing.T) { 489 var p Provider 490 491 if p.Stopped() { 492 t.Fatal("should not be stopped") 493 } 494 495 // Verify stopch blocks 496 ch := p.StopContext().Done() 497 select { 498 case <-ch: 499 t.Fatal("should not be stopped") 500 case <-time.After(10 * time.Millisecond): 501 } 502 503 // Stop it 504 if err := p.Stop(); err != nil { 505 t.Fatalf("err: %s", err) 506 } 507 508 // Verify 509 if !p.Stopped() { 510 t.Fatal("should be stopped") 511 } 512 513 select { 514 case <-ch: 515 case <-time.After(10 * time.Millisecond): 516 t.Fatal("should be stopped") 517 } 518 } 519 520 func TestProviderStop_stopFirst(t *testing.T) { 521 var p Provider 522 523 // Stop it 524 if err := p.Stop(); err != nil { 525 t.Fatalf("err: %s", err) 526 } 527 528 // Verify 529 if !p.Stopped() { 530 t.Fatal("should be stopped") 531 } 532 533 select { 534 case <-p.StopContext().Done(): 535 case <-time.After(10 * time.Millisecond): 536 t.Fatal("should be stopped") 537 } 538 } 539 540 func TestProviderReset(t *testing.T) { 541 var p Provider 542 stopCtx := p.StopContext() 543 p.MetaReset = func() error { 544 stopCtx = p.StopContext() 545 return nil 546 } 547 548 // cancel the current context 549 p.Stop() 550 551 if err := p.TestReset(); err != nil { 552 t.Fatal(err) 553 } 554 555 // the first context should have been replaced 556 if err := stopCtx.Err(); err != nil { 557 t.Fatal(err) 558 } 559 560 // we should not get a canceled context here either 561 if err := p.StopContext().Err(); err != nil { 562 t.Fatal(err) 563 } 564 } 565 566 func TestProvider_InternalValidate(t *testing.T) { 567 cases := []struct { 568 P *Provider 569 ExpectedErr error 570 }{ 571 { 572 P: &Provider{ 573 Schema: map[string]*Schema{ 574 "foo": { 575 Type: TypeBool, 576 Optional: true, 577 }, 578 }, 579 }, 580 ExpectedErr: nil, 581 }, 582 { // Reserved resource fields should be allowed in provider block 583 P: &Provider{ 584 Schema: map[string]*Schema{ 585 "provisioner": { 586 Type: TypeString, 587 Optional: true, 588 }, 589 "count": { 590 Type: TypeInt, 591 Optional: true, 592 }, 593 }, 594 }, 595 ExpectedErr: nil, 596 }, 597 { // Reserved provider fields should not be allowed 598 P: &Provider{ 599 Schema: map[string]*Schema{ 600 "alias": { 601 Type: TypeString, 602 Optional: true, 603 }, 604 }, 605 }, 606 ExpectedErr: fmt.Errorf("%s is a reserved field name for a provider", "alias"), 607 }, 608 } 609 610 for i, tc := range cases { 611 err := tc.P.InternalValidate() 612 if tc.ExpectedErr == nil { 613 if err != nil { 614 t.Fatalf("%d: Error returned (expected no error): %s", i, err) 615 } 616 continue 617 } 618 if tc.ExpectedErr != nil && err == nil { 619 t.Fatalf("%d: Expected error (%s), but no error returned", i, tc.ExpectedErr) 620 } 621 if err.Error() != tc.ExpectedErr.Error() { 622 t.Fatalf("%d: Errors don't match. Expected: %#v Given: %#v", i, tc.ExpectedErr, err) 623 } 624 } 625 }