From 35f00e157fa64a1e465d250f43781371d1551cd4 Mon Sep 17 00:00:00 2001 From: Karl Kirch Date: Tue, 15 Feb 2022 10:51:28 -0600 Subject: [PATCH] Add registry provider client and integration tests --- helper_test.go | 79 ++++- registry_provider.go | 248 +++++++++++++++ registry_provider_integration_test.go | 442 ++++++++++++++++++++++++++ registry_provider_platform.go | 16 + registry_provider_version.go | 16 + tfe.go | 2 + 6 files changed, 798 insertions(+), 5 deletions(-) create mode 100644 registry_provider.go create mode 100644 registry_provider_integration_test.go create mode 100644 registry_provider_platform.go create mode 100644 registry_provider_version.go diff --git a/helper_test.go b/helper_test.go index fe4d0eac7..4dc1389bc 100644 --- a/helper_test.go +++ b/helper_test.go @@ -717,23 +717,24 @@ func createRegistryModuleWithVersion(t *testing.T, client *Client, org *Organiza } func createRunTask(t *testing.T, client *Client, org *Organization) (*RunTask, func()) { + runTaskURL := os.Getenv("TFC_RUN_TASK_URL") + if runTaskURL == "" { + t.Error("Cannot create a run task with an empty URL. You must set TFC_RUN_TASK_URL for run task related tests.") + } + var orgCleanup func() if org == nil { org, orgCleanup = createOrganization(t, client) } - runTaskURL := os.Getenv("TFC_RUN_TASK_URL") - if runTaskURL == "" { - t.Error("Cannot create a run task with an empty URL. You must set TFC_RUN_TASK_URL for run task related tests.") - } - ctx := context.Background() r, err := client.RunTasks.Create(ctx, org.Name, RunTaskCreateOptions{ Name: "tst-" + randomString(t), URL: runTaskURL, Category: "task", }) + if err != nil { t.Fatal(err) } @@ -751,6 +752,74 @@ func createRunTask(t *testing.T, client *Client, org *Organization) (*RunTask, f } } +func createPrivateRegistryProvider(t *testing.T, client *Client, org *Organization) (*RegistryProvider, func()) { + var orgCleanup func() + + if org == nil { + org, orgCleanup = createOrganization(t, client) + } + + ctx := context.Background() + + privateName := PrivateRegistry + + options := RegistryProviderCreateOptions{ + Name: String("tst-name-" + randomString(t)), + Namespace: &org.Name, + RegistryName: &privateName, + } + prv, err := client.RegistryProviders.Create(ctx, org.Name, options) + if err != nil { + t.Fatal(err) + } + + return prv, func() { + if err := client.RegistryProviders.Delete(ctx, org.Name, prv.RegistryName, prv.Namespace, prv.Name); err != nil { + t.Errorf("Error destroying registry provider! WARNING: Dangling resources\n"+ + "may exist! The full error is shown below.\n\n"+ + "Registry Provider: %s/%s\nError: %s", prv.Namespace, prv.Name, err) + } + + if orgCleanup != nil { + orgCleanup() + } + } +} + +func createPublicRegistryProvider(t *testing.T, client *Client, org *Organization) (*RegistryProvider, func()) { + var orgCleanup func() + + if org == nil { + org, orgCleanup = createOrganization(t, client) + } + + ctx := context.Background() + + publicName := PublicRegistry + + options := RegistryProviderCreateOptions{ + Name: String("tst-name-" + randomString(t)), + Namespace: String("tst-namespace-" + randomString(t)), + RegistryName: &publicName, + } + prv, err := client.RegistryProviders.Create(ctx, org.Name, options) + if err != nil { + t.Fatal(err) + } + + return prv, func() { + if err := client.RegistryProviders.Delete(ctx, org.Name, prv.RegistryName, prv.Namespace, prv.Name); err != nil { + t.Errorf("Error destroying registry provider! WARNING: Dangling resources\n"+ + "may exist! The full error is shown below.\n\n"+ + "Registry Provider: %s/%s\nError: %s", prv.Namespace, prv.Name, err) + } + + if orgCleanup != nil { + orgCleanup() + } + } +} + func createSSHKey(t *testing.T, client *Client, org *Organization) (*SSHKey, func()) { var orgCleanup func() diff --git a/registry_provider.go b/registry_provider.go new file mode 100644 index 000000000..e02e94956 --- /dev/null +++ b/registry_provider.go @@ -0,0 +1,248 @@ +package tfe + +import ( + "context" + "errors" + "fmt" + "net/url" +) + +// Compile-time proof of interface implementation. +var _ RegistryProviders = (*registryProviders)(nil) + +// RegistryProviders describes all the registry provider related methods that the Terraform +// Enterprise API supports. +// +// TFE API docs: https://www.terraform.io/docs/cloud/api/providers.html +type RegistryProviders interface { + // List all the providers within an organization. + List(ctx context.Context, organization string, options *RegistryProviderListOptions) (*RegistryProviderList, error) + + // Create a registry provider + Create(ctx context.Context, organization string, options RegistryProviderCreateOptions) (*RegistryProvider, error) + + // Read a registry provider + Read(ctx context.Context, organization string, registryName RegistryName, namespace string, name string, options *RegistryProviderReadOptions) (*RegistryProvider, error) + + // Delete a registry provider + Delete(ctx context.Context, organization string, registryName RegistryName, namespace string, name string) error +} + +// registryProviders implements RegistryProviders. +type registryProviders struct { + client *Client +} + +// RegistryName represents which registry is being targeted +type RegistryName string + +// List of available registry names +const ( + PrivateRegistry RegistryName = "private" + PublicRegistry RegistryName = "public" +) + +// RegistryProvider represents a registry provider +type RegistryProvider struct { + ID string `jsonapi:"primary,registry-providers"` + Namespace string `jsonapi:"attr,namespace"` + Name string `jsonapi:"attr,name"` + RegistryName RegistryName `jsonapi:"attr,registry-name"` + Permissions *RegistryProviderPermissions `jsonapi:"attr,permissions"` + CreatedAt string `jsonapi:"attr,created-at"` + UpdatedAt string `jsonapi:"attr,updated-at"` + + // Relations + Organization *Organization `jsonapi:"relation,organization"` + RegistryProviderVersions []RegistryProviderVersion `jsonapi:"relation,registry-provider-version"` +} + +type RegistryProviderPermissions struct { + CanDelete bool `jsonapi:"attr,can-delete"` +} + +type RegistryProviderListOptions struct { + ListOptions + // A query string to filter by registry_name + RegistryName *RegistryName `url:"filter[registry_name],omitempty"` + // A query string to filter by organization + OrganizationName *string `url:"filter[organization_name],omitempty"` + // A query string to do a fuzzy search + Search *string `url:"q,omitempty"` +} + +type RegistryProviderList struct { + *Pagination + Items []*RegistryProvider +} + +func (o RegistryProviderListOptions) valid() error { + return nil +} + +func (r *registryProviders) List(ctx context.Context, organization string, options *RegistryProviderListOptions) (*RegistryProviderList, error) { + + if !validStringID(&organization) { + return nil, ErrInvalidOrg + } + if options != nil { + if err := options.valid(); err != nil { + return nil, err + } + } + + u := fmt.Sprintf("organizations/%s/registry-providers", url.QueryEscape(organization)) + req, err := r.client.newRequest("GET", u, options) + if err != nil { + return nil, err + } + + pl := &RegistryProviderList{} + err = r.client.do(ctx, req, pl) + if err != nil { + return nil, err + } + + return pl, nil +} + +// RegistryProviderCreateOptions is used when creating a registry provider +type RegistryProviderCreateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,registry-providers"` + + Namespace *string `jsonapi:"attr,namespace"` + Name *string `jsonapi:"attr,name"` + RegistryName *RegistryName `jsonapi:"attr,registry-name"` +} + +func (o RegistryProviderCreateOptions) valid() error { + if !validString(o.Name) { + return ErrRequiredName + } + if !validStringID(o.Name) { + return ErrInvalidName + } + if !validString(o.Namespace) { + return errors.New("namespace is required") + } + if !validStringID(o.Namespace) { + return errors.New("invalid value for namespace") + } + if !validString((*string)(o.RegistryName)) { + return errors.New("registry-name is required") + } + return nil +} + +func (r *registryProviders) Create(ctx context.Context, organization string, options RegistryProviderCreateOptions) (*RegistryProvider, error) { + if !validStringID(&organization) { + return nil, ErrInvalidOrg + } + if err := options.valid(); err != nil { + return nil, err + } + // Private providers must match their namespace and organization name + // This is enforced by the API as well + if *options.RegistryName == PrivateRegistry && organization != *options.Namespace { + return nil, errors.New("namespace must match organization name for private providers") + } + + u := fmt.Sprintf( + "organizations/%s/registry-providers", + url.QueryEscape(organization), + ) + req, err := r.client.newRequest("POST", u, &options) + if err != nil { + return nil, err + } + prv := &RegistryProvider{} + err = r.client.do(ctx, req, prv) + if err != nil { + return nil, err + } + + return prv, nil +} + +type RegistryProviderReadOptions struct { +} + +func (r *registryProviders) Read(ctx context.Context, organization string, registryName RegistryName, namespace string, name string, options *RegistryProviderReadOptions) (*RegistryProvider, error) { + if !validStringID(&organization) { + return nil, ErrInvalidOrg + } + if !validString(&name) { + return nil, ErrRequiredName + } + if !validStringID(&name) { + return nil, ErrInvalidName + } + if !validString(&namespace) { + return nil, errors.New("namespace is required") + } + if !validStringID(&namespace) { + return nil, errors.New("invalid value for namespace") + } + if !validString((*string)(®istryName)) { + return nil, errors.New("registry-name is required") + } + + u := fmt.Sprintf( + "organizations/%s/registry-providers/%s/%s/%s", + url.QueryEscape(organization), + url.QueryEscape(string(registryName)), + url.QueryEscape(namespace), + url.QueryEscape(name), + ) + req, err := r.client.newRequest("GET", u, options) + if err != nil { + return nil, err + } + + prv := &RegistryProvider{} + err = r.client.do(ctx, req, prv) + if err != nil { + return nil, err + } + + return prv, nil +} + +func (r *registryProviders) Delete(ctx context.Context, organization string, registryName RegistryName, namespace string, name string) error { + if !validStringID(&organization) { + return ErrInvalidOrg + } + if !validString(&name) { + return ErrRequiredName + } + if !validStringID(&name) { + return ErrInvalidName + } + if !validString(&namespace) { + return errors.New("namespace is required") + } + if !validStringID(&namespace) { + return errors.New("invalid value for namespace") + } + if !validString((*string)(®istryName)) { + return errors.New("registry-name is required") + } + + u := fmt.Sprintf( + "organizations/%s/registry-providers/%s/%s/%s", + url.QueryEscape(organization), + url.QueryEscape(string(registryName)), + url.QueryEscape(namespace), + url.QueryEscape(name), + ) + req, err := r.client.newRequest("DELETE", u, nil) + if err != nil { + return err + } + + return r.client.do(ctx, req, nil) +} diff --git a/registry_provider_integration_test.go b/registry_provider_integration_test.go new file mode 100644 index 000000000..9f26bda28 --- /dev/null +++ b/registry_provider_integration_test.go @@ -0,0 +1,442 @@ +package tfe + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRegistryProvidersList(t *testing.T) { + client := testClient(t) + ctx := context.Background() + + t.Run("with providers", func(t *testing.T) { + orgTest, orgTestCleanup := createOrganization(t, client) + defer orgTestCleanup() + + createN := 10 + providers := make([]*RegistryProvider, 0) + // these providers will be destroyed when the org is cleaned up + for i := 0; i < createN; i++ { + providerTest, _ := createPublicRegistryProvider(t, client, orgTest) + providers = append(providers, providerTest) + } + for i := 0; i < createN; i++ { + providerTest, _ := createPrivateRegistryProvider(t, client, orgTest) + providers = append(providers, providerTest) + } + providerN := len(providers) + publicProviderN := createN + + t.Run("returns all providers", func(t *testing.T) { + returnedProviders, err := client.RegistryProviders.List(ctx, orgTest.Name, &RegistryProviderListOptions{ + ListOptions: ListOptions{ + PageNumber: 0, + PageSize: providerN, + }, + }) + require.NoError(t, err) + assert.NotEmpty(t, returnedProviders.Items) + assert.Equal(t, providerN, returnedProviders.TotalCount) + assert.Equal(t, 1, returnedProviders.TotalPages) + for _, rp := range returnedProviders.Items { + foundProvider := false + for _, p := range providers { + if rp.ID == p.ID { + foundProvider = true + break + } + } + assert.True(t, foundProvider, "Expected to find provider %s but did not:\nexpected:\n%v\nreturned\n%v", rp.ID, providers, returnedProviders) + } + }) + + t.Run("returns pages", func(t *testing.T) { + pageN := 2 + pageSize := providerN / pageN + + for page := 0; page < pageN; page++ { + testName := fmt.Sprintf("returns page %d of providers", page) + t.Run(testName, func(t *testing.T) { + returnedProviders, err := client.RegistryProviders.List(ctx, orgTest.Name, &RegistryProviderListOptions{ + ListOptions: ListOptions{ + PageNumber: page, + PageSize: pageSize, + }, + }) + require.NoError(t, err) + assert.NotEmpty(t, returnedProviders.Items) + assert.Equal(t, providerN, returnedProviders.TotalCount) + assert.Equal(t, pageN, returnedProviders.TotalPages) + assert.Equal(t, pageSize, len(returnedProviders.Items)) + for _, rp := range returnedProviders.Items { + foundProvider := false + for _, p := range providers { + if rp.ID == p.ID { + foundProvider = true + break + } + } + assert.True(t, foundProvider, "Expected to find provider %s but did not:\nexpected:\n%v\nreturned\n%v", rp.ID, providers, returnedProviders) + } + }) + } + }) + + t.Run("filters on registry name", func(t *testing.T) { + publicName := PublicRegistry + returnedProviders, err := client.RegistryProviders.List(ctx, orgTest.Name, &RegistryProviderListOptions{ + RegistryName: &publicName, + ListOptions: ListOptions{ + PageNumber: 0, + PageSize: providerN, + }, + }) + require.NoError(t, err) + assert.NotEmpty(t, returnedProviders.Items) + assert.Equal(t, publicProviderN, returnedProviders.TotalCount) + assert.Equal(t, 1, returnedProviders.TotalPages) + for _, rp := range returnedProviders.Items { + foundProvider := false + for _, p := range providers { + if rp.ID == p.ID { + foundProvider = true + break + } + } + assert.Equal(t, publicName, rp.RegistryName) + assert.True(t, foundProvider, "Expected to find provider %s but did not:\nexpected:\n%v\nreturned\n%v", rp.ID, providers, returnedProviders) + } + }) + + t.Run("searches", func(t *testing.T) { + expectedProvider := providers[0] + returnedProviders, err := client.RegistryProviders.List(ctx, orgTest.Name, &RegistryProviderListOptions{ + Search: &expectedProvider.Name, + ListOptions: ListOptions{ + PageNumber: 0, + PageSize: providerN, + }, + }) + require.NoError(t, err) + assert.NotEmpty(t, returnedProviders.Items) + assert.Equal(t, 1, returnedProviders.TotalCount) + assert.Equal(t, 1, returnedProviders.TotalPages) + foundProvider := returnedProviders.Items[0] + + assert.Equal(t, foundProvider.ID, expectedProvider.ID, "Expected to find provider %s but did not:\nexpected:\n%v\nreturned\n%v", expectedProvider.ID, providers, returnedProviders) + }) + }) + + t.Run("without providers", func(t *testing.T) { + orgTest, orgTestCleanup := createOrganization(t, client) + defer orgTestCleanup() + + providers, err := client.RegistryProviders.List(ctx, orgTest.Name, nil) + require.NoError(t, err) + assert.Empty(t, providers.Items) + assert.Equal(t, 0, providers.TotalCount) + assert.Equal(t, 0, providers.TotalPages) + }) +} + +func TestRegistryProvidersCreate(t *testing.T) { + client := testClient(t) + ctx := context.Background() + + orgTest, orgTestCleanup := createOrganization(t, client) + defer orgTestCleanup() + + publicName := PublicRegistry + privateName := PrivateRegistry + + t.Run("with valid options", func(t *testing.T) { + + publicProviderOptions := RegistryProviderCreateOptions{ + Name: String("provider_name"), + Namespace: String("public_namespace"), + RegistryName: &publicName, + } + privateProviderOptions := RegistryProviderCreateOptions{ + Name: String("provider_name"), + Namespace: &orgTest.Name, + RegistryName: &privateName, + } + + registryOptions := []RegistryProviderCreateOptions{publicProviderOptions, privateProviderOptions} + + for _, options := range registryOptions { + testName := fmt.Sprintf("with %s provider", *options.RegistryName) + t.Run(testName, func(t *testing.T) { + prv, err := client.RegistryProviders.Create(ctx, orgTest.Name, options) + require.NoError(t, err) + assert.NotEmpty(t, prv.ID) + assert.Equal(t, *options.Name, prv.Name) + assert.Equal(t, *options.Namespace, prv.Namespace) + assert.Equal(t, *options.RegistryName, prv.RegistryName) + + t.Run("permissions are properly decoded", func(t *testing.T) { + assert.True(t, prv.Permissions.CanDelete) + }) + + t.Run("relationships are properly decoded", func(t *testing.T) { + assert.Equal(t, orgTest.Name, prv.Organization.Name) + }) + + t.Run("timestamps are properly decoded", func(t *testing.T) { + assert.NotEmpty(t, prv.CreatedAt) + assert.NotEmpty(t, prv.UpdatedAt) + }) + }) + } + }) + + t.Run("with invalid options", func(t *testing.T) { + t.Run("without a name", func(t *testing.T) { + options := RegistryProviderCreateOptions{ + Namespace: String("namespace"), + RegistryName: &publicName, + } + rm, err := client.RegistryProviders.Create(ctx, orgTest.Name, options) + assert.Nil(t, rm) + assert.EqualError(t, err, ErrRequiredName.Error()) + }) + + t.Run("with an invalid name", func(t *testing.T) { + options := RegistryProviderCreateOptions{ + Name: String("invalid name"), + Namespace: String("namespace"), + RegistryName: &publicName, + } + rm, err := client.RegistryProviders.Create(ctx, orgTest.Name, options) + assert.Nil(t, rm) + assert.EqualError(t, err, ErrInvalidName.Error()) + }) + + t.Run("without a namespace", func(t *testing.T) { + options := RegistryProviderCreateOptions{ + Name: String("name"), + RegistryName: &publicName, + } + rm, err := client.RegistryProviders.Create(ctx, orgTest.Name, options) + assert.Nil(t, rm) + assert.EqualError(t, err, "namespace is required") + }) + + t.Run("with an invalid namespace", func(t *testing.T) { + options := RegistryProviderCreateOptions{ + Name: String("name"), + Namespace: String("invalid namespace"), + RegistryName: &publicName, + } + rm, err := client.RegistryProviders.Create(ctx, orgTest.Name, options) + assert.Nil(t, rm) + assert.EqualError(t, err, "invalid value for namespace") + }) + + t.Run("without a registry-name", func(t *testing.T) { + options := RegistryProviderCreateOptions{ + Name: String("name"), + Namespace: String("namespace"), + } + rm, err := client.RegistryProviders.Create(ctx, orgTest.Name, options) + assert.Nil(t, rm) + assert.EqualError(t, err, "registry-name is required") + }) + }) + + t.Run("without a valid organization", func(t *testing.T) { + options := RegistryProviderCreateOptions{ + Name: String("name"), + Namespace: String("namespace"), + RegistryName: &publicName, + } + rm, err := client.RegistryProviders.Create(ctx, badIdentifier, options) + assert.Nil(t, rm) + assert.EqualError(t, err, ErrInvalidOrg.Error()) + }) + + t.Run("without a matching namespace organization.name for private registry", func(t *testing.T) { + options := RegistryProviderCreateOptions{ + Name: String("name"), + Namespace: String("namespace"), + RegistryName: &privateName, + } + rm, err := client.RegistryProviders.Create(ctx, orgTest.Name, options) + assert.Nil(t, rm) + assert.EqualError(t, err, "namespace must match organization name for private providers") + }) +} + +func TestRegistryProvidersRead(t *testing.T) { + client := testClient(t) + ctx := context.Background() + + orgTest, orgTestCleanup := createOrganization(t, client) + defer orgTestCleanup() + + type ProviderContext struct { + ProviderCreator func(t *testing.T, client *Client, org *Organization) (*RegistryProvider, func()) + RegistryName RegistryName + } + + providerContexts := []ProviderContext{ + { + ProviderCreator: createPublicRegistryProvider, + RegistryName: PublicRegistry, + }, + { + ProviderCreator: createPrivateRegistryProvider, + RegistryName: PrivateRegistry, + }, + } + + for _, prvCtx := range providerContexts { + testName := fmt.Sprintf("with %s provider", prvCtx.RegistryName) + t.Run(testName, func(t *testing.T) { + t.Run("with valid provider", func(t *testing.T) { + registryProviderTest, providerTestCleanup := prvCtx.ProviderCreator(t, client, orgTest) + defer providerTestCleanup() + + prv, err := client.RegistryProviders.Read(ctx, orgTest.Name, registryProviderTest.RegistryName, registryProviderTest.Namespace, registryProviderTest.Name, nil) + assert.NoError(t, err) + assert.NotEmpty(t, prv.ID) + assert.Equal(t, registryProviderTest.Name, prv.Name) + assert.Equal(t, registryProviderTest.Namespace, prv.Namespace) + assert.Equal(t, registryProviderTest.RegistryName, prv.RegistryName) + + t.Run("permissions are properly decoded", func(t *testing.T) { + assert.True(t, prv.Permissions.CanDelete) + }) + + t.Run("relationships are properly decoded", func(t *testing.T) { + assert.Equal(t, orgTest.Name, prv.Organization.Name) + }) + + t.Run("timestamps are properly decoded", func(t *testing.T) { + assert.NotEmpty(t, prv.CreatedAt) + assert.NotEmpty(t, prv.UpdatedAt) + }) + }) + + t.Run("when the registry provider does not exist", func(t *testing.T) { + _, err := client.RegistryProviders.Read(ctx, orgTest.Name, prvCtx.RegistryName, "nonexistent", "nonexistent", nil) + assert.Error(t, err) + // Local TFC/E will return a forbidden here when TFC/E is in development mode + // In non development mode this returns a 404 + assert.Equal(t, ErrResourceNotFound, err) + }) + + t.Run("without a name", func(t *testing.T) { + _, err := client.RegistryProviders.Read(ctx, orgTest.Name, prvCtx.RegistryName, "namespace", "", nil) + assert.EqualError(t, err, ErrRequiredName.Error()) + }) + + t.Run("with an invalid name", func(t *testing.T) { + _, err := client.RegistryProviders.Read(ctx, orgTest.Name, prvCtx.RegistryName, "namespace", badIdentifier, nil) + assert.EqualError(t, err, ErrInvalidName.Error()) + }) + + t.Run("without a namespace", func(t *testing.T) { + _, err := client.RegistryProviders.Read(ctx, orgTest.Name, prvCtx.RegistryName, "", "name", nil) + assert.EqualError(t, err, "namespace is required") + }) + + t.Run("with an invalid namespace", func(t *testing.T) { + _, err := client.RegistryProviders.Read(ctx, orgTest.Name, prvCtx.RegistryName, badIdentifier, "name", nil) + assert.EqualError(t, err, "invalid value for namespace") + }) + + t.Run("without a registry-name", func(t *testing.T) { + _, err := client.RegistryProviders.Read(ctx, orgTest.Name, "", "namespace", "name", nil) + assert.EqualError(t, err, "registry-name is required") + }) + + t.Run("without a valid organization", func(t *testing.T) { + _, err := client.RegistryProviders.Read(ctx, badIdentifier, prvCtx.RegistryName, "namespace", "name", nil) + assert.EqualError(t, err, ErrInvalidOrg.Error()) + }) + }) + } +} + +func TestRegistryProvidersDelete(t *testing.T) { + client := testClient(t) + ctx := context.Background() + + orgTest, orgTestCleanup := createOrganization(t, client) + defer orgTestCleanup() + + type ProviderContext struct { + ProviderCreator func(t *testing.T, client *Client, org *Organization) (*RegistryProvider, func()) + RegistryName RegistryName + } + + providerContexts := []ProviderContext{ + { + ProviderCreator: createPublicRegistryProvider, + RegistryName: PublicRegistry, + }, + { + ProviderCreator: createPrivateRegistryProvider, + RegistryName: PrivateRegistry, + }, + } + + for _, prvCtx := range providerContexts { + testName := fmt.Sprintf("with %s provider", prvCtx.RegistryName) + t.Run(testName, func(t *testing.T) { + t.Run("with valid provider", func(t *testing.T) { + registryProviderTest, _ := prvCtx.ProviderCreator(t, client, orgTest) + + err := client.RegistryProviders.Delete(ctx, orgTest.Name, registryProviderTest.RegistryName, registryProviderTest.Namespace, registryProviderTest.Name) + require.NoError(t, err) + + prv, err := client.RegistryProviders.Read(ctx, orgTest.Name, registryProviderTest.RegistryName, registryProviderTest.Namespace, registryProviderTest.Name, nil) + assert.Nil(t, prv) + assert.Error(t, err) + }) + + t.Run("when the registry provider does not exist", func(t *testing.T) { + err := client.RegistryProviders.Delete(ctx, orgTest.Name, prvCtx.RegistryName, "nonexistent", "nonexistent") + assert.Error(t, err) + // Local TFC/E will return a forbidden here when TFC/E is in development mode + // In non development mode this returns a 404 + assert.Equal(t, ErrResourceNotFound, err) + }) + + t.Run("without a name", func(t *testing.T) { + err := client.RegistryProviders.Delete(ctx, orgTest.Name, prvCtx.RegistryName, "namespace", "") + assert.EqualError(t, err, ErrRequiredName.Error()) + }) + + t.Run("with an invalid name", func(t *testing.T) { + err := client.RegistryProviders.Delete(ctx, orgTest.Name, prvCtx.RegistryName, "namespace", badIdentifier) + assert.EqualError(t, err, ErrInvalidName.Error()) + }) + + t.Run("without a namespace", func(t *testing.T) { + err := client.RegistryProviders.Delete(ctx, orgTest.Name, prvCtx.RegistryName, "", "name") + assert.EqualError(t, err, "namespace is required") + }) + + t.Run("with an invalid namespace", func(t *testing.T) { + err := client.RegistryProviders.Delete(ctx, orgTest.Name, prvCtx.RegistryName, badIdentifier, "name") + assert.EqualError(t, err, "invalid value for namespace") + }) + + t.Run("without a registry-name", func(t *testing.T) { + err := client.RegistryProviders.Delete(ctx, orgTest.Name, "", "namespace", "name") + assert.EqualError(t, err, "registry-name is required") + }) + + t.Run("without a valid organization", func(t *testing.T) { + err := client.RegistryProviders.Delete(ctx, badIdentifier, prvCtx.RegistryName, "namespace", "name") + assert.EqualError(t, err, ErrInvalidOrg.Error()) + }) + }) + } +} diff --git a/registry_provider_platform.go b/registry_provider_platform.go new file mode 100644 index 000000000..4f6db5f6f --- /dev/null +++ b/registry_provider_platform.go @@ -0,0 +1,16 @@ +package tfe + +// 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"` + + // Relations + RegistryProviderVersion *RegistryProviderVersion `jsonapi:"relation,registry-provider-version"` + + // Links + Links map[string]interface{} `jsonapi:"links,omitempty"` +} diff --git a/registry_provider_version.go b/registry_provider_version.go new file mode 100644 index 000000000..a7cf8311f --- /dev/null +++ b/registry_provider_version.go @@ -0,0 +1,16 @@ +package tfe + +// RegistryProviderVersion represents a registry provider version +type RegistryProviderVersion struct { + ID string `jsonapi:"primary,registry-provider-versions"` + Version string `jsonapi:"attr,version"` + KeyID string `jsonapi:"attr,key-id"` + Protocols []string `jsonapi:"attr,protocols,omitempty"` + + // Relations + RegistryProvider *RegistryProvider `jsonapi:"relation,registry-provider"` + RegistryProviderPlatforms []RegistryProviderPlatform `jsonapi:"relation,registry-provider-platform"` + + // Links + Links map[string]interface{} `jsonapi:"links,omitempty"` +} diff --git a/tfe.go b/tfe.go index 22512c0cf..d8c5e72c3 100644 --- a/tfe.go +++ b/tfe.go @@ -128,6 +128,7 @@ type Client struct { PolicySetVersions PolicySetVersions PolicySets PolicySets RegistryModules RegistryModules + RegistryProviders RegistryProviders Runs Runs RunTasks RunTasks RunTriggers RunTriggers @@ -272,6 +273,7 @@ func NewClient(cfg *Config) (*Client, error) { client.PolicySetVersions = &policySetVersions{client: client} client.PolicySets = &policySets{client: client} client.RegistryModules = ®istryModules{client: client} + client.RegistryProviders = ®istryProviders{client: client} client.Runs = &runs{client: client} client.RunTasks = &runTasks{client: client} client.RunTriggers = &runTriggers{client: client}