github.com/hugorut/terraform@v1.1.3/src/terraform/context_validate_test.go (about) 1 package terraform 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 "testing" 8 9 "github.com/zclconf/go-cty/cty" 10 11 "github.com/hugorut/terraform/src/addrs" 12 "github.com/hugorut/terraform/src/configs/configschema" 13 "github.com/hugorut/terraform/src/providers" 14 "github.com/hugorut/terraform/src/provisioners" 15 "github.com/hugorut/terraform/src/states" 16 "github.com/hugorut/terraform/src/tfdiags" 17 ) 18 19 func TestContext2Validate_badCount(t *testing.T) { 20 p := testProvider("aws") 21 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 22 ResourceTypes: map[string]*configschema.Block{ 23 "aws_instance": { 24 Attributes: map[string]*configschema.Attribute{}, 25 }, 26 }, 27 }) 28 29 m := testModule(t, "validate-bad-count") 30 c := testContext2(t, &ContextOpts{ 31 Providers: map[addrs.Provider]providers.Factory{ 32 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 33 }, 34 }) 35 36 diags := c.Validate(m) 37 if !diags.HasErrors() { 38 t.Fatalf("succeeded; want error") 39 } 40 } 41 42 func TestContext2Validate_badResource_reference(t *testing.T) { 43 p := testProvider("aws") 44 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 45 ResourceTypes: map[string]*configschema.Block{ 46 "aws_instance": { 47 Attributes: map[string]*configschema.Attribute{}, 48 }, 49 }, 50 }) 51 52 m := testModule(t, "validate-bad-resource-count") 53 c := testContext2(t, &ContextOpts{ 54 Providers: map[addrs.Provider]providers.Factory{ 55 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 56 }, 57 }) 58 59 diags := c.Validate(m) 60 if !diags.HasErrors() { 61 t.Fatalf("succeeded; want error") 62 } 63 } 64 65 func TestContext2Validate_badVar(t *testing.T) { 66 p := testProvider("aws") 67 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 68 ResourceTypes: map[string]*configschema.Block{ 69 "aws_instance": { 70 Attributes: map[string]*configschema.Attribute{ 71 "foo": {Type: cty.String, Optional: true}, 72 "num": {Type: cty.String, Optional: true}, 73 }, 74 }, 75 }, 76 }) 77 78 m := testModule(t, "validate-bad-var") 79 c := testContext2(t, &ContextOpts{ 80 Providers: map[addrs.Provider]providers.Factory{ 81 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 82 }, 83 }) 84 85 diags := c.Validate(m) 86 if !diags.HasErrors() { 87 t.Fatalf("succeeded; want error") 88 } 89 } 90 91 func TestContext2Validate_varNoDefaultExplicitType(t *testing.T) { 92 m := testModule(t, "validate-var-no-default-explicit-type") 93 c, diags := NewContext(&ContextOpts{}) 94 if diags.HasErrors() { 95 t.Fatalf("unexpected NewContext errors: %s", diags.Err()) 96 } 97 98 // NOTE: This test has grown idiosyncratic because originally Terraform 99 // would (optionally) check variables during validation, and then in 100 // Terraform v0.12 we switched to checking variables during NewContext, 101 // and now most recently we've switched to checking variables only during 102 // planning because root variables are a plan option. Therefore this has 103 // grown into a plan test rather than a validate test, but it lives on 104 // here in order to make it easier to navigate through that history in 105 // version control. 106 _, diags = c.Plan(m, states.NewState(), DefaultPlanOpts) 107 if !diags.HasErrors() { 108 // Error should be: The input variable "maybe_a_map" has not been assigned a value. 109 t.Fatalf("succeeded; want error") 110 } 111 } 112 113 func TestContext2Validate_computedVar(t *testing.T) { 114 p := testProvider("aws") 115 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 116 Provider: providers.Schema{ 117 Block: &configschema.Block{ 118 Attributes: map[string]*configschema.Attribute{ 119 "value": {Type: cty.String, Optional: true}, 120 }, 121 }, 122 }, 123 ResourceTypes: map[string]providers.Schema{ 124 "aws_instance": { 125 Block: &configschema.Block{ 126 Attributes: map[string]*configschema.Attribute{}, 127 }, 128 }, 129 }, 130 } 131 pt := testProvider("test") 132 pt.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 133 ResourceTypes: map[string]providers.Schema{ 134 "test_instance": { 135 Block: &configschema.Block{ 136 Attributes: map[string]*configschema.Attribute{ 137 "id": {Type: cty.String, Computed: true}, 138 "value": {Type: cty.String, Optional: true}, 139 }, 140 }, 141 }, 142 }, 143 } 144 145 m := testModule(t, "validate-computed-var") 146 c := testContext2(t, &ContextOpts{ 147 Providers: map[addrs.Provider]providers.Factory{ 148 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 149 addrs.NewDefaultProvider("test"): testProviderFuncFixed(pt), 150 }, 151 }) 152 153 p.ValidateProviderConfigFn = func(req providers.ValidateProviderConfigRequest) (resp providers.ValidateProviderConfigResponse) { 154 val := req.Config.GetAttr("value") 155 if val.IsKnown() { 156 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value isn't computed")) 157 } 158 159 return 160 } 161 162 diags := c.Validate(m) 163 if diags.HasErrors() { 164 t.Fatalf("unexpected error: %s", diags.Err()) 165 } 166 if p.ConfigureProviderCalled { 167 t.Fatal("Configure should not be called for provider") 168 } 169 } 170 171 func TestContext2Validate_computedInFunction(t *testing.T) { 172 p := testProvider("aws") 173 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 174 ResourceTypes: map[string]providers.Schema{ 175 "aws_instance": { 176 Block: &configschema.Block{ 177 Attributes: map[string]*configschema.Attribute{ 178 "attr": {Type: cty.Number, Optional: true}, 179 }, 180 }, 181 }, 182 }, 183 DataSources: map[string]providers.Schema{ 184 "aws_data_source": { 185 Block: &configschema.Block{ 186 Attributes: map[string]*configschema.Attribute{ 187 "optional_attr": {Type: cty.String, Optional: true}, 188 "computed": {Type: cty.String, Computed: true}, 189 }, 190 }, 191 }, 192 }, 193 } 194 195 m := testModule(t, "validate-computed-in-function") 196 c := testContext2(t, &ContextOpts{ 197 Providers: map[addrs.Provider]providers.Factory{ 198 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 199 }, 200 }) 201 202 diags := c.Validate(m) 203 if diags.HasErrors() { 204 t.Fatalf("unexpected error: %s", diags.Err()) 205 } 206 } 207 208 // Test that validate allows through computed counts. We do this and allow 209 // them to fail during "plan" since we can't know if the computed values 210 // can be realized during a plan. 211 func TestContext2Validate_countComputed(t *testing.T) { 212 p := testProvider("aws") 213 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 214 ResourceTypes: map[string]providers.Schema{ 215 "aws_instance": { 216 Block: &configschema.Block{ 217 Attributes: map[string]*configschema.Attribute{}, 218 }, 219 }, 220 }, 221 DataSources: map[string]providers.Schema{ 222 "aws_data_source": { 223 Block: &configschema.Block{ 224 Attributes: map[string]*configschema.Attribute{ 225 "compute": {Type: cty.String, Optional: true}, 226 "value": {Type: cty.String, Computed: true}, 227 }, 228 }, 229 }, 230 }, 231 } 232 233 m := testModule(t, "validate-count-computed") 234 c := testContext2(t, &ContextOpts{ 235 Providers: map[addrs.Provider]providers.Factory{ 236 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 237 }, 238 }) 239 240 diags := c.Validate(m) 241 if diags.HasErrors() { 242 t.Fatalf("unexpected error: %s", diags.Err()) 243 } 244 } 245 246 func TestContext2Validate_countNegative(t *testing.T) { 247 p := testProvider("aws") 248 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 249 ResourceTypes: map[string]providers.Schema{ 250 "aws_instance": { 251 Block: &configschema.Block{ 252 Attributes: map[string]*configschema.Attribute{}, 253 }, 254 }, 255 }, 256 } 257 m := testModule(t, "validate-count-negative") 258 c := testContext2(t, &ContextOpts{ 259 Providers: map[addrs.Provider]providers.Factory{ 260 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 261 }, 262 }) 263 264 diags := c.Validate(m) 265 if !diags.HasErrors() { 266 t.Fatalf("succeeded; want error") 267 } 268 } 269 270 func TestContext2Validate_countVariable(t *testing.T) { 271 p := testProvider("aws") 272 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 273 ResourceTypes: map[string]providers.Schema{ 274 "aws_instance": { 275 Block: &configschema.Block{ 276 Attributes: map[string]*configschema.Attribute{ 277 "foo": {Type: cty.String, Optional: true}, 278 }, 279 }, 280 }, 281 }, 282 } 283 m := testModule(t, "apply-count-variable") 284 c := testContext2(t, &ContextOpts{ 285 Providers: map[addrs.Provider]providers.Factory{ 286 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 287 }, 288 }) 289 290 diags := c.Validate(m) 291 if diags.HasErrors() { 292 t.Fatalf("unexpected error: %s", diags.Err()) 293 } 294 } 295 296 func TestContext2Validate_countVariableNoDefault(t *testing.T) { 297 p := testProvider("aws") 298 m := testModule(t, "validate-count-variable") 299 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 300 ResourceTypes: map[string]providers.Schema{ 301 "aws_instance": { 302 Block: &configschema.Block{ 303 Attributes: map[string]*configschema.Attribute{ 304 "foo": {Type: cty.String, Optional: true}, 305 }, 306 }, 307 }, 308 }, 309 } 310 c, diags := NewContext(&ContextOpts{ 311 Providers: map[addrs.Provider]providers.Factory{ 312 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 313 }, 314 }) 315 assertNoDiagnostics(t, diags) 316 317 _, diags = c.Plan(m, nil, &PlanOpts{}) 318 if !diags.HasErrors() { 319 // Error should be: The input variable "foo" has not been assigned a value. 320 t.Fatalf("succeeded; want error") 321 } 322 } 323 324 func TestContext2Validate_moduleBadOutput(t *testing.T) { 325 p := testProvider("aws") 326 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 327 ResourceTypes: map[string]providers.Schema{ 328 "aws_instance": { 329 Block: &configschema.Block{ 330 Attributes: map[string]*configschema.Attribute{ 331 "foo": {Type: cty.String, Optional: true}, 332 }, 333 }, 334 }, 335 }, 336 } 337 m := testModule(t, "validate-bad-module-output") 338 c := testContext2(t, &ContextOpts{ 339 Providers: map[addrs.Provider]providers.Factory{ 340 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 341 }, 342 }) 343 344 diags := c.Validate(m) 345 if !diags.HasErrors() { 346 t.Fatalf("succeeded; want error") 347 } 348 } 349 350 func TestContext2Validate_moduleGood(t *testing.T) { 351 p := testProvider("aws") 352 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 353 ResourceTypes: map[string]providers.Schema{ 354 "aws_instance": { 355 Block: &configschema.Block{ 356 Attributes: map[string]*configschema.Attribute{ 357 "foo": {Type: cty.String, Optional: true}, 358 }, 359 }, 360 }, 361 }, 362 } 363 m := testModule(t, "validate-good-module") 364 c := testContext2(t, &ContextOpts{ 365 Providers: map[addrs.Provider]providers.Factory{ 366 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 367 }, 368 }) 369 370 diags := c.Validate(m) 371 if diags.HasErrors() { 372 t.Fatalf("unexpected error: %s", diags.Err()) 373 } 374 } 375 376 func TestContext2Validate_moduleBadResource(t *testing.T) { 377 m := testModule(t, "validate-module-bad-rc") 378 p := testProvider("aws") 379 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 380 ResourceTypes: map[string]providers.Schema{ 381 "aws_instance": { 382 Block: &configschema.Block{ 383 Attributes: map[string]*configschema.Attribute{}, 384 }, 385 }, 386 }, 387 } 388 389 c := testContext2(t, &ContextOpts{ 390 Providers: map[addrs.Provider]providers.Factory{ 391 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 392 }, 393 }) 394 395 p.ValidateResourceConfigResponse = &providers.ValidateResourceConfigResponse{ 396 Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("bad")), 397 } 398 399 diags := c.Validate(m) 400 if !diags.HasErrors() { 401 t.Fatalf("succeeded; want error") 402 } 403 } 404 405 func TestContext2Validate_moduleDepsShouldNotCycle(t *testing.T) { 406 m := testModule(t, "validate-module-deps-cycle") 407 p := testProvider("aws") 408 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 409 ResourceTypes: map[string]providers.Schema{ 410 "aws_instance": { 411 Block: &configschema.Block{ 412 Attributes: map[string]*configschema.Attribute{ 413 "id": {Type: cty.String, Optional: true}, 414 }, 415 }, 416 }, 417 }, 418 } 419 420 ctx := testContext2(t, &ContextOpts{ 421 Providers: map[addrs.Provider]providers.Factory{ 422 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 423 }, 424 }) 425 426 diags := ctx.Validate(m) 427 if diags.HasErrors() { 428 t.Fatalf("unexpected error: %s", diags.Err()) 429 } 430 } 431 432 func TestContext2Validate_moduleProviderVar(t *testing.T) { 433 m := testModule(t, "validate-module-pc-vars") 434 p := testProvider("aws") 435 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 436 Provider: providers.Schema{ 437 Block: &configschema.Block{ 438 Attributes: map[string]*configschema.Attribute{ 439 "foo": {Type: cty.String, Optional: true}, 440 }, 441 }, 442 }, 443 ResourceTypes: map[string]providers.Schema{ 444 "aws_instance": { 445 Block: &configschema.Block{ 446 Attributes: map[string]*configschema.Attribute{ 447 "foo": {Type: cty.String, Optional: true}, 448 }, 449 }, 450 }, 451 }, 452 } 453 454 c := testContext2(t, &ContextOpts{ 455 Providers: map[addrs.Provider]providers.Factory{ 456 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 457 }, 458 }) 459 460 p.ValidateProviderConfigFn = func(req providers.ValidateProviderConfigRequest) (resp providers.ValidateProviderConfigResponse) { 461 if req.Config.GetAttr("foo").IsNull() { 462 resp.Diagnostics = resp.Diagnostics.Append(errors.New("foo is null")) 463 } 464 return 465 } 466 467 diags := c.Validate(m) 468 if diags.HasErrors() { 469 t.Fatalf("unexpected error: %s", diags.Err()) 470 } 471 } 472 473 func TestContext2Validate_moduleProviderInheritUnused(t *testing.T) { 474 m := testModule(t, "validate-module-pc-inherit-unused") 475 p := testProvider("aws") 476 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 477 Provider: providers.Schema{ 478 Block: &configschema.Block{ 479 Attributes: map[string]*configschema.Attribute{ 480 "foo": {Type: cty.String, Optional: true}, 481 }, 482 }, 483 }, 484 ResourceTypes: map[string]providers.Schema{ 485 "aws_instance": { 486 Block: &configschema.Block{ 487 Attributes: map[string]*configschema.Attribute{ 488 "foo": {Type: cty.String, Optional: true}, 489 }, 490 }, 491 }, 492 }, 493 } 494 495 c := testContext2(t, &ContextOpts{ 496 Providers: map[addrs.Provider]providers.Factory{ 497 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 498 }, 499 }) 500 501 p.ValidateProviderConfigFn = func(req providers.ValidateProviderConfigRequest) (resp providers.ValidateProviderConfigResponse) { 502 if req.Config.GetAttr("foo").IsNull() { 503 resp.Diagnostics = resp.Diagnostics.Append(errors.New("foo is null")) 504 } 505 return 506 } 507 508 diags := c.Validate(m) 509 if diags.HasErrors() { 510 t.Fatalf("unexpected error: %s", diags.Err()) 511 } 512 } 513 514 func TestContext2Validate_orphans(t *testing.T) { 515 p := testProvider("aws") 516 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 517 ResourceTypes: map[string]providers.Schema{ 518 "aws_instance": { 519 Block: &configschema.Block{ 520 Attributes: map[string]*configschema.Attribute{ 521 "foo": {Type: cty.String, Optional: true}, 522 "num": {Type: cty.String, Optional: true}, 523 }, 524 }, 525 }, 526 }, 527 } 528 529 m := testModule(t, "validate-good") 530 531 c := testContext2(t, &ContextOpts{ 532 Providers: map[addrs.Provider]providers.Factory{ 533 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 534 }, 535 }) 536 537 p.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse { 538 var diags tfdiags.Diagnostics 539 if req.Config.GetAttr("foo").IsNull() { 540 diags = diags.Append(errors.New("foo is not set")) 541 } 542 return providers.ValidateResourceConfigResponse{ 543 Diagnostics: diags, 544 } 545 } 546 547 diags := c.Validate(m) 548 if diags.HasErrors() { 549 t.Fatalf("unexpected error: %s", diags.Err()) 550 } 551 } 552 553 func TestContext2Validate_providerConfig_bad(t *testing.T) { 554 m := testModule(t, "validate-bad-pc") 555 p := testProvider("aws") 556 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 557 Provider: providers.Schema{ 558 Block: &configschema.Block{ 559 Attributes: map[string]*configschema.Attribute{ 560 "foo": {Type: cty.String, Optional: true}, 561 }, 562 }, 563 }, 564 ResourceTypes: map[string]providers.Schema{ 565 "aws_instance": { 566 Block: &configschema.Block{ 567 Attributes: map[string]*configschema.Attribute{}, 568 }, 569 }, 570 }, 571 } 572 573 c := testContext2(t, &ContextOpts{ 574 Providers: map[addrs.Provider]providers.Factory{ 575 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 576 }, 577 }) 578 579 p.ValidateProviderConfigResponse = &providers.ValidateProviderConfigResponse{ 580 Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("bad")), 581 } 582 583 diags := c.Validate(m) 584 if len(diags) != 1 { 585 t.Fatalf("wrong number of diagnostics %d; want %d", len(diags), 1) 586 } 587 if !strings.Contains(diags.Err().Error(), "bad") { 588 t.Fatalf("bad: %s", diags.Err().Error()) 589 } 590 } 591 592 func TestContext2Validate_providerConfig_skippedEmpty(t *testing.T) { 593 m := testModule(t, "validate-skipped-pc-empty") 594 p := testProvider("aws") 595 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 596 Provider: providers.Schema{ 597 Block: &configschema.Block{ 598 Attributes: map[string]*configschema.Attribute{ 599 "foo": {Type: cty.String, Optional: true}, 600 }, 601 }, 602 }, 603 ResourceTypes: map[string]providers.Schema{ 604 "aws_instance": { 605 Block: &configschema.Block{ 606 Attributes: map[string]*configschema.Attribute{}, 607 }, 608 }, 609 }, 610 } 611 612 c := testContext2(t, &ContextOpts{ 613 Providers: map[addrs.Provider]providers.Factory{ 614 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 615 }, 616 }) 617 618 p.ValidateProviderConfigResponse = &providers.ValidateProviderConfigResponse{ 619 Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("should not be called")), 620 } 621 622 diags := c.Validate(m) 623 if diags.HasErrors() { 624 t.Fatalf("unexpected error: %s", diags.Err()) 625 } 626 } 627 628 func TestContext2Validate_providerConfig_good(t *testing.T) { 629 m := testModule(t, "validate-bad-pc") 630 p := testProvider("aws") 631 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 632 Provider: providers.Schema{ 633 Block: &configschema.Block{ 634 Attributes: map[string]*configschema.Attribute{ 635 "foo": {Type: cty.String, Optional: true}, 636 }, 637 }, 638 }, 639 ResourceTypes: map[string]providers.Schema{ 640 "aws_instance": { 641 Block: &configschema.Block{ 642 Attributes: map[string]*configschema.Attribute{}, 643 }, 644 }, 645 }, 646 } 647 648 c := testContext2(t, &ContextOpts{ 649 Providers: map[addrs.Provider]providers.Factory{ 650 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 651 }, 652 }) 653 654 diags := c.Validate(m) 655 if diags.HasErrors() { 656 t.Fatalf("unexpected error: %s", diags.Err()) 657 } 658 } 659 660 // In this test there is a mismatch between the provider's fqn (hashicorp/test) 661 // and it's local name set in required_providers (arbitrary). 662 func TestContext2Validate_requiredProviderConfig(t *testing.T) { 663 m := testModule(t, "validate-required-provider-config") 664 p := testProvider("aws") 665 666 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 667 Provider: providers.Schema{ 668 Block: &configschema.Block{ 669 Attributes: map[string]*configschema.Attribute{ 670 "required_attribute": {Type: cty.String, Required: true}, 671 }, 672 }, 673 }, 674 ResourceTypes: map[string]providers.Schema{ 675 "aws_instance": { 676 Block: &configschema.Block{ 677 Attributes: map[string]*configschema.Attribute{}, 678 }, 679 }, 680 }, 681 } 682 683 c := testContext2(t, &ContextOpts{ 684 Providers: map[addrs.Provider]providers.Factory{ 685 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 686 }, 687 }) 688 689 diags := c.Validate(m) 690 if diags.HasErrors() { 691 t.Fatalf("unexpected error: %s", diags.Err()) 692 } 693 } 694 695 func TestContext2Validate_provisionerConfig_bad(t *testing.T) { 696 m := testModule(t, "validate-bad-prov-conf") 697 p := testProvider("aws") 698 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 699 ResourceTypes: map[string]providers.Schema{ 700 "aws_instance": { 701 Block: &configschema.Block{ 702 Attributes: map[string]*configschema.Attribute{ 703 "foo": {Type: cty.String, Optional: true}, 704 }, 705 }, 706 }, 707 }, 708 } 709 710 pr := simpleMockProvisioner() 711 712 c := testContext2(t, &ContextOpts{ 713 Providers: map[addrs.Provider]providers.Factory{ 714 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 715 }, 716 Provisioners: map[string]provisioners.Factory{ 717 "shell": testProvisionerFuncFixed(pr), 718 }, 719 }) 720 721 p.ValidateProviderConfigResponse = &providers.ValidateProviderConfigResponse{ 722 Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("bad")), 723 } 724 725 diags := c.Validate(m) 726 if !diags.HasErrors() { 727 t.Fatalf("succeeded; want error") 728 } 729 } 730 731 func TestContext2Validate_badResourceConnection(t *testing.T) { 732 m := testModule(t, "validate-bad-resource-connection") 733 p := testProvider("aws") 734 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 735 ResourceTypes: map[string]providers.Schema{ 736 "aws_instance": { 737 Block: &configschema.Block{ 738 Attributes: map[string]*configschema.Attribute{ 739 "foo": {Type: cty.String, Optional: true}, 740 }, 741 }, 742 }, 743 }, 744 } 745 746 pr := simpleMockProvisioner() 747 748 c := testContext2(t, &ContextOpts{ 749 Providers: map[addrs.Provider]providers.Factory{ 750 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 751 }, 752 Provisioners: map[string]provisioners.Factory{ 753 "shell": testProvisionerFuncFixed(pr), 754 }, 755 }) 756 757 diags := c.Validate(m) 758 t.Log(diags.Err()) 759 if !diags.HasErrors() { 760 t.Fatalf("succeeded; want error") 761 } 762 } 763 764 func TestContext2Validate_badProvisionerConnection(t *testing.T) { 765 m := testModule(t, "validate-bad-prov-connection") 766 p := testProvider("aws") 767 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 768 ResourceTypes: map[string]providers.Schema{ 769 "aws_instance": { 770 Block: &configschema.Block{ 771 Attributes: map[string]*configschema.Attribute{ 772 "foo": {Type: cty.String, Optional: true}, 773 }, 774 }, 775 }, 776 }, 777 } 778 779 pr := simpleMockProvisioner() 780 781 c := testContext2(t, &ContextOpts{ 782 Providers: map[addrs.Provider]providers.Factory{ 783 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 784 }, 785 Provisioners: map[string]provisioners.Factory{ 786 "shell": testProvisionerFuncFixed(pr), 787 }, 788 }) 789 790 diags := c.Validate(m) 791 t.Log(diags.Err()) 792 if !diags.HasErrors() { 793 t.Fatalf("succeeded; want error") 794 } 795 } 796 797 func TestContext2Validate_provisionerConfig_good(t *testing.T) { 798 m := testModule(t, "validate-bad-prov-conf") 799 p := testProvider("aws") 800 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 801 Provider: providers.Schema{ 802 Block: &configschema.Block{ 803 Attributes: map[string]*configschema.Attribute{ 804 "foo": {Type: cty.String, Optional: true}, 805 }, 806 }, 807 }, 808 ResourceTypes: map[string]providers.Schema{ 809 "aws_instance": { 810 Block: &configschema.Block{ 811 Attributes: map[string]*configschema.Attribute{ 812 "foo": {Type: cty.String, Optional: true}, 813 }, 814 }, 815 }, 816 }, 817 } 818 819 pr := simpleMockProvisioner() 820 pr.ValidateProvisionerConfigFn = func(req provisioners.ValidateProvisionerConfigRequest) provisioners.ValidateProvisionerConfigResponse { 821 var diags tfdiags.Diagnostics 822 if req.Config.GetAttr("test_string").IsNull() { 823 diags = diags.Append(errors.New("test_string is not set")) 824 } 825 return provisioners.ValidateProvisionerConfigResponse{ 826 Diagnostics: diags, 827 } 828 } 829 830 c := testContext2(t, &ContextOpts{ 831 Providers: map[addrs.Provider]providers.Factory{ 832 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 833 }, 834 Provisioners: map[string]provisioners.Factory{ 835 "shell": testProvisionerFuncFixed(pr), 836 }, 837 }) 838 839 diags := c.Validate(m) 840 if diags.HasErrors() { 841 t.Fatalf("unexpected error: %s", diags.Err()) 842 } 843 } 844 845 func TestContext2Validate_requiredVar(t *testing.T) { 846 m := testModule(t, "validate-required-var") 847 p := testProvider("aws") 848 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 849 ResourceTypes: map[string]providers.Schema{ 850 "aws_instance": { 851 Block: &configschema.Block{ 852 Attributes: map[string]*configschema.Attribute{ 853 "ami": {Type: cty.String, Optional: true}, 854 }, 855 }, 856 }, 857 }, 858 } 859 c, diags := NewContext(&ContextOpts{ 860 Providers: map[addrs.Provider]providers.Factory{ 861 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 862 }, 863 }) 864 assertNoDiagnostics(t, diags) 865 866 // NOTE: This test has grown idiosyncratic because originally Terraform 867 // would (optionally) check variables during validation, and then in 868 // Terraform v0.12 we switched to checking variables during NewContext, 869 // and now most recently we've switched to checking variables only during 870 // planning because root variables are a plan option. Therefore this has 871 // grown into a plan test rather than a validate test, but it lives on 872 // here in order to make it easier to navigate through that history in 873 // version control. 874 _, diags = c.Plan(m, states.NewState(), DefaultPlanOpts) 875 if !diags.HasErrors() { 876 // Error should be: The input variable "foo" has not been assigned a value. 877 t.Fatalf("succeeded; want error") 878 } 879 } 880 881 func TestContext2Validate_resourceConfig_bad(t *testing.T) { 882 m := testModule(t, "validate-bad-rc") 883 p := testProvider("aws") 884 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 885 ResourceTypes: map[string]providers.Schema{ 886 "aws_instance": { 887 Block: &configschema.Block{ 888 Attributes: map[string]*configschema.Attribute{ 889 "foo": {Type: cty.String, Optional: true}, 890 }, 891 }, 892 }, 893 }, 894 } 895 c := testContext2(t, &ContextOpts{ 896 Providers: map[addrs.Provider]providers.Factory{ 897 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 898 }, 899 }) 900 901 p.ValidateResourceConfigResponse = &providers.ValidateResourceConfigResponse{ 902 Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("bad")), 903 } 904 905 diags := c.Validate(m) 906 if !diags.HasErrors() { 907 t.Fatalf("succeeded; want error") 908 } 909 } 910 911 func TestContext2Validate_resourceConfig_good(t *testing.T) { 912 m := testModule(t, "validate-bad-rc") 913 p := testProvider("aws") 914 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 915 ResourceTypes: map[string]providers.Schema{ 916 "aws_instance": { 917 Block: &configschema.Block{ 918 Attributes: map[string]*configschema.Attribute{ 919 "foo": {Type: cty.String, Optional: true}, 920 }, 921 }, 922 }, 923 }, 924 } 925 c := testContext2(t, &ContextOpts{ 926 Providers: map[addrs.Provider]providers.Factory{ 927 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 928 }, 929 }) 930 931 diags := c.Validate(m) 932 if diags.HasErrors() { 933 t.Fatalf("unexpected error: %s", diags.Err()) 934 } 935 } 936 937 func TestContext2Validate_tainted(t *testing.T) { 938 p := testProvider("aws") 939 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 940 ResourceTypes: map[string]providers.Schema{ 941 "aws_instance": { 942 Block: &configschema.Block{ 943 Attributes: map[string]*configschema.Attribute{ 944 "foo": {Type: cty.String, Optional: true}, 945 "num": {Type: cty.String, Optional: true}, 946 }, 947 }, 948 }, 949 }, 950 } 951 952 m := testModule(t, "validate-good") 953 c := testContext2(t, &ContextOpts{ 954 Providers: map[addrs.Provider]providers.Factory{ 955 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 956 }, 957 }) 958 959 p.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse { 960 var diags tfdiags.Diagnostics 961 if req.Config.GetAttr("foo").IsNull() { 962 diags = diags.Append(errors.New("foo is not set")) 963 } 964 return providers.ValidateResourceConfigResponse{ 965 Diagnostics: diags, 966 } 967 } 968 969 diags := c.Validate(m) 970 if diags.HasErrors() { 971 t.Fatalf("unexpected error: %s", diags.Err()) 972 } 973 } 974 975 func TestContext2Validate_targetedDestroy(t *testing.T) { 976 m := testModule(t, "validate-targeted") 977 p := testProvider("aws") 978 pr := simpleMockProvisioner() 979 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 980 ResourceTypes: map[string]providers.Schema{ 981 "aws_instance": { 982 Block: &configschema.Block{ 983 Attributes: map[string]*configschema.Attribute{ 984 "foo": {Type: cty.String, Optional: true}, 985 "num": {Type: cty.String, Optional: true}, 986 }, 987 }, 988 }, 989 }, 990 } 991 992 state := states.NewState() 993 root := state.EnsureModule(addrs.RootModuleInstance) 994 testSetResourceInstanceCurrent(root, "aws_instance.foo", `{"id":"i-bcd345"}`, `provider["registry.terraform.io/hashicorp/aws"]`) 995 testSetResourceInstanceCurrent(root, "aws_instance.bar", `{"id":"i-abc123"}`, `provider["registry.terraform.io/hashicorp/aws"]`) 996 997 ctx := testContext2(t, &ContextOpts{ 998 Providers: map[addrs.Provider]providers.Factory{ 999 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1000 }, 1001 Provisioners: map[string]provisioners.Factory{ 1002 "shell": testProvisionerFuncFixed(pr), 1003 }, 1004 }) 1005 1006 diags := ctx.Validate(m) 1007 if diags.HasErrors() { 1008 t.Fatalf("unexpected error: %s", diags.Err()) 1009 } 1010 } 1011 1012 func TestContext2Validate_varRefUnknown(t *testing.T) { 1013 m := testModule(t, "validate-variable-ref") 1014 p := testProvider("aws") 1015 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 1016 ResourceTypes: map[string]providers.Schema{ 1017 "aws_instance": { 1018 Block: &configschema.Block{ 1019 Attributes: map[string]*configschema.Attribute{ 1020 "foo": {Type: cty.String, Optional: true}, 1021 }, 1022 }, 1023 }, 1024 }, 1025 } 1026 c := testContext2(t, &ContextOpts{ 1027 Providers: map[addrs.Provider]providers.Factory{ 1028 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1029 }, 1030 }) 1031 1032 var value cty.Value 1033 p.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse { 1034 value = req.Config.GetAttr("foo") 1035 return providers.ValidateResourceConfigResponse{} 1036 } 1037 1038 c.Validate(m) 1039 1040 // Input variables are always unknown during the validate walk, because 1041 // we're checking for validity of all possible input values. Validity 1042 // against specific input values is checked during the plan walk. 1043 if !value.RawEquals(cty.UnknownVal(cty.String)) { 1044 t.Fatalf("bad: %#v", value) 1045 } 1046 } 1047 1048 // Module variables weren't being interpolated during Validate phase. 1049 // related to https://github.com/hugorut/terraform/issues/5322 1050 func TestContext2Validate_interpolateVar(t *testing.T) { 1051 input := new(MockUIInput) 1052 1053 m := testModule(t, "input-interpolate-var") 1054 p := testProvider("null") 1055 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 1056 ResourceTypes: map[string]providers.Schema{ 1057 "template_file": { 1058 Block: &configschema.Block{ 1059 Attributes: map[string]*configschema.Attribute{ 1060 "template": {Type: cty.String, Optional: true}, 1061 }, 1062 }, 1063 }, 1064 }, 1065 } 1066 1067 ctx := testContext2(t, &ContextOpts{ 1068 Providers: map[addrs.Provider]providers.Factory{ 1069 addrs.NewDefaultProvider("template"): testProviderFuncFixed(p), 1070 }, 1071 UIInput: input, 1072 }) 1073 1074 diags := ctx.Validate(m) 1075 if diags.HasErrors() { 1076 t.Fatalf("unexpected error: %s", diags.Err()) 1077 } 1078 } 1079 1080 // When module vars reference something that is actually computed, this 1081 // shouldn't cause validation to fail. 1082 func TestContext2Validate_interpolateComputedModuleVarDef(t *testing.T) { 1083 input := new(MockUIInput) 1084 1085 m := testModule(t, "validate-computed-module-var-ref") 1086 p := testProvider("aws") 1087 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 1088 ResourceTypes: map[string]providers.Schema{ 1089 "aws_instance": { 1090 Block: &configschema.Block{ 1091 Attributes: map[string]*configschema.Attribute{ 1092 "attr": {Type: cty.String, Optional: true}, 1093 }, 1094 }, 1095 }, 1096 }, 1097 } 1098 1099 ctx := testContext2(t, &ContextOpts{ 1100 Providers: map[addrs.Provider]providers.Factory{ 1101 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1102 }, 1103 UIInput: input, 1104 }) 1105 1106 diags := ctx.Validate(m) 1107 if diags.HasErrors() { 1108 t.Fatalf("unexpected error: %s", diags.Err()) 1109 } 1110 } 1111 1112 // Computed values are lost when a map is output from a module 1113 func TestContext2Validate_interpolateMap(t *testing.T) { 1114 input := new(MockUIInput) 1115 1116 m := testModule(t, "issue-9549") 1117 p := testProvider("template") 1118 1119 ctx := testContext2(t, &ContextOpts{ 1120 Providers: map[addrs.Provider]providers.Factory{ 1121 addrs.NewDefaultProvider("template"): testProviderFuncFixed(p), 1122 }, 1123 UIInput: input, 1124 }) 1125 1126 diags := ctx.Validate(m) 1127 if diags.HasErrors() { 1128 t.Fatalf("unexpected error: %s", diags.Err()) 1129 } 1130 } 1131 1132 func TestContext2Validate_varSensitive(t *testing.T) { 1133 // Smoke test through validate where a variable has sensitive applied 1134 m := testModuleInline(t, map[string]string{ 1135 "main.tf": ` 1136 variable "foo" { 1137 default = "xyz" 1138 sensitive = true 1139 } 1140 1141 variable "bar" { 1142 sensitive = true 1143 } 1144 1145 data "aws_data_source" "bar" { 1146 foo = var.bar 1147 } 1148 1149 resource "aws_instance" "foo" { 1150 foo = var.foo 1151 } 1152 `, 1153 }) 1154 1155 p := testProvider("aws") 1156 p.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse { 1157 // Providers receive unmarked values 1158 if got, want := req.Config.GetAttr("foo"), cty.UnknownVal(cty.String); !got.RawEquals(want) { 1159 t.Fatalf("wrong value for foo\ngot: %#v\nwant: %#v", got, want) 1160 } 1161 return providers.ValidateResourceConfigResponse{} 1162 } 1163 p.ValidateDataResourceConfigFn = func(req providers.ValidateDataResourceConfigRequest) (resp providers.ValidateDataResourceConfigResponse) { 1164 if got, want := req.Config.GetAttr("foo"), cty.UnknownVal(cty.String); !got.RawEquals(want) { 1165 t.Fatalf("wrong value for foo\ngot: %#v\nwant: %#v", got, want) 1166 } 1167 return providers.ValidateDataResourceConfigResponse{} 1168 } 1169 1170 ctx := testContext2(t, &ContextOpts{ 1171 Providers: map[addrs.Provider]providers.Factory{ 1172 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1173 }, 1174 }) 1175 1176 diags := ctx.Validate(m) 1177 if diags.HasErrors() { 1178 t.Fatal(diags.Err()) 1179 } 1180 1181 if !p.ValidateResourceConfigCalled { 1182 t.Fatal("expected ValidateResourceConfigFn to be called") 1183 } 1184 1185 if !p.ValidateDataResourceConfigCalled { 1186 t.Fatal("expected ValidateDataSourceConfigFn to be called") 1187 } 1188 } 1189 1190 // Manually validate using the new PlanGraphBuilder 1191 func TestContext2Validate_PlanGraphBuilder(t *testing.T) { 1192 fixture := contextFixtureApplyVars(t) 1193 opts := fixture.ContextOpts() 1194 c := testContext2(t, opts) 1195 1196 graph, diags := ValidateGraphBuilder(&PlanGraphBuilder{ 1197 Config: fixture.Config, 1198 State: states.NewState(), 1199 Plugins: c.plugins, 1200 }).Build(addrs.RootModuleInstance) 1201 if diags.HasErrors() { 1202 t.Fatalf("errors from PlanGraphBuilder: %s", diags.Err()) 1203 } 1204 defer c.acquireRun("validate-test")() 1205 walker, diags := c.walk(graph, walkValidate, &graphWalkOpts{ 1206 Config: fixture.Config, 1207 }) 1208 if diags.HasErrors() { 1209 t.Fatal(diags.Err()) 1210 } 1211 if len(walker.NonFatalDiagnostics) > 0 { 1212 t.Fatal(walker.NonFatalDiagnostics.Err()) 1213 } 1214 } 1215 1216 func TestContext2Validate_invalidOutput(t *testing.T) { 1217 m := testModuleInline(t, map[string]string{ 1218 "main.tf": ` 1219 data "aws_data_source" "name" {} 1220 1221 output "out" { 1222 value = "${data.aws_data_source.name.missing}" 1223 }`, 1224 }) 1225 1226 p := testProvider("aws") 1227 ctx := testContext2(t, &ContextOpts{ 1228 Providers: map[addrs.Provider]providers.Factory{ 1229 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1230 }, 1231 }) 1232 1233 diags := ctx.Validate(m) 1234 if !diags.HasErrors() { 1235 t.Fatal("succeeded; want errors") 1236 } 1237 // Should get this error: 1238 // Unsupported attribute: This object does not have an attribute named "missing" 1239 if got, want := diags.Err().Error(), "Unsupported attribute"; !strings.Contains(got, want) { 1240 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 1241 } 1242 } 1243 1244 func TestContext2Validate_invalidModuleOutput(t *testing.T) { 1245 m := testModuleInline(t, map[string]string{ 1246 "child/main.tf": ` 1247 data "aws_data_source" "name" {} 1248 1249 output "out" { 1250 value = "${data.aws_data_source.name.missing}" 1251 }`, 1252 "main.tf": ` 1253 module "child" { 1254 source = "./child" 1255 } 1256 1257 resource "aws_instance" "foo" { 1258 foo = "${module.child.out}" 1259 }`, 1260 }) 1261 1262 p := testProvider("aws") 1263 ctx := testContext2(t, &ContextOpts{ 1264 Providers: map[addrs.Provider]providers.Factory{ 1265 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1266 }, 1267 }) 1268 1269 diags := ctx.Validate(m) 1270 if !diags.HasErrors() { 1271 t.Fatal("succeeded; want errors") 1272 } 1273 // Should get this error: 1274 // Unsupported attribute: This object does not have an attribute named "missing" 1275 if got, want := diags.Err().Error(), "Unsupported attribute"; !strings.Contains(got, want) { 1276 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 1277 } 1278 } 1279 1280 func TestContext2Validate_sensitiveRootModuleOutput(t *testing.T) { 1281 m := testModuleInline(t, map[string]string{ 1282 "child/main.tf": ` 1283 variable "foo" { 1284 default = "xyz" 1285 sensitive = true 1286 } 1287 1288 output "out" { 1289 value = var.foo 1290 }`, 1291 "main.tf": ` 1292 module "child" { 1293 source = "./child" 1294 } 1295 1296 output "root" { 1297 value = module.child.out 1298 sensitive = true 1299 }`, 1300 }) 1301 1302 ctx := testContext2(t, &ContextOpts{}) 1303 1304 diags := ctx.Validate(m) 1305 if diags.HasErrors() { 1306 t.Fatal(diags.Err()) 1307 } 1308 } 1309 1310 func TestContext2Validate_legacyResourceCount(t *testing.T) { 1311 m := testModuleInline(t, map[string]string{ 1312 "main.tf": ` 1313 resource "aws_instance" "test" {} 1314 1315 output "out" { 1316 value = aws_instance.test.count 1317 }`, 1318 }) 1319 1320 p := testProvider("aws") 1321 ctx := testContext2(t, &ContextOpts{ 1322 Providers: map[addrs.Provider]providers.Factory{ 1323 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1324 }, 1325 }) 1326 1327 diags := ctx.Validate(m) 1328 if !diags.HasErrors() { 1329 t.Fatal("succeeded; want errors") 1330 } 1331 // Should get this error: 1332 // Invalid resource count attribute: The special "count" attribute is no longer supported after Terraform v0.12. Instead, use length(aws_instance.test) to count resource instances. 1333 if got, want := diags.Err().Error(), "Invalid resource count attribute:"; !strings.Contains(got, want) { 1334 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 1335 } 1336 } 1337 1338 func TestContext2Validate_invalidModuleRef(t *testing.T) { 1339 // This test is verifying that we properly validate and report on references 1340 // to modules that are not declared, since we were missing some validation 1341 // here in early 0.12.0 alphas that led to a panic. 1342 m := testModuleInline(t, map[string]string{ 1343 "main.tf": ` 1344 output "out" { 1345 # Intentionally referencing undeclared module to ensure error 1346 value = module.foo 1347 }`, 1348 }) 1349 1350 p := testProvider("aws") 1351 ctx := testContext2(t, &ContextOpts{ 1352 Providers: map[addrs.Provider]providers.Factory{ 1353 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1354 }, 1355 }) 1356 1357 diags := ctx.Validate(m) 1358 if !diags.HasErrors() { 1359 t.Fatal("succeeded; want errors") 1360 } 1361 // Should get this error: 1362 // Reference to undeclared module: No module call named "foo" is declared in the root module. 1363 if got, want := diags.Err().Error(), "Reference to undeclared module:"; !strings.Contains(got, want) { 1364 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 1365 } 1366 } 1367 1368 func TestContext2Validate_invalidModuleOutputRef(t *testing.T) { 1369 // This test is verifying that we properly validate and report on references 1370 // to modules that are not declared, since we were missing some validation 1371 // here in early 0.12.0 alphas that led to a panic. 1372 m := testModuleInline(t, map[string]string{ 1373 "main.tf": ` 1374 output "out" { 1375 # Intentionally referencing undeclared module to ensure error 1376 value = module.foo.bar 1377 }`, 1378 }) 1379 1380 p := testProvider("aws") 1381 ctx := testContext2(t, &ContextOpts{ 1382 Providers: map[addrs.Provider]providers.Factory{ 1383 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1384 }, 1385 }) 1386 1387 diags := ctx.Validate(m) 1388 if !diags.HasErrors() { 1389 t.Fatal("succeeded; want errors") 1390 } 1391 // Should get this error: 1392 // Reference to undeclared module: No module call named "foo" is declared in the root module. 1393 if got, want := diags.Err().Error(), "Reference to undeclared module:"; !strings.Contains(got, want) { 1394 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 1395 } 1396 } 1397 1398 func TestContext2Validate_invalidDependsOnResourceRef(t *testing.T) { 1399 // This test is verifying that we raise an error if depends_on 1400 // refers to something that doesn't exist in configuration. 1401 m := testModuleInline(t, map[string]string{ 1402 "main.tf": ` 1403 resource "test_instance" "bar" { 1404 depends_on = [test_resource.nonexistant] 1405 } 1406 `, 1407 }) 1408 1409 p := testProvider("test") 1410 ctx := testContext2(t, &ContextOpts{ 1411 Providers: map[addrs.Provider]providers.Factory{ 1412 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 1413 }, 1414 }) 1415 1416 diags := ctx.Validate(m) 1417 if !diags.HasErrors() { 1418 t.Fatal("succeeded; want errors") 1419 } 1420 // Should get this error: 1421 // Reference to undeclared module: No module call named "foo" is declared in the root module. 1422 if got, want := diags.Err().Error(), "Reference to undeclared resource:"; !strings.Contains(got, want) { 1423 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 1424 } 1425 } 1426 1427 func TestContext2Validate_invalidResourceIgnoreChanges(t *testing.T) { 1428 // This test is verifying that we raise an error if ignore_changes 1429 // refers to something that can be statically detected as not conforming 1430 // to the resource type schema. 1431 m := testModuleInline(t, map[string]string{ 1432 "main.tf": ` 1433 resource "test_instance" "bar" { 1434 lifecycle { 1435 ignore_changes = [does_not_exist_in_schema] 1436 } 1437 } 1438 `, 1439 }) 1440 1441 p := testProvider("test") 1442 ctx := testContext2(t, &ContextOpts{ 1443 Providers: map[addrs.Provider]providers.Factory{ 1444 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 1445 }, 1446 }) 1447 1448 diags := ctx.Validate(m) 1449 if !diags.HasErrors() { 1450 t.Fatal("succeeded; want errors") 1451 } 1452 // Should get this error: 1453 // Reference to undeclared module: No module call named "foo" is declared in the root module. 1454 if got, want := diags.Err().Error(), `no argument, nested block, or exported attribute named "does_not_exist_in_schema"`; !strings.Contains(got, want) { 1455 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 1456 } 1457 } 1458 1459 func TestContext2Validate_variableCustomValidationsFail(t *testing.T) { 1460 // This test is for custom validation rules associated with root module 1461 // variables, and specifically that we handle the situation where the 1462 // given value is invalid in a child module. 1463 m := testModule(t, "validate-variable-custom-validations-child") 1464 1465 p := testProvider("test") 1466 ctx := testContext2(t, &ContextOpts{ 1467 Providers: map[addrs.Provider]providers.Factory{ 1468 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 1469 }, 1470 }) 1471 1472 diags := ctx.Validate(m) 1473 if !diags.HasErrors() { 1474 t.Fatal("succeeded; want errors") 1475 } 1476 if got, want := diags.Err().Error(), `Invalid value for variable: Value must not be "nope".`; !strings.Contains(got, want) { 1477 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 1478 } 1479 } 1480 1481 func TestContext2Validate_variableCustomValidationsRoot(t *testing.T) { 1482 // This test is for custom validation rules associated with root module 1483 // variables, and specifically that we handle the situation where their 1484 // values are unknown during validation, skipping the validation check 1485 // altogether. (Root module variables are never known during validation.) 1486 m := testModuleInline(t, map[string]string{ 1487 "main.tf": ` 1488 variable "test" { 1489 type = string 1490 1491 validation { 1492 condition = var.test != "nope" 1493 error_message = "Value must not be \"nope\"." 1494 } 1495 } 1496 `, 1497 }) 1498 1499 p := testProvider("test") 1500 ctx := testContext2(t, &ContextOpts{ 1501 Providers: map[addrs.Provider]providers.Factory{ 1502 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 1503 }, 1504 }) 1505 1506 diags := ctx.Validate(m) 1507 if diags.HasErrors() { 1508 t.Fatalf("unexpected error\ngot: %s", diags.Err().Error()) 1509 } 1510 } 1511 1512 func TestContext2Validate_expandModules(t *testing.T) { 1513 m := testModuleInline(t, map[string]string{ 1514 "main.tf": ` 1515 module "mod1" { 1516 for_each = toset(["a", "b"]) 1517 source = "./mod" 1518 } 1519 1520 module "mod2" { 1521 for_each = module.mod1 1522 source = "./mod" 1523 input = module.mod1["a"].out 1524 } 1525 1526 module "mod3" { 1527 count = length(module.mod2) 1528 source = "./mod" 1529 } 1530 `, 1531 "mod/main.tf": ` 1532 resource "aws_instance" "foo" { 1533 } 1534 1535 output "out" { 1536 value = 1 1537 } 1538 1539 variable "input" { 1540 type = number 1541 default = 0 1542 } 1543 1544 module "nested" { 1545 count = 2 1546 source = "./nested" 1547 input = count.index 1548 } 1549 `, 1550 "mod/nested/main.tf": ` 1551 variable "input" { 1552 } 1553 1554 resource "aws_instance" "foo" { 1555 count = var.input 1556 } 1557 `, 1558 }) 1559 1560 p := testProvider("aws") 1561 ctx := testContext2(t, &ContextOpts{ 1562 Providers: map[addrs.Provider]providers.Factory{ 1563 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1564 }, 1565 }) 1566 1567 diags := ctx.Validate(m) 1568 if diags.HasErrors() { 1569 t.Fatal(diags.ErrWithWarnings()) 1570 } 1571 } 1572 1573 func TestContext2Validate_expandModulesInvalidCount(t *testing.T) { 1574 m := testModuleInline(t, map[string]string{ 1575 "main.tf": ` 1576 module "mod1" { 1577 count = -1 1578 source = "./mod" 1579 } 1580 `, 1581 "mod/main.tf": ` 1582 resource "aws_instance" "foo" { 1583 } 1584 `, 1585 }) 1586 1587 p := testProvider("aws") 1588 ctx := testContext2(t, &ContextOpts{ 1589 Providers: map[addrs.Provider]providers.Factory{ 1590 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1591 }, 1592 }) 1593 1594 diags := ctx.Validate(m) 1595 if !diags.HasErrors() { 1596 t.Fatal("succeeded; want errors") 1597 } 1598 if got, want := diags.Err().Error(), `Invalid count argument`; !strings.Contains(got, want) { 1599 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 1600 } 1601 } 1602 1603 func TestContext2Validate_expandModulesInvalidForEach(t *testing.T) { 1604 m := testModuleInline(t, map[string]string{ 1605 "main.tf": ` 1606 module "mod1" { 1607 for_each = ["a", "b"] 1608 source = "./mod" 1609 } 1610 `, 1611 "mod/main.tf": ` 1612 resource "aws_instance" "foo" { 1613 } 1614 `, 1615 }) 1616 1617 p := testProvider("aws") 1618 ctx := testContext2(t, &ContextOpts{ 1619 Providers: map[addrs.Provider]providers.Factory{ 1620 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1621 }, 1622 }) 1623 1624 diags := ctx.Validate(m) 1625 if !diags.HasErrors() { 1626 t.Fatal("succeeded; want errors") 1627 } 1628 if got, want := diags.Err().Error(), `Invalid for_each argument`; !strings.Contains(got, want) { 1629 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 1630 } 1631 } 1632 1633 func TestContext2Validate_expandMultipleNestedModules(t *testing.T) { 1634 m := testModuleInline(t, map[string]string{ 1635 "main.tf": ` 1636 module "modA" { 1637 for_each = { 1638 first = "m" 1639 second = "n" 1640 } 1641 source = "./modA" 1642 } 1643 `, 1644 "modA/main.tf": ` 1645 locals { 1646 m = { 1647 first = "m" 1648 second = "n" 1649 } 1650 } 1651 1652 module "modB" { 1653 for_each = local.m 1654 source = "./modB" 1655 y = each.value 1656 } 1657 1658 module "modC" { 1659 for_each = local.m 1660 source = "./modC" 1661 x = module.modB[each.key].out 1662 y = module.modB[each.key].out 1663 } 1664 1665 `, 1666 "modA/modB/main.tf": ` 1667 variable "y" { 1668 type = string 1669 } 1670 1671 resource "aws_instance" "foo" { 1672 foo = var.y 1673 } 1674 1675 output "out" { 1676 value = aws_instance.foo.id 1677 } 1678 `, 1679 "modA/modC/main.tf": ` 1680 variable "x" { 1681 type = string 1682 } 1683 1684 variable "y" { 1685 type = string 1686 } 1687 1688 resource "aws_instance" "foo" { 1689 foo = var.x 1690 } 1691 1692 output "out" { 1693 value = var.y 1694 } 1695 `, 1696 }) 1697 1698 p := testProvider("aws") 1699 ctx := testContext2(t, &ContextOpts{ 1700 Providers: map[addrs.Provider]providers.Factory{ 1701 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1702 }, 1703 }) 1704 1705 diags := ctx.Validate(m) 1706 if diags.HasErrors() { 1707 t.Fatal(diags.ErrWithWarnings()) 1708 } 1709 } 1710 1711 func TestContext2Validate_invalidModuleDependsOn(t *testing.T) { 1712 // validate module and output depends_on 1713 m := testModuleInline(t, map[string]string{ 1714 "main.tf": ` 1715 module "mod1" { 1716 source = "./mod" 1717 depends_on = [resource_foo.bar.baz] 1718 } 1719 1720 module "mod2" { 1721 source = "./mod" 1722 depends_on = [resource_foo.bar.baz] 1723 } 1724 `, 1725 "mod/main.tf": ` 1726 output "out" { 1727 value = "foo" 1728 } 1729 `, 1730 }) 1731 1732 diags := testContext2(t, &ContextOpts{}).Validate(m) 1733 if !diags.HasErrors() { 1734 t.Fatal("succeeded; want errors") 1735 } 1736 1737 if len(diags) != 2 { 1738 t.Fatalf("wanted 2 diagnostic errors, got %q", diags) 1739 } 1740 1741 for _, d := range diags { 1742 des := d.Description().Summary 1743 if !strings.Contains(des, "Invalid depends_on reference") { 1744 t.Fatalf(`expected "Invalid depends_on reference", got %q`, des) 1745 } 1746 } 1747 } 1748 1749 func TestContext2Validate_invalidOutputDependsOn(t *testing.T) { 1750 // validate module and output depends_on 1751 m := testModuleInline(t, map[string]string{ 1752 "main.tf": ` 1753 module "mod1" { 1754 source = "./mod" 1755 } 1756 1757 output "out" { 1758 value = "bar" 1759 depends_on = [resource_foo.bar.baz] 1760 } 1761 `, 1762 "mod/main.tf": ` 1763 output "out" { 1764 value = "bar" 1765 depends_on = [resource_foo.bar.baz] 1766 } 1767 `, 1768 }) 1769 1770 diags := testContext2(t, &ContextOpts{}).Validate(m) 1771 if !diags.HasErrors() { 1772 t.Fatal("succeeded; want errors") 1773 } 1774 1775 if len(diags) != 2 { 1776 t.Fatalf("wanted 2 diagnostic errors, got %q", diags) 1777 } 1778 1779 for _, d := range diags { 1780 des := d.Description().Summary 1781 if !strings.Contains(des, "Invalid depends_on reference") { 1782 t.Fatalf(`expected "Invalid depends_on reference", got %q`, des) 1783 } 1784 } 1785 } 1786 1787 func TestContext2Validate_rpcDiagnostics(t *testing.T) { 1788 // validate module and output depends_on 1789 m := testModuleInline(t, map[string]string{ 1790 "main.tf": ` 1791 resource "test_instance" "a" { 1792 } 1793 `, 1794 }) 1795 1796 p := testProvider("test") 1797 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 1798 ResourceTypes: map[string]providers.Schema{ 1799 "test_instance": { 1800 Block: &configschema.Block{ 1801 Attributes: map[string]*configschema.Attribute{ 1802 "id": {Type: cty.String, Computed: true}, 1803 }, 1804 }, 1805 }, 1806 }, 1807 } 1808 1809 p.ValidateResourceConfigResponse = &providers.ValidateResourceConfigResponse{ 1810 Diagnostics: tfdiags.Diagnostics(nil).Append(tfdiags.SimpleWarning("don't frobble")), 1811 } 1812 1813 ctx := testContext2(t, &ContextOpts{ 1814 Providers: map[addrs.Provider]providers.Factory{ 1815 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 1816 }, 1817 }) 1818 diags := ctx.Validate(m) 1819 if diags.HasErrors() { 1820 t.Fatal(diags.Err()) 1821 } 1822 1823 if len(diags) == 0 { 1824 t.Fatal("expected warnings") 1825 } 1826 1827 for _, d := range diags { 1828 des := d.Description().Summary 1829 if !strings.Contains(des, "frobble") { 1830 t.Fatalf(`expected frobble, got %q`, des) 1831 } 1832 } 1833 } 1834 1835 func TestContext2Validate_sensitiveProvisionerConfig(t *testing.T) { 1836 m := testModule(t, "validate-sensitive-provisioner-config") 1837 p := testProvider("aws") 1838 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 1839 ResourceTypes: map[string]providers.Schema{ 1840 "aws_instance": { 1841 Block: &configschema.Block{ 1842 Attributes: map[string]*configschema.Attribute{ 1843 "foo": {Type: cty.String, Optional: true}, 1844 }, 1845 }, 1846 }, 1847 }, 1848 } 1849 1850 pr := simpleMockProvisioner() 1851 1852 c := testContext2(t, &ContextOpts{ 1853 Providers: map[addrs.Provider]providers.Factory{ 1854 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1855 }, 1856 Provisioners: map[string]provisioners.Factory{ 1857 "test": testProvisionerFuncFixed(pr), 1858 }, 1859 }) 1860 1861 pr.ValidateProvisionerConfigFn = func(r provisioners.ValidateProvisionerConfigRequest) provisioners.ValidateProvisionerConfigResponse { 1862 if r.Config.ContainsMarked() { 1863 t.Errorf("provisioner config contains marked values") 1864 } 1865 return pr.ValidateProvisionerConfigResponse 1866 } 1867 1868 diags := c.Validate(m) 1869 if diags.HasErrors() { 1870 t.Fatalf("unexpected error: %s", diags.Err()) 1871 } 1872 if !pr.ValidateProvisionerConfigCalled { 1873 t.Fatal("ValidateProvisionerConfig not called") 1874 } 1875 } 1876 1877 func TestContext2Plan_validateMinMaxDynamicBlock(t *testing.T) { 1878 p := new(MockProvider) 1879 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1880 ResourceTypes: map[string]*configschema.Block{ 1881 "test_instance": { 1882 Attributes: map[string]*configschema.Attribute{ 1883 "id": { 1884 Type: cty.String, 1885 Computed: true, 1886 }, 1887 "things": { 1888 Type: cty.List(cty.String), 1889 Computed: true, 1890 }, 1891 }, 1892 BlockTypes: map[string]*configschema.NestedBlock{ 1893 "foo": { 1894 Block: configschema.Block{ 1895 Attributes: map[string]*configschema.Attribute{ 1896 "bar": {Type: cty.String, Optional: true}, 1897 }, 1898 }, 1899 Nesting: configschema.NestingList, 1900 MinItems: 2, 1901 MaxItems: 3, 1902 }, 1903 }, 1904 }, 1905 }, 1906 }) 1907 1908 m := testModuleInline(t, map[string]string{ 1909 "main.tf": ` 1910 resource "test_instance" "a" { 1911 // MinItems 2 1912 foo { 1913 bar = "a" 1914 } 1915 foo { 1916 bar = "b" 1917 } 1918 } 1919 1920 resource "test_instance" "b" { 1921 // one dymamic block can satisfy MinItems of 2 1922 dynamic "foo" { 1923 for_each = test_instance.a.things 1924 content { 1925 bar = foo.value 1926 } 1927 } 1928 } 1929 1930 resource "test_instance" "c" { 1931 // we may have more than MaxItems dynamic blocks when they are unknown 1932 foo { 1933 bar = "b" 1934 } 1935 dynamic "foo" { 1936 for_each = test_instance.a.things 1937 content { 1938 bar = foo.value 1939 } 1940 } 1941 dynamic "foo" { 1942 for_each = test_instance.a.things 1943 content { 1944 bar = "${foo.value}-2" 1945 } 1946 } 1947 dynamic "foo" { 1948 for_each = test_instance.b.things 1949 content { 1950 bar = foo.value 1951 } 1952 } 1953 } 1954 `}) 1955 1956 ctx := testContext2(t, &ContextOpts{ 1957 Providers: map[addrs.Provider]providers.Factory{ 1958 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 1959 }, 1960 }) 1961 1962 diags := ctx.Validate(m) 1963 if diags.HasErrors() { 1964 t.Fatal(diags.ErrWithWarnings()) 1965 } 1966 } 1967 1968 func TestContext2Validate_passInheritedProvider(t *testing.T) { 1969 m := testModuleInline(t, map[string]string{ 1970 "main.tf": ` 1971 terraform { 1972 required_providers { 1973 test = { 1974 source = "hashicorp/test" 1975 } 1976 } 1977 } 1978 1979 module "first" { 1980 source = "./first" 1981 providers = { 1982 test = test 1983 } 1984 } 1985 `, 1986 1987 // This module does not define a config for the test provider, but we 1988 // should be able to pass whatever the implied config is to a child 1989 // module. 1990 "first/main.tf": ` 1991 terraform { 1992 required_providers { 1993 test = { 1994 source = "hashicorp/test" 1995 } 1996 } 1997 } 1998 1999 module "second" { 2000 source = "./second" 2001 providers = { 2002 test.alias = test 2003 } 2004 }`, 2005 2006 "first/second/main.tf": ` 2007 terraform { 2008 required_providers { 2009 test = { 2010 source = "hashicorp/test" 2011 configuration_aliases = [test.alias] 2012 } 2013 } 2014 } 2015 2016 resource "test_object" "t" { 2017 provider = test.alias 2018 } 2019 `, 2020 }) 2021 2022 p := simpleMockProvider() 2023 ctx := testContext2(t, &ContextOpts{ 2024 Providers: map[addrs.Provider]providers.Factory{ 2025 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 2026 }, 2027 }) 2028 2029 diags := ctx.Validate(m) 2030 if diags.HasErrors() { 2031 t.Fatal(diags.ErrWithWarnings()) 2032 } 2033 } 2034 2035 func TestContext2Plan_lookupMismatchedObjectTypes(t *testing.T) { 2036 p := new(MockProvider) 2037 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2038 ResourceTypes: map[string]*configschema.Block{ 2039 "test_instance": { 2040 Attributes: map[string]*configschema.Attribute{ 2041 "id": { 2042 Type: cty.String, 2043 Computed: true, 2044 }, 2045 "things": { 2046 Type: cty.List(cty.String), 2047 Optional: true, 2048 }, 2049 }, 2050 }, 2051 }, 2052 }) 2053 2054 m := testModuleInline(t, map[string]string{ 2055 "main.tf": ` 2056 variable "items" { 2057 type = list(string) 2058 default = [] 2059 } 2060 2061 resource "test_instance" "a" { 2062 for_each = length(var.items) > 0 ? { default = {} } : {} 2063 } 2064 2065 output "out" { 2066 // Strictly speaking, this expression is incorrect because the map element 2067 // type is a different type from the default value, and the lookup 2068 // implementation expects to be able to convert the default to match the 2069 // element type. 2070 // There are two reasons this works which we need to maintain for 2071 // compatibility. First during validation the 'test_instance.a' expression 2072 // only returns a dynamic value, preventing any type comparison. Later during 2073 // plan and apply 'test_instance.a' is an object and not a map, and the 2074 // lookup implementation skips the type comparison when the keys are known 2075 // statically. 2076 value = lookup(test_instance.a, "default", { id = null })["id"] 2077 } 2078 `}) 2079 2080 ctx := testContext2(t, &ContextOpts{ 2081 Providers: map[addrs.Provider]providers.Factory{ 2082 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 2083 }, 2084 }) 2085 2086 diags := ctx.Validate(m) 2087 if diags.HasErrors() { 2088 t.Fatal(diags.ErrWithWarnings()) 2089 } 2090 }