From 005dfbfcc2856ff77b45a833aef108105c517485 Mon Sep 17 00:00:00 2001 From: Anna Winkler <3526523+annawinkler@users.noreply.github.com> Date: Thu, 5 May 2022 19:05:43 -0600 Subject: [PATCH] Add registry provider platform --- errors.go | 12 + generate_mocks.sh | 1 + helper_test.go | 82 +++- mocks/registry_provider_mocks.go | 95 +++++ mocks/registry_provider_platform_mocks.go | 95 +++++ mocks/registry_provider_version_mocks.go | 95 +++++ registry_provider_platform.go | 220 +++++++++- ...stry_provider_platform_integration_test.go | 398 ++++++++++++++++++ registry_provider_version.go | 2 +- registry_provider_version_integration_test.go | 27 +- tfe.go | 2 + 11 files changed, 1003 insertions(+), 26 deletions(-) create mode 100644 mocks/registry_provider_mocks.go create mode 100644 mocks/registry_provider_platform_mocks.go create mode 100644 mocks/registry_provider_version_mocks.go create mode 100644 registry_provider_platform_integration_test.go diff --git a/errors.go b/errors.go index a53e0064a..581f16a9b 100644 --- a/errors.go +++ b/errors.go @@ -163,6 +163,10 @@ var ( ErrInvalidRegistryNameType = errors.New("invalid type for registry-name. Please use 'RegistryName'") ErrInvalidKeyID = errors.New("invalid value for key-id") + + ErrInvalidOS = errors.New("invalid value for OS") + + ErrInvalidArch = errors.New("invalid value for arch") ) // Missing values for required field/option @@ -278,4 +282,12 @@ var ( ErrInvalidEmail = errors.New("email is invalid") ErrRequiredPrivateRegistry = errors.New("only private registry is allowed") + + ErrRequiredOS = errors.New("OS is required") + + ErrRequiredArch = errors.New("arch is required") + + ErrRequiredShasum = errors.New("shasum is required") + + ErrRequiredFilename = errors.New("filename is required") ) diff --git a/generate_mocks.sh b/generate_mocks.sh index af9a0b20f..9eece4f4f 100755 --- a/generate_mocks.sh +++ b/generate_mocks.sh @@ -36,6 +36,7 @@ mockgen -source=policy_set_parameter.go -destination=mocks/policy_set_parameter_ mockgen -source=policy_set_version.go -destination=mocks/policy_set_version_mocks.go -package=mocks mockgen -source=registry_module.go -destination=mocks/registry_module_mocks.go -package=mocks mockgen -source=registry_provider.go -destination=mocks/registry_provider_mocks.go -package=mocks +mockgen -source=registry_provider_platform.go -destination=mocks/registry_provider_platform_mocks.go -package=mocks mockgen -source=registry_provider_version.go -destination=mocks/registry_provider_version_mocks.go -package=mocks mockgen -source=run.go -destination=mocks/run_mocks.go -package=mocks mockgen -source=run_task.go -destination=mocks/run_tasks.go -package=mocks diff --git a/helper_test.go b/helper_test.go index e9a6381c3..1a52a146f 100644 --- a/helper_test.go +++ b/helper_test.go @@ -393,7 +393,7 @@ func createOAuthToken(t *testing.T, client *Client, org *Organization) (*OAuthTo func createOrganization(t *testing.T, client *Client) (*Organization, func()) { ctx := context.Background() org, err := client.Organizations.Create(ctx, OrganizationCreateOptions{ - Name: String("tst-" + randomString(t)), + Name: String("org-" + randomString(t)), // Max of 40 chars Email: String(fmt.Sprintf("%s@tfe.local", randomString(t))), }) if err != nil { @@ -759,7 +759,7 @@ func createPrivateRegistryProvider(t *testing.T, client *Client, org *Organizati ctx := context.Background() options := RegistryProviderCreateOptions{ - Name: "tst-name-" + randomString(t), + Name: "test-registry-provider-" + randomString(t), Namespace: org.Name, RegistryName: PrivateRegistry, } @@ -773,7 +773,7 @@ func createPrivateRegistryProvider(t *testing.T, client *Client, org *Organizati prv.Organization = org return prv, func() { - id := RegistryProviderID { + id := RegistryProviderID{ OrganizationName: org.Name, RegistryName: prv.RegistryName, Namespace: prv.Namespace, @@ -802,8 +802,8 @@ func createPublicRegistryProvider(t *testing.T, client *Client, org *Organizatio ctx := context.Background() options := RegistryProviderCreateOptions{ - Name: "tst-name-" + randomString(t), - Namespace: "tst-namespace-" + randomString(t), + Name: "test-name-" + randomString(t), + Namespace: "test-namespace-" + randomString(t), RegistryName: PublicRegistry, } @@ -816,7 +816,7 @@ func createPublicRegistryProvider(t *testing.T, client *Client, org *Organizatio prv.Organization = org return prv, func() { - id := RegistryProviderID { + id := RegistryProviderID{ OrganizationName: org.Name, RegistryName: prv.RegistryName, Namespace: prv.Namespace, @@ -835,6 +835,67 @@ func createPublicRegistryProvider(t *testing.T, client *Client, org *Organizatio } } +func createRegistryProviderPlatform(t *testing.T, client *Client, provider *RegistryProvider, version *RegistryProviderVersion) (*RegistryProviderPlatform, func()) { + var providerCleanup func() + var versionCleanup func() + + if provider == nil { + provider, providerCleanup = createPrivateRegistryProvider(t, client, nil) + } + + providerID := RegistryProviderID{ + OrganizationName: provider.Organization.Name, + RegistryName: provider.RegistryName, + Namespace: provider.Namespace, + Name: provider.Name, + } + + if version == nil { + version, versionCleanup = createRegistryProviderVersion(t, client, provider) + } + + versionID := RegistryProviderVersionID{ + RegistryProviderID: providerID, + Version: version.Version, + } + + ctx := context.Background() + + options := RegistryProviderPlatformCreateOptions{ + OS: randomString(t), + Arch: randomString(t), + Shasum: genSha(t, "secret", "data"), + Filename: randomString(t), + } + + rpp, err := client.RegistryProviderPlatforms.Create(ctx, versionID, options) + + if err != nil { + t.Fatal(err) + } + + return rpp, func() { + platformID := RegistryProviderPlatformID{ + RegistryProviderVersionID: versionID, + OS: rpp.OS, + Arch: rpp.Arch, + } + + if err := client.RegistryProviderPlatforms.Delete(ctx, platformID); err != nil { + t.Errorf("Error destroying registry provider platform! WARNING: Dangling resources\n"+ + "may exist! The full error is shown below.\n\n"+ + "Registry Provider Version: %s/%s/%s/%s\nError: %s", rpp.RegistryProviderVersion.RegistryProvider.Namespace, rpp.RegistryProviderVersion.RegistryProvider.Name, rpp.OS, rpp.Arch, err) + } + + if versionCleanup != nil { + versionCleanup() + } + if providerCleanup != nil { + providerCleanup() + } + } +} + func createRegistryProviderVersion(t *testing.T, client *Client, provider *RegistryProvider) (*RegistryProviderVersion, func()) { var providerCleanup func() @@ -842,7 +903,7 @@ func createRegistryProviderVersion(t *testing.T, client *Client, provider *Regis provider, providerCleanup = createPrivateRegistryProvider(t, client, nil) } - providerID := RegistryProviderID { + providerID := RegistryProviderID{ OrganizationName: provider.Organization.Name, RegistryName: provider.RegistryName, Namespace: provider.Namespace, @@ -852,8 +913,9 @@ func createRegistryProviderVersion(t *testing.T, client *Client, provider *Regis ctx := context.Background() options := RegistryProviderVersionCreateOptions{ - Version: randomSemver(t), - KeyID: randomString(t), + Version: randomSemver(t), + KeyID: randomString(t), + Protocols: []string{"4.0", "5.0", "6.0"}, } prvv, err := client.RegistryProviderVersions.Create(ctx, providerID, options) @@ -865,7 +927,7 @@ func createRegistryProviderVersion(t *testing.T, client *Client, provider *Regis prvv.RegistryProvider = provider return prvv, func() { - id := RegistryProviderVersionID { + id := RegistryProviderVersionID{ Version: options.Version, RegistryProviderID: providerID, } diff --git a/mocks/registry_provider_mocks.go b/mocks/registry_provider_mocks.go new file mode 100644 index 000000000..06285fdce --- /dev/null +++ b/mocks/registry_provider_mocks.go @@ -0,0 +1,95 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: registry_provider.go + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + tfe "github.com/hashicorp/go-tfe" +) + +// MockRegistryProviders is a mock of RegistryProviders interface. +type MockRegistryProviders struct { + ctrl *gomock.Controller + recorder *MockRegistryProvidersMockRecorder +} + +// MockRegistryProvidersMockRecorder is the mock recorder for MockRegistryProviders. +type MockRegistryProvidersMockRecorder struct { + mock *MockRegistryProviders +} + +// NewMockRegistryProviders creates a new mock instance. +func NewMockRegistryProviders(ctrl *gomock.Controller) *MockRegistryProviders { + mock := &MockRegistryProviders{ctrl: ctrl} + mock.recorder = &MockRegistryProvidersMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRegistryProviders) EXPECT() *MockRegistryProvidersMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockRegistryProviders) Create(ctx context.Context, organization string, options tfe.RegistryProviderCreateOptions) (*tfe.RegistryProvider, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", ctx, organization, options) + ret0, _ := ret[0].(*tfe.RegistryProvider) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Create indicates an expected call of Create. +func (mr *MockRegistryProvidersMockRecorder) Create(ctx, organization, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockRegistryProviders)(nil).Create), ctx, organization, options) +} + +// Delete mocks base method. +func (m *MockRegistryProviders) Delete(ctx context.Context, providerID tfe.RegistryProviderID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", ctx, providerID) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockRegistryProvidersMockRecorder) Delete(ctx, providerID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockRegistryProviders)(nil).Delete), ctx, providerID) +} + +// List mocks base method. +func (m *MockRegistryProviders) List(ctx context.Context, organization string, options *tfe.RegistryProviderListOptions) (*tfe.RegistryProviderList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "List", ctx, organization, options) + ret0, _ := ret[0].(*tfe.RegistryProviderList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockRegistryProvidersMockRecorder) List(ctx, organization, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockRegistryProviders)(nil).List), ctx, organization, options) +} + +// Read mocks base method. +func (m *MockRegistryProviders) Read(ctx context.Context, providerID tfe.RegistryProviderID, options *tfe.RegistryProviderReadOptions) (*tfe.RegistryProvider, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Read", ctx, providerID, options) + ret0, _ := ret[0].(*tfe.RegistryProvider) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Read indicates an expected call of Read. +func (mr *MockRegistryProvidersMockRecorder) Read(ctx, providerID, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockRegistryProviders)(nil).Read), ctx, providerID, options) +} diff --git a/mocks/registry_provider_platform_mocks.go b/mocks/registry_provider_platform_mocks.go new file mode 100644 index 000000000..7c518abf4 --- /dev/null +++ b/mocks/registry_provider_platform_mocks.go @@ -0,0 +1,95 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: registry_provider_platform.go + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + tfe "github.com/hashicorp/go-tfe" +) + +// MockRegistryProviderPlatforms is a mock of RegistryProviderPlatforms interface. +type MockRegistryProviderPlatforms struct { + ctrl *gomock.Controller + recorder *MockRegistryProviderPlatformsMockRecorder +} + +// MockRegistryProviderPlatformsMockRecorder is the mock recorder for MockRegistryProviderPlatforms. +type MockRegistryProviderPlatformsMockRecorder struct { + mock *MockRegistryProviderPlatforms +} + +// NewMockRegistryProviderPlatforms creates a new mock instance. +func NewMockRegistryProviderPlatforms(ctrl *gomock.Controller) *MockRegistryProviderPlatforms { + mock := &MockRegistryProviderPlatforms{ctrl: ctrl} + mock.recorder = &MockRegistryProviderPlatformsMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRegistryProviderPlatforms) EXPECT() *MockRegistryProviderPlatformsMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockRegistryProviderPlatforms) Create(ctx context.Context, versionID tfe.RegistryProviderVersionID, options tfe.RegistryProviderPlatformCreateOptions) (*tfe.RegistryProviderPlatform, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", ctx, versionID, options) + ret0, _ := ret[0].(*tfe.RegistryProviderPlatform) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Create indicates an expected call of Create. +func (mr *MockRegistryProviderPlatformsMockRecorder) Create(ctx, versionID, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockRegistryProviderPlatforms)(nil).Create), ctx, versionID, options) +} + +// Delete mocks base method. +func (m *MockRegistryProviderPlatforms) Delete(ctx context.Context, platformID tfe.RegistryProviderPlatformID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", ctx, platformID) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockRegistryProviderPlatformsMockRecorder) Delete(ctx, platformID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockRegistryProviderPlatforms)(nil).Delete), ctx, platformID) +} + +// List mocks base method. +func (m *MockRegistryProviderPlatforms) List(ctx context.Context, versionID tfe.RegistryProviderVersionID, options *tfe.RegistryProviderPlatformListOptions) (*tfe.RegistryProviderPlatformList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "List", ctx, versionID, options) + ret0, _ := ret[0].(*tfe.RegistryProviderPlatformList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockRegistryProviderPlatformsMockRecorder) List(ctx, versionID, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockRegistryProviderPlatforms)(nil).List), ctx, versionID, options) +} + +// Read mocks base method. +func (m *MockRegistryProviderPlatforms) Read(ctx context.Context, platformID tfe.RegistryProviderPlatformID) (*tfe.RegistryProviderPlatform, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Read", ctx, platformID) + ret0, _ := ret[0].(*tfe.RegistryProviderPlatform) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Read indicates an expected call of Read. +func (mr *MockRegistryProviderPlatformsMockRecorder) Read(ctx, platformID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockRegistryProviderPlatforms)(nil).Read), ctx, platformID) +} diff --git a/mocks/registry_provider_version_mocks.go b/mocks/registry_provider_version_mocks.go new file mode 100644 index 000000000..7cfa320b6 --- /dev/null +++ b/mocks/registry_provider_version_mocks.go @@ -0,0 +1,95 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: registry_provider_version.go + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + tfe "github.com/hashicorp/go-tfe" +) + +// MockRegistryProviderVersions is a mock of RegistryProviderVersions interface. +type MockRegistryProviderVersions struct { + ctrl *gomock.Controller + recorder *MockRegistryProviderVersionsMockRecorder +} + +// MockRegistryProviderVersionsMockRecorder is the mock recorder for MockRegistryProviderVersions. +type MockRegistryProviderVersionsMockRecorder struct { + mock *MockRegistryProviderVersions +} + +// NewMockRegistryProviderVersions creates a new mock instance. +func NewMockRegistryProviderVersions(ctrl *gomock.Controller) *MockRegistryProviderVersions { + mock := &MockRegistryProviderVersions{ctrl: ctrl} + mock.recorder = &MockRegistryProviderVersionsMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRegistryProviderVersions) EXPECT() *MockRegistryProviderVersionsMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockRegistryProviderVersions) Create(ctx context.Context, providerID tfe.RegistryProviderID, options tfe.RegistryProviderVersionCreateOptions) (*tfe.RegistryProviderVersion, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", ctx, providerID, options) + ret0, _ := ret[0].(*tfe.RegistryProviderVersion) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Create indicates an expected call of Create. +func (mr *MockRegistryProviderVersionsMockRecorder) Create(ctx, providerID, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockRegistryProviderVersions)(nil).Create), ctx, providerID, options) +} + +// Delete mocks base method. +func (m *MockRegistryProviderVersions) Delete(ctx context.Context, versionID tfe.RegistryProviderVersionID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", ctx, versionID) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockRegistryProviderVersionsMockRecorder) Delete(ctx, versionID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockRegistryProviderVersions)(nil).Delete), ctx, versionID) +} + +// List mocks base method. +func (m *MockRegistryProviderVersions) List(ctx context.Context, providerID tfe.RegistryProviderID, options *tfe.RegistryProviderVersionListOptions) (*tfe.RegistryProviderVersionList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "List", ctx, providerID, options) + ret0, _ := ret[0].(*tfe.RegistryProviderVersionList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockRegistryProviderVersionsMockRecorder) List(ctx, providerID, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockRegistryProviderVersions)(nil).List), ctx, providerID, options) +} + +// Read mocks base method. +func (m *MockRegistryProviderVersions) Read(ctx context.Context, versionID tfe.RegistryProviderVersionID, options *tfe.RegistryProviderVersionReadOptions) (*tfe.RegistryProviderVersion, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Read", ctx, versionID, options) + ret0, _ := ret[0].(*tfe.RegistryProviderVersion) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Read indicates an expected call of Read. +func (mr *MockRegistryProviderVersionsMockRecorder) Read(ctx, versionID, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockRegistryProviderVersions)(nil).Read), ctx, versionID, options) +} diff --git a/registry_provider_platform.go b/registry_provider_platform.go index 4f6db5f6f..ad80e3326 100644 --- a/registry_provider_platform.go +++ b/registry_provider_platform.go @@ -1,12 +1,43 @@ package tfe +import ( + "context" + "fmt" + "net/url" +) + +// Compile-time proof of interface implementation +var _ RegistryProviderPlatforms = (*registryProviderPlatforms)(nil) + +// RegistryProviderPlatforms describes the registry provider platform methods supported by the Terraform Enterprise API. +// +// TFE API docs: https://www.terraform.io/cloud-docs/api-docs/private-registry/provider-versions-platforms#private-provider-versions-and-platforms-api +type RegistryProviderPlatforms interface { + // Create a provider platform for an organization + Create(ctx context.Context, versionID RegistryProviderVersionID, options RegistryProviderPlatformCreateOptions) (*RegistryProviderPlatform, error) + + // List all provider platforms for a single version + List(ctx context.Context, versionID RegistryProviderVersionID, options *RegistryProviderPlatformListOptions) (*RegistryProviderPlatformList, error) + + // Read a provider platform by ID + Read(ctx context.Context, platformID RegistryProviderPlatformID) (*RegistryProviderPlatform, error) + + // Delete a provider platform + Delete(ctx context.Context, platformID RegistryProviderPlatformID) error +} + +// registryProviders implements RegistryProviders +type registryProviderPlatforms struct { + client *Client +} + // RegistryProviderPlatform represents a registry provider platform type RegistryProviderPlatform struct { ID string `jsonapi:"primary,registry-provider-platforms"` - Os string `jsonapi:"attr,os"` + OS string `jsonapi:"attr,os"` Arch string `jsonapi:"attr,arch"` Filename string `jsonapi:"attr,filename"` - SHASUM string `jsonapi:"attr,shasum"` + Shasum string `jsonapi:"attr,shasum"` // Relations RegistryProviderVersion *RegistryProviderVersion `jsonapi:"relation,registry-provider-version"` @@ -14,3 +45,188 @@ type RegistryProviderPlatform struct { // Links Links map[string]interface{} `jsonapi:"links,omitempty"` } + +// RegistryProviderPlatformID is the multi key ID for identifying a provider platform +type RegistryProviderPlatformID struct { + RegistryProviderVersionID + OS string `jsonapi:"attr,os"` + Arch string `jsonapi:"attr,arch"` +} + +// RegistryProviderPlatformCreateOptions represents the set of options for creating a registry provider platform +type RegistryProviderPlatformCreateOptions struct { + OS string `jsonapi:"attr,os"` + Arch string `jsonapi:"attr,arch"` + Shasum string `jsonapi:"attr,shasum"` + Filename string `jsonapi:"attr,filename"` +} + +type RegistryProviderPlatformList struct { + *Pagination + Items []*RegistryProviderPlatform +} + +type RegistryProviderPlatformListOptions struct { + ListOptions +} + +// Create a new registry provider platform +func (r *registryProviderPlatforms) Create(ctx context.Context, versionID RegistryProviderVersionID, options RegistryProviderPlatformCreateOptions) (*RegistryProviderPlatform, error) { + if err := versionID.valid(); err != nil { + return nil, err + } + + if versionID.RegistryName != PrivateRegistry { + return nil, ErrRequiredPrivateRegistry + } + + if err := options.valid(); err != nil { + return nil, err + } + + // POST /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name/versions/:version/platforms + u := fmt.Sprintf( + "organizations/%s/registry-providers/%s/%s/%s/versions/%s/platforms", + url.QueryEscape(versionID.OrganizationName), + url.QueryEscape(string(versionID.RegistryName)), + url.QueryEscape(versionID.Namespace), + url.QueryEscape(versionID.Name), + url.QueryEscape(versionID.Version), + ) + req, err := r.client.newRequest("POST", u, &options) + if err != nil { + return nil, err + } + + rpp := &RegistryProviderPlatform{} + err = r.client.do(ctx, req, rpp) + if err != nil { + return nil, err + } + + return rpp, nil +} + +// List all provider platforms for a single version +func (r *registryProviderPlatforms) List(ctx context.Context, versionID RegistryProviderVersionID, options *RegistryProviderPlatformListOptions) (*RegistryProviderPlatformList, error) { + if err := versionID.valid(); err != nil { + return nil, err + } + if options != nil { + if err := options.valid(); err != nil { + return nil, err + } + } + + // GET /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name/versions/:version/platforms + u := fmt.Sprintf( + "organizations/%s/registry-providers/%s/%s/%s/versions/%s/platforms", + url.QueryEscape(versionID.RegistryProviderID.OrganizationName), + url.QueryEscape(string(versionID.RegistryProviderID.RegistryName)), + url.QueryEscape(versionID.RegistryProviderID.Namespace), + url.QueryEscape(versionID.RegistryProviderID.Name), + url.QueryEscape(versionID.Version), + ) + req, err := r.client.newRequest("GET", u, options) + if err != nil { + return nil, err + } + + ppl := &RegistryProviderPlatformList{} + err = r.client.do(ctx, req, ppl) + if err != nil { + return nil, err + } + + return ppl, nil +} + +// Read is used to read an organization's example by ID +func (r *registryProviderPlatforms) Read(ctx context.Context, platformID RegistryProviderPlatformID) (*RegistryProviderPlatform, error) { + if err := platformID.valid(); err != nil { + return nil, err + } + + // GET /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name/versions/:version/platforms/:os/:arch + u := fmt.Sprintf( + "organizations/%s/registry-providers/%s/%s/%s/versions/%s/platforms/%s/%s", + url.QueryEscape(platformID.RegistryProviderID.OrganizationName), + url.QueryEscape(string(platformID.RegistryProviderID.RegistryName)), + url.QueryEscape(platformID.RegistryProviderID.Namespace), + url.QueryEscape(platformID.RegistryProviderID.Name), + url.QueryEscape(platformID.RegistryProviderVersionID.Version), + url.QueryEscape(platformID.OS), + url.QueryEscape(platformID.Arch), + ) + req, err := r.client.newRequest("GET", u, nil) + if err != nil { + return nil, err + } + + rpp := &RegistryProviderPlatform{} + err = r.client.do(ctx, req, rpp) + + if err != nil { + return nil, err + } + + return rpp, nil +} + +// Delete a registry provider platform +func (r *registryProviderPlatforms) Delete(ctx context.Context, platformID RegistryProviderPlatformID) error { + if err := platformID.valid(); err != nil { + return err + } + + // DELETE /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name/versions/:version/platforms/:os/:arch + u := fmt.Sprintf( + "organizations/%s/registry-providers/%s/%s/%s/versions/%s/platforms/%s/%s", + url.QueryEscape(platformID.OrganizationName), + url.QueryEscape(string(platformID.RegistryName)), + url.QueryEscape(platformID.Namespace), + url.QueryEscape(platformID.Name), + url.QueryEscape(platformID.Version), + url.QueryEscape(platformID.OS), + url.QueryEscape(platformID.Arch), + ) + req, err := r.client.newRequest("DELETE", u, nil) + if err != nil { + return err + } + + return r.client.do(ctx, req, nil) +} + +func (id RegistryProviderPlatformID) valid() error { + if err := id.RegistryProviderID.valid(); err != nil { + return err + } + if !validString(&id.OS) { + return ErrInvalidOS + } + if !validString(&id.Arch) { + return ErrInvalidArch + } + return nil +} + +func (o RegistryProviderPlatformCreateOptions) valid() error { + if !validString(&o.OS) { + return ErrRequiredOS + } + if !validString(&o.Arch) { + return ErrRequiredArch + } + if !validStringID(&o.Shasum) { + return ErrRequiredShasum + } + if !validStringID(&o.Filename) { + return ErrRequiredFilename + } + return nil +} + +func (o RegistryProviderPlatformListOptions) valid() error { + return nil +} diff --git a/registry_provider_platform_integration_test.go b/registry_provider_platform_integration_test.go new file mode 100644 index 000000000..6000cf371 --- /dev/null +++ b/registry_provider_platform_integration_test.go @@ -0,0 +1,398 @@ +//go:build integration +// +build integration + +package tfe + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRegistryProviderPlatformsCreate(t *testing.T) { + client := testClient(t) + ctx := context.Background() + + provider, providerTestCleanup := createPrivateRegistryProvider(t, client, nil) + defer providerTestCleanup() + + version, versionCleanup := createRegistryProviderVersion(t, client, provider) + defer versionCleanup() + + versionID := RegistryProviderVersionID{ + RegistryProviderID: RegistryProviderID{ + OrganizationName: provider.Organization.Name, + RegistryName: provider.RegistryName, + Namespace: provider.Namespace, + Name: provider.Name, + }, + Version: version.Version, + } + + t.Run("with valid options", func(t *testing.T) { + options := RegistryProviderPlatformCreateOptions{ + OS: "foo", + Arch: "scrimbles", + Shasum: "shasum", + Filename: "filename", + } + + rpp, err := client.RegistryProviderPlatforms.Create(ctx, versionID, options) + require.NoError(t, err) + assert.NotEmpty(t, rpp.ID) + assert.Equal(t, options.OS, rpp.OS) + assert.Equal(t, options.Arch, rpp.Arch) + assert.Equal(t, options.Shasum, rpp.Shasum) + assert.Equal(t, options.Filename, rpp.Filename) + + t.Run("relationships are properly decoded", func(t *testing.T) { + assert.Equal(t, version.ID, rpp.RegistryProviderVersion.ID) + }) + + t.Run("attributes are properly decoded", func(t *testing.T) { + assert.NotEmpty(t, rpp.Arch) + assert.NotEmpty(t, rpp.OS) + assert.NotEmpty(t, rpp.Shasum) + assert.NotEmpty(t, rpp.Filename) + }) + }) + + t.Run("with invalid options", func(t *testing.T) { + t.Run("without an OS", func(t *testing.T) { + options := RegistryProviderPlatformCreateOptions{ + OS: "", + Arch: "scrimbles", + Shasum: "shasum", + Filename: "filename", + } + + sad_rpp, err := client.RegistryProviderPlatforms.Create(ctx, versionID, options) + + assert.Nil(t, sad_rpp) + assert.EqualError(t, err, ErrRequiredOS.Error()) + }) + + t.Run("without an arch", func(t *testing.T) { + options := RegistryProviderPlatformCreateOptions{ + OS: "os", + Arch: "", + Shasum: "shasum", + Filename: "filename", + } + + sad_rpp, err := client.RegistryProviderPlatforms.Create(ctx, versionID, options) + + assert.Nil(t, sad_rpp) + assert.EqualError(t, err, ErrRequiredArch.Error()) + }) + + t.Run("without a shasum", func(t *testing.T) { + options := RegistryProviderPlatformCreateOptions{ + OS: "os", + Arch: "scrimbles", + Shasum: "", + Filename: "filename", + } + + sad_rpp, err := client.RegistryProviderPlatforms.Create(ctx, versionID, options) + + assert.Nil(t, sad_rpp) + assert.EqualError(t, err, ErrRequiredShasum.Error()) + }) + + t.Run("without a filename", func(t *testing.T) { + options := RegistryProviderPlatformCreateOptions{ + OS: "os", + Arch: "scrimbles", + Shasum: "shasum", + Filename: "", + } + + sad_rpp, err := client.RegistryProviderPlatforms.Create(ctx, versionID, options) + + assert.Nil(t, sad_rpp) + assert.EqualError(t, err, ErrRequiredFilename.Error()) + }) + + t.Run("with a public provider", func(t *testing.T) { + options := RegistryProviderPlatformCreateOptions{ + OS: "os", + Arch: "scrimbles", + Shasum: "shasum", + Filename: "filename", + } + + versionID = RegistryProviderVersionID{ + RegistryProviderID: RegistryProviderID{ + OrganizationName: provider.Organization.Name, + RegistryName: PublicRegistry, + Namespace: provider.Namespace, + Name: provider.Name, + }, + Version: version.Version, + } + + rm, err := client.RegistryProviderPlatforms.Create(ctx, versionID, options) + assert.Nil(t, rm) + assert.EqualError(t, err, ErrRequiredPrivateRegistry.Error()) + }) + + t.Run("without a valid registry provider version id", func(t *testing.T) { + options := RegistryProviderPlatformCreateOptions{ + OS: "os", + Arch: "scrimbles", + Shasum: "shasum", + Filename: "filename", + } + + versionID = RegistryProviderVersionID{ + RegistryProviderID: RegistryProviderID{ + OrganizationName: badIdentifier, + RegistryName: provider.RegistryName, + Namespace: provider.Namespace, + Name: provider.Name, + }, + Version: version.Version, + } + + rm, err := client.RegistryProviderPlatforms.Create(ctx, versionID, options) + assert.Nil(t, rm) + assert.EqualError(t, err, ErrInvalidOrg.Error()) + }) + }) +} + +func TestRegistryProviderPlatformsDelete(t *testing.T) { + client := testClient(t) + ctx := context.Background() + + provider, providerCleanup := createPrivateRegistryProvider(t, client, nil) + defer providerCleanup() + + version, versionCleanup := createRegistryProviderVersion(t, client, provider) + defer versionCleanup() + + versionID := RegistryProviderVersionID{ + RegistryProviderID: RegistryProviderID{ + OrganizationName: provider.Organization.Name, + RegistryName: provider.RegistryName, + Namespace: provider.Namespace, + Name: provider.Name, + }, + Version: version.Version, + } + + t.Run("with a valid version", func(t *testing.T) { + platform, _ := createRegistryProviderPlatform(t, client, provider, version) + + platformID := RegistryProviderPlatformID{ + RegistryProviderVersionID: versionID, + OS: platform.OS, + Arch: platform.Arch, + } + + err := client.RegistryProviderPlatforms.Delete(ctx, platformID) + assert.NoError(t, err) + }) + + t.Run("with a non-existant version", func(t *testing.T) { + platformID := RegistryProviderPlatformID{ + RegistryProviderVersionID: versionID, + OS: "nope", + Arch: "no", + } + + err := client.RegistryProviderPlatforms.Delete(ctx, platformID) + assert.Error(t, err) + }) +} + +func TestRegistryProviderPlatformsRead(t *testing.T) { + client := testClient(t) + ctx := context.Background() + + provider, providerCleanup := createPrivateRegistryProvider(t, client, nil) + defer providerCleanup() + + providerID := RegistryProviderID{ + OrganizationName: provider.Organization.Name, + Namespace: provider.Namespace, + Name: provider.Name, + RegistryName: provider.RegistryName, + } + + version, versionCleanup := createRegistryProviderVersion(t, client, provider) + defer versionCleanup() + + versionID := RegistryProviderVersionID{ + RegistryProviderID: providerID, + Version: version.Version, + } + + platform, platformCleanup := createRegistryProviderPlatform(t, client, provider, version) + defer platformCleanup() + + t.Run("with valid platform", func(t *testing.T) { + platformID := RegistryProviderPlatformID{ + RegistryProviderVersionID: versionID, + OS: platform.OS, + Arch: platform.Arch, + } + + readPlatform, err := client.RegistryProviderPlatforms.Read(ctx, platformID) + assert.NoError(t, err) + assert.Equal(t, platformID.OS, readPlatform.OS) + assert.Equal(t, platformID.Arch, readPlatform.Arch) + assert.Equal(t, platform.Filename, readPlatform.Filename) + assert.Equal(t, platform.Shasum, readPlatform.Shasum) + + t.Run("relationships are properly decoded", func(t *testing.T) { + assert.Equal(t, platform.RegistryProviderVersion.ID, readPlatform.RegistryProviderVersion.ID) + }) + + t.Run("includes provider binary upload link", func(t *testing.T) { + expectedLinks := []string{ + "provider-binary-upload", + } + for _, l := range expectedLinks { + _, ok := readPlatform.Links[l].(string) + assert.True(t, ok, "Expect upload link: %s", l) + } + }) + }) + + t.Run("with non-existant os", func(t *testing.T) { + platformID := RegistryProviderPlatformID{ + RegistryProviderVersionID: versionID, + OS: "DoesNotExist", + Arch: platform.Arch, + } + + _, err := client.RegistryProviderPlatforms.Read(ctx, platformID) + assert.Error(t, err) + }) + + t.Run("with non-existant arch", func(t *testing.T) { + platformID := RegistryProviderPlatformID{ + RegistryProviderVersionID: versionID, + OS: platform.OS, + Arch: "DoesNotExist", + } + + _, err := client.RegistryProviderPlatforms.Read(ctx, platformID) + assert.Error(t, err) + }) +} + +func TestRegistryProviderPlatformsList(t *testing.T) { + client := testClient(t) + ctx := context.Background() + + t.Run("with platforms", func(t *testing.T) { + provider, providerCleanup := createPrivateRegistryProvider(t, client, nil) + defer providerCleanup() + + version, versionCleanup := createRegistryProviderVersion(t, client, provider) + defer versionCleanup() + + numToCreate := 10 + platforms := make([]*RegistryProviderPlatform, 0) + for i := 0; i < numToCreate; i++ { + platform, _ := createRegistryProviderPlatform(t, client, provider, version) + platforms = append(platforms, platform) + } + numPlatforms := len(platforms) + + providerID := RegistryProviderID{ + OrganizationName: provider.Organization.Name, + Namespace: provider.Namespace, + Name: provider.Name, + RegistryName: provider.RegistryName, + } + versionID := RegistryProviderVersionID{ + RegistryProviderID: providerID, + Version: version.Version, + } + + t.Run("returns all platforms", func(t *testing.T) { + returnedPlatforms, err := client.RegistryProviderPlatforms.List(ctx, versionID, &RegistryProviderPlatformListOptions{ + ListOptions: ListOptions{ + PageNumber: 0, + PageSize: numPlatforms, + }, + }) + require.NoError(t, err) + assert.NotEmpty(t, returnedPlatforms.Items) + assert.Equal(t, numPlatforms, returnedPlatforms.TotalCount) + assert.Equal(t, 1, returnedPlatforms.TotalPages) + for _, rp := range returnedPlatforms.Items { + foundPlatform := false + for _, p := range platforms { + if rp.ID == p.ID { + foundPlatform = true + break + } + } + assert.True(t, foundPlatform, "Expected to find platform %s but did not:\nexpected:\n%v\nreturned\n%v", rp.ID, platforms, returnedPlatforms) + } + }) + + t.Run("returns pages of platforms", func(t *testing.T) { + numPages := 2 + pageSize := numPlatforms / numPages + + for page := 0; page < numPages; page++ { + testName := fmt.Sprintf("returns page %d of platforms", page) + t.Run(testName, func(t *testing.T) { + returnedPlatforms, err := client.RegistryProviderPlatforms.List(ctx, versionID, &RegistryProviderPlatformListOptions{ + ListOptions: ListOptions{ + PageNumber: page, + PageSize: pageSize, + }, + }) + require.NoError(t, err) + assert.NotEmpty(t, returnedPlatforms.Items) + assert.Equal(t, numPlatforms, returnedPlatforms.TotalCount) + assert.Equal(t, numPages, returnedPlatforms.TotalPages) + assert.Equal(t, pageSize, len(returnedPlatforms.Items)) + for _, rp := range returnedPlatforms.Items { + foundPlatform := false + for _, p := range platforms { + if rp.ID == p.ID { + foundPlatform = true + break + } + } + assert.True(t, foundPlatform, "Expected to find platform %s but did not:\nexpected:\n%v\nreturned\n%v", rp.ID, platforms, returnedPlatforms) + } + }) + } + }) + }) + + t.Run("without platforms", func(t *testing.T) { + provider, providerCleanup := createPrivateRegistryProvider(t, client, nil) + defer providerCleanup() + + version, versionCleanup := createRegistryProviderVersion(t, client, provider) + defer versionCleanup() + + versionID := RegistryProviderVersionID{ + RegistryProviderID: RegistryProviderID{ + OrganizationName: provider.Organization.Name, + Namespace: provider.Namespace, + Name: provider.Name, + RegistryName: provider.RegistryName, + }, + Version: version.Version, + } + platforms, err := client.RegistryProviderPlatforms.List(ctx, versionID, nil) + require.NoError(t, err) + assert.Empty(t, platforms.Items) + assert.Equal(t, 0, platforms.TotalCount) + assert.Equal(t, 0, platforms.TotalPages) + }) +} diff --git a/registry_provider_version.go b/registry_provider_version.go index a11230b24..5f52dc2c6 100644 --- a/registry_provider_version.go +++ b/registry_provider_version.go @@ -27,7 +27,7 @@ type RegistryProviderVersions interface { Delete(ctx context.Context, versionID RegistryProviderVersionID) error } -// registryProviders implements RegistryProviders. +// registryProvidersVersions implements RegistryProvidersVersions type registryProviderVersions struct { client *Client } diff --git a/registry_provider_version_integration_test.go b/registry_provider_version_integration_test.go index 964a27a40..d91ecdb73 100644 --- a/registry_provider_version_integration_test.go +++ b/registry_provider_version_integration_test.go @@ -214,7 +214,7 @@ func TestRegistryProviderVersionsList(t *testing.T) { } versionN := len(versions) - id := RegistryProviderID{ + providerID := RegistryProviderID{ OrganizationName: provider.Organization.Name, Namespace: provider.Namespace, Name: provider.Name, @@ -222,7 +222,7 @@ func TestRegistryProviderVersionsList(t *testing.T) { } t.Run("returns all versions", func(t *testing.T) { - returnedVersions, err := client.RegistryProviderVersions.List(ctx, id, &RegistryProviderVersionListOptions{ + returnedVersions, err := client.RegistryProviderVersions.List(ctx, providerID, &RegistryProviderVersionListOptions{ ListOptions: ListOptions{ PageNumber: 0, PageSize: versionN, @@ -251,7 +251,7 @@ func TestRegistryProviderVersionsList(t *testing.T) { for page := 0; page < pageN; page++ { testName := fmt.Sprintf("returns page %d of versions", page) t.Run(testName, func(t *testing.T) { - returnedVersions, err := client.RegistryProviderVersions.List(ctx, id, &RegistryProviderVersionListOptions{ + returnedVersions, err := client.RegistryProviderVersions.List(ctx, providerID, &RegistryProviderVersionListOptions{ ListOptions: ListOptions{ PageNumber: page, PageSize: pageSize, @@ -281,20 +281,21 @@ func TestRegistryProviderVersionsList(t *testing.T) { provider, providerCleanup := createPrivateRegistryProvider(t, client, nil) defer providerCleanup() - id := RegistryProviderID{ + providerID := RegistryProviderID{ OrganizationName: provider.Organization.Name, Namespace: provider.Namespace, Name: provider.Name, RegistryName: provider.RegistryName, } - versions, err := client.RegistryProviderVersions.List(ctx, id, nil) + versions, err := client.RegistryProviderVersions.List(ctx, providerID, nil) require.NoError(t, err) assert.Empty(t, versions.Items) assert.Equal(t, 0, versions.TotalCount) assert.Equal(t, 0, versions.TotalPages) }) + // TODO t.Run("with include provider platforms", func(t *testing.T) { }) } @@ -309,7 +310,7 @@ func TestRegistryProviderVersionsDelete(t *testing.T) { t.Run("with valid version", func(t *testing.T) { version, _ := createRegistryProviderVersion(t, client, provider) - versionId := RegistryProviderVersionID{ + versionID := RegistryProviderVersionID{ RegistryProviderID: RegistryProviderID{ OrganizationName: version.RegistryProvider.Organization.Name, RegistryName: version.RegistryProvider.RegistryName, @@ -319,12 +320,12 @@ func TestRegistryProviderVersionsDelete(t *testing.T) { Version: version.Version, } - err := client.RegistryProviderVersions.Delete(ctx, versionId) + err := client.RegistryProviderVersions.Delete(ctx, versionID) assert.NoError(t, err) }) t.Run("with non existing version", func(t *testing.T) { - versionId := RegistryProviderVersionID{ + versionID := RegistryProviderVersionID{ RegistryProviderID: RegistryProviderID{ OrganizationName: provider.Organization.Name, RegistryName: provider.RegistryName, @@ -334,7 +335,7 @@ func TestRegistryProviderVersionsDelete(t *testing.T) { Version: "1.0.0", } - err := client.RegistryProviderVersions.Delete(ctx, versionId) + err := client.RegistryProviderVersions.Delete(ctx, versionID) assert.Error(t, err) }) } @@ -347,7 +348,7 @@ func TestRegistryProviderVersionsRead(t *testing.T) { version, versionCleanup := createRegistryProviderVersion(t, client, nil) defer versionCleanup() - versionId := RegistryProviderVersionID{ + versionID := RegistryProviderVersionID{ RegistryProviderID: RegistryProviderID{ OrganizationName: version.RegistryProvider.Organization.Name, RegistryName: version.RegistryProvider.RegistryName, @@ -357,7 +358,7 @@ func TestRegistryProviderVersionsRead(t *testing.T) { Version: version.Version, } - readVersion, err := client.RegistryProviderVersions.Read(ctx, versionId, nil) + readVersion, err := client.RegistryProviderVersions.Read(ctx, versionID, nil) assert.NoError(t, err) assert.Equal(t, version.ID, readVersion.ID) assert.Equal(t, version.Version, readVersion.Version) @@ -388,7 +389,7 @@ func TestRegistryProviderVersionsRead(t *testing.T) { provider, providerCleanup := createPrivateRegistryProvider(t, client, nil) defer providerCleanup() - versionId := RegistryProviderVersionID{ + versionID := RegistryProviderVersionID{ RegistryProviderID: RegistryProviderID{ OrganizationName: provider.Organization.Name, RegistryName: provider.RegistryName, @@ -398,7 +399,7 @@ func TestRegistryProviderVersionsRead(t *testing.T) { Version: "1.0.0", } - _, err := client.RegistryProviderVersions.Read(ctx, versionId, nil) + _, err := client.RegistryProviderVersions.Read(ctx, versionID, nil) assert.Error(t, err) }) diff --git a/tfe.go b/tfe.go index 5ec530dba..0022e84d6 100644 --- a/tfe.go +++ b/tfe.go @@ -129,6 +129,7 @@ type Client struct { PolicySets PolicySets RegistryModules RegistryModules RegistryProviders RegistryProviders + RegistryProviderPlatforms RegistryProviderPlatforms RegistryProviderVersions RegistryProviderVersions Runs Runs RunTasks RunTasks @@ -275,6 +276,7 @@ func NewClient(cfg *Config) (*Client, error) { client.PolicySets = &policySets{client: client} client.RegistryModules = ®istryModules{client: client} client.RegistryProviders = ®istryProviders{client: client} + client.RegistryProviderPlatforms = ®istryProviderPlatforms{client: client} client.RegistryProviderVersions = ®istryProviderVersions{client: client} client.Runs = &runs{client: client} client.RunTasks = &runTasks{client: client}