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