| package schema |
| |
| import ( |
| "fmt" |
| "reflect" |
| "strings" |
| "testing" |
| "time" |
| |
| "github.com/google/go-cmp/cmp" |
| "github.com/zclconf/go-cty/cty" |
| |
| "github.com/hashicorp/terraform/internal/configs/configschema" |
| "github.com/hashicorp/terraform/internal/legacy/terraform" |
| ) |
| |
| func TestProvider_impl(t *testing.T) { |
| var _ terraform.ResourceProvider = new(Provider) |
| } |
| |
| func TestProviderGetSchema(t *testing.T) { |
| // This functionality is already broadly tested in core_schema_test.go, |
| // so this is just to ensure that the call passes through correctly. |
| p := &Provider{ |
| Schema: map[string]*Schema{ |
| "bar": { |
| Type: TypeString, |
| Required: true, |
| }, |
| }, |
| ResourcesMap: map[string]*Resource{ |
| "foo": &Resource{ |
| Schema: map[string]*Schema{ |
| "bar": { |
| Type: TypeString, |
| Required: true, |
| }, |
| }, |
| }, |
| }, |
| DataSourcesMap: map[string]*Resource{ |
| "baz": &Resource{ |
| Schema: map[string]*Schema{ |
| "bur": { |
| Type: TypeString, |
| Required: true, |
| }, |
| }, |
| }, |
| }, |
| } |
| |
| want := &terraform.ProviderSchema{ |
| Provider: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "bar": &configschema.Attribute{ |
| Type: cty.String, |
| Required: true, |
| }, |
| }, |
| BlockTypes: map[string]*configschema.NestedBlock{}, |
| }, |
| ResourceTypes: map[string]*configschema.Block{ |
| "foo": testResource(&configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "bar": &configschema.Attribute{ |
| Type: cty.String, |
| Required: true, |
| }, |
| }, |
| BlockTypes: map[string]*configschema.NestedBlock{}, |
| }), |
| }, |
| DataSources: map[string]*configschema.Block{ |
| "baz": testResource(&configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "bur": &configschema.Attribute{ |
| Type: cty.String, |
| Required: true, |
| }, |
| }, |
| BlockTypes: map[string]*configschema.NestedBlock{}, |
| }), |
| }, |
| } |
| got, err := p.GetSchema(&terraform.ProviderSchemaRequest{ |
| ResourceTypes: []string{"foo", "bar"}, |
| DataSources: []string{"baz", "bar"}, |
| }) |
| if err != nil { |
| t.Fatalf("unexpected error %s", err) |
| } |
| |
| if !cmp.Equal(got, want, equateEmpty, typeComparer) { |
| t.Error("wrong result:\n", cmp.Diff(got, want, equateEmpty, typeComparer)) |
| } |
| } |
| |
| func TestProviderConfigure(t *testing.T) { |
| cases := []struct { |
| P *Provider |
| Config map[string]interface{} |
| Err bool |
| }{ |
| { |
| P: &Provider{}, |
| Config: nil, |
| Err: false, |
| }, |
| |
| { |
| P: &Provider{ |
| Schema: map[string]*Schema{ |
| "foo": &Schema{ |
| Type: TypeInt, |
| Optional: true, |
| }, |
| }, |
| |
| ConfigureFunc: func(d *ResourceData) (interface{}, error) { |
| if d.Get("foo").(int) == 42 { |
| return nil, nil |
| } |
| |
| return nil, fmt.Errorf("nope") |
| }, |
| }, |
| Config: map[string]interface{}{ |
| "foo": 42, |
| }, |
| Err: false, |
| }, |
| |
| { |
| P: &Provider{ |
| Schema: map[string]*Schema{ |
| "foo": &Schema{ |
| Type: TypeInt, |
| Optional: true, |
| }, |
| }, |
| |
| ConfigureFunc: func(d *ResourceData) (interface{}, error) { |
| if d.Get("foo").(int) == 42 { |
| return nil, nil |
| } |
| |
| return nil, fmt.Errorf("nope") |
| }, |
| }, |
| Config: map[string]interface{}{ |
| "foo": 52, |
| }, |
| Err: true, |
| }, |
| } |
| |
| for i, tc := range cases { |
| c := terraform.NewResourceConfigRaw(tc.Config) |
| err := tc.P.Configure(c) |
| if err != nil != tc.Err { |
| t.Fatalf("%d: %s", i, err) |
| } |
| } |
| } |
| |
| func TestProviderResources(t *testing.T) { |
| cases := []struct { |
| P *Provider |
| Result []terraform.ResourceType |
| }{ |
| { |
| P: &Provider{}, |
| Result: []terraform.ResourceType{}, |
| }, |
| |
| { |
| P: &Provider{ |
| ResourcesMap: map[string]*Resource{ |
| "foo": nil, |
| "bar": nil, |
| }, |
| }, |
| Result: []terraform.ResourceType{ |
| terraform.ResourceType{Name: "bar", SchemaAvailable: true}, |
| terraform.ResourceType{Name: "foo", SchemaAvailable: true}, |
| }, |
| }, |
| |
| { |
| P: &Provider{ |
| ResourcesMap: map[string]*Resource{ |
| "foo": nil, |
| "bar": &Resource{Importer: &ResourceImporter{}}, |
| "baz": nil, |
| }, |
| }, |
| Result: []terraform.ResourceType{ |
| terraform.ResourceType{Name: "bar", Importable: true, SchemaAvailable: true}, |
| terraform.ResourceType{Name: "baz", SchemaAvailable: true}, |
| terraform.ResourceType{Name: "foo", SchemaAvailable: true}, |
| }, |
| }, |
| } |
| |
| for i, tc := range cases { |
| actual := tc.P.Resources() |
| if !reflect.DeepEqual(actual, tc.Result) { |
| t.Fatalf("%d: %#v", i, actual) |
| } |
| } |
| } |
| |
| func TestProviderDataSources(t *testing.T) { |
| cases := []struct { |
| P *Provider |
| Result []terraform.DataSource |
| }{ |
| { |
| P: &Provider{}, |
| Result: []terraform.DataSource{}, |
| }, |
| |
| { |
| P: &Provider{ |
| DataSourcesMap: map[string]*Resource{ |
| "foo": nil, |
| "bar": nil, |
| }, |
| }, |
| Result: []terraform.DataSource{ |
| terraform.DataSource{Name: "bar", SchemaAvailable: true}, |
| terraform.DataSource{Name: "foo", SchemaAvailable: true}, |
| }, |
| }, |
| } |
| |
| for i, tc := range cases { |
| actual := tc.P.DataSources() |
| if !reflect.DeepEqual(actual, tc.Result) { |
| t.Fatalf("%d: got %#v; want %#v", i, actual, tc.Result) |
| } |
| } |
| } |
| |
| func TestProviderValidate(t *testing.T) { |
| cases := []struct { |
| P *Provider |
| Config map[string]interface{} |
| Err bool |
| }{ |
| { |
| P: &Provider{ |
| Schema: map[string]*Schema{ |
| "foo": &Schema{}, |
| }, |
| }, |
| Config: nil, |
| Err: true, |
| }, |
| } |
| |
| for i, tc := range cases { |
| c := terraform.NewResourceConfigRaw(tc.Config) |
| _, es := tc.P.Validate(c) |
| if len(es) > 0 != tc.Err { |
| t.Fatalf("%d: %#v", i, es) |
| } |
| } |
| } |
| |
| func TestProviderDiff_legacyTimeoutType(t *testing.T) { |
| p := &Provider{ |
| ResourcesMap: map[string]*Resource{ |
| "blah": &Resource{ |
| Schema: map[string]*Schema{ |
| "foo": { |
| Type: TypeInt, |
| Optional: true, |
| }, |
| }, |
| Timeouts: &ResourceTimeout{ |
| Create: DefaultTimeout(10 * time.Minute), |
| }, |
| }, |
| }, |
| } |
| |
| invalidCfg := map[string]interface{}{ |
| "foo": 42, |
| "timeouts": []interface{}{ |
| map[string]interface{}{ |
| "create": "40m", |
| }, |
| }, |
| } |
| ic := terraform.NewResourceConfigRaw(invalidCfg) |
| _, err := p.Diff( |
| &terraform.InstanceInfo{ |
| Type: "blah", |
| }, |
| nil, |
| ic, |
| ) |
| if err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| func TestProviderDiff_timeoutInvalidValue(t *testing.T) { |
| p := &Provider{ |
| ResourcesMap: map[string]*Resource{ |
| "blah": &Resource{ |
| Schema: map[string]*Schema{ |
| "foo": { |
| Type: TypeInt, |
| Optional: true, |
| }, |
| }, |
| Timeouts: &ResourceTimeout{ |
| Create: DefaultTimeout(10 * time.Minute), |
| }, |
| }, |
| }, |
| } |
| |
| invalidCfg := map[string]interface{}{ |
| "foo": 42, |
| "timeouts": map[string]interface{}{ |
| "create": "invalid", |
| }, |
| } |
| ic := terraform.NewResourceConfigRaw(invalidCfg) |
| _, err := p.Diff( |
| &terraform.InstanceInfo{ |
| Type: "blah", |
| }, |
| nil, |
| ic, |
| ) |
| if err == nil { |
| t.Fatal("Expected provider.Diff to fail with invalid timeout value") |
| } |
| expectedErrMsg := `time: invalid duration "invalid"` |
| if !strings.Contains(err.Error(), expectedErrMsg) { |
| t.Fatalf("Unexpected error message: %q\nExpected message to contain %q", |
| err.Error(), |
| expectedErrMsg) |
| } |
| } |
| |
| func TestProviderValidateResource(t *testing.T) { |
| cases := []struct { |
| P *Provider |
| Type string |
| Config map[string]interface{} |
| Err bool |
| }{ |
| { |
| P: &Provider{}, |
| Type: "foo", |
| Config: nil, |
| Err: true, |
| }, |
| |
| { |
| P: &Provider{ |
| ResourcesMap: map[string]*Resource{ |
| "foo": &Resource{}, |
| }, |
| }, |
| Type: "foo", |
| Config: nil, |
| Err: false, |
| }, |
| } |
| |
| for i, tc := range cases { |
| c := terraform.NewResourceConfigRaw(tc.Config) |
| _, es := tc.P.ValidateResource(tc.Type, c) |
| if len(es) > 0 != tc.Err { |
| t.Fatalf("%d: %#v", i, es) |
| } |
| } |
| } |
| |
| func TestProviderImportState_default(t *testing.T) { |
| p := &Provider{ |
| ResourcesMap: map[string]*Resource{ |
| "foo": &Resource{ |
| Importer: &ResourceImporter{}, |
| }, |
| }, |
| } |
| |
| states, err := p.ImportState(&terraform.InstanceInfo{ |
| Type: "foo", |
| }, "bar") |
| if err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| |
| if len(states) != 1 { |
| t.Fatalf("bad: %#v", states) |
| } |
| if states[0].ID != "bar" { |
| t.Fatalf("bad: %#v", states) |
| } |
| } |
| |
| func TestProviderImportState_setsId(t *testing.T) { |
| var val string |
| stateFunc := func(d *ResourceData, meta interface{}) ([]*ResourceData, error) { |
| val = d.Id() |
| return []*ResourceData{d}, nil |
| } |
| |
| p := &Provider{ |
| ResourcesMap: map[string]*Resource{ |
| "foo": &Resource{ |
| Importer: &ResourceImporter{ |
| State: stateFunc, |
| }, |
| }, |
| }, |
| } |
| |
| _, err := p.ImportState(&terraform.InstanceInfo{ |
| Type: "foo", |
| }, "bar") |
| if err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| |
| if val != "bar" { |
| t.Fatal("should set id") |
| } |
| } |
| |
| func TestProviderImportState_setsType(t *testing.T) { |
| var tVal string |
| stateFunc := func(d *ResourceData, meta interface{}) ([]*ResourceData, error) { |
| d.SetId("foo") |
| tVal = d.State().Ephemeral.Type |
| return []*ResourceData{d}, nil |
| } |
| |
| p := &Provider{ |
| ResourcesMap: map[string]*Resource{ |
| "foo": &Resource{ |
| Importer: &ResourceImporter{ |
| State: stateFunc, |
| }, |
| }, |
| }, |
| } |
| |
| _, err := p.ImportState(&terraform.InstanceInfo{ |
| Type: "foo", |
| }, "bar") |
| if err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| |
| if tVal != "foo" { |
| t.Fatal("should set type") |
| } |
| } |
| |
| func TestProviderMeta(t *testing.T) { |
| p := new(Provider) |
| if v := p.Meta(); v != nil { |
| t.Fatalf("bad: %#v", v) |
| } |
| |
| expected := 42 |
| p.SetMeta(42) |
| if v := p.Meta(); !reflect.DeepEqual(v, expected) { |
| t.Fatalf("bad: %#v", v) |
| } |
| } |
| |
| func TestProviderStop(t *testing.T) { |
| var p Provider |
| |
| if p.Stopped() { |
| t.Fatal("should not be stopped") |
| } |
| |
| // Verify stopch blocks |
| ch := p.StopContext().Done() |
| select { |
| case <-ch: |
| t.Fatal("should not be stopped") |
| case <-time.After(10 * time.Millisecond): |
| } |
| |
| // Stop it |
| if err := p.Stop(); err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| |
| // Verify |
| if !p.Stopped() { |
| t.Fatal("should be stopped") |
| } |
| |
| select { |
| case <-ch: |
| case <-time.After(10 * time.Millisecond): |
| t.Fatal("should be stopped") |
| } |
| } |
| |
| func TestProviderStop_stopFirst(t *testing.T) { |
| var p Provider |
| |
| // Stop it |
| if err := p.Stop(); err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| |
| // Verify |
| if !p.Stopped() { |
| t.Fatal("should be stopped") |
| } |
| |
| select { |
| case <-p.StopContext().Done(): |
| case <-time.After(10 * time.Millisecond): |
| t.Fatal("should be stopped") |
| } |
| } |
| |
| func TestProviderReset(t *testing.T) { |
| var p Provider |
| stopCtx := p.StopContext() |
| p.MetaReset = func() error { |
| stopCtx = p.StopContext() |
| return nil |
| } |
| |
| // cancel the current context |
| p.Stop() |
| |
| if err := p.TestReset(); err != nil { |
| t.Fatal(err) |
| } |
| |
| // the first context should have been replaced |
| if err := stopCtx.Err(); err != nil { |
| t.Fatal(err) |
| } |
| |
| // we should not get a canceled context here either |
| if err := p.StopContext().Err(); err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| func TestProvider_InternalValidate(t *testing.T) { |
| cases := []struct { |
| P *Provider |
| ExpectedErr error |
| }{ |
| { |
| P: &Provider{ |
| Schema: map[string]*Schema{ |
| "foo": { |
| Type: TypeBool, |
| Optional: true, |
| }, |
| }, |
| }, |
| ExpectedErr: nil, |
| }, |
| { // Reserved resource fields should be allowed in provider block |
| P: &Provider{ |
| Schema: map[string]*Schema{ |
| "provisioner": { |
| Type: TypeString, |
| Optional: true, |
| }, |
| "count": { |
| Type: TypeInt, |
| Optional: true, |
| }, |
| }, |
| }, |
| ExpectedErr: nil, |
| }, |
| { // Reserved provider fields should not be allowed |
| P: &Provider{ |
| Schema: map[string]*Schema{ |
| "alias": { |
| Type: TypeString, |
| Optional: true, |
| }, |
| }, |
| }, |
| ExpectedErr: fmt.Errorf("%s is a reserved field name for a provider", "alias"), |
| }, |
| } |
| |
| for i, tc := range cases { |
| err := tc.P.InternalValidate() |
| if tc.ExpectedErr == nil { |
| if err != nil { |
| t.Fatalf("%d: Error returned (expected no error): %s", i, err) |
| } |
| continue |
| } |
| if tc.ExpectedErr != nil && err == nil { |
| t.Fatalf("%d: Expected error (%s), but no error returned", i, tc.ExpectedErr) |
| } |
| if err.Error() != tc.ExpectedErr.Error() { |
| t.Fatalf("%d: Errors don't match. Expected: %#v Given: %#v", i, tc.ExpectedErr, err) |
| } |
| } |
| } |