Skip to content

Commit

Permalink
Feat: support gpg key resources (#642)
Browse files Browse the repository at this point in the history
* Feat: support gpg key resources

* fix integration test

* some minor changes based on PR comments

* added additional fields to the response struct
  • Loading branch information
TomerHeber authored May 8, 2023
1 parent 57b174c commit ac7953b
Show file tree
Hide file tree
Showing 15 changed files with 763 additions and 2 deletions.
3 changes: 3 additions & 0 deletions client/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
44 changes: 44 additions & 0 deletions client/api_client_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 56 additions & 0 deletions client/gpg_key.go
Original file line number Diff line number Diff line change
@@ -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
}
98 changes: 98 additions & 0 deletions client/gpg_key_test.go
Original file line number Diff line number Diff line change
@@ -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))
})
})
})
64 changes: 64 additions & 0 deletions env0/data_gpg_key.go
Original file line number Diff line number Diff line change
@@ -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
}
93 changes: 93 additions & 0 deletions env0/data_gpg_key_test.go
Original file line number Diff line number Diff line change
@@ -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}),
)
})
}
4 changes: 2 additions & 2 deletions env0/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
Expand Down
Loading

0 comments on commit ac7953b

Please sign in to comment.