diff --git a/errors.go b/errors.go index a04a4d17e..c1e3f28fe 100644 --- a/errors.go +++ b/errors.go @@ -29,6 +29,8 @@ var ( ErrUnsupportedRunTriggerType = errors.New(`"RunTriggerType" must be "inbound" when requesting "include" query params`) ErrUnsupportedBothTriggerPatternsAndPrefixes = errors.New(`"TriggerPatterns" and "TriggerPrefixes" cannot be populated at the same time`) + + ErrUnsupportedBothNamespaceAndPrivateRegistryName = errors.New(`"Namespace" cannot be populated when "RegistryName" is "private"`) ) // Library errors that usually indicate a bug in the implementation of go-tfe @@ -294,4 +296,6 @@ var ( ErrRequiredFilename = errors.New("filename is required") ErrInvalidAsciiArmor = errors.New("ascii armor is invalid") + + ErrRequiredNamespace = errors.New("namespace is required for public registry") ) diff --git a/registry_module.go b/registry_module.go index 1e0605f3d..9089633f7 100644 --- a/registry_module.go +++ b/registry_module.go @@ -95,6 +95,8 @@ type RegistryModule struct { ID string `jsonapi:"primary,registry-modules"` Name string `jsonapi:"attr,name"` Provider string `jsonapi:"attr,provider"` + RegistryName RegistryName `jsonapi:"attr,registry-name"` + Namespace string `jsonapi:"attr,namespace"` Permissions *RegistryModulePermissions `jsonapi:"attr,permissions"` Status RegistryModuleStatus `jsonapi:"attr,status"` VCSRepo *VCSRepo `jsonapi:"attr,vcs-repo"` @@ -150,6 +152,11 @@ type RegistryModuleCreateOptions struct { Name *string `jsonapi:"attr,name"` // Required: Provider *string `jsonapi:"attr,provider"` + // Optional: Whether this is a publicly maintained module or private. Must be either public or private. + // Defaults to private if not specified + RegistryName RegistryName `jsonapi:"attr,registry-name,omitempty"` + // Optional: The namespace of this module. Required for public modules only. + Namespace string `jsonapi:"attr,namespace,omitempty"` } // RegistryModuleCreateVersionOptions is used when creating a registry module version @@ -436,6 +443,22 @@ func (o RegistryModuleCreateOptions) valid() error { if !validStringID(o.Provider) { return ErrInvalidProvider } + + // RegistryName is optional, only validate if specified + if validString((*string)(&o.RegistryName)) { + registryNamesMap := map[RegistryName]RegistryName{PublicRegistry: PublicRegistry, PrivateRegistry: PrivateRegistry} + if _, ok := registryNamesMap[o.RegistryName]; !ok { + return ErrInvalidRegistryName + } + + if o.RegistryName == PublicRegistry && !validString(&o.Namespace) { + return ErrRequiredNamespace + } + + if o.RegistryName == PrivateRegistry && validString(&o.Namespace) { + return ErrUnsupportedBothNamespaceAndPrivateRegistryName + } + } return nil } diff --git a/registry_module_integration_test.go b/registry_module_integration_test.go index 5e4711405..96fe57dd7 100644 --- a/registry_module_integration_test.go +++ b/registry_module_integration_test.go @@ -71,29 +71,72 @@ func TestRegistryModulesCreate(t *testing.T) { defer orgTestCleanup() t.Run("with valid options", func(t *testing.T) { - options := RegistryModuleCreateOptions{ - Name: String("name"), - Provider: String("provider"), + assertRegistryModuleAttributes := func(t *testing.T, registryModule *RegistryModule) { + t.Run("permissions are properly decoded", func(t *testing.T) { + assert.True(t, registryModule.Permissions.CanDelete) + assert.True(t, registryModule.Permissions.CanResync) + assert.True(t, registryModule.Permissions.CanRetry) + }) + + t.Run("relationships are properly decoded", func(t *testing.T) { + assert.Equal(t, orgTest.Name, registryModule.Organization.Name) + }) + + t.Run("timestamps are properly decoded", func(t *testing.T) { + assert.NotEmpty(t, registryModule.CreatedAt) + assert.NotEmpty(t, registryModule.UpdatedAt) + }) } - rm, err := client.RegistryModules.Create(ctx, orgTest.Name, options) - require.NoError(t, err) - assert.NotEmpty(t, rm.ID) - assert.Equal(t, *options.Name, rm.Name) - assert.Equal(t, *options.Provider, rm.Provider) - t.Run("permissions are properly decoded", func(t *testing.T) { - assert.True(t, rm.Permissions.CanDelete) - assert.True(t, rm.Permissions.CanResync) - assert.True(t, rm.Permissions.CanRetry) + t.Run("without RegistryName", func(t *testing.T) { + options := RegistryModuleCreateOptions{ + Name: String("name"), + Provider: String("provider"), + } + rm, err := client.RegistryModules.Create(ctx, orgTest.Name, options) + require.NoError(t, err) + assert.NotEmpty(t, rm.ID) + assert.Equal(t, *options.Name, rm.Name) + assert.Equal(t, *options.Provider, rm.Provider) + assert.Equal(t, PrivateRegistry, rm.RegistryName) + assert.Equal(t, orgTest.Name, rm.Namespace) + + assertRegistryModuleAttributes(t, rm) }) - t.Run("relationships are properly decoded", func(t *testing.T) { - assert.Equal(t, orgTest.Name, rm.Organization.Name) + t.Run("with private RegistryName", func(t *testing.T) { + options := RegistryModuleCreateOptions{ + Name: String("another_name"), + Provider: String("provider"), + RegistryName: PrivateRegistry, + } + rm, err := client.RegistryModules.Create(ctx, orgTest.Name, options) + require.NoError(t, err) + assert.NotEmpty(t, rm.ID) + assert.Equal(t, *options.Name, rm.Name) + assert.Equal(t, *options.Provider, rm.Provider) + assert.Equal(t, options.RegistryName, rm.RegistryName) + assert.Equal(t, orgTest.Name, rm.Namespace) + + assertRegistryModuleAttributes(t, rm) }) - t.Run("timestamps are properly decoded", func(t *testing.T) { - assert.NotEmpty(t, rm.CreatedAt) - assert.NotEmpty(t, rm.UpdatedAt) + t.Run("with public RegistryName", func(t *testing.T) { + options := RegistryModuleCreateOptions{ + Name: String("vpc"), + Provider: String("aws"), + RegistryName: PublicRegistry, + Namespace: "terraform-aws-modules", + } + rm, err := client.RegistryModules.Create(ctx, orgTest.Name, options) + require.NoError(t, err) + assert.NotEmpty(t, rm.ID) + assert.Equal(t, *options.Name, rm.Name) + assert.Equal(t, *options.Provider, rm.Provider) + assert.Equal(t, options.RegistryName, rm.RegistryName) + assert.Equal(t, options.Namespace, rm.Namespace) + + assertRegistryModuleAttributes(t, rm) }) }) @@ -135,6 +178,40 @@ func TestRegistryModulesCreate(t *testing.T) { assert.Nil(t, rm) assert.Equal(t, err, ErrInvalidProvider) }) + + t.Run("with an invalid registry name", func(t *testing.T) { + options := RegistryModuleCreateOptions{ + Name: String("name"), + Provider: String("provider"), + RegistryName: "PRIVATE", + } + rm, err := client.RegistryModules.Create(ctx, orgTest.Name, options) + assert.Nil(t, rm) + assert.Equal(t, err, ErrInvalidRegistryName) + }) + + t.Run("without a namespace for public registry name", func(t *testing.T) { + options := RegistryModuleCreateOptions{ + Name: String("name"), + Provider: String("provider"), + RegistryName: PublicRegistry, + } + rm, err := client.RegistryModules.Create(ctx, orgTest.Name, options) + assert.Nil(t, rm) + assert.Equal(t, err, ErrRequiredNamespace) + }) + + t.Run("with a namespace for private registry name", func(t *testing.T) { + options := RegistryModuleCreateOptions{ + Name: String("name"), + Provider: String("provider"), + RegistryName: PrivateRegistry, + Namespace: "namespace", + } + rm, err := client.RegistryModules.Create(ctx, orgTest.Name, options) + assert.Nil(t, rm) + assert.Equal(t, err, ErrUnsupportedBothNamespaceAndPrivateRegistryName) + }) }) t.Run("without a valid organization", func(t *testing.T) {