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