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