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  }