github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/terraform/node_provider_test.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/hashicorp/hcl/v2"
     9  	"github.com/hashicorp/terraform/internal/addrs"
    10  	"github.com/hashicorp/terraform/internal/configs"
    11  	"github.com/hashicorp/terraform/internal/configs/configschema"
    12  	"github.com/hashicorp/terraform/internal/lang/marks"
    13  	"github.com/hashicorp/terraform/internal/providers"
    14  	"github.com/hashicorp/terraform/internal/tfdiags"
    15  	"github.com/zclconf/go-cty/cty"
    16  )
    17  
    18  func TestNodeApplyableProviderExecute(t *testing.T) {
    19  	config := &configs.Provider{
    20  		Name: "foo",
    21  		Config: configs.SynthBody("", map[string]cty.Value{
    22  			"user": cty.StringVal("hello"),
    23  		}),
    24  	}
    25  
    26  	schema := &configschema.Block{
    27  		Attributes: map[string]*configschema.Attribute{
    28  			"user": {
    29  				Type:     cty.String,
    30  				Required: true,
    31  			},
    32  			"pw": {
    33  				Type:     cty.String,
    34  				Required: true,
    35  			},
    36  		},
    37  	}
    38  	provider := mockProviderWithConfigSchema(schema)
    39  	providerAddr := addrs.AbsProviderConfig{
    40  		Module:   addrs.RootModule,
    41  		Provider: addrs.NewDefaultProvider("foo"),
    42  	}
    43  
    44  	n := &NodeApplyableProvider{&NodeAbstractProvider{
    45  		Addr:   providerAddr,
    46  		Config: config,
    47  	}}
    48  
    49  	ctx := &MockEvalContext{ProviderProvider: provider}
    50  	ctx.installSimpleEval()
    51  	ctx.ProviderInputValues = map[string]cty.Value{
    52  		"pw": cty.StringVal("so secret"),
    53  	}
    54  
    55  	if diags := n.Execute(ctx, walkApply); diags.HasErrors() {
    56  		t.Fatalf("err: %s", diags.Err())
    57  	}
    58  
    59  	if !ctx.ConfigureProviderCalled {
    60  		t.Fatal("should be called")
    61  	}
    62  
    63  	gotObj := ctx.ConfigureProviderConfig
    64  	if !gotObj.Type().HasAttribute("user") {
    65  		t.Fatal("configuration object does not have \"user\" attribute")
    66  	}
    67  	if got, want := gotObj.GetAttr("user"), cty.StringVal("hello"); !got.RawEquals(want) {
    68  		t.Errorf("wrong configuration value\ngot:  %#v\nwant: %#v", got, want)
    69  	}
    70  
    71  	if !gotObj.Type().HasAttribute("pw") {
    72  		t.Fatal("configuration object does not have \"pw\" attribute")
    73  	}
    74  	if got, want := gotObj.GetAttr("pw"), cty.StringVal("so secret"); !got.RawEquals(want) {
    75  		t.Errorf("wrong configuration value\ngot:  %#v\nwant: %#v", got, want)
    76  	}
    77  }
    78  
    79  func TestNodeApplyableProviderExecute_unknownImport(t *testing.T) {
    80  	config := &configs.Provider{
    81  		Name: "foo",
    82  		Config: configs.SynthBody("", map[string]cty.Value{
    83  			"test_string": cty.UnknownVal(cty.String),
    84  		}),
    85  	}
    86  	provider := mockProviderWithConfigSchema(simpleTestSchema())
    87  	providerAddr := addrs.AbsProviderConfig{
    88  		Module:   addrs.RootModule,
    89  		Provider: addrs.NewDefaultProvider("foo"),
    90  	}
    91  	n := &NodeApplyableProvider{&NodeAbstractProvider{
    92  		Addr:   providerAddr,
    93  		Config: config,
    94  	}}
    95  
    96  	ctx := &MockEvalContext{ProviderProvider: provider}
    97  	ctx.installSimpleEval()
    98  
    99  	diags := n.Execute(ctx, walkImport)
   100  	if !diags.HasErrors() {
   101  		t.Fatal("expected error, got success")
   102  	}
   103  
   104  	detail := `Invalid provider configuration: The configuration for provider["registry.terraform.io/hashicorp/foo"] depends on values that cannot be determined until apply.`
   105  	if got, want := diags.Err().Error(), detail; got != want {
   106  		t.Errorf("wrong diagnostic detail\n got: %q\nwant: %q", got, want)
   107  	}
   108  
   109  	if ctx.ConfigureProviderCalled {
   110  		t.Fatal("should not be called")
   111  	}
   112  }
   113  
   114  func TestNodeApplyableProviderExecute_unknownApply(t *testing.T) {
   115  	config := &configs.Provider{
   116  		Name: "foo",
   117  		Config: configs.SynthBody("", map[string]cty.Value{
   118  			"test_string": cty.UnknownVal(cty.String),
   119  		}),
   120  	}
   121  	provider := mockProviderWithConfigSchema(simpleTestSchema())
   122  	providerAddr := addrs.AbsProviderConfig{
   123  		Module:   addrs.RootModule,
   124  		Provider: addrs.NewDefaultProvider("foo"),
   125  	}
   126  	n := &NodeApplyableProvider{&NodeAbstractProvider{
   127  		Addr:   providerAddr,
   128  		Config: config,
   129  	}}
   130  	ctx := &MockEvalContext{ProviderProvider: provider}
   131  	ctx.installSimpleEval()
   132  
   133  	if err := n.Execute(ctx, walkApply); err != nil {
   134  		t.Fatalf("err: %s", err)
   135  	}
   136  
   137  	if !ctx.ConfigureProviderCalled {
   138  		t.Fatal("should be called")
   139  	}
   140  
   141  	gotObj := ctx.ConfigureProviderConfig
   142  	if !gotObj.Type().HasAttribute("test_string") {
   143  		t.Fatal("configuration object does not have \"test_string\" attribute")
   144  	}
   145  	if got, want := gotObj.GetAttr("test_string"), cty.UnknownVal(cty.String); !got.RawEquals(want) {
   146  		t.Errorf("wrong configuration value\ngot:  %#v\nwant: %#v", got, want)
   147  	}
   148  }
   149  
   150  func TestNodeApplyableProviderExecute_sensitive(t *testing.T) {
   151  	config := &configs.Provider{
   152  		Name: "foo",
   153  		Config: configs.SynthBody("", map[string]cty.Value{
   154  			"test_string": cty.StringVal("hello").Mark(marks.Sensitive),
   155  		}),
   156  	}
   157  	provider := mockProviderWithConfigSchema(simpleTestSchema())
   158  	providerAddr := addrs.AbsProviderConfig{
   159  		Module:   addrs.RootModule,
   160  		Provider: addrs.NewDefaultProvider("foo"),
   161  	}
   162  
   163  	n := &NodeApplyableProvider{&NodeAbstractProvider{
   164  		Addr:   providerAddr,
   165  		Config: config,
   166  	}}
   167  
   168  	ctx := &MockEvalContext{ProviderProvider: provider}
   169  	ctx.installSimpleEval()
   170  	if err := n.Execute(ctx, walkApply); err != nil {
   171  		t.Fatalf("err: %s", err)
   172  	}
   173  
   174  	if !ctx.ConfigureProviderCalled {
   175  		t.Fatal("should be called")
   176  	}
   177  
   178  	gotObj := ctx.ConfigureProviderConfig
   179  	if !gotObj.Type().HasAttribute("test_string") {
   180  		t.Fatal("configuration object does not have \"test_string\" attribute")
   181  	}
   182  	if got, want := gotObj.GetAttr("test_string"), cty.StringVal("hello"); !got.RawEquals(want) {
   183  		t.Errorf("wrong configuration value\ngot:  %#v\nwant: %#v", got, want)
   184  	}
   185  }
   186  
   187  func TestNodeApplyableProviderExecute_sensitiveValidate(t *testing.T) {
   188  	config := &configs.Provider{
   189  		Name: "foo",
   190  		Config: configs.SynthBody("", map[string]cty.Value{
   191  			"test_string": cty.StringVal("hello").Mark(marks.Sensitive),
   192  		}),
   193  	}
   194  	provider := mockProviderWithConfigSchema(simpleTestSchema())
   195  	providerAddr := addrs.AbsProviderConfig{
   196  		Module:   addrs.RootModule,
   197  		Provider: addrs.NewDefaultProvider("foo"),
   198  	}
   199  
   200  	n := &NodeApplyableProvider{&NodeAbstractProvider{
   201  		Addr:   providerAddr,
   202  		Config: config,
   203  	}}
   204  
   205  	ctx := &MockEvalContext{ProviderProvider: provider}
   206  	ctx.installSimpleEval()
   207  	if err := n.Execute(ctx, walkValidate); err != nil {
   208  		t.Fatalf("err: %s", err)
   209  	}
   210  
   211  	if !provider.ValidateProviderConfigCalled {
   212  		t.Fatal("should be called")
   213  	}
   214  
   215  	gotObj := provider.ValidateProviderConfigRequest.Config
   216  	if !gotObj.Type().HasAttribute("test_string") {
   217  		t.Fatal("configuration object does not have \"test_string\" attribute")
   218  	}
   219  	if got, want := gotObj.GetAttr("test_string"), cty.StringVal("hello"); !got.RawEquals(want) {
   220  		t.Errorf("wrong configuration value\ngot:  %#v\nwant: %#v", got, want)
   221  	}
   222  }
   223  
   224  func TestNodeApplyableProviderExecute_emptyValidate(t *testing.T) {
   225  	config := &configs.Provider{
   226  		Name:   "foo",
   227  		Config: configs.SynthBody("", map[string]cty.Value{}),
   228  	}
   229  	provider := mockProviderWithConfigSchema(&configschema.Block{
   230  		Attributes: map[string]*configschema.Attribute{
   231  			"test_string": {
   232  				Type:     cty.String,
   233  				Required: true,
   234  			},
   235  		},
   236  	})
   237  	providerAddr := addrs.AbsProviderConfig{
   238  		Module:   addrs.RootModule,
   239  		Provider: addrs.NewDefaultProvider("foo"),
   240  	}
   241  
   242  	n := &NodeApplyableProvider{&NodeAbstractProvider{
   243  		Addr:   providerAddr,
   244  		Config: config,
   245  	}}
   246  
   247  	ctx := &MockEvalContext{ProviderProvider: provider}
   248  	ctx.installSimpleEval()
   249  	if err := n.Execute(ctx, walkValidate); err != nil {
   250  		t.Fatalf("err: %s", err)
   251  	}
   252  
   253  	if ctx.ConfigureProviderCalled {
   254  		t.Fatal("should not be called")
   255  	}
   256  }
   257  
   258  func TestNodeApplyableProvider_Validate(t *testing.T) {
   259  	provider := mockProviderWithConfigSchema(&configschema.Block{
   260  		Attributes: map[string]*configschema.Attribute{
   261  			"region": {
   262  				Type:     cty.String,
   263  				Required: true,
   264  			},
   265  		},
   266  	})
   267  	ctx := &MockEvalContext{ProviderProvider: provider}
   268  	ctx.installSimpleEval()
   269  
   270  	t.Run("valid", func(t *testing.T) {
   271  		config := &configs.Provider{
   272  			Name: "test",
   273  			Config: configs.SynthBody("", map[string]cty.Value{
   274  				"region": cty.StringVal("mars"),
   275  			}),
   276  		}
   277  
   278  		node := NodeApplyableProvider{
   279  			NodeAbstractProvider: &NodeAbstractProvider{
   280  				Addr:   mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   281  				Config: config,
   282  			},
   283  		}
   284  
   285  		diags := node.ValidateProvider(ctx, provider)
   286  		if diags.HasErrors() {
   287  			t.Errorf("unexpected error with valid config: %s", diags.Err())
   288  		}
   289  	})
   290  
   291  	t.Run("invalid", func(t *testing.T) {
   292  		config := &configs.Provider{
   293  			Name: "test",
   294  			Config: configs.SynthBody("", map[string]cty.Value{
   295  				"region": cty.MapValEmpty(cty.String),
   296  			}),
   297  		}
   298  
   299  		node := NodeApplyableProvider{
   300  			NodeAbstractProvider: &NodeAbstractProvider{
   301  				Addr:   mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   302  				Config: config,
   303  			},
   304  		}
   305  
   306  		diags := node.ValidateProvider(ctx, provider)
   307  		if !diags.HasErrors() {
   308  			t.Error("missing expected error with invalid config")
   309  		}
   310  	})
   311  
   312  	t.Run("empty config", func(t *testing.T) {
   313  		node := NodeApplyableProvider{
   314  			NodeAbstractProvider: &NodeAbstractProvider{
   315  				Addr: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   316  			},
   317  		}
   318  
   319  		diags := node.ValidateProvider(ctx, provider)
   320  		if diags.HasErrors() {
   321  			t.Errorf("unexpected error with empty config: %s", diags.Err())
   322  		}
   323  	})
   324  }
   325  
   326  // This test specifically tests responses from the
   327  // providers.ValidateProviderConfigFn. See
   328  // TestNodeApplyableProvider_ConfigProvider_config_fn_err for
   329  // providers.ConfigureProviderRequest responses.
   330  func TestNodeApplyableProvider_ConfigProvider(t *testing.T) {
   331  	provider := mockProviderWithConfigSchema(&configschema.Block{
   332  		Attributes: map[string]*configschema.Attribute{
   333  			"region": {
   334  				Type:     cty.String,
   335  				Optional: true,
   336  			},
   337  		},
   338  	})
   339  	// For this test, we're returning an error for an optional argument. This
   340  	// can happen for example if an argument is only conditionally required.
   341  	provider.ValidateProviderConfigFn = func(req providers.ValidateProviderConfigRequest) (resp providers.ValidateProviderConfigResponse) {
   342  		region := req.Config.GetAttr("region")
   343  		if region.IsNull() {
   344  			resp.Diagnostics = resp.Diagnostics.Append(
   345  				tfdiags.WholeContainingBody(tfdiags.Error, "value is not found", "you did not supply a required value"))
   346  		}
   347  		return
   348  	}
   349  	ctx := &MockEvalContext{ProviderProvider: provider}
   350  	ctx.installSimpleEval()
   351  
   352  	t.Run("valid", func(t *testing.T) {
   353  		config := &configs.Provider{
   354  			Name: "test",
   355  			Config: configs.SynthBody("", map[string]cty.Value{
   356  				"region": cty.StringVal("mars"),
   357  			}),
   358  		}
   359  
   360  		node := NodeApplyableProvider{
   361  			NodeAbstractProvider: &NodeAbstractProvider{
   362  				Addr:   mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   363  				Config: config,
   364  			},
   365  		}
   366  
   367  		diags := node.ConfigureProvider(ctx, provider, false)
   368  		if diags.HasErrors() {
   369  			t.Errorf("unexpected error with valid config: %s", diags.Err())
   370  		}
   371  	})
   372  
   373  	t.Run("missing required config (no config at all)", func(t *testing.T) {
   374  		node := NodeApplyableProvider{
   375  			NodeAbstractProvider: &NodeAbstractProvider{
   376  				Addr: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   377  			},
   378  		}
   379  
   380  		diags := node.ConfigureProvider(ctx, provider, false)
   381  		if !diags.HasErrors() {
   382  			t.Fatal("missing expected error with nil config")
   383  		}
   384  		if !strings.Contains(diags.Err().Error(), "requires explicit configuration") {
   385  			t.Errorf("diagnostic is missing \"requires explicit configuration\" message: %s", diags.Err())
   386  		}
   387  	})
   388  
   389  	t.Run("missing required config", func(t *testing.T) {
   390  		config := &configs.Provider{
   391  			Name:   "test",
   392  			Config: hcl.EmptyBody(),
   393  		}
   394  		node := NodeApplyableProvider{
   395  			NodeAbstractProvider: &NodeAbstractProvider{
   396  				Addr:   mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   397  				Config: config,
   398  			},
   399  		}
   400  
   401  		diags := node.ConfigureProvider(ctx, provider, false)
   402  		if !diags.HasErrors() {
   403  			t.Fatal("missing expected error with invalid config")
   404  		}
   405  		if !strings.Contains(diags.Err().Error(), "value is not found") {
   406  			t.Errorf("wrong diagnostic: %s", diags.Err())
   407  		}
   408  	})
   409  
   410  }
   411  
   412  // This test is similar to TestNodeApplyableProvider_ConfigProvider, but tests responses from the providers.ConfigureProviderRequest
   413  func TestNodeApplyableProvider_ConfigProvider_config_fn_err(t *testing.T) {
   414  	provider := mockProviderWithConfigSchema(&configschema.Block{
   415  		Attributes: map[string]*configschema.Attribute{
   416  			"region": {
   417  				Type:     cty.String,
   418  				Optional: true,
   419  			},
   420  		},
   421  	})
   422  	ctx := &MockEvalContext{ProviderProvider: provider}
   423  	ctx.installSimpleEval()
   424  	// For this test, provider.PrepareConfigFn will succeed every time but the
   425  	// ctx.ConfigureProviderFn will return an error if a value is not found.
   426  	//
   427  	// This is an unlikely but real situation that occurs:
   428  	// https://github.com/hashicorp/terraform/issues/23087
   429  	ctx.ConfigureProviderFn = func(addr addrs.AbsProviderConfig, cfg cty.Value) (diags tfdiags.Diagnostics) {
   430  		if cfg.IsNull() {
   431  			diags = diags.Append(fmt.Errorf("no config provided"))
   432  		} else {
   433  			region := cfg.GetAttr("region")
   434  			if region.IsNull() {
   435  				diags = diags.Append(fmt.Errorf("value is not found"))
   436  			}
   437  		}
   438  		return
   439  	}
   440  
   441  	t.Run("valid", func(t *testing.T) {
   442  		config := &configs.Provider{
   443  			Name: "test",
   444  			Config: configs.SynthBody("", map[string]cty.Value{
   445  				"region": cty.StringVal("mars"),
   446  			}),
   447  		}
   448  
   449  		node := NodeApplyableProvider{
   450  			NodeAbstractProvider: &NodeAbstractProvider{
   451  				Addr:   mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   452  				Config: config,
   453  			},
   454  		}
   455  
   456  		diags := node.ConfigureProvider(ctx, provider, false)
   457  		if diags.HasErrors() {
   458  			t.Errorf("unexpected error with valid config: %s", diags.Err())
   459  		}
   460  	})
   461  
   462  	t.Run("missing required config (no config at all)", func(t *testing.T) {
   463  		node := NodeApplyableProvider{
   464  			NodeAbstractProvider: &NodeAbstractProvider{
   465  				Addr: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   466  			},
   467  		}
   468  
   469  		diags := node.ConfigureProvider(ctx, provider, false)
   470  		if !diags.HasErrors() {
   471  			t.Fatal("missing expected error with nil config")
   472  		}
   473  		if !strings.Contains(diags.Err().Error(), "requires explicit configuration") {
   474  			t.Errorf("diagnostic is missing \"requires explicit configuration\" message: %s", diags.Err())
   475  		}
   476  	})
   477  
   478  	t.Run("missing required config", func(t *testing.T) {
   479  		config := &configs.Provider{
   480  			Name:   "test",
   481  			Config: hcl.EmptyBody(),
   482  		}
   483  		node := NodeApplyableProvider{
   484  			NodeAbstractProvider: &NodeAbstractProvider{
   485  				Addr:   mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   486  				Config: config,
   487  			},
   488  		}
   489  
   490  		diags := node.ConfigureProvider(ctx, provider, false)
   491  		if !diags.HasErrors() {
   492  			t.Fatal("missing expected error with invalid config")
   493  		}
   494  		if diags.Err().Error() != "value is not found" {
   495  			t.Errorf("wrong diagnostic: %s", diags.Err())
   496  		}
   497  	})
   498  }
   499  
   500  func TestGetSchemaError(t *testing.T) {
   501  	provider := &MockProvider{
   502  		GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
   503  			Diagnostics: tfdiags.Diagnostics.Append(nil, tfdiags.WholeContainingBody(tfdiags.Error, "oops", "error")),
   504  		},
   505  	}
   506  
   507  	providerAddr := mustProviderConfig(`provider["terraform.io/some/provider"]`)
   508  	ctx := &MockEvalContext{ProviderProvider: provider}
   509  	ctx.installSimpleEval()
   510  	node := NodeApplyableProvider{
   511  		NodeAbstractProvider: &NodeAbstractProvider{
   512  			Addr: providerAddr,
   513  		},
   514  	}
   515  
   516  	diags := node.ConfigureProvider(ctx, provider, false)
   517  	for _, d := range diags {
   518  		desc := d.Description()
   519  		if desc.Address != providerAddr.String() {
   520  			t.Fatalf("missing provider address from diagnostics: %#v", desc)
   521  		}
   522  	}
   523  
   524  }