diff --git a/client/api_client.go b/client/api_client.go index e503daab..a06e37b4 100644 --- a/client/api_client.go +++ b/client/api_client.go @@ -125,6 +125,11 @@ type ApiClientInterface interface { GpgKeyCreate(payload *GpgKeyCreatePayload) (*GpgKey, error) GpgKeyDelete(id string) error GpgKeys() ([]GpgKey, error) + ProviderCreate(payload ProviderCreatePayload) (*Provider, error) + Provider(providerId string) (*Provider, error) + ProviderDelete(providerId string) error + ProviderUpdate(providerId string, payload ProviderUpdatePayload) (*Provider, error) + Providers() ([]Provider, error) } func NewApiClient(client http.HttpClientInterface, defaultOrganizationId string) ApiClientInterface { diff --git a/client/api_client_mock.go b/client/api_client_mock.go index 8fc1c193..9b4ea710 100644 --- a/client/api_client_mock.go +++ b/client/api_client_mock.go @@ -1143,6 +1143,80 @@ func (mr *MockApiClientInterfaceMockRecorder) ProjectsAgentsAssignments() *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProjectsAgentsAssignments", reflect.TypeOf((*MockApiClientInterface)(nil).ProjectsAgentsAssignments)) } +// Provider mocks base method. +func (m *MockApiClientInterface) Provider(arg0 string) (*Provider, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Provider", arg0) + ret0, _ := ret[0].(*Provider) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Provider indicates an expected call of Provider. +func (mr *MockApiClientInterfaceMockRecorder) Provider(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Provider", reflect.TypeOf((*MockApiClientInterface)(nil).Provider), arg0) +} + +// ProviderCreate mocks base method. +func (m *MockApiClientInterface) ProviderCreate(arg0 ProviderCreatePayload) (*Provider, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ProviderCreate", arg0) + ret0, _ := ret[0].(*Provider) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ProviderCreate indicates an expected call of ProviderCreate. +func (mr *MockApiClientInterfaceMockRecorder) ProviderCreate(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProviderCreate", reflect.TypeOf((*MockApiClientInterface)(nil).ProviderCreate), arg0) +} + +// ProviderDelete mocks base method. +func (m *MockApiClientInterface) ProviderDelete(arg0 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ProviderDelete", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// ProviderDelete indicates an expected call of ProviderDelete. +func (mr *MockApiClientInterfaceMockRecorder) ProviderDelete(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProviderDelete", reflect.TypeOf((*MockApiClientInterface)(nil).ProviderDelete), arg0) +} + +// ProviderUpdate mocks base method. +func (m *MockApiClientInterface) ProviderUpdate(arg0 string, arg1 ProviderUpdatePayload) (*Provider, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ProviderUpdate", arg0, arg1) + ret0, _ := ret[0].(*Provider) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ProviderUpdate indicates an expected call of ProviderUpdate. +func (mr *MockApiClientInterfaceMockRecorder) ProviderUpdate(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProviderUpdate", reflect.TypeOf((*MockApiClientInterface)(nil).ProviderUpdate), arg0, arg1) +} + +// Providers mocks base method. +func (m *MockApiClientInterface) Providers() ([]Provider, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Providers") + ret0, _ := ret[0].([]Provider) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Providers indicates an expected call of Providers. +func (mr *MockApiClientInterfaceMockRecorder) Providers() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Providers", reflect.TypeOf((*MockApiClientInterface)(nil).Providers)) +} + // RemoteStateAccessConfiguration mocks base method. func (m *MockApiClientInterface) RemoteStateAccessConfiguration(arg0 string) (*RemoteStateAccessConfiguration, error) { m.ctrl.T.Helper() diff --git a/client/provider.go b/client/provider.go new file mode 100644 index 00000000..0372ff18 --- /dev/null +++ b/client/provider.go @@ -0,0 +1,74 @@ +package client + +type Provider struct { + Id string `json:"id"` + Type string `json:"type"` + Description string `json:"description"` +} + +type ProviderCreatePayload struct { + Type string `json:"type"` + Description string `json:"description"` +} + +type ProviderUpdatePayload struct { + Description string `json:"description"` +} + +func (client *ApiClient) ProviderCreate(payload ProviderCreatePayload) (*Provider, error) { + organizationId, err := client.OrganizationId() + if err != nil { + return nil, err + } + + payloadWithOrganizationId := struct { + ProviderCreatePayload + OrganizationId string `json:"organizationId"` + }{ + payload, + organizationId, + } + + var result Provider + if err := client.http.Post("/providers", payloadWithOrganizationId, &result); err != nil { + return nil, err + } + + return &result, nil +} + +func (client *ApiClient) Provider(providerId string) (*Provider, error) { + var result Provider + if err := client.http.Get("/providers/"+providerId, nil, &result); err != nil { + return nil, err + } + + return &result, nil +} + +func (client *ApiClient) ProviderDelete(providerId string) error { + return client.http.Delete("/providers/" + providerId) +} + +func (client *ApiClient) ProviderUpdate(providerId string, payload ProviderUpdatePayload) (*Provider, error) { + var result Provider + if err := client.http.Put("/providers/"+providerId, payload, &result); err != nil { + return nil, err + } + + return &result, nil +} + +func (client *ApiClient) Providers() ([]Provider, error) { + organizationId, err := client.OrganizationId() + if err != nil { + return nil, err + } + + var result []Provider + if err := client.http.Get("/providers", map[string]string{"organizationId": organizationId}, &result); err != nil { + return nil, err + } + + return result, err +} diff --git a/client/provider_test.go b/client/provider_test.go new file mode 100644 index 00000000..c1393586 --- /dev/null +++ b/client/provider_test.go @@ -0,0 +1,115 @@ +package client_test + +import ( + . "github.com/env0/terraform-provider-env0/client" + "github.com/golang/mock/gomock" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Provider Client", func() { + mockProvider := Provider{ + Id: "id", + Type: "type", + Description: "description", + } + + Describe("Get Provider", func() { + var returnedProvider *Provider + + BeforeEach(func() { + httpCall = mockHttpClient.EXPECT(). + Get("/providers/"+mockProvider.Id, gomock.Nil(), gomock.Any()). + Do(func(path string, request interface{}, response *Provider) { + *response = mockProvider + }).Times(1) + returnedProvider, _ = apiClient.Provider(mockProvider.Id) + }) + + It("Should return provider", func() { + Expect(*returnedProvider).To(Equal(mockProvider)) + }) + }) + + Describe("Get All Providers", func() { + var returnedProviders []Provider + mockProviders := []Provider{mockProvider} + + BeforeEach(func() { + mockOrganizationIdCall(organizationId).Times(1) + httpCall = mockHttpClient.EXPECT(). + Get("/providers", map[string]string{"organizationId": organizationId}, gomock.Any()). + Do(func(path string, request interface{}, response *[]Provider) { + *response = mockProviders + }).Times(1) + returnedProviders, _ = apiClient.Providers() + }) + + It("Should return providers", func() { + Expect(returnedProviders).To(Equal(mockProviders)) + }) + }) + + Describe("Create Provider", func() { + var createdProvider *Provider + + BeforeEach(func() { + mockOrganizationIdCall(organizationId).Times(1) + + createProviderPayload := ProviderCreatePayload{ + Type: mockProvider.Type, + Description: mockProvider.Description, + } + + expectedCreateRequest := struct { + ProviderCreatePayload + OrganizationId string `json:"organizationId"` + }{ + createProviderPayload, + organizationId, + } + + httpCall = mockHttpClient.EXPECT(). + Post("/providers", expectedCreateRequest, gomock.Any()). + Do(func(path string, request interface{}, response *Provider) { + *response = mockProvider + }).Times(1) + + createdProvider, _ = apiClient.ProviderCreate(createProviderPayload) + }) + It("Should return created provider", func() { + Expect(*createdProvider).To(Equal(mockProvider)) + }) + }) + + Describe("Delete Provider", func() { + BeforeEach(func() { + httpCall = mockHttpClient.EXPECT().Delete("/providers/" + mockProvider.Id).Times(1) + apiClient.ProviderDelete(mockProvider.Id) + }) + + It("Should send DELETE request with provider id", func() {}) + }) + + Describe("Update Provider", func() { + var updatedProvider *Provider + + updatedMockProvider := mockProvider + updatedMockProvider.Description = "new-description" + + BeforeEach(func() { + updateProviderPayload := ProviderUpdatePayload{Description: updatedMockProvider.Description} + httpCall = mockHttpClient.EXPECT(). + Put("/providers/"+mockProvider.Id, updateProviderPayload, gomock.Any()). + Do(func(path string, request interface{}, response *Provider) { + *response = updatedMockProvider + }).Times(1) + + updatedProvider, _ = apiClient.ProviderUpdate(mockProvider.Id, updateProviderPayload) + }) + + It("Should return updated provider received from API", func() { + Expect(*updatedProvider).To(Equal(updatedMockProvider)) + }) + }) +}) diff --git a/env0/data_provider.go b/env0/data_provider.go new file mode 100644 index 00000000..8c83ec6c --- /dev/null +++ b/env0/data_provider.go @@ -0,0 +1,58 @@ +package env0 + +import ( + "context" + + "github.com/env0/terraform-provider-env0/client" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataProvider() *schema.Resource { + return &schema.Resource{ + ReadContext: dataProviderRead, + + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Description: "the type/name of the provider", + Optional: true, + ExactlyOneOf: []string{"type", "id"}, + }, + "id": { + Type: schema.TypeString, + Description: "id of the provider", + Optional: true, + ExactlyOneOf: []string{"type", "id"}, + }, + "description": { + Type: schema.TypeString, + Description: "the description of the provider", + Computed: true, + }, + }, + } +} + +func dataProviderRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var provider *client.Provider + var err error + + id, ok := d.GetOk("id") + if ok { + provider, err = meta.(client.ApiClientInterface).Provider(id.(string)) + } else { + name := d.Get("type").(string) + provider, err = getProviderByName(name, meta) + } + + if err != nil { + return DataGetFailure("provider", id, err) + } + + if err := writeResourceData(provider, d); err != nil { + return diag.Errorf("schema resource data serialization failed: %v", err) + } + + return nil +} diff --git a/env0/data_provider_test.go b/env0/data_provider_test.go new file mode 100644 index 00000000..00557333 --- /dev/null +++ b/env0/data_provider_test.go @@ -0,0 +1,99 @@ +package env0 + +import ( + "regexp" + "testing" + + "github.com/env0/terraform-provider-env0/client" + "github.com/env0/terraform-provider-env0/client/http" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestProviderDataSource(t *testing.T) { + provider := client.Provider{ + Id: "id0", + Type: "type0", + Description: "des0", + } + + otherProvider := client.Provider{ + Id: "id1", + Type: "type1", + Description: "des1", + } + + providerFieldByName := map[string]interface{}{"type": provider.Type} + providerFieldById := map[string]interface{}{"id": provider.Id} + + resourceType := "env0_provider" + resourceName := "test_provider" + accessor := dataSourceAccessor(resourceType, resourceName) + + getValidTestCase := func(input map[string]interface{}) resource.TestCase { + return resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: dataSourceConfigCreate(resourceType, resourceName, input), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(accessor, "id", provider.Id), + resource.TestCheckResourceAttr(accessor, "type", provider.Type), + resource.TestCheckResourceAttr(accessor, "description", provider.Description), + ), + }, + }, + } + } + + getErrorTestCase := func(input map[string]interface{}, expectedError string) resource.TestCase { + return resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: dataSourceConfigCreate(resourceType, resourceName, input), + ExpectError: regexp.MustCompile(expectedError), + }, + }, + } + } + + mockListProvidersCall := func(returnValue []client.Provider) func(mockFunc *client.MockApiClientInterface) { + return func(mock *client.MockApiClientInterface) { + mock.EXPECT().Providers().AnyTimes().Return(returnValue, nil) + } + } + + mockProviderCall := func(returnValue *client.Provider) func(mockFunc *client.MockApiClientInterface) { + return func(mock *client.MockApiClientInterface) { + mock.EXPECT().Provider(provider.Id).AnyTimes().Return(returnValue, nil) + } + } + + t.Run("By ID", func(t *testing.T) { + runUnitTest(t, + getValidTestCase(providerFieldById), + mockProviderCall(&provider), + ) + }) + + t.Run("By Name", func(t *testing.T) { + runUnitTest(t, + getValidTestCase(providerFieldByName), + mockListProvidersCall([]client.Provider{otherProvider, provider}), + ) + }) + + t.Run("Throw error when by name and more than one provider exists", func(t *testing.T) { + runUnitTest(t, + getErrorTestCase(providerFieldByName, "found multiple providers"), + mockListProvidersCall([]client.Provider{provider, otherProvider, provider}), + ) + }) + + t.Run("Throw error when by id and no provider found with that id", func(t *testing.T) { + runUnitTest(t, + getErrorTestCase(providerFieldById, "could not read provider: id "+provider.Id+" not found"), + func(mock *client.MockApiClientInterface) { + mock.EXPECT().Provider(provider.Id).Times(1).Return(nil, http.NewMockFailedResponseError(404)) + }, + ) + }) +} diff --git a/env0/provider.go b/env0/provider.go index 007a6d72..0cd6d6c9 100644 --- a/env0/provider.go +++ b/env0/provider.go @@ -90,6 +90,7 @@ func Provider(version string) plugin.ProviderFunc { "env0_custom_role": dataCustomRole(), "env0_custom_roles": dataCustomRoles(), "env0_gpg_key": dataGpgKey(), + "env0_provider": dataProvider(), }, ResourcesMap: map[string]*schema.Resource{ "env0_project": resourceProject(), @@ -128,6 +129,7 @@ func Provider(version string) plugin.ProviderFunc { "env0_custom_flow_assignment": resourceCustomFlowAssignment(), "env0_environment_state_access": resourceEnvironmentStateAccess(), "env0_gpg_key": resourceGpgKey(), + "env0_provider": resourceProvider(), }, } diff --git a/env0/resource_provider.go b/env0/resource_provider.go new file mode 100644 index 00000000..ec642cd8 --- /dev/null +++ b/env0/resource_provider.go @@ -0,0 +1,146 @@ +package env0 + +import ( + "context" + "fmt" + "log" + + "github.com/env0/terraform-provider-env0/client" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceProvider() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceProviderCreate, + ReadContext: resourceProviderRead, + UpdateContext: resourceProviderUpdate, + DeleteContext: resourceProviderDelete, + + Importer: &schema.ResourceImporter{StateContext: resourceProviderImport}, + + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Description: `type of the provider (Match pattern: ^[0-9a-zA-Z](?:[0-9a-zA-Z-]{0,30}[0-9a-zA-Z])?$). Your provider’s type is essentially it’s name, and should match your provider’s files. For example, if your binaries look like terraform-provider-aws_1.1.1_linux_amd64.zip, than your provider’s type should be aws.`, + Required: true, + ForceNew: true, + ValidateDiagFunc: NewRegexValidator(`^[0-9a-zA-Z](?:[0-9a-zA-Z-]{0,30}[0-9a-zA-Z])?$`), + }, + "description": { + Type: schema.TypeString, + Description: "description of the provider", + Optional: true, + }, + }, + } +} + +func resourceProviderCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(client.ApiClientInterface) + + var payload client.ProviderCreatePayload + if err := readResourceData(&payload, d); err != nil { + return diag.Errorf("schema resource data deserialization failed: %v", err) + } + + provider, err := apiClient.ProviderCreate(payload) + if err != nil { + return diag.Errorf("could not create provider: %v", err) + } + + d.SetId(provider.Id) + + return nil +} + +func resourceProviderRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(client.ApiClientInterface) + + provider, err := apiClient.Provider(d.Id()) + if err != nil { + return ResourceGetFailure("provider", d, err) + } + + if err := writeResourceData(provider, d); err != nil { + return diag.Errorf("schema resource data serialization failed: %v", err) + } + + return nil +} + +func resourceProviderUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(client.ApiClientInterface) + + var payload client.ProviderUpdatePayload + if err := readResourceData(&payload, d); err != nil { + return diag.Errorf("schema resource data deserialization failed: %v", err) + } + + if _, err := apiClient.ProviderUpdate(d.Id(), payload); err != nil { + return diag.Errorf("could not update provider: %v", err) + } + + return nil +} + +func resourceProviderDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(client.ApiClientInterface) + + if err := apiClient.ProviderDelete(d.Id()); err != nil { + return diag.Errorf("could not delete provider: %v", err) + } + + return nil +} + +func getProviderByName(name string, meta interface{}) (*client.Provider, error) { + apiClient := meta.(client.ApiClientInterface) + + providers, err := apiClient.Providers() + if err != nil { + return nil, err + } + + var foundProviders []client.Provider + for _, provider := range providers { + if provider.Type == name { + foundProviders = append(foundProviders, provider) + } + } + + if len(foundProviders) == 0 { + return nil, fmt.Errorf("provider with type %v not found", name) + } + + if len(foundProviders) > 1 { + return nil, fmt.Errorf("found multiple providers with name/type: %s. Use id instead or make sure provider names are unique %v", name, foundProviders) + } + + return &foundProviders[0], nil +} + +func getProvider(idOrName string, meta interface{}) (*client.Provider, error) { + _, err := uuid.Parse(idOrName) + if err == nil { + log.Println("[INFO] Resolving provider by id: ", idOrName) + return meta.(client.ApiClientInterface).Provider(idOrName) + } else { + log.Println("[INFO] Resolving provider by name: ", idOrName) + return getProviderByName(idOrName, meta) + } +} + +func resourceProviderImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + provider, err := getProvider(d.Id(), meta) + if err != nil { + return nil, err + } + + if err := writeResourceData(provider, d); err != nil { + return nil, fmt.Errorf("schema resource data serialization failed: %v", err) + } + + return []*schema.ResourceData{d}, nil +} diff --git a/env0/resource_provider_test.go b/env0/resource_provider_test.go new file mode 100644 index 00000000..407df8a0 --- /dev/null +++ b/env0/resource_provider_test.go @@ -0,0 +1,240 @@ +package env0 + +import ( + "errors" + "regexp" + "testing" + + "github.com/env0/terraform-provider-env0/client" + "github.com/golang/mock/gomock" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestUnitProviderResource(t *testing.T) { + resourceType := "env0_provider" + resourceName := "test" + resourceNameImport := resourceType + "." + resourceName + accessor := resourceAccessor(resourceType, resourceName) + + provider := client.Provider{ + Id: uuid.NewString(), + Type: "aws", + Description: "des", + } + + updatedProvider := client.Provider{ + Id: provider.Id, + Type: provider.Type, + Description: "des-updated", + } + + t.Run("Success", func(t *testing.T) { + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "type": provider.Type, + "description": provider.Description, + }), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(accessor, "id", provider.Id), + resource.TestCheckResourceAttr(accessor, "type", provider.Type), + resource.TestCheckResourceAttr(accessor, "description", provider.Description), + ), + }, + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "type": updatedProvider.Type, + "description": updatedProvider.Description, + }), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(accessor, "id", updatedProvider.Id), + resource.TestCheckResourceAttr(accessor, "type", updatedProvider.Type), + resource.TestCheckResourceAttr(accessor, "description", updatedProvider.Description), + ), + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { + gomock.InOrder( + mock.EXPECT().ProviderCreate(client.ProviderCreatePayload{ + Type: provider.Type, + Description: provider.Description, + }).Times(1).Return(&provider, nil), + mock.EXPECT().Provider(provider.Id).Times(2).Return(&provider, nil), + mock.EXPECT().ProviderUpdate(updatedProvider.Id, client.ProviderUpdatePayload{ + Description: updatedProvider.Description, + }).Times(1).Return(&updatedProvider, nil), + mock.EXPECT().Provider(updatedProvider.Id).Times(1).Return(&updatedProvider, nil), + mock.EXPECT().ProviderDelete(updatedProvider.Id).Times(1), + ) + }) + }) + + t.Run("Create Failure", func(t *testing.T) { + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "type": provider.Type, + "description": provider.Description, + }), + ExpectError: regexp.MustCompile("could not create provider: error"), + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { + mock.EXPECT().ProviderCreate(client.ProviderCreatePayload{ + Type: provider.Type, + Description: provider.Description, + }).Times(1).Return(nil, errors.New("error")) + }) + }) + + t.Run("Create Failure - Invalid Type", func(t *testing.T) { + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "type": "-" + provider.Type, + "description": provider.Description, + }), + ExpectError: regexp.MustCompile("must match pattern"), + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) {}) + }) + + t.Run("Update Failure", func(t *testing.T) { + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "type": provider.Type, + "description": provider.Description, + }), + }, + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "type": updatedProvider.Type, + "description": updatedProvider.Description, + }), + ExpectError: regexp.MustCompile("could not update provider: error"), + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { + gomock.InOrder( + mock.EXPECT().ProviderCreate(client.ProviderCreatePayload{ + Type: provider.Type, + Description: provider.Description, + }).Times(1).Return(&provider, nil), + mock.EXPECT().Provider(provider.Id).Times(2).Return(&provider, nil), + mock.EXPECT().ProviderUpdate(updatedProvider.Id, client.ProviderUpdatePayload{ + Description: updatedProvider.Description, + }).Times(1).Return(nil, errors.New("error")), + mock.EXPECT().ProviderDelete(updatedProvider.Id).Times(1), + ) + }) + }) + + t.Run("Import By Name", func(t *testing.T) { + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "type": provider.Type, + "description": provider.Description, + }), + }, + { + ResourceName: resourceNameImport, + ImportState: true, + ImportStateId: provider.Type, + ImportStateVerify: true, + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { + gomock.InOrder( + mock.EXPECT().ProviderCreate(client.ProviderCreatePayload{ + Type: provider.Type, + Description: provider.Description, + }).Times(1).Return(&provider, nil), + mock.EXPECT().Provider(provider.Id).Times(1).Return(&provider, nil), + mock.EXPECT().Providers().Times(1).Return([]client.Provider{provider}, nil), + mock.EXPECT().Provider(provider.Id).Times(1).Return(&provider, nil), + mock.EXPECT().ProviderDelete(provider.Id).Times(1), + ) + }) + }) + + t.Run("Import By Id", func(t *testing.T) { + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "type": provider.Type, + "description": provider.Description, + }), + }, + { + ResourceName: resourceNameImport, + ImportState: true, + ImportStateId: provider.Id, + ImportStateVerify: true, + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { + gomock.InOrder( + mock.EXPECT().ProviderCreate(client.ProviderCreatePayload{ + Type: provider.Type, + Description: provider.Description, + }).Times(1).Return(&provider, nil), + mock.EXPECT().Provider(provider.Id).Times(3).Return(&provider, nil), + mock.EXPECT().ProviderDelete(provider.Id).Times(1), + ) + }) + }) + + t.Run("Import By Id Not Found", func(t *testing.T) { + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "type": provider.Type, + "description": provider.Description, + }), + }, + { + ResourceName: resourceNameImport, + ImportState: true, + ImportStateId: provider.Id, + ImportStateVerify: true, + ExpectError: regexp.MustCompile("not found"), + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { + gomock.InOrder( + mock.EXPECT().ProviderCreate(client.ProviderCreatePayload{ + Type: provider.Type, + Description: provider.Description, + }).Times(1).Return(&provider, nil), + mock.EXPECT().Provider(provider.Id).Times(1).Return(&provider, nil), + mock.EXPECT().Provider(provider.Id).Times(1).Return(nil, &client.NotFoundError{}), + mock.EXPECT().ProviderDelete(provider.Id).Times(1), + ) + }) + }) +} diff --git a/examples/resources/env0_gpg_key/import.sh b/examples/resources/env0_gpg_key/import.sh index c779b228..aeb45022 100644 --- a/examples/resources/env0_gpg_key/import.sh +++ b/examples/resources/env0_gpg_key/import.sh @@ -1,2 +1,2 @@ terraform import env0_gpg_key.by_id ddda7b30-6789-4d24-937c-22322754934e -terraform import env0_gpg_key.by_name gpg-key-name" +terraform import env0_gpg_key.by_name gpg-key-name diff --git a/examples/resources/env0_provider/import.sh b/examples/resources/env0_provider/import.sh new file mode 100644 index 00000000..37ed25c0 --- /dev/null +++ b/examples/resources/env0_provider/import.sh @@ -0,0 +1,2 @@ +terraform import env0_provider.by_id ddda7b30-6789-4d24-937c-22322754934e +terraform import env0_provider.by_type aws diff --git a/examples/resources/env0_provider/resource.tf b/examples/resources/env0_provider/resource.tf new file mode 100644 index 00000000..813c0401 --- /dev/null +++ b/examples/resources/env0_provider/resource.tf @@ -0,0 +1,4 @@ +resource "env0_provider" "example" { + type = "aws-key-example" + description = "description example" +} diff --git a/tests/integration/029_provider/conf.tf b/tests/integration/029_provider/conf.tf new file mode 100644 index 00000000..8d6d2954 --- /dev/null +++ b/tests/integration/029_provider/conf.tf @@ -0,0 +1,15 @@ +terraform { + backend "local" { + } + required_providers { + env0 = { + source = "terraform-registry.env0.com/env0/env0" + } + } +} + +provider "env0" {} + +variable "second_run" { + default = false +} diff --git a/tests/integration/029_provider/expected_outputs.json b/tests/integration/029_provider/expected_outputs.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/tests/integration/029_provider/expected_outputs.json @@ -0,0 +1 @@ +{} diff --git a/tests/integration/029_provider/main.tf b/tests/integration/029_provider/main.tf new file mode 100644 index 00000000..83556adc --- /dev/null +++ b/tests/integration/029_provider/main.tf @@ -0,0 +1,21 @@ +provider "random" {} + +resource "random_string" "random" { + length = 5 + special = false + min_lower = 5 +} + +resource "env0_provider" "test_provider" { + type = "aws-${random_string.random.result}" + description = var.second_run ? "des1" : "des2" +} + +data "env0_provider" "test_provider_data" { + type = env0_provider.test_provider.type +} + +resource "env0_provider" "test_provider-type-change" { + type = var.second_run ? "aws2-${random_string.random.result}" : "aws1-${random_string.random.result}" + description = var.second_run ? "des1" : "des2" +}