Skip to content

Commit

Permalink
Merge pull request #602 from hashicorp/super-ticket
Browse files Browse the repository at this point in the history
Add `List()` to GPG Keys, `ProviderBinaryUploaded` to registry platforms
  • Loading branch information
sebasslash committed Dec 8, 2022
2 parents 2355d59 + 199d4b1 commit 21e8cad
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,8 @@
* Project names were being incorrectly validated as ID's @brandonc [#608](https://github.com/hashicorp/go-tfe/pull/608)

## Enhancements
* Adds `List()` method to `GPGKeys` interface by @sebasslash [#602](https://github.com/hashicorp/go-tfe/pull/602)
* Adds `ProviderBinaryUploaded` field to `RegistryPlatforms` struct by @sebasslash [#602](https://github.com/hashicorp/go-tfe/pull/602)

# v1.15.0

Expand Down
52 changes: 52 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.
ListPrivate(ctx context.Context, 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,27 @@ type GPGKeyUpdateOptions struct {
Namespace string `jsonapi:"attr,namespace"`
}

// ListPrivate lists the private registry GPG keys for specified namespaces.
func (s *gpgKeys) ListPrivate(ctx context.Context, options GPGKeyListOptions) (*GPGKeyList, error) {
if err := options.valid(); err != nil {
return nil, err
}

u := fmt.Sprintf("/api/registry/%s/v2/gpg-keys", url.QueryEscape(string(PrivateRegistry)))
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 +217,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
85 changes: 85 additions & 0 deletions gpg_key_integration_test.go
Expand Up @@ -8,6 +8,91 @@ 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.ListPrivate(ctx, 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.ListPrivate(ctx, 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.ListPrivate(ctx, 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 namespace", func(t *testing.T) {
opts := GPGKeyListOptions{
Namespaces: []string{},
}
_, err := client.GPGKeys.ListPrivate(ctx, opts)
require.EqualError(t, err, ErrInvalidNamespace.Error())
})
})
}

func TestGPGKeyCreate(t *testing.T) {
client := testClient(t)
ctx := context.Background()
Expand Down
15 changes: 15 additions & 0 deletions mocks/gpg_key_mocks.go

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

11 changes: 6 additions & 5 deletions registry_provider_platform.go
Expand Up @@ -33,11 +33,12 @@ type registryProviderPlatforms struct {

// RegistryProviderPlatform represents a registry provider platform
type RegistryProviderPlatform struct {
ID string `jsonapi:"primary,registry-provider-platforms"`
OS string `jsonapi:"attr,os"`
Arch string `jsonapi:"attr,arch"`
Filename string `jsonapi:"attr,filename"`
Shasum string `jsonapi:"attr,shasum"`
ID string `jsonapi:"primary,registry-provider-platforms"`
OS string `jsonapi:"attr,os"`
Arch string `jsonapi:"attr,arch"`
Filename string `jsonapi:"attr,filename"`
Shasum string `jsonapi:"attr,shasum"`
ProviderBinaryUploaded bool `jsonapi:"attr,provider-binary-uploaded"`

// Relations
RegistryProviderVersion *RegistryProviderVersion `jsonapi:"relation,registry-provider-version"`
Expand Down
2 changes: 2 additions & 0 deletions registry_provider_platform_integration_test.go
Expand Up @@ -43,6 +43,7 @@ func TestRegistryProviderPlatformsCreate(t *testing.T) {
assert.Equal(t, options.Arch, rpp.Arch)
assert.Equal(t, options.Shasum, rpp.Shasum)
assert.Equal(t, options.Filename, rpp.Filename)
assert.False(t, rpp.ProviderBinaryUploaded)

t.Run("relationships are properly decoded", func(t *testing.T) {
assert.Equal(t, version.ID, rpp.RegistryProviderVersion.ID)
Expand Down Expand Up @@ -245,6 +246,7 @@ func TestRegistryProviderPlatformsRead(t *testing.T) {
assert.Equal(t, platformID.Arch, readPlatform.Arch)
assert.Equal(t, platform.Filename, readPlatform.Filename)
assert.Equal(t, platform.Shasum, readPlatform.Shasum)
assert.Equal(t, platform.ProviderBinaryUploaded, readPlatform.ProviderBinaryUploaded)

t.Run("relationships are properly decoded", func(t *testing.T) {
assert.Equal(t, platform.RegistryProviderVersion.ID, readPlatform.RegistryProviderVersion.ID)
Expand Down

0 comments on commit 21e8cad

Please sign in to comment.