github.com/hashicorp/terraform-plugin-sdk@v1.17.2/helper/schema/provider_test.go (about)

     1  package schema
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strings"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/google/go-cmp/cmp"
    11  	"github.com/zclconf/go-cty/cty"
    12  
    13  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema"
    14  	"github.com/hashicorp/terraform-plugin-sdk/terraform"
    15  )
    16  
    17  func TestProvider_impl(t *testing.T) {
    18  	var _ terraform.ResourceProvider = new(Provider)
    19  }
    20  
    21  func TestProviderGetSchema(t *testing.T) {
    22  	// This functionality is already broadly tested in core_schema_test.go,
    23  	// so this is just to ensure that the call passes through correctly.
    24  	p := &Provider{
    25  		Schema: map[string]*Schema{
    26  			"bar": {
    27  				Type:     TypeString,
    28  				Required: true,
    29  			},
    30  		},
    31  		ResourcesMap: map[string]*Resource{
    32  			"foo": {
    33  				Schema: map[string]*Schema{
    34  					"bar": {
    35  						Type:     TypeString,
    36  						Required: true,
    37  					},
    38  				},
    39  			},
    40  		},
    41  		DataSourcesMap: map[string]*Resource{
    42  			"baz": {
    43  				Schema: map[string]*Schema{
    44  					"bur": {
    45  						Type:     TypeString,
    46  						Required: true,
    47  					},
    48  				},
    49  			},
    50  		},
    51  	}
    52  
    53  	want := &terraform.ProviderSchema{
    54  		Provider: &configschema.Block{
    55  			Attributes: map[string]*configschema.Attribute{
    56  				"bar": {
    57  					Type:     cty.String,
    58  					Required: true,
    59  				},
    60  			},
    61  			BlockTypes: map[string]*configschema.NestedBlock{},
    62  		},
    63  		ResourceTypes: map[string]*configschema.Block{
    64  			"foo": testResource(&configschema.Block{
    65  				Attributes: map[string]*configschema.Attribute{
    66  					"bar": {
    67  						Type:     cty.String,
    68  						Required: true,
    69  					},
    70  				},
    71  				BlockTypes: map[string]*configschema.NestedBlock{},
    72  			}),
    73  		},
    74  		DataSources: map[string]*configschema.Block{
    75  			"baz": testResource(&configschema.Block{
    76  				Attributes: map[string]*configschema.Attribute{
    77  					"bur": {
    78  						Type:     cty.String,
    79  						Required: true,
    80  					},
    81  				},
    82  				BlockTypes: map[string]*configschema.NestedBlock{},
    83  			}),
    84  		},
    85  	}
    86  	got, err := p.GetSchema(&terraform.ProviderSchemaRequest{
    87  		ResourceTypes: []string{"foo", "bar"},
    88  		DataSources:   []string{"baz", "bar"},
    89  	})
    90  	if err != nil {
    91  		t.Fatalf("unexpected error %s", err)
    92  	}
    93  
    94  	if !cmp.Equal(got, want, equateEmpty, typeComparer) {
    95  		t.Error("wrong result:\n", cmp.Diff(got, want, equateEmpty, typeComparer))
    96  	}
    97  }
    98  
    99  func TestProviderConfigure(t *testing.T) {
   100  	cases := []struct {
   101  		P      *Provider
   102  		Config map[string]interface{}
   103  		Err    bool
   104  	}{
   105  		{
   106  			P:      &Provider{},
   107  			Config: nil,
   108  			Err:    false,
   109  		},
   110  
   111  		{
   112  			P: &Provider{
   113  				Schema: map[string]*Schema{
   114  					"foo": {
   115  						Type:     TypeInt,
   116  						Optional: true,
   117  					},
   118  				},
   119  
   120  				ConfigureFunc: func(d *ResourceData) (interface{}, error) {
   121  					if d.Get("foo").(int) == 42 {
   122  						return nil, nil
   123  					}
   124  
   125  					return nil, fmt.Errorf("nope")
   126  				},
   127  			},
   128  			Config: map[string]interface{}{
   129  				"foo": 42,
   130  			},
   131  			Err: false,
   132  		},
   133  
   134  		{
   135  			P: &Provider{
   136  				Schema: map[string]*Schema{
   137  					"foo": {
   138  						Type:     TypeInt,
   139  						Optional: true,
   140  					},
   141  				},
   142  
   143  				ConfigureFunc: func(d *ResourceData) (interface{}, error) {
   144  					if d.Get("foo").(int) == 42 {
   145  						return nil, nil
   146  					}
   147  
   148  					return nil, fmt.Errorf("nope")
   149  				},
   150  			},
   151  			Config: map[string]interface{}{
   152  				"foo": 52,
   153  			},
   154  			Err: true,
   155  		},
   156  	}
   157  
   158  	for i, tc := range cases {
   159  		c := terraform.NewResourceConfigRaw(tc.Config)
   160  		err := tc.P.Configure(c)
   161  		if err != nil != tc.Err {
   162  			t.Fatalf("%d: %s", i, err)
   163  		}
   164  	}
   165  }
   166  
   167  func TestProviderResources(t *testing.T) {
   168  	cases := []struct {
   169  		P      *Provider
   170  		Result []terraform.ResourceType
   171  	}{
   172  		{
   173  			P:      &Provider{},
   174  			Result: []terraform.ResourceType{},
   175  		},
   176  
   177  		{
   178  			P: &Provider{
   179  				ResourcesMap: map[string]*Resource{
   180  					"foo": nil,
   181  					"bar": nil,
   182  				},
   183  			},
   184  			Result: []terraform.ResourceType{
   185  				{Name: "bar", SchemaAvailable: true},
   186  				{Name: "foo", SchemaAvailable: true},
   187  			},
   188  		},
   189  
   190  		{
   191  			P: &Provider{
   192  				ResourcesMap: map[string]*Resource{
   193  					"foo": nil,
   194  					"bar": {Importer: &ResourceImporter{}},
   195  					"baz": nil,
   196  				},
   197  			},
   198  			Result: []terraform.ResourceType{
   199  				{Name: "bar", Importable: true, SchemaAvailable: true},
   200  				{Name: "baz", SchemaAvailable: true},
   201  				{Name: "foo", SchemaAvailable: true},
   202  			},
   203  		},
   204  	}
   205  
   206  	for i, tc := range cases {
   207  		actual := tc.P.Resources()
   208  		if !reflect.DeepEqual(actual, tc.Result) {
   209  			t.Fatalf("%d: %#v", i, actual)
   210  		}
   211  	}
   212  }
   213  
   214  func TestProviderDataSources(t *testing.T) {
   215  	cases := []struct {
   216  		P      *Provider
   217  		Result []terraform.DataSource
   218  	}{
   219  		{
   220  			P:      &Provider{},
   221  			Result: []terraform.DataSource{},
   222  		},
   223  
   224  		{
   225  			P: &Provider{
   226  				DataSourcesMap: map[string]*Resource{
   227  					"foo": nil,
   228  					"bar": nil,
   229  				},
   230  			},
   231  			Result: []terraform.DataSource{
   232  				{Name: "bar", SchemaAvailable: true},
   233  				{Name: "foo", SchemaAvailable: true},
   234  			},
   235  		},
   236  	}
   237  
   238  	for i, tc := range cases {
   239  		actual := tc.P.DataSources()
   240  		if !reflect.DeepEqual(actual, tc.Result) {
   241  			t.Fatalf("%d: got %#v; want %#v", i, actual, tc.Result)
   242  		}
   243  	}
   244  }
   245  
   246  func TestProviderValidate(t *testing.T) {
   247  	cases := []struct {
   248  		P      *Provider
   249  		Config map[string]interface{}
   250  		Err    bool
   251  	}{
   252  		{
   253  			P: &Provider{
   254  				Schema: map[string]*Schema{
   255  					"foo": {},
   256  				},
   257  			},
   258  			Config: nil,
   259  			Err:    true,
   260  		},
   261  	}
   262  
   263  	for i, tc := range cases {
   264  		c := terraform.NewResourceConfigRaw(tc.Config)
   265  		_, es := tc.P.Validate(c)
   266  		if len(es) > 0 != tc.Err {
   267  			t.Fatalf("%d: %#v", i, es)
   268  		}
   269  	}
   270  }
   271  
   272  func TestProviderDiff_legacyTimeoutType(t *testing.T) {
   273  	p := &Provider{
   274  		ResourcesMap: map[string]*Resource{
   275  			"blah": {
   276  				Schema: map[string]*Schema{
   277  					"foo": {
   278  						Type:     TypeInt,
   279  						Optional: true,
   280  					},
   281  				},
   282  				Timeouts: &ResourceTimeout{
   283  					Create: DefaultTimeout(10 * time.Minute),
   284  				},
   285  			},
   286  		},
   287  	}
   288  
   289  	invalidCfg := map[string]interface{}{
   290  		"foo": 42,
   291  		"timeouts": []interface{}{
   292  			map[string]interface{}{
   293  				"create": "40m",
   294  			},
   295  		},
   296  	}
   297  	ic := terraform.NewResourceConfigRaw(invalidCfg)
   298  	_, err := p.Diff(
   299  		&terraform.InstanceInfo{
   300  			Type: "blah",
   301  		},
   302  		nil,
   303  		ic,
   304  	)
   305  	if err != nil {
   306  		t.Fatal(err)
   307  	}
   308  }
   309  
   310  func TestProviderDiff_timeoutInvalidValue(t *testing.T) {
   311  	p := &Provider{
   312  		ResourcesMap: map[string]*Resource{
   313  			"blah": {
   314  				Schema: map[string]*Schema{
   315  					"foo": {
   316  						Type:     TypeInt,
   317  						Optional: true,
   318  					},
   319  				},
   320  				Timeouts: &ResourceTimeout{
   321  					Create: DefaultTimeout(10 * time.Minute),
   322  				},
   323  			},
   324  		},
   325  	}
   326  
   327  	invalidCfg := map[string]interface{}{
   328  		"foo": 42,
   329  		"timeouts": map[string]interface{}{
   330  			"create": "invalid",
   331  		},
   332  	}
   333  	ic := terraform.NewResourceConfigRaw(invalidCfg)
   334  	_, err := p.Diff(
   335  		&terraform.InstanceInfo{
   336  			Type: "blah",
   337  		},
   338  		nil,
   339  		ic,
   340  	)
   341  	if err == nil {
   342  		t.Fatal("Expected provider.Diff to fail with invalid timeout value")
   343  	}
   344  	if !strings.Contains(err.Error(), invalidDurationErrMsg) {
   345  		t.Fatalf("Unexpected error message: %q\nExpected message to contain %q",
   346  			err.Error(),
   347  			invalidDurationErrMsg)
   348  	}
   349  }
   350  
   351  func TestProviderValidateResource(t *testing.T) {
   352  	cases := []struct {
   353  		P      *Provider
   354  		Type   string
   355  		Config map[string]interface{}
   356  		Err    bool
   357  	}{
   358  		{
   359  			P:      &Provider{},
   360  			Type:   "foo",
   361  			Config: nil,
   362  			Err:    true,
   363  		},
   364  
   365  		{
   366  			P: &Provider{
   367  				ResourcesMap: map[string]*Resource{
   368  					"foo": {},
   369  				},
   370  			},
   371  			Type:   "foo",
   372  			Config: nil,
   373  			Err:    false,
   374  		},
   375  	}
   376  
   377  	for i, tc := range cases {
   378  		c := terraform.NewResourceConfigRaw(tc.Config)
   379  		_, es := tc.P.ValidateResource(tc.Type, c)
   380  		if len(es) > 0 != tc.Err {
   381  			t.Fatalf("%d: %#v", i, es)
   382  		}
   383  	}
   384  }
   385  
   386  func TestProviderImportState_default(t *testing.T) {
   387  	p := &Provider{
   388  		ResourcesMap: map[string]*Resource{
   389  			"foo": {
   390  				Importer: &ResourceImporter{},
   391  			},
   392  		},
   393  	}
   394  
   395  	states, err := p.ImportState(&terraform.InstanceInfo{
   396  		Type: "foo",
   397  	}, "bar")
   398  	if err != nil {
   399  		t.Fatalf("err: %s", err)
   400  	}
   401  
   402  	if len(states) != 1 {
   403  		t.Fatalf("bad: %#v", states)
   404  	}
   405  	if states[0].ID != "bar" {
   406  		t.Fatalf("bad: %#v", states)
   407  	}
   408  }
   409  
   410  func TestProviderImportState_setsId(t *testing.T) {
   411  	var val string
   412  	stateFunc := func(d *ResourceData, meta interface{}) ([]*ResourceData, error) {
   413  		val = d.Id()
   414  		return []*ResourceData{d}, nil
   415  	}
   416  
   417  	p := &Provider{
   418  		ResourcesMap: map[string]*Resource{
   419  			"foo": {
   420  				Importer: &ResourceImporter{
   421  					State: stateFunc,
   422  				},
   423  			},
   424  		},
   425  	}
   426  
   427  	_, err := p.ImportState(&terraform.InstanceInfo{
   428  		Type: "foo",
   429  	}, "bar")
   430  	if err != nil {
   431  		t.Fatalf("err: %s", err)
   432  	}
   433  
   434  	if val != "bar" {
   435  		t.Fatal("should set id")
   436  	}
   437  }
   438  
   439  func TestProviderImportState_setsType(t *testing.T) {
   440  	var tVal string
   441  	stateFunc := func(d *ResourceData, meta interface{}) ([]*ResourceData, error) {
   442  		d.SetId("foo")
   443  		tVal = d.State().Ephemeral.Type
   444  		return []*ResourceData{d}, nil
   445  	}
   446  
   447  	p := &Provider{
   448  		ResourcesMap: map[string]*Resource{
   449  			"foo": {
   450  				Importer: &ResourceImporter{
   451  					State: stateFunc,
   452  				},
   453  			},
   454  		},
   455  	}
   456  
   457  	_, err := p.ImportState(&terraform.InstanceInfo{
   458  		Type: "foo",
   459  	}, "bar")
   460  	if err != nil {
   461  		t.Fatalf("err: %s", err)
   462  	}
   463  
   464  	if tVal != "foo" {
   465  		t.Fatal("should set type")
   466  	}
   467  }
   468  
   469  func TestProviderMeta(t *testing.T) {
   470  	p := new(Provider)
   471  	if v := p.Meta(); v != nil {
   472  		t.Fatalf("bad: %#v", v)
   473  	}
   474  
   475  	expected := 42
   476  	p.SetMeta(42)
   477  	if v := p.Meta(); !reflect.DeepEqual(v, expected) {
   478  		t.Fatalf("bad: %#v", v)
   479  	}
   480  }
   481  
   482  func TestProviderStop(t *testing.T) {
   483  	var p Provider
   484  
   485  	if p.Stopped() {
   486  		t.Fatal("should not be stopped")
   487  	}
   488  
   489  	// Verify stopch blocks
   490  	ch := p.StopContext().Done()
   491  	select {
   492  	case <-ch:
   493  		t.Fatal("should not be stopped")
   494  	case <-time.After(10 * time.Millisecond):
   495  	}
   496  
   497  	// Stop it
   498  	if err := p.Stop(); err != nil {
   499  		t.Fatalf("err: %s", err)
   500  	}
   501  
   502  	// Verify
   503  	if !p.Stopped() {
   504  		t.Fatal("should be stopped")
   505  	}
   506  
   507  	select {
   508  	case <-ch:
   509  	case <-time.After(10 * time.Millisecond):
   510  		t.Fatal("should be stopped")
   511  	}
   512  }
   513  
   514  func TestProviderStop_stopFirst(t *testing.T) {
   515  	var p Provider
   516  
   517  	// Stop it
   518  	if err := p.Stop(); err != nil {
   519  		t.Fatalf("err: %s", err)
   520  	}
   521  
   522  	// Verify
   523  	if !p.Stopped() {
   524  		t.Fatal("should be stopped")
   525  	}
   526  
   527  	select {
   528  	case <-p.StopContext().Done():
   529  	case <-time.After(10 * time.Millisecond):
   530  		t.Fatal("should be stopped")
   531  	}
   532  }
   533  
   534  func TestProviderReset(t *testing.T) {
   535  	var p Provider
   536  	stopCtx := p.StopContext()
   537  	p.MetaReset = func() error {
   538  		stopCtx = p.StopContext()
   539  		return nil
   540  	}
   541  
   542  	// cancel the current context
   543  	p.Stop()
   544  
   545  	if err := p.TestReset(); err != nil {
   546  		t.Fatal(err)
   547  	}
   548  
   549  	// the first context should have been replaced
   550  	if err := stopCtx.Err(); err != nil {
   551  		t.Fatal(err)
   552  	}
   553  
   554  	// we should not get a canceled context here either
   555  	if err := p.StopContext().Err(); err != nil {
   556  		t.Fatal(err)
   557  	}
   558  }
   559  
   560  func TestProvider_InternalValidate(t *testing.T) {
   561  	cases := []struct {
   562  		P           *Provider
   563  		ExpectedErr error
   564  	}{
   565  		{
   566  			P: &Provider{
   567  				Schema: map[string]*Schema{
   568  					"foo": {
   569  						Type:     TypeBool,
   570  						Optional: true,
   571  					},
   572  				},
   573  			},
   574  			ExpectedErr: nil,
   575  		},
   576  		{ // Reserved resource fields should be allowed in provider block
   577  			P: &Provider{
   578  				Schema: map[string]*Schema{
   579  					"provisioner": {
   580  						Type:     TypeString,
   581  						Optional: true,
   582  					},
   583  					"count": {
   584  						Type:     TypeInt,
   585  						Optional: true,
   586  					},
   587  				},
   588  			},
   589  			ExpectedErr: nil,
   590  		},
   591  		{ // Reserved provider fields should not be allowed
   592  			P: &Provider{
   593  				Schema: map[string]*Schema{
   594  					"alias": {
   595  						Type:     TypeString,
   596  						Optional: true,
   597  					},
   598  				},
   599  			},
   600  			ExpectedErr: fmt.Errorf("%s is a reserved field name for a provider", "alias"),
   601  		},
   602  	}
   603  
   604  	for i, tc := range cases {
   605  		err := tc.P.InternalValidate()
   606  		if tc.ExpectedErr == nil {
   607  			if err != nil {
   608  				t.Fatalf("%d: Error returned (expected no error): %s", i, err)
   609  			}
   610  			continue
   611  		}
   612  		if tc.ExpectedErr != nil && err == nil {
   613  			t.Fatalf("%d: Expected error (%s), but no error returned", i, tc.ExpectedErr)
   614  		}
   615  		if err.Error() != tc.ExpectedErr.Error() {
   616  			t.Fatalf("%d: Errors don't match. Expected: %#v Given: %#v", i, tc.ExpectedErr, err)
   617  		}
   618  	}
   619  }