From ac7953bc9659fda932ee3ef6312d1b657578ff32 Mon Sep 17 00:00:00 2001 From: Tomer Heber Date: Mon, 8 May 2023 07:13:03 -0500 Subject: [PATCH] Feat: support gpg key resources (#642) * Feat: support gpg key resources * fix integration test * some minor changes based on PR comments * added additional fields to the response struct --- client/api_client.go | 3 + client/api_client_mock.go | 44 ++++ client/gpg_key.go | 56 +++++ client/gpg_key_test.go | 98 +++++++++ env0/data_gpg_key.go | 64 ++++++ env0/data_gpg_key_test.go | 93 ++++++++ env0/errors.go | 4 +- env0/provider.go | 2 + env0/resource_gpg_key.go | 152 +++++++++++++ env0/resource_gpg_key_test.go | 199 ++++++++++++++++++ examples/resources/env0_gpg_key/import.sh | 2 + examples/resources/env0_gpg_key/resource.tf | 5 + tests/integration/028_gpg_key/conf.tf | 15 ++ .../028_gpg_key/expected_outputs.json | 1 + tests/integration/028_gpg_key/main.tf | 27 +++ 15 files changed, 763 insertions(+), 2 deletions(-) create mode 100644 client/gpg_key.go create mode 100644 client/gpg_key_test.go create mode 100644 env0/data_gpg_key.go create mode 100644 env0/data_gpg_key_test.go create mode 100644 env0/resource_gpg_key.go create mode 100644 env0/resource_gpg_key_test.go create mode 100644 examples/resources/env0_gpg_key/import.sh create mode 100644 examples/resources/env0_gpg_key/resource.tf create mode 100644 tests/integration/028_gpg_key/conf.tf create mode 100644 tests/integration/028_gpg_key/expected_outputs.json create mode 100644 tests/integration/028_gpg_key/main.tf diff --git a/client/api_client.go b/client/api_client.go index 636aa640..5e978859 100644 --- a/client/api_client.go +++ b/client/api_client.go @@ -121,6 +121,9 @@ type ApiClientInterface interface { RemoteStateAccessConfiguration(environmentId string) (*RemoteStateAccessConfiguration, error) RemoteStateAccessConfigurationCreate(environmentId string, payload RemoteStateAccessConfigurationCreate) (*RemoteStateAccessConfiguration, error) RemoteStateAccessConfigurationDelete(environmentId string) error + GpgKeyCreate(payload *GpgKeyCreatePayload) (*GpgKey, error) + GpgKeyDelete(id string) error + GpgKeys() ([]GpgKey, error) } func NewApiClient(client http.HttpClientInterface, defaultOrganizationId string) ApiClientInterface { diff --git a/client/api_client_mock.go b/client/api_client_mock.go index c260b223..802dd253 100644 --- a/client/api_client_mock.go +++ b/client/api_client_mock.go @@ -729,6 +729,50 @@ func (mr *MockApiClientInterfaceMockRecorder) GitTokens() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitTokens", reflect.TypeOf((*MockApiClientInterface)(nil).GitTokens)) } +// GpgKeyCreate mocks base method. +func (m *MockApiClientInterface) GpgKeyCreate(arg0 *GpgKeyCreatePayload) (*GpgKey, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GpgKeyCreate", arg0) + ret0, _ := ret[0].(*GpgKey) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GpgKeyCreate indicates an expected call of GpgKeyCreate. +func (mr *MockApiClientInterfaceMockRecorder) GpgKeyCreate(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GpgKeyCreate", reflect.TypeOf((*MockApiClientInterface)(nil).GpgKeyCreate), arg0) +} + +// GpgKeyDelete mocks base method. +func (m *MockApiClientInterface) GpgKeyDelete(arg0 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GpgKeyDelete", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// GpgKeyDelete indicates an expected call of GpgKeyDelete. +func (mr *MockApiClientInterfaceMockRecorder) GpgKeyDelete(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GpgKeyDelete", reflect.TypeOf((*MockApiClientInterface)(nil).GpgKeyDelete), arg0) +} + +// GpgKeys mocks base method. +func (m *MockApiClientInterface) GpgKeys() ([]GpgKey, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GpgKeys") + ret0, _ := ret[0].([]GpgKey) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GpgKeys indicates an expected call of GpgKeys. +func (mr *MockApiClientInterfaceMockRecorder) GpgKeys() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GpgKeys", reflect.TypeOf((*MockApiClientInterface)(nil).GpgKeys)) +} + // Module mocks base method. func (m *MockApiClientInterface) Module(arg0 string) (*Module, error) { m.ctrl.T.Helper() diff --git a/client/gpg_key.go b/client/gpg_key.go new file mode 100644 index 00000000..64e3a9df --- /dev/null +++ b/client/gpg_key.go @@ -0,0 +1,56 @@ +package client + +type GpgKey struct { + Id string `json:"id"` + Name string `json:"name"` + OrganizationId string `json:"organizationId"` + KeyId string `json:"keyId"` + Content string `json:"content"` + CreatedBy string `json:"createdBy"` +} + +type GpgKeyCreatePayload struct { + Name string `json:"name"` + KeyId string `json:"keyId"` + Content string `json:"content"` +} + +func (client *ApiClient) GpgKeyCreate(payload *GpgKeyCreatePayload) (*GpgKey, error) { + organizationId, err := client.OrganizationId() + if err != nil { + return nil, err + } + + payloadWithOrganzationId := struct { + OrganizationId string `json:"organizationId"` + GpgKeyCreatePayload + }{ + organizationId, + *payload, + } + + var result GpgKey + if err := client.http.Post("/gpg-keys", payloadWithOrganzationId, &result); err != nil { + return nil, err + } + + return &result, nil +} + +func (client *ApiClient) GpgKeyDelete(id string) error { + return client.http.Delete("/gpg-keys/" + id) +} + +func (client *ApiClient) GpgKeys() ([]GpgKey, error) { + organizationId, err := client.OrganizationId() + if err != nil { + return nil, err + } + + var result []GpgKey + if err := client.http.Get("/gpg-keys", map[string]string{"organizationId": organizationId}, &result); err != nil { + return nil, err + } + + return result, err +} diff --git a/client/gpg_key_test.go b/client/gpg_key_test.go new file mode 100644 index 00000000..fb71950b --- /dev/null +++ b/client/gpg_key_test.go @@ -0,0 +1,98 @@ +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("Gpg Token Client", func() { + mockGpgKey := GpgKey{ + Id: "id", + Name: "name", + KeyId: "keyId", + Content: "content", + } + + Describe("Get All Gpg Keys", func() { + var returnedGpgKeys []GpgKey + mockGpgKeys := []GpgKey{mockGpgKey} + + BeforeEach(func() { + mockOrganizationIdCall(organizationId) + mockHttpClient.EXPECT(). + Get("/gpg-keys", map[string]string{"organizationId": organizationId}, gomock.Any()). + Do(func(path string, request interface{}, response *[]GpgKey) { + *response = mockGpgKeys + }) + returnedGpgKeys, _ = apiClient.GpgKeys() + }) + + It("Should get organization id", func() { + organizationIdCall.Times(1) + }) + + It("Should return GpgKeys", func() { + Expect(returnedGpgKeys).To(Equal(mockGpgKeys)) + }) + }) + + Describe("Delete Gpg Key", func() { + var err error + + BeforeEach(func() { + mockHttpClient.EXPECT().Delete("/gpg-keys/" + mockGpgKey.Id) + err = apiClient.GpgKeyDelete(mockGpgKey.Id) + }) + + It("Should not return error", func() { + Expect(err).To(BeNil()) + }) + }) + + Describe("Create GpgKey", func() { + var createdGpgKey *GpgKey + var err error + + BeforeEach(func() { + mockOrganizationIdCall(organizationId) + + payload := struct { + OrganizationId string `json:"organizationId"` + GpgKeyCreatePayload + }{ + organizationId, + GpgKeyCreatePayload{ + Name: mockGpgKey.Name, + KeyId: mockGpgKey.KeyId, + Content: mockGpgKey.Content, + }, + } + + httpCall = mockHttpClient.EXPECT(). + Post("/gpg-keys", payload, gomock.Any()). + Do(func(path string, request interface{}, response *GpgKey) { + *response = mockGpgKey + }) + + createdGpgKey, err = apiClient.GpgKeyCreate(&GpgKeyCreatePayload{ + Name: mockGpgKey.Name, + KeyId: mockGpgKey.KeyId, + Content: mockGpgKey.Content, + }) + }) + + It("Should get organization id", func() { + organizationIdCall.Times(1) + }) + + It("Should not return error", func() { + Expect(err).To(BeNil()) + }) + + It("Should return created GpgToken", func() { + Expect(*createdGpgKey).To(Equal(mockGpgKey)) + }) + }) +}) diff --git a/env0/data_gpg_key.go b/env0/data_gpg_key.go new file mode 100644 index 00000000..3e8d6edf --- /dev/null +++ b/env0/data_gpg_key.go @@ -0,0 +1,64 @@ +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 dataGpgKey() *schema.Resource { + return &schema.Resource{ + ReadContext: dataGpgKeyRead, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "the name of the api key", + Optional: true, + ExactlyOneOf: []string{"name", "id"}, + }, + "id": { + Type: schema.TypeString, + Description: "the id of the api key", + Optional: true, + ExactlyOneOf: []string{"name", "id"}, + }, + "content": { + Type: schema.TypeString, + Description: "the gpg public key block", + Computed: true, + }, + "key_id": { + Type: schema.TypeString, + Description: "the gpg key id", + Computed: true, + }, + }, + } +} + +func dataGpgKeyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var gpgKey *client.GpgKey + var err error + + id, ok := d.GetOk("id") + if ok { + gpgKey, err = getGpgKeyById(id.(string), meta) + if err != nil { + return diag.Errorf("could not read gpg key: %v", err) + } + } else { + gpgKey, err = getGpgKeyByName(d.Get("name").(string), meta) + if err != nil { + return diag.Errorf("could not read api key: %v", err) + } + } + + if err := writeResourceData(gpgKey, d); err != nil { + return diag.Errorf("schema resource data serialization failed: %v", err) + } + + return nil +} diff --git a/env0/data_gpg_key_test.go b/env0/data_gpg_key_test.go new file mode 100644 index 00000000..946ea4dc --- /dev/null +++ b/env0/data_gpg_key_test.go @@ -0,0 +1,93 @@ +package env0 + +import ( + "regexp" + "testing" + + "github.com/env0/terraform-provider-env0/client" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestGpgKeyDataSource(t *testing.T) { + gpgKey := client.GpgKey{ + Id: "id0", + Name: "name0", + KeyId: "ABCDABCDABCD1113", + Content: "content1", + } + + otherGpgKey := client.GpgKey{ + Id: "id1", + Name: "name1", + KeyId: "ABCDABCDABCD1112", + Content: "content2", + } + + gpgKeyFieldsByName := map[string]interface{}{"name": gpgKey.Name} + gpgKeyFieldsById := map[string]interface{}{"id": gpgKey.Id} + + resourceType := "env0_gpg_key" + resourceName := "test_gpg_key" + 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", gpgKey.Id), + resource.TestCheckResourceAttr(accessor, "name", gpgKey.Name), + resource.TestCheckResourceAttr(accessor, "key_id", gpgKey.KeyId), + resource.TestCheckResourceAttr(accessor, "content", gpgKey.Content), + ), + }, + }, + } + } + + 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), + }, + }, + } + } + + mockListGpgKeysCall := func(returnValue []client.GpgKey) func(mockFunc *client.MockApiClientInterface) { + return func(mock *client.MockApiClientInterface) { + mock.EXPECT().GpgKeys().AnyTimes().Return(returnValue, nil) + } + } + + t.Run("By ID", func(t *testing.T) { + runUnitTest(t, + getValidTestCase(gpgKeyFieldsById), + mockListGpgKeysCall([]client.GpgKey{gpgKey, otherGpgKey}), + ) + }) + + t.Run("By Name", func(t *testing.T) { + runUnitTest(t, + getValidTestCase(gpgKeyFieldsByName), + mockListGpgKeysCall([]client.GpgKey{gpgKey, otherGpgKey}), + ) + }) + + t.Run("Throw error when by name and more than one gpg key exists", func(t *testing.T) { + runUnitTest(t, + getErrorTestCase(gpgKeyFieldsByName, "found multiple gpg keys"), + mockListGpgKeysCall([]client.GpgKey{gpgKey, otherGpgKey, gpgKey}), + ) + }) + + t.Run("Throw error when by id and no gpg key found with that id", func(t *testing.T) { + runUnitTest(t, + getErrorTestCase(gpgKeyFieldsById, "could not read gpg key: not found"), + mockListGpgKeysCall([]client.GpgKey{otherGpgKey}), + ) + }) +} diff --git a/env0/errors.go b/env0/errors.go index 0b8aa1f2..9638afda 100644 --- a/env0/errors.go +++ b/env0/errors.go @@ -9,7 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func driftDetected(d *schema.ResourceData, err error) bool { +func driftDetected(err error) bool { if frerr, ok := err.(*http.FailedResponseError); ok && frerr.NotFound() { return true } @@ -22,7 +22,7 @@ func driftDetected(d *schema.ResourceData, err error) bool { } func ResourceGetFailure(resourceName string, d *schema.ResourceData, err error) diag.Diagnostics { - if driftDetected(d, err) { + if driftDetected(err) { log.Printf("[WARN] Drift Detected: Terraform will remove %s from state", d.Id()) d.SetId("") return nil diff --git a/env0/provider.go b/env0/provider.go index 4c4559cc..007a6d72 100644 --- a/env0/provider.go +++ b/env0/provider.go @@ -89,6 +89,7 @@ func Provider(version string) plugin.ProviderFunc { "env0_agent_values": dataAgentValues(), "env0_custom_role": dataCustomRole(), "env0_custom_roles": dataCustomRoles(), + "env0_gpg_key": dataGpgKey(), }, ResourcesMap: map[string]*schema.Resource{ "env0_project": resourceProject(), @@ -126,6 +127,7 @@ func Provider(version string) plugin.ProviderFunc { "env0_custom_flow": resourceCustomFlow(), "env0_custom_flow_assignment": resourceCustomFlowAssignment(), "env0_environment_state_access": resourceEnvironmentStateAccess(), + "env0_gpg_key": resourceGpgKey(), }, } diff --git a/env0/resource_gpg_key.go b/env0/resource_gpg_key.go new file mode 100644 index 00000000..1ea43186 --- /dev/null +++ b/env0/resource_gpg_key.go @@ -0,0 +1,152 @@ +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 resourceGpgKey() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceGpgKeyCreate, + ReadContext: resourceGpgKeyRead, + DeleteContext: resourceGpgKeyDelete, + + Importer: &schema.ResourceImporter{StateContext: resourceGpgKeyImport}, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "the gpg key name", + Required: true, + ForceNew: true, + }, + "content": { + Type: schema.TypeString, + Description: "the gpg public key block", + Required: true, + ForceNew: true, + }, + "key_id": { + Type: schema.TypeString, + Description: "the gpg key id", + Required: true, + ForceNew: true, + ValidateDiagFunc: NewRegexValidator(`[0-9A-F]{16}`), + }, + }, + } +} + +func resourceGpgKeyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(client.ApiClientInterface) + + var payload client.GpgKeyCreatePayload + if err := readResourceData(&payload, d); err != nil { + return diag.Errorf("schema resource data deserialization failed: %v", err) + } + + gpgKey, err := apiClient.GpgKeyCreate(&payload) + if err != nil { + return diag.Errorf("could not create gpg key: %v", err) + } + + d.SetId(gpgKey.Id) + + return nil +} + +func resourceGpgKeyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + gpgKey, err := getGpgKeyById(d.Id(), meta) + if err != nil { + return ResourceGetFailure("gpg key", d, err) + } + + if err := writeResourceData(gpgKey, d); err != nil { + return diag.Errorf("schema resource data serialization failed: %v", err) + } + + return nil +} + +func resourceGpgKeyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(client.ApiClientInterface) + + if err := apiClient.GpgKeyDelete(d.Id()); err != nil { + return diag.Errorf("could not delete gpg key: %v", err) + } + + return nil +} + +func getGpgKeyById(id string, meta interface{}) (*client.GpgKey, error) { + apiClient := meta.(client.ApiClientInterface) + + gpgKeys, err := apiClient.GpgKeys() + if err != nil { + return nil, err + } + + for _, gpgKey := range gpgKeys { + if gpgKey.Id == id { + return &gpgKey, nil + } + } + + return nil, &client.NotFoundError{} +} + +func getGpgKeyByName(name string, meta interface{}) (*client.GpgKey, error) { + apiClient := meta.(client.ApiClientInterface) + + gpgKeys, err := apiClient.GpgKeys() + if err != nil { + return nil, err + } + + var foundGpgKeys []client.GpgKey + for _, gpgKey := range gpgKeys { + if gpgKey.Name == name { + foundGpgKeys = append(foundGpgKeys, gpgKey) + } + } + + if len(foundGpgKeys) == 0 { + return nil, fmt.Errorf("gpg key with name %v not found", name) + } + + if len(foundGpgKeys) > 1 { + return nil, fmt.Errorf("found multiple gpg keys with name: %s. Use id instead or make sure gpg key names are unique %v", name, foundGpgKeys) + } + + return &foundGpgKeys[0], nil +} + +func getGpgKey(idOrName string, meta interface{}) (*client.GpgKey, error) { + _, err := uuid.Parse(idOrName) + if err == nil { + log.Println("[INFO] Resolving gpg key by id: ", idOrName) + return getGpgKeyById(idOrName, meta) + } else { + log.Println("[INFO] Resolving gpg key by name: ", idOrName) + return getGpgKeyByName(idOrName, meta) + } +} + +func resourceGpgKeyImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + gpgKey, err := getGpgKey(d.Id(), meta) + if err != nil { + return nil, err + } + + if err := writeResourceData(gpgKey, d); err != nil { + return nil, fmt.Errorf("schema resource data serialization failed: %v", err) + } + + return []*schema.ResourceData{d}, nil +} diff --git a/env0/resource_gpg_key_test.go b/env0/resource_gpg_key_test.go new file mode 100644 index 00000000..f1f3e625 --- /dev/null +++ b/env0/resource_gpg_key_test.go @@ -0,0 +1,199 @@ +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 TestUnitGpgKeyyResource(t *testing.T) { + resourceType := "env0_gpg_key" + resourceName := "test" + resourceNameImport := resourceType + "." + resourceName + accessor := resourceAccessor(resourceType, resourceName) + + gpgKey := client.GpgKey{ + Id: uuid.NewString(), + Name: "name", + KeyId: "ABCD0123ABCD0123", + Content: "content", + } + + updatedGpgKey := client.GpgKey{ + Id: uuid.NewString(), + Name: "name2", + KeyId: "11110123ABCD4567", + Content: "content2", + } + + t.Run("Success", func(t *testing.T) { + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "name": gpgKey.Name, + "key_id": gpgKey.KeyId, + "content": gpgKey.Content, + }), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(accessor, "id", gpgKey.Id), + resource.TestCheckResourceAttr(accessor, "name", gpgKey.Name), + resource.TestCheckResourceAttr(accessor, "key_id", gpgKey.KeyId), + resource.TestCheckResourceAttr(accessor, "content", gpgKey.Content), + ), + }, + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "name": updatedGpgKey.Name, + "key_id": updatedGpgKey.KeyId, + "content": updatedGpgKey.Content, + }), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(accessor, "id", updatedGpgKey.Id), + resource.TestCheckResourceAttr(accessor, "name", updatedGpgKey.Name), + resource.TestCheckResourceAttr(accessor, "key_id", updatedGpgKey.KeyId), + resource.TestCheckResourceAttr(accessor, "content", updatedGpgKey.Content), + ), + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { + gomock.InOrder( + mock.EXPECT().GpgKeyCreate(&client.GpgKeyCreatePayload{ + Name: gpgKey.Name, + KeyId: gpgKey.KeyId, + Content: gpgKey.Content, + }).Times(1).Return(&gpgKey, nil), + mock.EXPECT().GpgKeys().Times(2).Return([]client.GpgKey{gpgKey}, nil), + mock.EXPECT().GpgKeyDelete(gpgKey.Id).Times(1), + mock.EXPECT().GpgKeyCreate(&client.GpgKeyCreatePayload{ + Name: updatedGpgKey.Name, + KeyId: updatedGpgKey.KeyId, + Content: updatedGpgKey.Content, + }).Times(1).Return(&updatedGpgKey, nil), + mock.EXPECT().GpgKeys().Times(1).Return([]client.GpgKey{updatedGpgKey}, nil), + mock.EXPECT().GpgKeyDelete(updatedGpgKey.Id).Times(1), + ) + }) + }) + + t.Run("Create Failure", func(t *testing.T) { + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "name": gpgKey.Name, + "key_id": gpgKey.KeyId, + "content": gpgKey.Content, + }), + ExpectError: regexp.MustCompile("could not create gpg key: error"), + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { + mock.EXPECT().GpgKeyCreate(&client.GpgKeyCreatePayload{ + Name: gpgKey.Name, + KeyId: gpgKey.KeyId, + Content: gpgKey.Content, + }).Times(1).Return(nil, errors.New("error")) + }) + }) + + t.Run("Import By Name", func(t *testing.T) { + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "name": gpgKey.Name, + "key_id": gpgKey.KeyId, + "content": gpgKey.Content, + }), + }, + { + ResourceName: resourceNameImport, + ImportState: true, + ImportStateId: gpgKey.Name, + ImportStateVerify: true, + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { + mock.EXPECT().GpgKeyCreate(&client.GpgKeyCreatePayload{ + Name: gpgKey.Name, + KeyId: gpgKey.KeyId, + Content: gpgKey.Content, + }).Times(1).Return(&gpgKey, nil) + mock.EXPECT().GpgKeys().Times(3).Return([]client.GpgKey{gpgKey}, nil) + mock.EXPECT().GpgKeyDelete(gpgKey.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{}{ + "name": gpgKey.Name, + "key_id": gpgKey.KeyId, + "content": gpgKey.Content, + }), + }, + { + ResourceName: resourceNameImport, + ImportState: true, + ImportStateId: gpgKey.Id, + ImportStateVerify: true, + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { + mock.EXPECT().GpgKeyCreate(&client.GpgKeyCreatePayload{ + Name: gpgKey.Name, + KeyId: gpgKey.KeyId, + Content: gpgKey.Content, + }).Times(1).Return(&gpgKey, nil) + mock.EXPECT().GpgKeys().Times(3).Return([]client.GpgKey{gpgKey}, nil) + mock.EXPECT().GpgKeyDelete(gpgKey.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{}{ + "name": updatedGpgKey.Name, + "key_id": updatedGpgKey.KeyId, + "content": updatedGpgKey.Content, + }), + }, + { + ResourceName: resourceNameImport, + ImportState: true, + ImportStateId: gpgKey.Id, + ImportStateVerify: true, + ExpectError: regexp.MustCompile("not found"), + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { + mock.EXPECT().GpgKeyCreate(&client.GpgKeyCreatePayload{ + Name: updatedGpgKey.Name, + KeyId: updatedGpgKey.KeyId, + Content: updatedGpgKey.Content, + }).Times(1).Return(&updatedGpgKey, nil) + mock.EXPECT().GpgKeys().Times(2).Return([]client.GpgKey{updatedGpgKey}, nil) + mock.EXPECT().GpgKeyDelete(updatedGpgKey.Id).Times(1) + }) + }) +} diff --git a/examples/resources/env0_gpg_key/import.sh b/examples/resources/env0_gpg_key/import.sh new file mode 100644 index 00000000..c779b228 --- /dev/null +++ b/examples/resources/env0_gpg_key/import.sh @@ -0,0 +1,2 @@ +terraform import env0_gpg_key.by_id ddda7b30-6789-4d24-937c-22322754934e +terraform import env0_gpg_key.by_name gpg-key-name" diff --git a/examples/resources/env0_gpg_key/resource.tf b/examples/resources/env0_gpg_key/resource.tf new file mode 100644 index 00000000..324d5cd8 --- /dev/null +++ b/examples/resources/env0_gpg_key/resource.tf @@ -0,0 +1,5 @@ +resource "env0_gpg_key" "example" { + name = "gpg-key-example" + key_id = "ABCDABCDABCDABCD" + content = "key block" +} diff --git a/tests/integration/028_gpg_key/conf.tf b/tests/integration/028_gpg_key/conf.tf new file mode 100644 index 00000000..8d6d2954 --- /dev/null +++ b/tests/integration/028_gpg_key/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/028_gpg_key/expected_outputs.json b/tests/integration/028_gpg_key/expected_outputs.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/tests/integration/028_gpg_key/expected_outputs.json @@ -0,0 +1 @@ +{} diff --git a/tests/integration/028_gpg_key/main.tf b/tests/integration/028_gpg_key/main.tf new file mode 100644 index 00000000..02768083 --- /dev/null +++ b/tests/integration/028_gpg_key/main.tf @@ -0,0 +1,27 @@ +provider "random" {} + +resource "random_string" "random" { + length = 5 + special = false + min_lower = 5 +} + +resource "random_id" "random_key_id" { + byte_length = 7 +} + +resource "env0_gpg_key" "test_gpg_key" { + name = "gpg-key-${random_string.random.result}" + key_id = upper("${random_id.random_key_id.hex}CD") + content = "dasdasdasd" +} + +data "env0_gpg_key" "test_gpg_key_data" { + name = env0_gpg_key.test_gpg_key.name +} + +resource "env0_gpg_key" "test_gpg_key_modify" { + name = "gpg-key-${random_string.random.result}-2" + key_id = upper("${random_id.random_key_id.hex}AB") + content = var.second_run ? "dasdasdasd" : "dsadasdsvcxvcx" +}