github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/eval_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/zclconf/go-cty/cty"
    11  
    12  	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
    13  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs"
    14  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema"
    15  	"github.com/hashicorp/terraform-plugin-sdk/internal/providers"
    16  	"github.com/hashicorp/terraform-plugin-sdk/internal/provisioners"
    17  	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
    18  )
    19  
    20  func TestEvalValidateResource_managedResource(t *testing.T) {
    21  	mp := simpleMockProvider()
    22  	mp.ValidateResourceTypeConfigFn = func(req providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse {
    23  		if got, want := req.TypeName, "test_object"; got != want {
    24  			t.Fatalf("wrong resource type\ngot:  %#v\nwant: %#v", got, want)
    25  		}
    26  		if got, want := req.Config.GetAttr("test_string"), cty.StringVal("bar"); !got.RawEquals(want) {
    27  			t.Fatalf("wrong value for test_string\ngot:  %#v\nwant: %#v", got, want)
    28  		}
    29  		return providers.ValidateResourceTypeConfigResponse{}
    30  	}
    31  
    32  	p := providers.Interface(mp)
    33  	rc := &configs.Resource{
    34  		Mode: addrs.ManagedResourceMode,
    35  		Type: "test_object",
    36  		Name: "foo",
    37  		Config: configs.SynthBody("", map[string]cty.Value{
    38  			"test_string": cty.StringVal("bar"),
    39  		}),
    40  	}
    41  	node := &EvalValidateResource{
    42  		Addr: addrs.Resource{
    43  			Mode: addrs.ManagedResourceMode,
    44  			Type: "aws_instance",
    45  			Name: "foo",
    46  		},
    47  		Provider:       &p,
    48  		Config:         rc,
    49  		ProviderSchema: &mp.GetSchemaReturn,
    50  	}
    51  
    52  	ctx := &MockEvalContext{}
    53  	ctx.installSimpleEval()
    54  
    55  	_, err := node.Eval(ctx)
    56  	if err != nil {
    57  		t.Fatalf("err: %s", err)
    58  	}
    59  
    60  	if !mp.ValidateResourceTypeConfigCalled {
    61  		t.Fatal("Expected ValidateResourceTypeConfig to be called, but it was not!")
    62  	}
    63  }
    64  
    65  func TestEvalValidateResource_managedResourceCount(t *testing.T) {
    66  	mp := simpleMockProvider()
    67  	mp.ValidateResourceTypeConfigFn = func(req providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse {
    68  		if got, want := req.TypeName, "test_object"; got != want {
    69  			t.Fatalf("wrong resource type\ngot:  %#v\nwant: %#v", got, want)
    70  		}
    71  		if got, want := req.Config.GetAttr("test_string"), cty.StringVal("bar"); !got.RawEquals(want) {
    72  			t.Fatalf("wrong value for test_string\ngot:  %#v\nwant: %#v", got, want)
    73  		}
    74  		return providers.ValidateResourceTypeConfigResponse{}
    75  	}
    76  
    77  	p := providers.Interface(mp)
    78  	rc := &configs.Resource{
    79  		Mode:  addrs.ManagedResourceMode,
    80  		Type:  "test_object",
    81  		Name:  "foo",
    82  		Count: hcltest.MockExprLiteral(cty.NumberIntVal(2)),
    83  		Config: configs.SynthBody("", map[string]cty.Value{
    84  			"test_string": cty.StringVal("bar"),
    85  		}),
    86  	}
    87  	node := &EvalValidateResource{
    88  		Addr: addrs.Resource{
    89  			Mode: addrs.ManagedResourceMode,
    90  			Type: "aws_instance",
    91  			Name: "foo",
    92  		},
    93  		Provider:       &p,
    94  		Config:         rc,
    95  		ProviderSchema: &mp.GetSchemaReturn,
    96  	}
    97  
    98  	ctx := &MockEvalContext{}
    99  	ctx.installSimpleEval()
   100  
   101  	_, err := node.Eval(ctx)
   102  	if err != nil {
   103  		t.Fatalf("err: %s", err)
   104  	}
   105  
   106  	if !mp.ValidateResourceTypeConfigCalled {
   107  		t.Fatal("Expected ValidateResourceTypeConfig to be called, but it was not!")
   108  	}
   109  }
   110  
   111  func TestEvalValidateResource_dataSource(t *testing.T) {
   112  	mp := simpleMockProvider()
   113  	mp.ValidateDataSourceConfigFn = func(req providers.ValidateDataSourceConfigRequest) providers.ValidateDataSourceConfigResponse {
   114  		if got, want := req.TypeName, "test_object"; got != want {
   115  			t.Fatalf("wrong resource type\ngot:  %#v\nwant: %#v", got, want)
   116  		}
   117  		if got, want := req.Config.GetAttr("test_string"), cty.StringVal("bar"); !got.RawEquals(want) {
   118  			t.Fatalf("wrong value for test_string\ngot:  %#v\nwant: %#v", got, want)
   119  		}
   120  		return providers.ValidateDataSourceConfigResponse{}
   121  	}
   122  
   123  	p := providers.Interface(mp)
   124  	rc := &configs.Resource{
   125  		Mode: addrs.DataResourceMode,
   126  		Type: "test_object",
   127  		Name: "foo",
   128  		Config: configs.SynthBody("", map[string]cty.Value{
   129  			"test_string": cty.StringVal("bar"),
   130  		}),
   131  	}
   132  
   133  	node := &EvalValidateResource{
   134  		Addr: addrs.Resource{
   135  			Mode: addrs.DataResourceMode,
   136  			Type: "aws_ami",
   137  			Name: "foo",
   138  		},
   139  		Provider:       &p,
   140  		Config:         rc,
   141  		ProviderSchema: &mp.GetSchemaReturn,
   142  	}
   143  
   144  	ctx := &MockEvalContext{}
   145  	ctx.installSimpleEval()
   146  
   147  	_, err := node.Eval(ctx)
   148  	if err != nil {
   149  		t.Fatalf("err: %s", err)
   150  	}
   151  
   152  	if !mp.ValidateDataSourceConfigCalled {
   153  		t.Fatal("Expected ValidateDataSourceConfig to be called, but it was not!")
   154  	}
   155  }
   156  
   157  func TestEvalValidateResource_validReturnsNilError(t *testing.T) {
   158  	mp := simpleMockProvider()
   159  	mp.ValidateResourceTypeConfigFn = func(req providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse {
   160  		return providers.ValidateResourceTypeConfigResponse{}
   161  	}
   162  
   163  	p := providers.Interface(mp)
   164  	rc := &configs.Resource{
   165  		Mode:   addrs.ManagedResourceMode,
   166  		Type:   "test_object",
   167  		Name:   "foo",
   168  		Config: configs.SynthBody("", map[string]cty.Value{}),
   169  	}
   170  	node := &EvalValidateResource{
   171  		Addr: addrs.Resource{
   172  			Mode: addrs.ManagedResourceMode,
   173  			Type: "test_object",
   174  			Name: "foo",
   175  		},
   176  		Provider:       &p,
   177  		Config:         rc,
   178  		ProviderSchema: &mp.GetSchemaReturn,
   179  	}
   180  
   181  	ctx := &MockEvalContext{}
   182  	ctx.installSimpleEval()
   183  
   184  	_, err := node.Eval(ctx)
   185  	if err != nil {
   186  		t.Fatalf("Expected nil error, got: %s", err)
   187  	}
   188  }
   189  
   190  func TestEvalValidateResource_warningsAndErrorsPassedThrough(t *testing.T) {
   191  	mp := simpleMockProvider()
   192  	mp.ValidateResourceTypeConfigFn = func(req providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse {
   193  		var diags tfdiags.Diagnostics
   194  		diags = diags.Append(tfdiags.SimpleWarning("warn"))
   195  		diags = diags.Append(errors.New("err"))
   196  		return providers.ValidateResourceTypeConfigResponse{
   197  			Diagnostics: diags,
   198  		}
   199  	}
   200  
   201  	p := providers.Interface(mp)
   202  	rc := &configs.Resource{
   203  		Mode:   addrs.ManagedResourceMode,
   204  		Type:   "test_object",
   205  		Name:   "foo",
   206  		Config: configs.SynthBody("", map[string]cty.Value{}),
   207  	}
   208  	node := &EvalValidateResource{
   209  		Addr: addrs.Resource{
   210  			Mode: addrs.ManagedResourceMode,
   211  			Type: "test_object",
   212  			Name: "foo",
   213  		},
   214  		Provider:       &p,
   215  		Config:         rc,
   216  		ProviderSchema: &mp.GetSchemaReturn,
   217  	}
   218  
   219  	ctx := &MockEvalContext{}
   220  	ctx.installSimpleEval()
   221  
   222  	_, err := node.Eval(ctx)
   223  	if err == nil {
   224  		t.Fatal("unexpected success; want error")
   225  	}
   226  
   227  	var diags tfdiags.Diagnostics
   228  	diags = diags.Append(err)
   229  	bySeverity := map[tfdiags.Severity]tfdiags.Diagnostics{}
   230  	for _, diag := range diags {
   231  		bySeverity[diag.Severity()] = append(bySeverity[diag.Severity()], diag)
   232  	}
   233  	if len(bySeverity[tfdiags.Warning]) != 1 || bySeverity[tfdiags.Warning][0].Description().Summary != "warn" {
   234  		t.Errorf("Expected 1 warning 'warn', got: %s", bySeverity[tfdiags.Warning].ErrWithWarnings())
   235  	}
   236  	if len(bySeverity[tfdiags.Error]) != 1 || bySeverity[tfdiags.Error][0].Description().Summary != "err" {
   237  		t.Errorf("Expected 1 error 'err', got: %s", bySeverity[tfdiags.Error].Err())
   238  	}
   239  }
   240  
   241  func TestEvalValidateResource_ignoreWarnings(t *testing.T) {
   242  	mp := simpleMockProvider()
   243  	mp.ValidateResourceTypeConfigFn = func(req providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse {
   244  		var diags tfdiags.Diagnostics
   245  		diags = diags.Append(tfdiags.SimpleWarning("warn"))
   246  		return providers.ValidateResourceTypeConfigResponse{
   247  			Diagnostics: diags,
   248  		}
   249  	}
   250  
   251  	p := providers.Interface(mp)
   252  	rc := &configs.Resource{
   253  		Mode:   addrs.ManagedResourceMode,
   254  		Type:   "test_object",
   255  		Name:   "foo",
   256  		Config: configs.SynthBody("", map[string]cty.Value{}),
   257  	}
   258  	node := &EvalValidateResource{
   259  		Addr: addrs.Resource{
   260  			Mode: addrs.ManagedResourceMode,
   261  			Type: "test-object",
   262  			Name: "foo",
   263  		},
   264  		Provider:       &p,
   265  		Config:         rc,
   266  		ProviderSchema: &mp.GetSchemaReturn,
   267  
   268  		IgnoreWarnings: true,
   269  	}
   270  
   271  	ctx := &MockEvalContext{}
   272  	ctx.installSimpleEval()
   273  
   274  	_, err := node.Eval(ctx)
   275  	if err != nil {
   276  		t.Fatalf("Expected no error, got: %s", err)
   277  	}
   278  }
   279  
   280  func TestEvalValidateResource_invalidDependsOn(t *testing.T) {
   281  	mp := simpleMockProvider()
   282  	mp.ValidateResourceTypeConfigFn = func(req providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse {
   283  		return providers.ValidateResourceTypeConfigResponse{}
   284  	}
   285  
   286  	// We'll check a _valid_ config first, to make sure we're not failing
   287  	// for some other reason, and then make it invalid.
   288  	p := providers.Interface(mp)
   289  	rc := &configs.Resource{
   290  		Mode:   addrs.ManagedResourceMode,
   291  		Type:   "test_object",
   292  		Name:   "foo",
   293  		Config: configs.SynthBody("", map[string]cty.Value{}),
   294  		DependsOn: []hcl.Traversal{
   295  			// Depending on path.module is pointless, since it is immediately
   296  			// available, but we allow all of the referencable addrs here
   297  			// for consistency: referencing them is harmless, and avoids the
   298  			// need for us to document a different subset of addresses that
   299  			// are valid in depends_on.
   300  			// For the sake of this test, it's a valid address we can use that
   301  			// doesn't require something else to exist in the configuration.
   302  			{
   303  				hcl.TraverseRoot{
   304  					Name: "path",
   305  				},
   306  				hcl.TraverseAttr{
   307  					Name: "module",
   308  				},
   309  			},
   310  		},
   311  	}
   312  	node := &EvalValidateResource{
   313  		Addr: addrs.Resource{
   314  			Mode: addrs.ManagedResourceMode,
   315  			Type: "aws_instance",
   316  			Name: "foo",
   317  		},
   318  		Provider:       &p,
   319  		Config:         rc,
   320  		ProviderSchema: &mp.GetSchemaReturn,
   321  	}
   322  
   323  	ctx := &MockEvalContext{}
   324  	ctx.installSimpleEval()
   325  
   326  	_, err := node.Eval(ctx)
   327  	if err != nil {
   328  		t.Fatalf("error for supposedly-valid config: %s", err)
   329  	}
   330  
   331  	// Now we'll make it invalid by adding additional traversal steps at
   332  	// the end of what we're referencing. This is intended to catch the
   333  	// situation where the user tries to depend on e.g. a specific resource
   334  	// attribute, rather than the whole resource, like aws_instance.foo.id.
   335  	rc.DependsOn = append(rc.DependsOn, hcl.Traversal{
   336  		hcl.TraverseRoot{
   337  			Name: "path",
   338  		},
   339  		hcl.TraverseAttr{
   340  			Name: "module",
   341  		},
   342  		hcl.TraverseAttr{
   343  			Name: "extra",
   344  		},
   345  	})
   346  
   347  	_, err = node.Eval(ctx)
   348  	if err == nil {
   349  		t.Fatal("no error for invalid depends_on")
   350  	}
   351  	if got, want := err.Error(), "Invalid depends_on reference"; !strings.Contains(got, want) {
   352  		t.Fatalf("wrong error\ngot:  %s\nwant: Message containing %q", got, want)
   353  	}
   354  
   355  	// Test for handling an unknown root without attribute, like a
   356  	// typo that omits the dot inbetween "path.module".
   357  	rc.DependsOn = append(rc.DependsOn, hcl.Traversal{
   358  		hcl.TraverseRoot{
   359  			Name: "pathmodule",
   360  		},
   361  	})
   362  
   363  	_, err = node.Eval(ctx)
   364  	if err == nil {
   365  		t.Fatal("no error for invalid depends_on")
   366  	}
   367  	if got, want := err.Error(), "Invalid depends_on reference"; !strings.Contains(got, want) {
   368  		t.Fatalf("wrong error\ngot:  %s\nwant: Message containing %q", got, want)
   369  	}
   370  }
   371  
   372  func TestEvalValidateProvisioner_valid(t *testing.T) {
   373  	mp := &MockProvisioner{}
   374  	var p provisioners.Interface = mp
   375  	ctx := &MockEvalContext{}
   376  	ctx.installSimpleEval()
   377  
   378  	schema := &configschema.Block{}
   379  
   380  	node := &EvalValidateProvisioner{
   381  		ResourceAddr: addrs.Resource{
   382  			Mode: addrs.ManagedResourceMode,
   383  			Type: "foo",
   384  			Name: "bar",
   385  		},
   386  		Provisioner: &p,
   387  		Schema:      &schema,
   388  		Config: &configs.Provisioner{
   389  			Type:   "baz",
   390  			Config: hcl.EmptyBody(),
   391  			Connection: &configs.Connection{
   392  				Config: configs.SynthBody("", map[string]cty.Value{
   393  					"host": cty.StringVal("localhost"),
   394  					"type": cty.StringVal("ssh"),
   395  				}),
   396  			},
   397  		},
   398  	}
   399  
   400  	result, err := node.Eval(ctx)
   401  	if err != nil {
   402  		t.Fatalf("node.Eval failed: %s", err)
   403  	}
   404  	if result != nil {
   405  		t.Errorf("node.Eval returned non-nil result")
   406  	}
   407  
   408  	if !mp.ValidateProvisionerConfigCalled {
   409  		t.Fatalf("p.ValidateProvisionerConfig not called")
   410  	}
   411  }
   412  
   413  func TestEvalValidateProvisioner_warning(t *testing.T) {
   414  	mp := &MockProvisioner{}
   415  	var p provisioners.Interface = mp
   416  	ctx := &MockEvalContext{}
   417  	ctx.installSimpleEval()
   418  
   419  	schema := &configschema.Block{
   420  		Attributes: map[string]*configschema.Attribute{
   421  			"type": {
   422  				Type:     cty.String,
   423  				Optional: true,
   424  			},
   425  		},
   426  	}
   427  
   428  	node := &EvalValidateProvisioner{
   429  		ResourceAddr: addrs.Resource{
   430  			Mode: addrs.ManagedResourceMode,
   431  			Type: "foo",
   432  			Name: "bar",
   433  		},
   434  		Provisioner: &p,
   435  		Schema:      &schema,
   436  		Config: &configs.Provisioner{
   437  			Type:   "baz",
   438  			Config: hcl.EmptyBody(),
   439  			Connection: &configs.Connection{
   440  				Config: configs.SynthBody("", map[string]cty.Value{
   441  					"host": cty.StringVal("localhost"),
   442  					"type": cty.StringVal("ssh"),
   443  				}),
   444  			},
   445  		},
   446  	}
   447  
   448  	{
   449  		var diags tfdiags.Diagnostics
   450  		diags = diags.Append(tfdiags.SimpleWarning("foo is deprecated"))
   451  		mp.ValidateProvisionerConfigResponse = provisioners.ValidateProvisionerConfigResponse{
   452  			Diagnostics: diags,
   453  		}
   454  	}
   455  
   456  	_, err := node.Eval(ctx)
   457  	if err == nil {
   458  		t.Fatalf("node.Eval succeeded; want error")
   459  	}
   460  
   461  	var diags tfdiags.Diagnostics
   462  	diags = diags.Append(err)
   463  	if len(diags) != 1 {
   464  		t.Fatalf("wrong number of diagnostics in %s; want one warning", diags.ErrWithWarnings())
   465  	}
   466  
   467  	if got, want := diags[0].Description().Summary, mp.ValidateProvisionerConfigResponse.Diagnostics[0].Description().Summary; got != want {
   468  		t.Fatalf("wrong warning %q; want %q", got, want)
   469  	}
   470  }
   471  
   472  func TestEvalValidateProvisioner_connectionInvalid(t *testing.T) {
   473  	var p provisioners.Interface = &MockProvisioner{}
   474  	ctx := &MockEvalContext{}
   475  	ctx.installSimpleEval()
   476  
   477  	schema := &configschema.Block{
   478  		Attributes: map[string]*configschema.Attribute{
   479  			"type": {
   480  				Type:     cty.String,
   481  				Optional: true,
   482  			},
   483  		},
   484  	}
   485  
   486  	node := &EvalValidateProvisioner{
   487  		ResourceAddr: addrs.Resource{
   488  			Mode: addrs.ManagedResourceMode,
   489  			Type: "foo",
   490  			Name: "bar",
   491  		},
   492  		Provisioner: &p,
   493  		Schema:      &schema,
   494  		Config: &configs.Provisioner{
   495  			Type:   "baz",
   496  			Config: hcl.EmptyBody(),
   497  			Connection: &configs.Connection{
   498  				Config: configs.SynthBody("", map[string]cty.Value{
   499  					"type":             cty.StringVal("ssh"),
   500  					"bananananananana": cty.StringVal("foo"),
   501  					"bazaz":            cty.StringVal("bar"),
   502  				}),
   503  			},
   504  		},
   505  	}
   506  
   507  	_, err := node.Eval(ctx)
   508  	if err == nil {
   509  		t.Fatalf("node.Eval succeeded; want error")
   510  	}
   511  
   512  	var diags tfdiags.Diagnostics
   513  	diags = diags.Append(err)
   514  	if len(diags) != 3 {
   515  		t.Fatalf("wrong number of diagnostics; want two errors\n\n%s", diags.Err())
   516  	}
   517  
   518  	errStr := diags.Err().Error()
   519  	if !(strings.Contains(errStr, "bananananananana") && strings.Contains(errStr, "bazaz")) {
   520  		t.Fatalf("wrong errors %q; want something about each of our invalid connInfo keys", errStr)
   521  	}
   522  }