github.com/pulumi/terraform@v1.4.0/pkg/terraform/node_resource_validate_test.go (about)

     1  package terraform
     2  
     3  import (
     4  	"errors"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/hashicorp/hcl/v2"
     9  	"github.com/hashicorp/hcl/v2/hcltest"
    10  	"github.com/pulumi/terraform/pkg/addrs"
    11  	"github.com/pulumi/terraform/pkg/configs"
    12  	"github.com/pulumi/terraform/pkg/configs/configschema"
    13  	"github.com/pulumi/terraform/pkg/lang/marks"
    14  	"github.com/pulumi/terraform/pkg/providers"
    15  	"github.com/pulumi/terraform/pkg/provisioners"
    16  	"github.com/pulumi/terraform/pkg/tfdiags"
    17  	"github.com/zclconf/go-cty/cty"
    18  )
    19  
    20  func TestNodeValidatableResource_ValidateProvisioner_valid(t *testing.T) {
    21  	ctx := &MockEvalContext{}
    22  	ctx.installSimpleEval()
    23  	mp := &MockProvisioner{}
    24  	ps := &configschema.Block{}
    25  	ctx.ProvisionerSchemaSchema = ps
    26  	ctx.ProvisionerProvisioner = mp
    27  
    28  	pc := &configs.Provisioner{
    29  		Type:   "baz",
    30  		Config: hcl.EmptyBody(),
    31  		Connection: &configs.Connection{
    32  			Config: configs.SynthBody("", map[string]cty.Value{
    33  				"host": cty.StringVal("localhost"),
    34  				"type": cty.StringVal("ssh"),
    35  				"port": cty.NumberIntVal(10022),
    36  			}),
    37  		},
    38  	}
    39  
    40  	rc := &configs.Resource{
    41  		Mode:   addrs.ManagedResourceMode,
    42  		Type:   "test_foo",
    43  		Name:   "bar",
    44  		Config: configs.SynthBody("", map[string]cty.Value{}),
    45  	}
    46  
    47  	node := NodeValidatableResource{
    48  		NodeAbstractResource: &NodeAbstractResource{
    49  			Addr:   mustConfigResourceAddr("test_foo.bar"),
    50  			Config: rc,
    51  		},
    52  	}
    53  
    54  	diags := node.validateProvisioner(ctx, pc)
    55  	if diags.HasErrors() {
    56  		t.Fatalf("node.Eval failed: %s", diags.Err())
    57  	}
    58  	if !mp.ValidateProvisionerConfigCalled {
    59  		t.Fatalf("p.ValidateProvisionerConfig not called")
    60  	}
    61  }
    62  
    63  func TestNodeValidatableResource_ValidateProvisioner__warning(t *testing.T) {
    64  	ctx := &MockEvalContext{}
    65  	ctx.installSimpleEval()
    66  	mp := &MockProvisioner{}
    67  	ps := &configschema.Block{}
    68  	ctx.ProvisionerSchemaSchema = ps
    69  	ctx.ProvisionerProvisioner = mp
    70  
    71  	pc := &configs.Provisioner{
    72  		Type:   "baz",
    73  		Config: hcl.EmptyBody(),
    74  	}
    75  
    76  	rc := &configs.Resource{
    77  		Mode:    addrs.ManagedResourceMode,
    78  		Type:    "test_foo",
    79  		Name:    "bar",
    80  		Config:  configs.SynthBody("", map[string]cty.Value{}),
    81  		Managed: &configs.ManagedResource{},
    82  	}
    83  
    84  	node := NodeValidatableResource{
    85  		NodeAbstractResource: &NodeAbstractResource{
    86  			Addr:   mustConfigResourceAddr("test_foo.bar"),
    87  			Config: rc,
    88  		},
    89  	}
    90  
    91  	{
    92  		var diags tfdiags.Diagnostics
    93  		diags = diags.Append(tfdiags.SimpleWarning("foo is deprecated"))
    94  		mp.ValidateProvisionerConfigResponse = provisioners.ValidateProvisionerConfigResponse{
    95  			Diagnostics: diags,
    96  		}
    97  	}
    98  
    99  	diags := node.validateProvisioner(ctx, pc)
   100  	if len(diags) != 1 {
   101  		t.Fatalf("wrong number of diagnostics in %s; want one warning", diags.ErrWithWarnings())
   102  	}
   103  
   104  	if got, want := diags[0].Description().Summary, mp.ValidateProvisionerConfigResponse.Diagnostics[0].Description().Summary; got != want {
   105  		t.Fatalf("wrong warning %q; want %q", got, want)
   106  	}
   107  }
   108  
   109  func TestNodeValidatableResource_ValidateProvisioner__connectionInvalid(t *testing.T) {
   110  	ctx := &MockEvalContext{}
   111  	ctx.installSimpleEval()
   112  	mp := &MockProvisioner{}
   113  	ps := &configschema.Block{}
   114  	ctx.ProvisionerSchemaSchema = ps
   115  	ctx.ProvisionerProvisioner = mp
   116  
   117  	pc := &configs.Provisioner{
   118  		Type:   "baz",
   119  		Config: hcl.EmptyBody(),
   120  		Connection: &configs.Connection{
   121  			Config: configs.SynthBody("", map[string]cty.Value{
   122  				"type":             cty.StringVal("ssh"),
   123  				"bananananananana": cty.StringVal("foo"),
   124  				"bazaz":            cty.StringVal("bar"),
   125  			}),
   126  		},
   127  	}
   128  
   129  	rc := &configs.Resource{
   130  		Mode:    addrs.ManagedResourceMode,
   131  		Type:    "test_foo",
   132  		Name:    "bar",
   133  		Config:  configs.SynthBody("", map[string]cty.Value{}),
   134  		Managed: &configs.ManagedResource{},
   135  	}
   136  
   137  	node := NodeValidatableResource{
   138  		NodeAbstractResource: &NodeAbstractResource{
   139  			Addr:   mustConfigResourceAddr("test_foo.bar"),
   140  			Config: rc,
   141  		},
   142  	}
   143  
   144  	diags := node.validateProvisioner(ctx, pc)
   145  	if !diags.HasErrors() {
   146  		t.Fatalf("node.Eval succeeded; want error")
   147  	}
   148  	if len(diags) != 3 {
   149  		t.Fatalf("wrong number of diagnostics; want two errors\n\n%s", diags.Err())
   150  	}
   151  
   152  	errStr := diags.Err().Error()
   153  	if !(strings.Contains(errStr, "bananananananana") && strings.Contains(errStr, "bazaz")) {
   154  		t.Fatalf("wrong errors %q; want something about each of our invalid connInfo keys", errStr)
   155  	}
   156  }
   157  
   158  func TestNodeValidatableResource_ValidateResource_managedResource(t *testing.T) {
   159  	mp := simpleMockProvider()
   160  	mp.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse {
   161  		if got, want := req.TypeName, "test_object"; got != want {
   162  			t.Fatalf("wrong resource type\ngot:  %#v\nwant: %#v", got, want)
   163  		}
   164  		if got, want := req.Config.GetAttr("test_string"), cty.StringVal("bar"); !got.RawEquals(want) {
   165  			t.Fatalf("wrong value for test_string\ngot:  %#v\nwant: %#v", got, want)
   166  		}
   167  		if got, want := req.Config.GetAttr("test_number"), cty.NumberIntVal(2); !got.RawEquals(want) {
   168  			t.Fatalf("wrong value for test_number\ngot:  %#v\nwant: %#v", got, want)
   169  		}
   170  		return providers.ValidateResourceConfigResponse{}
   171  	}
   172  
   173  	p := providers.Interface(mp)
   174  	rc := &configs.Resource{
   175  		Mode: addrs.ManagedResourceMode,
   176  		Type: "test_object",
   177  		Name: "foo",
   178  		Config: configs.SynthBody("", map[string]cty.Value{
   179  			"test_string": cty.StringVal("bar"),
   180  			"test_number": cty.NumberIntVal(2).Mark(marks.Sensitive),
   181  		}),
   182  	}
   183  	node := NodeValidatableResource{
   184  		NodeAbstractResource: &NodeAbstractResource{
   185  			Addr:             mustConfigResourceAddr("test_foo.bar"),
   186  			Config:           rc,
   187  			ResolvedProvider: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   188  		},
   189  	}
   190  
   191  	ctx := &MockEvalContext{}
   192  	ctx.installSimpleEval()
   193  	ctx.ProviderSchemaSchema = mp.ProviderSchema()
   194  	ctx.ProviderProvider = p
   195  
   196  	err := node.validateResource(ctx)
   197  	if err != nil {
   198  		t.Fatalf("err: %s", err)
   199  	}
   200  
   201  	if !mp.ValidateResourceConfigCalled {
   202  		t.Fatal("Expected ValidateResourceConfig to be called, but it was not!")
   203  	}
   204  }
   205  
   206  func TestNodeValidatableResource_ValidateResource_managedResourceCount(t *testing.T) {
   207  	// Setup
   208  	mp := simpleMockProvider()
   209  	mp.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse {
   210  		if got, want := req.TypeName, "test_object"; got != want {
   211  			t.Fatalf("wrong resource type\ngot:  %#v\nwant: %#v", got, want)
   212  		}
   213  		if got, want := req.Config.GetAttr("test_string"), cty.StringVal("bar"); !got.RawEquals(want) {
   214  			t.Fatalf("wrong value for test_string\ngot:  %#v\nwant: %#v", got, want)
   215  		}
   216  		return providers.ValidateResourceConfigResponse{}
   217  	}
   218  
   219  	p := providers.Interface(mp)
   220  
   221  	ctx := &MockEvalContext{}
   222  	ctx.installSimpleEval()
   223  	ctx.ProviderSchemaSchema = mp.ProviderSchema()
   224  	ctx.ProviderProvider = p
   225  
   226  	tests := []struct {
   227  		name  string
   228  		count hcl.Expression
   229  	}{
   230  		{
   231  			"simple count",
   232  			hcltest.MockExprLiteral(cty.NumberIntVal(2)),
   233  		},
   234  		{
   235  			"marked count value",
   236  			hcltest.MockExprLiteral(cty.NumberIntVal(3).Mark("marked")),
   237  		},
   238  	}
   239  
   240  	for _, test := range tests {
   241  		t.Run(test.name, func(t *testing.T) {
   242  			rc := &configs.Resource{
   243  				Mode:  addrs.ManagedResourceMode,
   244  				Type:  "test_object",
   245  				Name:  "foo",
   246  				Count: test.count,
   247  				Config: configs.SynthBody("", map[string]cty.Value{
   248  					"test_string": cty.StringVal("bar"),
   249  				}),
   250  			}
   251  			node := NodeValidatableResource{
   252  				NodeAbstractResource: &NodeAbstractResource{
   253  					Addr:             mustConfigResourceAddr("test_foo.bar"),
   254  					Config:           rc,
   255  					ResolvedProvider: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   256  				},
   257  			}
   258  
   259  			diags := node.validateResource(ctx)
   260  			if diags.HasErrors() {
   261  				t.Fatalf("err: %s", diags.Err())
   262  			}
   263  
   264  			if !mp.ValidateResourceConfigCalled {
   265  				t.Fatal("Expected ValidateResourceConfig to be called, but it was not!")
   266  			}
   267  		})
   268  	}
   269  }
   270  
   271  func TestNodeValidatableResource_ValidateResource_dataSource(t *testing.T) {
   272  	mp := simpleMockProvider()
   273  	mp.ValidateDataResourceConfigFn = func(req providers.ValidateDataResourceConfigRequest) providers.ValidateDataResourceConfigResponse {
   274  		if got, want := req.TypeName, "test_object"; got != want {
   275  			t.Fatalf("wrong resource type\ngot:  %#v\nwant: %#v", got, want)
   276  		}
   277  		if got, want := req.Config.GetAttr("test_string"), cty.StringVal("bar"); !got.RawEquals(want) {
   278  			t.Fatalf("wrong value for test_string\ngot:  %#v\nwant: %#v", got, want)
   279  		}
   280  		if got, want := req.Config.GetAttr("test_number"), cty.NumberIntVal(2); !got.RawEquals(want) {
   281  			t.Fatalf("wrong value for test_number\ngot:  %#v\nwant: %#v", got, want)
   282  		}
   283  		return providers.ValidateDataResourceConfigResponse{}
   284  	}
   285  
   286  	p := providers.Interface(mp)
   287  	rc := &configs.Resource{
   288  		Mode: addrs.DataResourceMode,
   289  		Type: "test_object",
   290  		Name: "foo",
   291  		Config: configs.SynthBody("", map[string]cty.Value{
   292  			"test_string": cty.StringVal("bar"),
   293  			"test_number": cty.NumberIntVal(2).Mark(marks.Sensitive),
   294  		}),
   295  	}
   296  
   297  	node := NodeValidatableResource{
   298  		NodeAbstractResource: &NodeAbstractResource{
   299  			Addr:             mustConfigResourceAddr("test_foo.bar"),
   300  			Config:           rc,
   301  			ResolvedProvider: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   302  		},
   303  	}
   304  
   305  	ctx := &MockEvalContext{}
   306  	ctx.installSimpleEval()
   307  	ctx.ProviderSchemaSchema = mp.ProviderSchema()
   308  	ctx.ProviderProvider = p
   309  
   310  	diags := node.validateResource(ctx)
   311  	if diags.HasErrors() {
   312  		t.Fatalf("err: %s", diags.Err())
   313  	}
   314  
   315  	if !mp.ValidateDataResourceConfigCalled {
   316  		t.Fatal("Expected ValidateDataSourceConfig to be called, but it was not!")
   317  	}
   318  }
   319  
   320  func TestNodeValidatableResource_ValidateResource_valid(t *testing.T) {
   321  	mp := simpleMockProvider()
   322  	mp.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse {
   323  		return providers.ValidateResourceConfigResponse{}
   324  	}
   325  
   326  	p := providers.Interface(mp)
   327  	rc := &configs.Resource{
   328  		Mode:   addrs.ManagedResourceMode,
   329  		Type:   "test_object",
   330  		Name:   "foo",
   331  		Config: configs.SynthBody("", map[string]cty.Value{}),
   332  	}
   333  	node := NodeValidatableResource{
   334  		NodeAbstractResource: &NodeAbstractResource{
   335  			Addr:             mustConfigResourceAddr("test_object.foo"),
   336  			Config:           rc,
   337  			ResolvedProvider: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   338  		},
   339  	}
   340  
   341  	ctx := &MockEvalContext{}
   342  	ctx.installSimpleEval()
   343  	ctx.ProviderSchemaSchema = mp.ProviderSchema()
   344  	ctx.ProviderProvider = p
   345  
   346  	diags := node.validateResource(ctx)
   347  	if diags.HasErrors() {
   348  		t.Fatalf("err: %s", diags.Err())
   349  	}
   350  }
   351  
   352  func TestNodeValidatableResource_ValidateResource_warningsAndErrorsPassedThrough(t *testing.T) {
   353  	mp := simpleMockProvider()
   354  	mp.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse {
   355  		var diags tfdiags.Diagnostics
   356  		diags = diags.Append(tfdiags.SimpleWarning("warn"))
   357  		diags = diags.Append(errors.New("err"))
   358  		return providers.ValidateResourceConfigResponse{
   359  			Diagnostics: diags,
   360  		}
   361  	}
   362  
   363  	p := providers.Interface(mp)
   364  	rc := &configs.Resource{
   365  		Mode:   addrs.ManagedResourceMode,
   366  		Type:   "test_object",
   367  		Name:   "foo",
   368  		Config: configs.SynthBody("", map[string]cty.Value{}),
   369  	}
   370  	node := NodeValidatableResource{
   371  		NodeAbstractResource: &NodeAbstractResource{
   372  			Addr:             mustConfigResourceAddr("test_foo.bar"),
   373  			Config:           rc,
   374  			ResolvedProvider: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   375  		},
   376  	}
   377  
   378  	ctx := &MockEvalContext{}
   379  	ctx.installSimpleEval()
   380  	ctx.ProviderSchemaSchema = mp.ProviderSchema()
   381  	ctx.ProviderProvider = p
   382  
   383  	diags := node.validateResource(ctx)
   384  	if !diags.HasErrors() {
   385  		t.Fatal("unexpected success; want error")
   386  	}
   387  
   388  	bySeverity := map[tfdiags.Severity]tfdiags.Diagnostics{}
   389  	for _, diag := range diags {
   390  		bySeverity[diag.Severity()] = append(bySeverity[diag.Severity()], diag)
   391  	}
   392  	if len(bySeverity[tfdiags.Warning]) != 1 || bySeverity[tfdiags.Warning][0].Description().Summary != "warn" {
   393  		t.Errorf("Expected 1 warning 'warn', got: %s", bySeverity[tfdiags.Warning].ErrWithWarnings())
   394  	}
   395  	if len(bySeverity[tfdiags.Error]) != 1 || bySeverity[tfdiags.Error][0].Description().Summary != "err" {
   396  		t.Errorf("Expected 1 error 'err', got: %s", bySeverity[tfdiags.Error].Err())
   397  	}
   398  }
   399  
   400  func TestNodeValidatableResource_ValidateResource_invalidDependsOn(t *testing.T) {
   401  	mp := simpleMockProvider()
   402  	mp.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse {
   403  		return providers.ValidateResourceConfigResponse{}
   404  	}
   405  
   406  	// We'll check a _valid_ config first, to make sure we're not failing
   407  	// for some other reason, and then make it invalid.
   408  	p := providers.Interface(mp)
   409  	rc := &configs.Resource{
   410  		Mode:   addrs.ManagedResourceMode,
   411  		Type:   "test_object",
   412  		Name:   "foo",
   413  		Config: configs.SynthBody("", map[string]cty.Value{}),
   414  		DependsOn: []hcl.Traversal{
   415  			// Depending on path.module is pointless, since it is immediately
   416  			// available, but we allow all of the referencable addrs here
   417  			// for consistency: referencing them is harmless, and avoids the
   418  			// need for us to document a different subset of addresses that
   419  			// are valid in depends_on.
   420  			// For the sake of this test, it's a valid address we can use that
   421  			// doesn't require something else to exist in the configuration.
   422  			{
   423  				hcl.TraverseRoot{
   424  					Name: "path",
   425  				},
   426  				hcl.TraverseAttr{
   427  					Name: "module",
   428  				},
   429  			},
   430  		},
   431  	}
   432  	node := NodeValidatableResource{
   433  		NodeAbstractResource: &NodeAbstractResource{
   434  			Addr:             mustConfigResourceAddr("test_foo.bar"),
   435  			Config:           rc,
   436  			ResolvedProvider: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   437  		},
   438  	}
   439  
   440  	ctx := &MockEvalContext{}
   441  	ctx.installSimpleEval()
   442  
   443  	ctx.ProviderSchemaSchema = mp.ProviderSchema()
   444  	ctx.ProviderProvider = p
   445  
   446  	diags := node.validateResource(ctx)
   447  	if diags.HasErrors() {
   448  		t.Fatalf("error for supposedly-valid config: %s", diags.ErrWithWarnings())
   449  	}
   450  
   451  	// Now we'll make it invalid by adding additional traversal steps at
   452  	// the end of what we're referencing. This is intended to catch the
   453  	// situation where the user tries to depend on e.g. a specific resource
   454  	// attribute, rather than the whole resource, like aws_instance.foo.id.
   455  	rc.DependsOn = append(rc.DependsOn, hcl.Traversal{
   456  		hcl.TraverseRoot{
   457  			Name: "path",
   458  		},
   459  		hcl.TraverseAttr{
   460  			Name: "module",
   461  		},
   462  		hcl.TraverseAttr{
   463  			Name: "extra",
   464  		},
   465  	})
   466  
   467  	diags = node.validateResource(ctx)
   468  	if !diags.HasErrors() {
   469  		t.Fatal("no error for invalid depends_on")
   470  	}
   471  	if got, want := diags.Err().Error(), "Invalid depends_on reference"; !strings.Contains(got, want) {
   472  		t.Fatalf("wrong error\ngot:  %s\nwant: Message containing %q", got, want)
   473  	}
   474  
   475  	// Test for handling an unknown root without attribute, like a
   476  	// typo that omits the dot inbetween "path.module".
   477  	rc.DependsOn = append(rc.DependsOn, hcl.Traversal{
   478  		hcl.TraverseRoot{
   479  			Name: "pathmodule",
   480  		},
   481  	})
   482  
   483  	diags = node.validateResource(ctx)
   484  	if !diags.HasErrors() {
   485  		t.Fatal("no error for invalid depends_on")
   486  	}
   487  	if got, want := diags.Err().Error(), "Invalid depends_on reference"; !strings.Contains(got, want) {
   488  		t.Fatalf("wrong error\ngot:  %s\nwant: Message containing %q", got, want)
   489  	}
   490  }
   491  
   492  func TestNodeValidatableResource_ValidateResource_invalidIgnoreChangesNonexistent(t *testing.T) {
   493  	mp := simpleMockProvider()
   494  	mp.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse {
   495  		return providers.ValidateResourceConfigResponse{}
   496  	}
   497  
   498  	// We'll check a _valid_ config first, to make sure we're not failing
   499  	// for some other reason, and then make it invalid.
   500  	p := providers.Interface(mp)
   501  	rc := &configs.Resource{
   502  		Mode:   addrs.ManagedResourceMode,
   503  		Type:   "test_object",
   504  		Name:   "foo",
   505  		Config: configs.SynthBody("", map[string]cty.Value{}),
   506  		Managed: &configs.ManagedResource{
   507  			IgnoreChanges: []hcl.Traversal{
   508  				{
   509  					hcl.TraverseAttr{
   510  						Name: "test_string",
   511  					},
   512  				},
   513  			},
   514  		},
   515  	}
   516  	node := NodeValidatableResource{
   517  		NodeAbstractResource: &NodeAbstractResource{
   518  			Addr:             mustConfigResourceAddr("test_foo.bar"),
   519  			Config:           rc,
   520  			ResolvedProvider: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   521  		},
   522  	}
   523  
   524  	ctx := &MockEvalContext{}
   525  	ctx.installSimpleEval()
   526  
   527  	ctx.ProviderSchemaSchema = mp.ProviderSchema()
   528  	ctx.ProviderProvider = p
   529  
   530  	diags := node.validateResource(ctx)
   531  	if diags.HasErrors() {
   532  		t.Fatalf("error for supposedly-valid config: %s", diags.ErrWithWarnings())
   533  	}
   534  
   535  	// Now we'll make it invalid by attempting to ignore a nonexistent
   536  	// attribute.
   537  	rc.Managed.IgnoreChanges = append(rc.Managed.IgnoreChanges, hcl.Traversal{
   538  		hcl.TraverseAttr{
   539  			Name: "nonexistent",
   540  		},
   541  	})
   542  
   543  	diags = node.validateResource(ctx)
   544  	if !diags.HasErrors() {
   545  		t.Fatal("no error for invalid ignore_changes")
   546  	}
   547  	if got, want := diags.Err().Error(), "Unsupported attribute: This object has no argument, nested block, or exported attribute named \"nonexistent\""; !strings.Contains(got, want) {
   548  		t.Fatalf("wrong error\ngot:  %s\nwant: Message containing %q", got, want)
   549  	}
   550  }
   551  
   552  func TestNodeValidatableResource_ValidateResource_invalidIgnoreChangesComputed(t *testing.T) {
   553  	// construct a schema with a computed attribute
   554  	ms := &configschema.Block{
   555  		Attributes: map[string]*configschema.Attribute{
   556  			"test_string": {
   557  				Type:     cty.String,
   558  				Optional: true,
   559  			},
   560  			"computed_string": {
   561  				Type:     cty.String,
   562  				Computed: true,
   563  				Optional: false,
   564  			},
   565  		},
   566  	}
   567  
   568  	mp := &MockProvider{
   569  		GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
   570  			Provider: providers.Schema{Block: ms},
   571  			ResourceTypes: map[string]providers.Schema{
   572  				"test_object": providers.Schema{Block: ms},
   573  			},
   574  		},
   575  	}
   576  
   577  	mp.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse {
   578  		return providers.ValidateResourceConfigResponse{}
   579  	}
   580  
   581  	// We'll check a _valid_ config first, to make sure we're not failing
   582  	// for some other reason, and then make it invalid.
   583  	p := providers.Interface(mp)
   584  	rc := &configs.Resource{
   585  		Mode:   addrs.ManagedResourceMode,
   586  		Type:   "test_object",
   587  		Name:   "foo",
   588  		Config: configs.SynthBody("", map[string]cty.Value{}),
   589  		Managed: &configs.ManagedResource{
   590  			IgnoreChanges: []hcl.Traversal{
   591  				{
   592  					hcl.TraverseAttr{
   593  						Name: "test_string",
   594  					},
   595  				},
   596  			},
   597  		},
   598  	}
   599  	node := NodeValidatableResource{
   600  		NodeAbstractResource: &NodeAbstractResource{
   601  			Addr:             mustConfigResourceAddr("test_foo.bar"),
   602  			Config:           rc,
   603  			ResolvedProvider: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   604  		},
   605  	}
   606  
   607  	ctx := &MockEvalContext{}
   608  	ctx.installSimpleEval()
   609  
   610  	ctx.ProviderSchemaSchema = mp.ProviderSchema()
   611  	ctx.ProviderProvider = p
   612  
   613  	diags := node.validateResource(ctx)
   614  	if diags.HasErrors() {
   615  		t.Fatalf("error for supposedly-valid config: %s", diags.ErrWithWarnings())
   616  	}
   617  
   618  	// Now we'll make it invalid by attempting to ignore a computed
   619  	// attribute.
   620  	rc.Managed.IgnoreChanges = append(rc.Managed.IgnoreChanges, hcl.Traversal{
   621  		hcl.TraverseAttr{
   622  			Name: "computed_string",
   623  		},
   624  	})
   625  
   626  	diags = node.validateResource(ctx)
   627  	if diags.HasErrors() {
   628  		t.Fatalf("got unexpected error: %s", diags.ErrWithWarnings())
   629  	}
   630  	if got, want := diags.ErrWithWarnings().Error(), `Redundant ignore_changes element: Adding an attribute name to ignore_changes tells Terraform to ignore future changes to the argument in configuration after the object has been created, retaining the value originally configured.
   631  
   632  The attribute computed_string is decided by the provider alone and therefore there can be no configured value to compare with. Including this attribute in ignore_changes has no effect. Remove the attribute from ignore_changes to quiet this warning.`; !strings.Contains(got, want) {
   633  		t.Fatalf("wrong error\ngot:  %s\nwant: Message containing %q", got, want)
   634  	}
   635  }