github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/legacy/helper/schema/provider_test.go (about)

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