github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/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/configs/configschema"
    14  	"github.com/hashicorp/terraform/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": &Resource{
    33  				Schema: map[string]*Schema{
    34  					"bar": {
    35  						Type:     TypeString,
    36  						Required: true,
    37  					},
    38  				},
    39  			},
    40  		},
    41  		DataSourcesMap: map[string]*Resource{
    42  			"baz": &Resource{
    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": &configschema.Attribute{
    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": &configschema.Attribute{
    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": &configschema.Attribute{
    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": &Schema{
   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": &Schema{
   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  				terraform.ResourceType{Name: "bar", SchemaAvailable: true},
   186  				terraform.ResourceType{Name: "foo", SchemaAvailable: true},
   187  			},
   188  		},
   189  
   190  		{
   191  			P: &Provider{
   192  				ResourcesMap: map[string]*Resource{
   193  					"foo": nil,
   194  					"bar": &Resource{Importer: &ResourceImporter{}},
   195  					"baz": nil,
   196  				},
   197  			},
   198  			Result: []terraform.ResourceType{
   199  				terraform.ResourceType{Name: "bar", Importable: true, SchemaAvailable: true},
   200  				terraform.ResourceType{Name: "baz", SchemaAvailable: true},
   201  				terraform.ResourceType{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  				terraform.DataSource{Name: "bar", SchemaAvailable: true},
   233  				terraform.DataSource{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": &Schema{},
   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": &Resource{
   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": &Resource{
   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  	expectedErrMsg := "time: invalid duration invalid"
   345  	if !strings.Contains(err.Error(), expectedErrMsg) {
   346  		t.Fatalf("Unexpected error message: %q\nExpected message to contain %q",
   347  			err.Error(),
   348  			expectedErrMsg)
   349  	}
   350  }
   351  
   352  func TestProviderValidateResource(t *testing.T) {
   353  	cases := []struct {
   354  		P      *Provider
   355  		Type   string
   356  		Config map[string]interface{}
   357  		Err    bool
   358  	}{
   359  		{
   360  			P:      &Provider{},
   361  			Type:   "foo",
   362  			Config: nil,
   363  			Err:    true,
   364  		},
   365  
   366  		{
   367  			P: &Provider{
   368  				ResourcesMap: map[string]*Resource{
   369  					"foo": &Resource{},
   370  				},
   371  			},
   372  			Type:   "foo",
   373  			Config: nil,
   374  			Err:    false,
   375  		},
   376  	}
   377  
   378  	for i, tc := range cases {
   379  		c := terraform.NewResourceConfigRaw(tc.Config)
   380  		_, es := tc.P.ValidateResource(tc.Type, c)
   381  		if len(es) > 0 != tc.Err {
   382  			t.Fatalf("%d: %#v", i, es)
   383  		}
   384  	}
   385  }
   386  
   387  func TestProviderImportState_default(t *testing.T) {
   388  	p := &Provider{
   389  		ResourcesMap: map[string]*Resource{
   390  			"foo": &Resource{
   391  				Importer: &ResourceImporter{},
   392  			},
   393  		},
   394  	}
   395  
   396  	states, err := p.ImportState(&terraform.InstanceInfo{
   397  		Type: "foo",
   398  	}, "bar")
   399  	if err != nil {
   400  		t.Fatalf("err: %s", err)
   401  	}
   402  
   403  	if len(states) != 1 {
   404  		t.Fatalf("bad: %#v", states)
   405  	}
   406  	if states[0].ID != "bar" {
   407  		t.Fatalf("bad: %#v", states)
   408  	}
   409  }
   410  
   411  func TestProviderImportState_setsId(t *testing.T) {
   412  	var val string
   413  	stateFunc := func(d *ResourceData, meta interface{}) ([]*ResourceData, error) {
   414  		val = d.Id()
   415  		return []*ResourceData{d}, nil
   416  	}
   417  
   418  	p := &Provider{
   419  		ResourcesMap: map[string]*Resource{
   420  			"foo": &Resource{
   421  				Importer: &ResourceImporter{
   422  					State: stateFunc,
   423  				},
   424  			},
   425  		},
   426  	}
   427  
   428  	_, err := p.ImportState(&terraform.InstanceInfo{
   429  		Type: "foo",
   430  	}, "bar")
   431  	if err != nil {
   432  		t.Fatalf("err: %s", err)
   433  	}
   434  
   435  	if val != "bar" {
   436  		t.Fatal("should set id")
   437  	}
   438  }
   439  
   440  func TestProviderImportState_setsType(t *testing.T) {
   441  	var tVal string
   442  	stateFunc := func(d *ResourceData, meta interface{}) ([]*ResourceData, error) {
   443  		d.SetId("foo")
   444  		tVal = d.State().Ephemeral.Type
   445  		return []*ResourceData{d}, nil
   446  	}
   447  
   448  	p := &Provider{
   449  		ResourcesMap: map[string]*Resource{
   450  			"foo": &Resource{
   451  				Importer: &ResourceImporter{
   452  					State: stateFunc,
   453  				},
   454  			},
   455  		},
   456  	}
   457  
   458  	_, err := p.ImportState(&terraform.InstanceInfo{
   459  		Type: "foo",
   460  	}, "bar")
   461  	if err != nil {
   462  		t.Fatalf("err: %s", err)
   463  	}
   464  
   465  	if tVal != "foo" {
   466  		t.Fatal("should set type")
   467  	}
   468  }
   469  
   470  func TestProviderMeta(t *testing.T) {
   471  	p := new(Provider)
   472  	if v := p.Meta(); v != nil {
   473  		t.Fatalf("bad: %#v", v)
   474  	}
   475  
   476  	expected := 42
   477  	p.SetMeta(42)
   478  	if v := p.Meta(); !reflect.DeepEqual(v, expected) {
   479  		t.Fatalf("bad: %#v", v)
   480  	}
   481  }
   482  
   483  func TestProviderStop(t *testing.T) {
   484  	var p Provider
   485  
   486  	if p.Stopped() {
   487  		t.Fatal("should not be stopped")
   488  	}
   489  
   490  	// Verify stopch blocks
   491  	ch := p.StopContext().Done()
   492  	select {
   493  	case <-ch:
   494  		t.Fatal("should not be stopped")
   495  	case <-time.After(10 * time.Millisecond):
   496  	}
   497  
   498  	// Stop it
   499  	if err := p.Stop(); err != nil {
   500  		t.Fatalf("err: %s", err)
   501  	}
   502  
   503  	// Verify
   504  	if !p.Stopped() {
   505  		t.Fatal("should be stopped")
   506  	}
   507  
   508  	select {
   509  	case <-ch:
   510  	case <-time.After(10 * time.Millisecond):
   511  		t.Fatal("should be stopped")
   512  	}
   513  }
   514  
   515  func TestProviderStop_stopFirst(t *testing.T) {
   516  	var p Provider
   517  
   518  	// Stop it
   519  	if err := p.Stop(); err != nil {
   520  		t.Fatalf("err: %s", err)
   521  	}
   522  
   523  	// Verify
   524  	if !p.Stopped() {
   525  		t.Fatal("should be stopped")
   526  	}
   527  
   528  	select {
   529  	case <-p.StopContext().Done():
   530  	case <-time.After(10 * time.Millisecond):
   531  		t.Fatal("should be stopped")
   532  	}
   533  }
   534  
   535  func TestProviderReset(t *testing.T) {
   536  	var p Provider
   537  	stopCtx := p.StopContext()
   538  	p.MetaReset = func() error {
   539  		stopCtx = p.StopContext()
   540  		return nil
   541  	}
   542  
   543  	// cancel the current context
   544  	p.Stop()
   545  
   546  	if err := p.TestReset(); err != nil {
   547  		t.Fatal(err)
   548  	}
   549  
   550  	// the first context should have been replaced
   551  	if err := stopCtx.Err(); err != nil {
   552  		t.Fatal(err)
   553  	}
   554  
   555  	// we should not get a canceled context here either
   556  	if err := p.StopContext().Err(); err != nil {
   557  		t.Fatal(err)
   558  	}
   559  }
   560  
   561  func TestProvider_InternalValidate(t *testing.T) {
   562  	cases := []struct {
   563  		P           *Provider
   564  		ExpectedErr error
   565  	}{
   566  		{
   567  			P: &Provider{
   568  				Schema: map[string]*Schema{
   569  					"foo": {
   570  						Type:     TypeBool,
   571  						Optional: true,
   572  					},
   573  				},
   574  			},
   575  			ExpectedErr: nil,
   576  		},
   577  		{ // Reserved resource fields should be allowed in provider block
   578  			P: &Provider{
   579  				Schema: map[string]*Schema{
   580  					"provisioner": {
   581  						Type:     TypeString,
   582  						Optional: true,
   583  					},
   584  					"count": {
   585  						Type:     TypeInt,
   586  						Optional: true,
   587  					},
   588  				},
   589  			},
   590  			ExpectedErr: nil,
   591  		},
   592  		{ // Reserved provider fields should not be allowed
   593  			P: &Provider{
   594  				Schema: map[string]*Schema{
   595  					"alias": {
   596  						Type:     TypeString,
   597  						Optional: true,
   598  					},
   599  				},
   600  			},
   601  			ExpectedErr: fmt.Errorf("%s is a reserved field name for a provider", "alias"),
   602  		},
   603  	}
   604  
   605  	for i, tc := range cases {
   606  		err := tc.P.InternalValidate()
   607  		if tc.ExpectedErr == nil {
   608  			if err != nil {
   609  				t.Fatalf("%d: Error returned (expected no error): %s", i, err)
   610  			}
   611  			continue
   612  		}
   613  		if tc.ExpectedErr != nil && err == nil {
   614  			t.Fatalf("%d: Expected error (%s), but no error returned", i, tc.ExpectedErr)
   615  		}
   616  		if err.Error() != tc.ExpectedErr.Error() {
   617  			t.Fatalf("%d: Errors don't match. Expected: %#v Given: %#v", i, tc.ExpectedErr, err)
   618  		}
   619  	}
   620  }