Skip to content

Commit

Permalink
Merge branch 'main' into SwiftEngineer/add-organization-workspace-limit
Browse files Browse the repository at this point in the history
  • Loading branch information
SwiftEngineer committed Jun 29, 2022
2 parents 3dc8f91 + b30e3d8 commit 56bab48
Show file tree
Hide file tree
Showing 16 changed files with 778 additions and 22 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,8 @@

## Enhancements
* Adds `RetryServerErrors` field to the `Config` object by @sebasslash [#439](https://github.com/hashicorp/go-tfe/pull/439)
* Adds support for the GPG Keys API by @sebasslash [#429](https://github.com/hashicorp/go-tfe/pull/429)
* [beta] Renames the optional StateVersion field `ExtState` to `JSONState` and changes to string for base64 encoding by @annawinkler [#444](https://github.com/hashicorp/go-tfe/pull/444)
* Add support for new `WorkspaceLimit` Admin setting for organizations [#425](https://github.com/hashicorp/go-tfe/pull/425)

# v1.3.0
Expand Down
4 changes: 2 additions & 2 deletions README.md
Expand Up @@ -89,8 +89,8 @@ This API client covers most of the existing Terraform Cloud API calls and is upd
- [x] Policy Set Parameters
- [ ] Private Registry
- [x] Modules
- [ ] Providers
- [ ] Provider Provider Versions and Platforms
- [x] Providers
- [x] Provider Provider Versions and Platforms
- [ ] GPG Keys
- [x] Runs
- [x] Run Tasks
Expand Down
6 changes: 6 additions & 0 deletions errors.go
Expand Up @@ -14,6 +14,10 @@ var (

// ErrMissingDirectory is returned when the path does not have an existing directory.
ErrMissingDirectory = errors.New("path needs to be an existing directory")

// ErrNamespaceNotAuthorized is returned when a user attempts to perform an action
// on a namespace (organization) they do not have access to.
ErrNamespaceNotAuthorized = errors.New("namespace not authorized")
)

// Options/fields that cannot be defined
Expand Down Expand Up @@ -288,4 +292,6 @@ var (
ErrRequiredShasum = errors.New("shasum is required")

ErrRequiredFilename = errors.New("filename is required")

ErrInvalidAsciiArmor = errors.New("ascii armor is invalid")
)
1 change: 1 addition & 0 deletions generate_mocks.sh
Expand Up @@ -20,6 +20,7 @@ mockgen -source=apply.go -destination=mocks/apply_mocks.go -package=mocks
mockgen -source=audit_trail.go -destination=mocks/audit_trail.go -package=mocks
mockgen -source=configuration_version.go -destination=mocks/configuration_version_mocks.go -package=mocks
mockgen -source=cost_estimate.go -destination=mocks/cost_estimate_mocks.go -package=mocks
mockgen -source=gpg_key.go -destination=mocks/gpg_key_mocks.go -package=mocks
mockgen -source=ip_ranges.go -destination=mocks/ip_ranges_mocks.go -package=mocks
mockgen -source=logreader.go -destination=mocks/logreader_mocks.go -package=mocks
mockgen -source=notification_configuration.go -destination=mocks/notification_configuration_mocks.go -package=mocks
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Expand Up @@ -7,10 +7,10 @@ require (
github.com/google/go-querystring v1.1.0
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/hashicorp/go-retryablehttp v0.7.1
github.com/hashicorp/go-slug v0.8.1
github.com/hashicorp/go-slug v0.9.1
github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d
github.com/stretchr/testify v1.7.3
github.com/stretchr/testify v1.8.0
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
)

Expand Down
8 changes: 4 additions & 4 deletions go.sum
Expand Up @@ -14,8 +14,8 @@ github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxC
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ=
github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-slug v0.8.1 h1:srN7ivgAjHfZddYY1DjBaihRCFy20+vCcOrlx1O2AfE=
github.com/hashicorp/go-slug v0.8.1/go.mod h1:Ib+IWBYfEfJGI1ZyXMGNbu2BU+aa3Dzu41RKLH301v4=
github.com/hashicorp/go-slug v0.9.1 h1:gYNVJ3t0jAWx8AT2eYZci3Xd7NBHyjayW9AR1DU4ki0=
github.com/hashicorp/go-slug v0.9.1/go.mod h1:Ib+IWBYfEfJGI1ZyXMGNbu2BU+aa3Dzu41RKLH301v4=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d h1:9ARUJJ1VVynB176G1HCwleORqCaXm/Vx0uUi0dL26I0=
Expand All @@ -26,8 +26,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.3 h1:dAm0YRdRQlWojc3CrCRgPBzG5f941d0zvAKu7qY4e+I=
github.com/stretchr/testify v1.7.3/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
Expand Down
200 changes: 200 additions & 0 deletions gpg_key.go
@@ -0,0 +1,200 @@
package tfe

import (
"context"
"fmt"
"net/url"
"strings"
"time"
)

// Compile-time proof of interface implementation
var _ GPGKeys = (*gpgKeys)(nil)

// GPGKeys describes all the GPG key related methods that the Terraform Private Registry API supports.
//
// TFE API Docs: https://www.terraform.io/cloud-docs/api-docs/private-registry/gpg-keys
type GPGKeys interface {
// Uploads a GPG Key to a private registry scoped with a namespace.
Create(ctx context.Context, registryName RegistryName, options GPGKeyCreateOptions) (*GPGKey, error)

// Read a GPG key.
Read(ctx context.Context, keyID GPGKeyID) (*GPGKey, error)

// Update a GPG key.
Update(ctx context.Context, keyID GPGKeyID, options GPGKeyUpdateOptions) (*GPGKey, error)

// Delete a GPG key.
Delete(ctx context.Context, keyID GPGKeyID) error
}

// gpgKeys implements GPGKeys
type gpgKeys struct {
client *Client
}

// GPGKey represents a signed GPG key for a TFC/E private provider.
type GPGKey struct {
ID string `jsonapi:"primary,gpg-keys"`
AsciiArmor string `jsonapi:"attr,ascii-armor"`
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
KeyID string `jsonapi:"attr,key-id"`
Namespace string `jsonapi:"attr,namespace"`
Source string `jsonapi:"attr,source"`
SourceURL *string `jsonapi:"attr,source-url"`
TrustSignature string `jsonapi:"attr,trust-signature"`
UpdatedAt time.Time `jsonapi:"attr,updated-at,iso8601"`
}

// GPGKeyID represents the set of identifiers used to fetch a GPG key.
type GPGKeyID struct {
RegistryName RegistryName
Namespace string
KeyID string
}

// GPGKeyCreateOptions represents all the available options used to create a GPG key.
type GPGKeyCreateOptions struct {
Type string `jsonapi:"primary,gpg-keys"`
Namespace string `jsonapi:"attr,namespace"`
AsciiArmor string `jsonapi:"attr,ascii-armor"`
}

// GPGKeyCreateOptions represents all the available options used to update a GPG key.
type GPGKeyUpdateOptions struct {
Type string `jsonapi:"primary,gpg-keys"`
Namespace string `jsonapi:"attr,namespace"`
}

func (s *gpgKeys) Create(ctx context.Context, registryName RegistryName, options GPGKeyCreateOptions) (*GPGKey, error) {
if err := options.valid(); err != nil {
return nil, err
}

if registryName != PrivateRegistry {
return nil, ErrInvalidRegistryName
}

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

g := &GPGKey{}
err = s.client.do(ctx, req, g)
if err != nil {
return nil, err
}

return g, nil
}

func (s *gpgKeys) Read(ctx context.Context, keyID GPGKeyID) (*GPGKey, error) {
if err := keyID.valid(); err != nil {
return nil, err
}

u := fmt.Sprintf("/api/registry/%s/v2/gpg-keys/%s/%s",
url.QueryEscape(string(keyID.RegistryName)),
url.QueryEscape(keyID.Namespace),
url.QueryEscape(keyID.KeyID),
)
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}

g := &GPGKey{}
err = s.client.do(ctx, req, g)
if err != nil {
return nil, err
}

return g, nil
}

func (s *gpgKeys) Update(ctx context.Context, keyID GPGKeyID, options GPGKeyUpdateOptions) (*GPGKey, error) {
if err := options.valid(); err != nil {
return nil, err
}

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

u := fmt.Sprintf("/api/registry/%s/v2/gpg-keys/%s/%s",
url.QueryEscape(string(keyID.RegistryName)),
url.QueryEscape(keyID.Namespace),
url.QueryEscape(keyID.KeyID),
)
req, err := s.client.newRequest("PATCH", u, &options)
if err != nil {
return nil, err
}

g := &GPGKey{}
err = s.client.do(ctx, req, g)
if err != nil {
if strings.Contains(err.Error(), "namespace not authorized") {
return nil, ErrNamespaceNotAuthorized
}
return nil, err
}

return g, nil
}

func (s *gpgKeys) Delete(ctx context.Context, keyID GPGKeyID) error {
if err := keyID.valid(); err != nil {
return err
}

u := fmt.Sprintf("/api/registry/%s/v2/gpg-keys/%s/%s",
url.QueryEscape(string(keyID.RegistryName)),
url.QueryEscape(keyID.Namespace),
url.QueryEscape(keyID.KeyID),
)
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}

return s.client.do(ctx, req, nil)
}

func (o GPGKeyID) valid() error {
if o.RegistryName != PrivateRegistry {
return ErrInvalidRegistryName
}

if !validString(&o.Namespace) {
return ErrInvalidNamespace
}

if !validString(&o.KeyID) {
return ErrInvalidKeyID
}

return nil
}

func (o GPGKeyCreateOptions) valid() error {
if !validString(&o.Namespace) {
return ErrInvalidNamespace
}

if !validString(&o.AsciiArmor) {
return ErrInvalidAsciiArmor
}

return nil
}

func (o GPGKeyUpdateOptions) valid() error {
if !validString(&o.Namespace) {
return ErrInvalidNamespace
}

return nil
}

0 comments on commit 56bab48

Please sign in to comment.