github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/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/hashicorp/terraform/internal/addrs" 12 "github.com/hashicorp/terraform/internal/configs/configschema" 13 "github.com/hashicorp/terraform/internal/providers" 14 "github.com/hashicorp/terraform/internal/provisioners" 15 "github.com/hashicorp/terraform/internal/states" 16 "github.com/hashicorp/terraform/internal/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/hashicorp/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 func TestContext2Validate_invalidOutput(t *testing.T) { 1191 m := testModuleInline(t, map[string]string{ 1192 "main.tf": ` 1193 data "aws_data_source" "name" {} 1194 1195 output "out" { 1196 value = "${data.aws_data_source.name.missing}" 1197 }`, 1198 }) 1199 1200 p := testProvider("aws") 1201 ctx := testContext2(t, &ContextOpts{ 1202 Providers: map[addrs.Provider]providers.Factory{ 1203 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1204 }, 1205 }) 1206 1207 diags := ctx.Validate(m) 1208 if !diags.HasErrors() { 1209 t.Fatal("succeeded; want errors") 1210 } 1211 // Should get this error: 1212 // Unsupported attribute: This object does not have an attribute named "missing" 1213 if got, want := diags.Err().Error(), "Unsupported attribute"; !strings.Contains(got, want) { 1214 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 1215 } 1216 } 1217 1218 func TestContext2Validate_invalidModuleOutput(t *testing.T) { 1219 m := testModuleInline(t, map[string]string{ 1220 "child/main.tf": ` 1221 data "aws_data_source" "name" {} 1222 1223 output "out" { 1224 value = "${data.aws_data_source.name.missing}" 1225 }`, 1226 "main.tf": ` 1227 module "child" { 1228 source = "./child" 1229 } 1230 1231 resource "aws_instance" "foo" { 1232 foo = "${module.child.out}" 1233 }`, 1234 }) 1235 1236 p := testProvider("aws") 1237 ctx := testContext2(t, &ContextOpts{ 1238 Providers: map[addrs.Provider]providers.Factory{ 1239 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1240 }, 1241 }) 1242 1243 diags := ctx.Validate(m) 1244 if !diags.HasErrors() { 1245 t.Fatal("succeeded; want errors") 1246 } 1247 // Should get this error: 1248 // Unsupported attribute: This object does not have an attribute named "missing" 1249 if got, want := diags.Err().Error(), "Unsupported attribute"; !strings.Contains(got, want) { 1250 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 1251 } 1252 } 1253 1254 func TestContext2Validate_sensitiveRootModuleOutput(t *testing.T) { 1255 m := testModuleInline(t, map[string]string{ 1256 "child/main.tf": ` 1257 variable "foo" { 1258 default = "xyz" 1259 sensitive = true 1260 } 1261 1262 output "out" { 1263 value = var.foo 1264 }`, 1265 "main.tf": ` 1266 module "child" { 1267 source = "./child" 1268 } 1269 1270 output "root" { 1271 value = module.child.out 1272 sensitive = true 1273 }`, 1274 }) 1275 1276 ctx := testContext2(t, &ContextOpts{}) 1277 1278 diags := ctx.Validate(m) 1279 if diags.HasErrors() { 1280 t.Fatal(diags.Err()) 1281 } 1282 } 1283 1284 func TestContext2Validate_legacyResourceCount(t *testing.T) { 1285 m := testModuleInline(t, map[string]string{ 1286 "main.tf": ` 1287 resource "aws_instance" "test" {} 1288 1289 output "out" { 1290 value = aws_instance.test.count 1291 }`, 1292 }) 1293 1294 p := testProvider("aws") 1295 ctx := testContext2(t, &ContextOpts{ 1296 Providers: map[addrs.Provider]providers.Factory{ 1297 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1298 }, 1299 }) 1300 1301 diags := ctx.Validate(m) 1302 if !diags.HasErrors() { 1303 t.Fatal("succeeded; want errors") 1304 } 1305 // Should get this error: 1306 // 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. 1307 if got, want := diags.Err().Error(), "Invalid resource count attribute:"; !strings.Contains(got, want) { 1308 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 1309 } 1310 } 1311 1312 func TestContext2Validate_invalidModuleRef(t *testing.T) { 1313 // This test is verifying that we properly validate and report on references 1314 // to modules that are not declared, since we were missing some validation 1315 // here in early 0.12.0 alphas that led to a panic. 1316 m := testModuleInline(t, map[string]string{ 1317 "main.tf": ` 1318 output "out" { 1319 # Intentionally referencing undeclared module to ensure error 1320 value = module.foo 1321 }`, 1322 }) 1323 1324 p := testProvider("aws") 1325 ctx := testContext2(t, &ContextOpts{ 1326 Providers: map[addrs.Provider]providers.Factory{ 1327 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1328 }, 1329 }) 1330 1331 diags := ctx.Validate(m) 1332 if !diags.HasErrors() { 1333 t.Fatal("succeeded; want errors") 1334 } 1335 // Should get this error: 1336 // Reference to undeclared module: No module call named "foo" is declared in the root module. 1337 if got, want := diags.Err().Error(), "Reference to undeclared module:"; !strings.Contains(got, want) { 1338 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 1339 } 1340 } 1341 1342 func TestContext2Validate_invalidModuleOutputRef(t *testing.T) { 1343 // This test is verifying that we properly validate and report on references 1344 // to modules that are not declared, since we were missing some validation 1345 // here in early 0.12.0 alphas that led to a panic. 1346 m := testModuleInline(t, map[string]string{ 1347 "main.tf": ` 1348 output "out" { 1349 # Intentionally referencing undeclared module to ensure error 1350 value = module.foo.bar 1351 }`, 1352 }) 1353 1354 p := testProvider("aws") 1355 ctx := testContext2(t, &ContextOpts{ 1356 Providers: map[addrs.Provider]providers.Factory{ 1357 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1358 }, 1359 }) 1360 1361 diags := ctx.Validate(m) 1362 if !diags.HasErrors() { 1363 t.Fatal("succeeded; want errors") 1364 } 1365 // Should get this error: 1366 // Reference to undeclared module: No module call named "foo" is declared in the root module. 1367 if got, want := diags.Err().Error(), "Reference to undeclared module:"; !strings.Contains(got, want) { 1368 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 1369 } 1370 } 1371 1372 func TestContext2Validate_invalidDependsOnResourceRef(t *testing.T) { 1373 // This test is verifying that we raise an error if depends_on 1374 // refers to something that doesn't exist in configuration. 1375 m := testModuleInline(t, map[string]string{ 1376 "main.tf": ` 1377 resource "test_instance" "bar" { 1378 depends_on = [test_resource.nonexistant] 1379 } 1380 `, 1381 }) 1382 1383 p := testProvider("test") 1384 ctx := testContext2(t, &ContextOpts{ 1385 Providers: map[addrs.Provider]providers.Factory{ 1386 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 1387 }, 1388 }) 1389 1390 diags := ctx.Validate(m) 1391 if !diags.HasErrors() { 1392 t.Fatal("succeeded; want errors") 1393 } 1394 // Should get this error: 1395 // Reference to undeclared module: No module call named "foo" is declared in the root module. 1396 if got, want := diags.Err().Error(), "Reference to undeclared resource:"; !strings.Contains(got, want) { 1397 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 1398 } 1399 } 1400 1401 func TestContext2Validate_invalidResourceIgnoreChanges(t *testing.T) { 1402 // This test is verifying that we raise an error if ignore_changes 1403 // refers to something that can be statically detected as not conforming 1404 // to the resource type schema. 1405 m := testModuleInline(t, map[string]string{ 1406 "main.tf": ` 1407 resource "test_instance" "bar" { 1408 lifecycle { 1409 ignore_changes = [does_not_exist_in_schema] 1410 } 1411 } 1412 `, 1413 }) 1414 1415 p := testProvider("test") 1416 ctx := testContext2(t, &ContextOpts{ 1417 Providers: map[addrs.Provider]providers.Factory{ 1418 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 1419 }, 1420 }) 1421 1422 diags := ctx.Validate(m) 1423 if !diags.HasErrors() { 1424 t.Fatal("succeeded; want errors") 1425 } 1426 // Should get this error: 1427 // Reference to undeclared module: No module call named "foo" is declared in the root module. 1428 if got, want := diags.Err().Error(), `no argument, nested block, or exported attribute named "does_not_exist_in_schema"`; !strings.Contains(got, want) { 1429 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 1430 } 1431 } 1432 1433 func TestContext2Validate_variableCustomValidationsFail(t *testing.T) { 1434 // This test is for custom validation rules associated with root module 1435 // variables, and specifically that we handle the situation where the 1436 // given value is invalid in a child module. 1437 m := testModule(t, "validate-variable-custom-validations-child") 1438 1439 p := testProvider("test") 1440 ctx := testContext2(t, &ContextOpts{ 1441 Providers: map[addrs.Provider]providers.Factory{ 1442 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 1443 }, 1444 }) 1445 1446 diags := ctx.Validate(m) 1447 if !diags.HasErrors() { 1448 t.Fatal("succeeded; want errors") 1449 } 1450 if got, want := diags.Err().Error(), `Invalid value for variable: Value must not be "nope".`; !strings.Contains(got, want) { 1451 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 1452 } 1453 } 1454 1455 func TestContext2Validate_variableCustomValidationsRoot(t *testing.T) { 1456 // This test is for custom validation rules associated with root module 1457 // variables, and specifically that we handle the situation where their 1458 // values are unknown during validation, skipping the validation check 1459 // altogether. (Root module variables are never known during validation.) 1460 m := testModuleInline(t, map[string]string{ 1461 "main.tf": ` 1462 variable "test" { 1463 type = string 1464 1465 validation { 1466 condition = var.test != "nope" 1467 error_message = "Value must not be \"nope\"." 1468 } 1469 } 1470 `, 1471 }) 1472 1473 p := testProvider("test") 1474 ctx := testContext2(t, &ContextOpts{ 1475 Providers: map[addrs.Provider]providers.Factory{ 1476 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 1477 }, 1478 }) 1479 1480 diags := ctx.Validate(m) 1481 if diags.HasErrors() { 1482 t.Fatalf("unexpected error\ngot: %s", diags.Err().Error()) 1483 } 1484 } 1485 1486 func TestContext2Validate_expandModules(t *testing.T) { 1487 m := testModuleInline(t, map[string]string{ 1488 "main.tf": ` 1489 module "mod1" { 1490 for_each = toset(["a", "b"]) 1491 source = "./mod" 1492 } 1493 1494 module "mod2" { 1495 for_each = module.mod1 1496 source = "./mod" 1497 input = module.mod1["a"].out 1498 } 1499 1500 module "mod3" { 1501 count = length(module.mod2) 1502 source = "./mod" 1503 } 1504 `, 1505 "mod/main.tf": ` 1506 resource "aws_instance" "foo" { 1507 } 1508 1509 output "out" { 1510 value = 1 1511 } 1512 1513 variable "input" { 1514 type = number 1515 default = 0 1516 } 1517 1518 module "nested" { 1519 count = 2 1520 source = "./nested" 1521 input = count.index 1522 } 1523 `, 1524 "mod/nested/main.tf": ` 1525 variable "input" { 1526 } 1527 1528 resource "aws_instance" "foo" { 1529 count = var.input 1530 } 1531 `, 1532 }) 1533 1534 p := testProvider("aws") 1535 ctx := testContext2(t, &ContextOpts{ 1536 Providers: map[addrs.Provider]providers.Factory{ 1537 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1538 }, 1539 }) 1540 1541 diags := ctx.Validate(m) 1542 if diags.HasErrors() { 1543 t.Fatal(diags.ErrWithWarnings()) 1544 } 1545 } 1546 1547 func TestContext2Validate_expandModulesInvalidCount(t *testing.T) { 1548 m := testModuleInline(t, map[string]string{ 1549 "main.tf": ` 1550 module "mod1" { 1551 count = -1 1552 source = "./mod" 1553 } 1554 `, 1555 "mod/main.tf": ` 1556 resource "aws_instance" "foo" { 1557 } 1558 `, 1559 }) 1560 1561 p := testProvider("aws") 1562 ctx := testContext2(t, &ContextOpts{ 1563 Providers: map[addrs.Provider]providers.Factory{ 1564 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1565 }, 1566 }) 1567 1568 diags := ctx.Validate(m) 1569 if !diags.HasErrors() { 1570 t.Fatal("succeeded; want errors") 1571 } 1572 if got, want := diags.Err().Error(), `Invalid count argument`; !strings.Contains(got, want) { 1573 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 1574 } 1575 } 1576 1577 func TestContext2Validate_expandModulesInvalidForEach(t *testing.T) { 1578 m := testModuleInline(t, map[string]string{ 1579 "main.tf": ` 1580 module "mod1" { 1581 for_each = ["a", "b"] 1582 source = "./mod" 1583 } 1584 `, 1585 "mod/main.tf": ` 1586 resource "aws_instance" "foo" { 1587 } 1588 `, 1589 }) 1590 1591 p := testProvider("aws") 1592 ctx := testContext2(t, &ContextOpts{ 1593 Providers: map[addrs.Provider]providers.Factory{ 1594 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1595 }, 1596 }) 1597 1598 diags := ctx.Validate(m) 1599 if !diags.HasErrors() { 1600 t.Fatal("succeeded; want errors") 1601 } 1602 if got, want := diags.Err().Error(), `Invalid for_each argument`; !strings.Contains(got, want) { 1603 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 1604 } 1605 } 1606 1607 func TestContext2Validate_expandMultipleNestedModules(t *testing.T) { 1608 m := testModuleInline(t, map[string]string{ 1609 "main.tf": ` 1610 module "modA" { 1611 for_each = { 1612 first = "m" 1613 second = "n" 1614 } 1615 source = "./modA" 1616 } 1617 `, 1618 "modA/main.tf": ` 1619 locals { 1620 m = { 1621 first = "m" 1622 second = "n" 1623 } 1624 } 1625 1626 module "modB" { 1627 for_each = local.m 1628 source = "./modB" 1629 y = each.value 1630 } 1631 1632 module "modC" { 1633 for_each = local.m 1634 source = "./modC" 1635 x = module.modB[each.key].out 1636 y = module.modB[each.key].out 1637 } 1638 1639 `, 1640 "modA/modB/main.tf": ` 1641 variable "y" { 1642 type = string 1643 } 1644 1645 resource "aws_instance" "foo" { 1646 foo = var.y 1647 } 1648 1649 output "out" { 1650 value = aws_instance.foo.id 1651 } 1652 `, 1653 "modA/modC/main.tf": ` 1654 variable "x" { 1655 type = string 1656 } 1657 1658 variable "y" { 1659 type = string 1660 } 1661 1662 resource "aws_instance" "foo" { 1663 foo = var.x 1664 } 1665 1666 output "out" { 1667 value = var.y 1668 } 1669 `, 1670 }) 1671 1672 p := testProvider("aws") 1673 ctx := testContext2(t, &ContextOpts{ 1674 Providers: map[addrs.Provider]providers.Factory{ 1675 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1676 }, 1677 }) 1678 1679 diags := ctx.Validate(m) 1680 if diags.HasErrors() { 1681 t.Fatal(diags.ErrWithWarnings()) 1682 } 1683 } 1684 1685 func TestContext2Validate_invalidModuleDependsOn(t *testing.T) { 1686 // validate module and output depends_on 1687 m := testModuleInline(t, map[string]string{ 1688 "main.tf": ` 1689 module "mod1" { 1690 source = "./mod" 1691 depends_on = [resource_foo.bar.baz] 1692 } 1693 1694 module "mod2" { 1695 source = "./mod" 1696 depends_on = [resource_foo.bar.baz] 1697 } 1698 `, 1699 "mod/main.tf": ` 1700 output "out" { 1701 value = "foo" 1702 } 1703 `, 1704 }) 1705 1706 diags := testContext2(t, &ContextOpts{}).Validate(m) 1707 if !diags.HasErrors() { 1708 t.Fatal("succeeded; want errors") 1709 } 1710 1711 if len(diags) != 2 { 1712 t.Fatalf("wanted 2 diagnostic errors, got %q", diags) 1713 } 1714 1715 for _, d := range diags { 1716 des := d.Description().Summary 1717 if !strings.Contains(des, "Invalid depends_on reference") { 1718 t.Fatalf(`expected "Invalid depends_on reference", got %q`, des) 1719 } 1720 } 1721 } 1722 1723 func TestContext2Validate_invalidOutputDependsOn(t *testing.T) { 1724 // validate module and output depends_on 1725 m := testModuleInline(t, map[string]string{ 1726 "main.tf": ` 1727 module "mod1" { 1728 source = "./mod" 1729 } 1730 1731 output "out" { 1732 value = "bar" 1733 depends_on = [resource_foo.bar.baz] 1734 } 1735 `, 1736 "mod/main.tf": ` 1737 output "out" { 1738 value = "bar" 1739 depends_on = [resource_foo.bar.baz] 1740 } 1741 `, 1742 }) 1743 1744 diags := testContext2(t, &ContextOpts{}).Validate(m) 1745 if !diags.HasErrors() { 1746 t.Fatal("succeeded; want errors") 1747 } 1748 1749 if len(diags) != 2 { 1750 t.Fatalf("wanted 2 diagnostic errors, got %q", diags) 1751 } 1752 1753 for _, d := range diags { 1754 des := d.Description().Summary 1755 if !strings.Contains(des, "Invalid depends_on reference") { 1756 t.Fatalf(`expected "Invalid depends_on reference", got %q`, des) 1757 } 1758 } 1759 } 1760 1761 func TestContext2Validate_rpcDiagnostics(t *testing.T) { 1762 // validate module and output depends_on 1763 m := testModuleInline(t, map[string]string{ 1764 "main.tf": ` 1765 resource "test_instance" "a" { 1766 } 1767 `, 1768 }) 1769 1770 p := testProvider("test") 1771 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 1772 ResourceTypes: map[string]providers.Schema{ 1773 "test_instance": { 1774 Block: &configschema.Block{ 1775 Attributes: map[string]*configschema.Attribute{ 1776 "id": {Type: cty.String, Computed: true}, 1777 }, 1778 }, 1779 }, 1780 }, 1781 } 1782 1783 p.ValidateResourceConfigResponse = &providers.ValidateResourceConfigResponse{ 1784 Diagnostics: tfdiags.Diagnostics(nil).Append(tfdiags.SimpleWarning("don't frobble")), 1785 } 1786 1787 ctx := testContext2(t, &ContextOpts{ 1788 Providers: map[addrs.Provider]providers.Factory{ 1789 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 1790 }, 1791 }) 1792 diags := ctx.Validate(m) 1793 if diags.HasErrors() { 1794 t.Fatal(diags.Err()) 1795 } 1796 1797 if len(diags) == 0 { 1798 t.Fatal("expected warnings") 1799 } 1800 1801 for _, d := range diags { 1802 des := d.Description().Summary 1803 if !strings.Contains(des, "frobble") { 1804 t.Fatalf(`expected frobble, got %q`, des) 1805 } 1806 } 1807 } 1808 1809 func TestContext2Validate_sensitiveProvisionerConfig(t *testing.T) { 1810 m := testModule(t, "validate-sensitive-provisioner-config") 1811 p := testProvider("aws") 1812 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 1813 ResourceTypes: map[string]providers.Schema{ 1814 "aws_instance": { 1815 Block: &configschema.Block{ 1816 Attributes: map[string]*configschema.Attribute{ 1817 "foo": {Type: cty.String, Optional: true}, 1818 }, 1819 }, 1820 }, 1821 }, 1822 } 1823 1824 pr := simpleMockProvisioner() 1825 1826 c := testContext2(t, &ContextOpts{ 1827 Providers: map[addrs.Provider]providers.Factory{ 1828 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1829 }, 1830 Provisioners: map[string]provisioners.Factory{ 1831 "test": testProvisionerFuncFixed(pr), 1832 }, 1833 }) 1834 1835 pr.ValidateProvisionerConfigFn = func(r provisioners.ValidateProvisionerConfigRequest) provisioners.ValidateProvisionerConfigResponse { 1836 if r.Config.ContainsMarked() { 1837 t.Errorf("provisioner config contains marked values") 1838 } 1839 return pr.ValidateProvisionerConfigResponse 1840 } 1841 1842 diags := c.Validate(m) 1843 if diags.HasErrors() { 1844 t.Fatalf("unexpected error: %s", diags.Err()) 1845 } 1846 if !pr.ValidateProvisionerConfigCalled { 1847 t.Fatal("ValidateProvisionerConfig not called") 1848 } 1849 } 1850 1851 func TestContext2Plan_validateMinMaxDynamicBlock(t *testing.T) { 1852 p := new(MockProvider) 1853 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1854 ResourceTypes: map[string]*configschema.Block{ 1855 "test_instance": { 1856 Attributes: map[string]*configschema.Attribute{ 1857 "id": { 1858 Type: cty.String, 1859 Computed: true, 1860 }, 1861 "things": { 1862 Type: cty.List(cty.String), 1863 Computed: true, 1864 }, 1865 }, 1866 BlockTypes: map[string]*configschema.NestedBlock{ 1867 "foo": { 1868 Block: configschema.Block{ 1869 Attributes: map[string]*configschema.Attribute{ 1870 "bar": {Type: cty.String, Optional: true}, 1871 }, 1872 }, 1873 Nesting: configschema.NestingList, 1874 MinItems: 2, 1875 MaxItems: 3, 1876 }, 1877 }, 1878 }, 1879 }, 1880 }) 1881 1882 m := testModuleInline(t, map[string]string{ 1883 "main.tf": ` 1884 resource "test_instance" "a" { 1885 // MinItems 2 1886 foo { 1887 bar = "a" 1888 } 1889 foo { 1890 bar = "b" 1891 } 1892 } 1893 1894 resource "test_instance" "b" { 1895 // one dymamic block can satisfy MinItems of 2 1896 dynamic "foo" { 1897 for_each = test_instance.a.things 1898 content { 1899 bar = foo.value 1900 } 1901 } 1902 } 1903 1904 resource "test_instance" "c" { 1905 // we may have more than MaxItems dynamic blocks when they are unknown 1906 foo { 1907 bar = "b" 1908 } 1909 dynamic "foo" { 1910 for_each = test_instance.a.things 1911 content { 1912 bar = foo.value 1913 } 1914 } 1915 dynamic "foo" { 1916 for_each = test_instance.a.things 1917 content { 1918 bar = "${foo.value}-2" 1919 } 1920 } 1921 dynamic "foo" { 1922 for_each = test_instance.b.things 1923 content { 1924 bar = foo.value 1925 } 1926 } 1927 } 1928 `}) 1929 1930 ctx := testContext2(t, &ContextOpts{ 1931 Providers: map[addrs.Provider]providers.Factory{ 1932 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 1933 }, 1934 }) 1935 1936 diags := ctx.Validate(m) 1937 if diags.HasErrors() { 1938 t.Fatal(diags.ErrWithWarnings()) 1939 } 1940 } 1941 1942 func TestContext2Validate_passInheritedProvider(t *testing.T) { 1943 m := testModuleInline(t, map[string]string{ 1944 "main.tf": ` 1945 terraform { 1946 required_providers { 1947 test = { 1948 source = "hashicorp/test" 1949 } 1950 } 1951 } 1952 1953 module "first" { 1954 source = "./first" 1955 providers = { 1956 test = test 1957 } 1958 } 1959 `, 1960 1961 // This module does not define a config for the test provider, but we 1962 // should be able to pass whatever the implied config is to a child 1963 // module. 1964 "first/main.tf": ` 1965 terraform { 1966 required_providers { 1967 test = { 1968 source = "hashicorp/test" 1969 } 1970 } 1971 } 1972 1973 module "second" { 1974 source = "./second" 1975 providers = { 1976 test.alias = test 1977 } 1978 }`, 1979 1980 "first/second/main.tf": ` 1981 terraform { 1982 required_providers { 1983 test = { 1984 source = "hashicorp/test" 1985 configuration_aliases = [test.alias] 1986 } 1987 } 1988 } 1989 1990 resource "test_object" "t" { 1991 provider = test.alias 1992 } 1993 `, 1994 }) 1995 1996 p := simpleMockProvider() 1997 ctx := testContext2(t, &ContextOpts{ 1998 Providers: map[addrs.Provider]providers.Factory{ 1999 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 2000 }, 2001 }) 2002 2003 diags := ctx.Validate(m) 2004 if diags.HasErrors() { 2005 t.Fatal(diags.ErrWithWarnings()) 2006 } 2007 } 2008 2009 func TestContext2Plan_lookupMismatchedObjectTypes(t *testing.T) { 2010 p := new(MockProvider) 2011 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2012 ResourceTypes: map[string]*configschema.Block{ 2013 "test_instance": { 2014 Attributes: map[string]*configschema.Attribute{ 2015 "id": { 2016 Type: cty.String, 2017 Computed: true, 2018 }, 2019 "things": { 2020 Type: cty.List(cty.String), 2021 Optional: true, 2022 }, 2023 }, 2024 }, 2025 }, 2026 }) 2027 2028 m := testModuleInline(t, map[string]string{ 2029 "main.tf": ` 2030 variable "items" { 2031 type = list(string) 2032 default = [] 2033 } 2034 2035 resource "test_instance" "a" { 2036 for_each = length(var.items) > 0 ? { default = {} } : {} 2037 } 2038 2039 output "out" { 2040 // Strictly speaking, this expression is incorrect because the map element 2041 // type is a different type from the default value, and the lookup 2042 // implementation expects to be able to convert the default to match the 2043 // element type. 2044 // There are two reasons this works which we need to maintain for 2045 // compatibility. First during validation the 'test_instance.a' expression 2046 // only returns a dynamic value, preventing any type comparison. Later during 2047 // plan and apply 'test_instance.a' is an object and not a map, and the 2048 // lookup implementation skips the type comparison when the keys are known 2049 // statically. 2050 value = lookup(test_instance.a, "default", { id = null })["id"] 2051 } 2052 `}) 2053 2054 ctx := testContext2(t, &ContextOpts{ 2055 Providers: map[addrs.Provider]providers.Factory{ 2056 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 2057 }, 2058 }) 2059 2060 diags := ctx.Validate(m) 2061 if diags.HasErrors() { 2062 t.Fatal(diags.ErrWithWarnings()) 2063 } 2064 } 2065 2066 func TestContext2Validate_nonNullableVariableDefaultValidation(t *testing.T) { 2067 m := testModuleInline(t, map[string]string{ 2068 "main.tf": ` 2069 module "first" { 2070 source = "./mod" 2071 input = null 2072 } 2073 `, 2074 2075 "mod/main.tf": ` 2076 variable "input" { 2077 type = string 2078 default = "default" 2079 nullable = false 2080 2081 // Validation expressions should receive the default with nullable=false and 2082 // a null input. 2083 validation { 2084 condition = var.input != null 2085 error_message = "Input cannot be null!" 2086 } 2087 } 2088 `, 2089 }) 2090 2091 ctx := testContext2(t, &ContextOpts{}) 2092 2093 diags := ctx.Validate(m) 2094 if diags.HasErrors() { 2095 t.Fatal(diags.ErrWithWarnings()) 2096 } 2097 } 2098 2099 func TestContext2Validate_precondition_good(t *testing.T) { 2100 p := testProvider("aws") 2101 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2102 ResourceTypes: map[string]*configschema.Block{ 2103 "aws_instance": { 2104 Attributes: map[string]*configschema.Attribute{ 2105 "foo": {Type: cty.String, Optional: true}, 2106 }, 2107 }, 2108 }, 2109 }) 2110 m := testModuleInline(t, map[string]string{ 2111 "main.tf": ` 2112 variable "input" { 2113 type = string 2114 default = "foo" 2115 } 2116 2117 resource "aws_instance" "test" { 2118 foo = var.input 2119 2120 lifecycle { 2121 precondition { 2122 condition = length(var.input) > 0 2123 error_message = "Input cannot be empty." 2124 } 2125 } 2126 } 2127 `, 2128 }) 2129 2130 ctx := testContext2(t, &ContextOpts{ 2131 Providers: map[addrs.Provider]providers.Factory{ 2132 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2133 }, 2134 }) 2135 2136 diags := ctx.Validate(m) 2137 if diags.HasErrors() { 2138 t.Fatal(diags.ErrWithWarnings()) 2139 } 2140 } 2141 2142 func TestContext2Validate_precondition_badCondition(t *testing.T) { 2143 p := testProvider("aws") 2144 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2145 ResourceTypes: map[string]*configschema.Block{ 2146 "aws_instance": { 2147 Attributes: map[string]*configschema.Attribute{ 2148 "foo": {Type: cty.String, Optional: true}, 2149 }, 2150 }, 2151 }, 2152 }) 2153 m := testModuleInline(t, map[string]string{ 2154 "main.tf": ` 2155 variable "input" { 2156 type = string 2157 default = "foo" 2158 } 2159 2160 resource "aws_instance" "test" { 2161 foo = var.input 2162 2163 lifecycle { 2164 precondition { 2165 condition = length(one(var.input)) == 1 2166 error_message = "You can't do that." 2167 } 2168 } 2169 } 2170 `, 2171 }) 2172 2173 ctx := testContext2(t, &ContextOpts{ 2174 Providers: map[addrs.Provider]providers.Factory{ 2175 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2176 }, 2177 }) 2178 2179 diags := ctx.Validate(m) 2180 if !diags.HasErrors() { 2181 t.Fatalf("succeeded; want error") 2182 } 2183 if got, want := diags.Err().Error(), "Invalid function argument"; !strings.Contains(got, want) { 2184 t.Errorf("unexpected error.\ngot: %s\nshould contain: %q", got, want) 2185 } 2186 } 2187 2188 func TestContext2Validate_precondition_badErrorMessage(t *testing.T) { 2189 p := testProvider("aws") 2190 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2191 ResourceTypes: map[string]*configschema.Block{ 2192 "aws_instance": { 2193 Attributes: map[string]*configschema.Attribute{ 2194 "foo": {Type: cty.String, Optional: true}, 2195 }, 2196 }, 2197 }, 2198 }) 2199 m := testModuleInline(t, map[string]string{ 2200 "main.tf": ` 2201 variable "input" { 2202 type = string 2203 default = "foo" 2204 } 2205 2206 resource "aws_instance" "test" { 2207 foo = var.input 2208 2209 lifecycle { 2210 precondition { 2211 condition = var.input != "foo" 2212 error_message = "This is a bad use of a function: ${one(var.input)}." 2213 } 2214 } 2215 } 2216 `, 2217 }) 2218 2219 ctx := testContext2(t, &ContextOpts{ 2220 Providers: map[addrs.Provider]providers.Factory{ 2221 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2222 }, 2223 }) 2224 2225 diags := ctx.Validate(m) 2226 if !diags.HasErrors() { 2227 t.Fatalf("succeeded; want error") 2228 } 2229 if got, want := diags.Err().Error(), "Invalid function argument"; !strings.Contains(got, want) { 2230 t.Errorf("unexpected error.\ngot: %s\nshould contain: %q", got, want) 2231 } 2232 } 2233 2234 func TestContext2Validate_postcondition_good(t *testing.T) { 2235 p := testProvider("aws") 2236 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2237 ResourceTypes: map[string]*configschema.Block{ 2238 "aws_instance": { 2239 Attributes: map[string]*configschema.Attribute{ 2240 "foo": {Type: cty.String, Optional: true}, 2241 }, 2242 }, 2243 }, 2244 }) 2245 m := testModuleInline(t, map[string]string{ 2246 "main.tf": ` 2247 resource "aws_instance" "test" { 2248 foo = "foo" 2249 2250 lifecycle { 2251 postcondition { 2252 condition = length(self.foo) > 0 2253 error_message = "Input cannot be empty." 2254 } 2255 } 2256 } 2257 `, 2258 }) 2259 2260 ctx := testContext2(t, &ContextOpts{ 2261 Providers: map[addrs.Provider]providers.Factory{ 2262 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2263 }, 2264 }) 2265 2266 diags := ctx.Validate(m) 2267 if diags.HasErrors() { 2268 t.Fatal(diags.ErrWithWarnings()) 2269 } 2270 } 2271 2272 func TestContext2Validate_postcondition_badCondition(t *testing.T) { 2273 p := testProvider("aws") 2274 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2275 ResourceTypes: map[string]*configschema.Block{ 2276 "aws_instance": { 2277 Attributes: map[string]*configschema.Attribute{ 2278 "foo": {Type: cty.String, Optional: true}, 2279 }, 2280 }, 2281 }, 2282 }) 2283 // This postcondition's condition expression does not refer to self, which 2284 // is unrealistic. This is because at the time of writing the test, self is 2285 // always an unknown value of dynamic type during validation. As a result, 2286 // validation of conditions which refer to resource arguments is not 2287 // possible until plan time. For now we exercise the code by referring to 2288 // an input variable. 2289 m := testModuleInline(t, map[string]string{ 2290 "main.tf": ` 2291 variable "input" { 2292 type = string 2293 default = "foo" 2294 } 2295 2296 resource "aws_instance" "test" { 2297 foo = var.input 2298 2299 lifecycle { 2300 postcondition { 2301 condition = length(one(var.input)) == 1 2302 error_message = "You can't do that." 2303 } 2304 } 2305 } 2306 `, 2307 }) 2308 2309 ctx := testContext2(t, &ContextOpts{ 2310 Providers: map[addrs.Provider]providers.Factory{ 2311 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2312 }, 2313 }) 2314 2315 diags := ctx.Validate(m) 2316 if !diags.HasErrors() { 2317 t.Fatalf("succeeded; want error") 2318 } 2319 if got, want := diags.Err().Error(), "Invalid function argument"; !strings.Contains(got, want) { 2320 t.Errorf("unexpected error.\ngot: %s\nshould contain: %q", got, want) 2321 } 2322 } 2323 2324 func TestContext2Validate_postcondition_badErrorMessage(t *testing.T) { 2325 p := testProvider("aws") 2326 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2327 ResourceTypes: map[string]*configschema.Block{ 2328 "aws_instance": { 2329 Attributes: map[string]*configschema.Attribute{ 2330 "foo": {Type: cty.String, Optional: true}, 2331 }, 2332 }, 2333 }, 2334 }) 2335 m := testModuleInline(t, map[string]string{ 2336 "main.tf": ` 2337 resource "aws_instance" "test" { 2338 foo = "foo" 2339 2340 lifecycle { 2341 postcondition { 2342 condition = self.foo != "foo" 2343 error_message = "This is a bad use of a function: ${one("foo")}." 2344 } 2345 } 2346 } 2347 `, 2348 }) 2349 2350 ctx := testContext2(t, &ContextOpts{ 2351 Providers: map[addrs.Provider]providers.Factory{ 2352 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2353 }, 2354 }) 2355 2356 diags := ctx.Validate(m) 2357 if !diags.HasErrors() { 2358 t.Fatalf("succeeded; want error") 2359 } 2360 if got, want := diags.Err().Error(), "Invalid function argument"; !strings.Contains(got, want) { 2361 t.Errorf("unexpected error.\ngot: %s\nshould contain: %q", got, want) 2362 } 2363 } 2364 2365 func TestContext2Validate_precondition_count(t *testing.T) { 2366 p := testProvider("aws") 2367 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2368 ResourceTypes: map[string]*configschema.Block{ 2369 "aws_instance": { 2370 Attributes: map[string]*configschema.Attribute{ 2371 "foo": {Type: cty.String, Optional: true}, 2372 }, 2373 }, 2374 }, 2375 }) 2376 m := testModuleInline(t, map[string]string{ 2377 "main.tf": ` 2378 locals { 2379 foos = ["bar", "baz"] 2380 } 2381 2382 resource "aws_instance" "test" { 2383 count = 3 2384 foo = local.foos[count.index] 2385 2386 lifecycle { 2387 precondition { 2388 condition = count.index < length(local.foos) 2389 error_message = "Insufficient foos." 2390 } 2391 } 2392 } 2393 `, 2394 }) 2395 2396 ctx := testContext2(t, &ContextOpts{ 2397 Providers: map[addrs.Provider]providers.Factory{ 2398 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2399 }, 2400 }) 2401 2402 diags := ctx.Validate(m) 2403 if diags.HasErrors() { 2404 t.Fatal(diags.ErrWithWarnings()) 2405 } 2406 } 2407 2408 func TestContext2Validate_postcondition_forEach(t *testing.T) { 2409 p := testProvider("aws") 2410 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2411 ResourceTypes: map[string]*configschema.Block{ 2412 "aws_instance": { 2413 Attributes: map[string]*configschema.Attribute{ 2414 "foo": {Type: cty.String, Optional: true}, 2415 }, 2416 }, 2417 }, 2418 }) 2419 m := testModuleInline(t, map[string]string{ 2420 "main.tf": ` 2421 locals { 2422 foos = toset(["bar", "baz", "boop"]) 2423 } 2424 2425 resource "aws_instance" "test" { 2426 for_each = local.foos 2427 foo = "foo" 2428 2429 lifecycle { 2430 postcondition { 2431 condition = length(each.value) == 3 2432 error_message = "Short foo required, not \"${each.key}\"." 2433 } 2434 } 2435 } 2436 `, 2437 }) 2438 2439 ctx := testContext2(t, &ContextOpts{ 2440 Providers: map[addrs.Provider]providers.Factory{ 2441 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2442 }, 2443 }) 2444 2445 diags := ctx.Validate(m) 2446 if diags.HasErrors() { 2447 t.Fatal(diags.ErrWithWarnings()) 2448 } 2449 } 2450 2451 func TestContext2Validate_deprecatedAttr(t *testing.T) { 2452 p := testProvider("aws") 2453 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2454 ResourceTypes: map[string]*configschema.Block{ 2455 "aws_instance": { 2456 Attributes: map[string]*configschema.Attribute{ 2457 "foo": {Type: cty.String, Optional: true, Deprecated: true}, 2458 }, 2459 }, 2460 }, 2461 }) 2462 m := testModuleInline(t, map[string]string{ 2463 "main.tf": ` 2464 resource "aws_instance" "test" { 2465 } 2466 locals { 2467 deprecated = aws_instance.test.foo 2468 } 2469 2470 `, 2471 }) 2472 2473 ctx := testContext2(t, &ContextOpts{ 2474 Providers: map[addrs.Provider]providers.Factory{ 2475 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2476 }, 2477 }) 2478 2479 diags := ctx.Validate(m) 2480 warn := diags.ErrWithWarnings().Error() 2481 if !strings.Contains(warn, `The attribute "foo" is deprecated`) { 2482 t.Fatalf("expected deprecated warning, got: %q\n", warn) 2483 } 2484 }