Skip to content

Commit

Permalink
Add support for List GPG Keys endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
sebasslash committed Dec 6, 2022
1 parent 6c89799 commit e8ea6c1
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -3,6 +3,7 @@
## Bug Fixes

## Enhancements
* Adds `List()` method to `GPGKeys` interface by @sebasslash [#]()

# v1.15.0

Expand Down
55 changes: 55 additions & 0 deletions gpg_key.go
Expand Up @@ -15,6 +15,9 @@ var _ GPGKeys = (*gpgKeys)(nil)
//
// TFE API Docs: https://www.terraform.io/cloud-docs/api-docs/private-registry/gpg-keys
type GPGKeys interface {
// Lists GPG keys in a private registry.
List(ctx context.Context, registryName RegistryName, options *GPGKeyListOptions) (*GPGKeyList, error)

// Uploads a GPG Key to a private registry scoped with a namespace.
Create(ctx context.Context, registryName RegistryName, options GPGKeyCreateOptions) (*GPGKey, error)

Expand All @@ -33,6 +36,12 @@ type gpgKeys struct {
client *Client
}

// GPGKeyList represents a list of GPG keys.
type GPGKeyList struct {
*Pagination
Items []*GPGKey
}

// GPGKey represents a signed GPG key for a TFC/E private provider.
type GPGKey struct {
ID string `jsonapi:"primary,gpg-keys"`
Expand All @@ -53,6 +62,14 @@ type GPGKeyID struct {
KeyID string
}

// GPGKeyListOptions represents all the available options to list keys in a registry.
type GPGKeyListOptions struct {
ListOptions

// Required: A list of one or more namespaces. Must be authorized TFC/E organization names.
Namespaces []string `url:"filter[namespace]"`
}

// GPGKeyCreateOptions represents all the available options used to create a GPG key.
type GPGKeyCreateOptions struct {
Type string `jsonapi:"primary,gpg-keys"`
Expand All @@ -66,6 +83,30 @@ type GPGKeyUpdateOptions struct {
Namespace string `jsonapi:"attr,namespace"`
}

func (s *gpgKeys) List(ctx context.Context, registryName RegistryName, options *GPGKeyListOptions) (*GPGKeyList, error) {
if registryName != PrivateRegistry {
return nil, ErrInvalidRegistryName
}

if err := options.valid(); err != nil {
return nil, err
}

u := fmt.Sprintf("/api/registry/%s/v2/gpg-keys", url.QueryEscape(string(registryName)))
req, err := s.client.NewRequest("GET", u, &options)
if err != nil {
return nil, err
}

keyl := &GPGKeyList{}
err = req.Do(ctx, keyl)
if err != nil {
return nil, err
}

return keyl, nil
}

func (s *gpgKeys) Create(ctx context.Context, registryName RegistryName, options GPGKeyCreateOptions) (*GPGKey, error) {
if err := options.valid(); err != nil {
return nil, err
Expand Down Expand Up @@ -179,6 +220,20 @@ func (o GPGKeyID) valid() error {
return nil
}

func (o *GPGKeyListOptions) valid() error {
if len(o.Namespaces) == 0 {
return ErrInvalidNamespace
}

for _, namespace := range o.Namespaces {
if namespace == "" || !validString(&namespace) {
return ErrInvalidNamespace
}
}

return nil
}

func (o GPGKeyCreateOptions) valid() error {
if !validString(&o.Namespace) {
return ErrInvalidNamespace
Expand Down
92 changes: 92 additions & 0 deletions gpg_key_integration_test.go
Expand Up @@ -8,6 +8,98 @@ import (
"github.com/stretchr/testify/require"
)

func TestGPGKeyList(t *testing.T) {
client := testClient(t)
ctx := context.Background()

org1, org1Cleanup := createOrganization(t, client)
t.Cleanup(org1Cleanup)

org2, org2Cleanup := createOrganization(t, client)
t.Cleanup(org2Cleanup)

upgradeOrganizationSubscription(t, client, org1)
upgradeOrganizationSubscription(t, client, org2)

provider1, provider1Cleanup := createRegistryProvider(t, client, org1, PrivateRegistry)
t.Cleanup(provider1Cleanup)

provider2, provider2Cleanup := createRegistryProvider(t, client, org2, PrivateRegistry)
t.Cleanup(provider2Cleanup)

gpgKey1, gpgKey1Cleanup := createGPGKey(t, client, org1, provider1)
t.Cleanup(gpgKey1Cleanup)

gpgKey2, gpgKey2Cleanup := createGPGKey(t, client, org2, provider2)
t.Cleanup(gpgKey2Cleanup)

t.Run("with single namespace", func(t *testing.T) {
opts := &GPGKeyListOptions{
Namespaces: []string{org1.Name},
}

keyl, err := client.GPGKeys.List(ctx, PrivateRegistry, opts)
require.NoError(t, err)

require.Len(t, keyl.Items, 1)
assert.Equal(t, gpgKey1.ID, keyl.Items[0].ID)
assert.Equal(t, gpgKey1.KeyID, keyl.Items[0].KeyID)
})

t.Run("with multiple namespaces", func(t *testing.T) {
t.Skip("Skipping due to GPG Key API not returning keys for multiple namespaces")

opts := &GPGKeyListOptions{
Namespaces: []string{org1.Name, org2.Name},
}

keyl, err := client.GPGKeys.List(ctx, PrivateRegistry, opts)
require.NoError(t, err)

require.Len(t, keyl.Items, 2)
for i, key := range []*GPGKey{
gpgKey1,
gpgKey2,
} {
assert.Equal(t, key.ID, keyl.Items[i].ID)
assert.Equal(t, key.KeyID, keyl.Items[i].KeyID)
}
})

t.Run("with list options", func(t *testing.T) {
opts := &GPGKeyListOptions{
Namespaces: []string{org1.Name},
ListOptions: ListOptions{
PageNumber: 999,
PageSize: 100,
},
}

keyl, err := client.GPGKeys.List(ctx, PrivateRegistry, opts)
require.NoError(t, err)
require.Empty(t, keyl.Items)
assert.Equal(t, 999, keyl.CurrentPage)
assert.Equal(t, 1, keyl.TotalCount)
})

t.Run("with invalid options", func(t *testing.T) {
t.Run("invalid registry name", func(t *testing.T) {
opts := &GPGKeyListOptions{
Namespaces: []string{org1.Name},
}
_, err := client.GPGKeys.List(ctx, PublicRegistry, opts)
require.EqualError(t, err, ErrInvalidRegistryName.Error())
})
t.Run("invalid namespace", func(t *testing.T) {
opts := &GPGKeyListOptions{
Namespaces: []string{},
}
_, err := client.GPGKeys.List(ctx, PrivateRegistry, opts)
require.EqualError(t, err, ErrInvalidNamespace.Error())
})
})
}

func TestGPGKeyCreate(t *testing.T) {
client := testClient(t)
ctx := context.Background()
Expand Down

0 comments on commit e8ea6c1

Please sign in to comment.