Skip to content

Commit

Permalink
Merge pull request #460 from hashicorp/uk1288-support-public-modules
Browse files Browse the repository at this point in the history
add support for both public and private module creation with no VCS connection
  • Loading branch information
Uk1288 committed Jul 20, 2022
2 parents 007ee45 + ddfffc7 commit 392b639
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 29 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
@@ -1,7 +1,9 @@
# Unreleased

# v1.6.0
## Enhancements
* Add `Name` field to `OAuthClient` by @barrettclark [#466](https://github.com/hashicorp/go-tfe/pull/466)
* Add support for creating both public and private `RegistryModule` with no VCS connection by @Uk1288 [#460](https://github.com/hashicorp/go-tfe/pull/460)
* Add `ConfigurationSourceAdo` configuration source option by @mjyocca [#467](https://github.com/hashicorp/go-tfe/pull/467)

# v1.5.0
Expand Down
2 changes: 2 additions & 0 deletions errors.go
Expand Up @@ -35,6 +35,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
Expand Down
12 changes: 9 additions & 3 deletions helper_test.go
Expand Up @@ -878,7 +878,7 @@ func createPlanExport(t *testing.T, client *Client, r *Run) (*PlanExport, func()
}
}

func createRegistryModule(t *testing.T, client *Client, org *Organization) (*RegistryModule, func()) {
func createRegistryModule(t *testing.T, client *Client, org *Organization, registryName RegistryName) (*RegistryModule, func()) {
var orgCleanup func()

if org == nil {
Expand All @@ -888,9 +888,15 @@ func createRegistryModule(t *testing.T, client *Client, org *Organization) (*Reg
ctx := context.Background()

options := RegistryModuleCreateOptions{
Name: String(randomString(t)),
Provider: String("provider"),
Name: String(randomString(t)),
Provider: String("provider"),
RegistryName: registryName,
}

if registryName == PublicRegistry {
options.Namespace = "namespace"
}

rm, err := client.RegistryModules.Create(ctx, org.Name, options)
if err != nil {
t.Fatal(err)
Expand Down
21 changes: 21 additions & 0 deletions registry_module.go
Expand Up @@ -159,6 +159,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
Expand Down Expand Up @@ -471,6 +476,22 @@ func (o RegistryModuleCreateOptions) valid() error {
if !validStringID(o.Provider) {
return ErrInvalidProvider
}

switch o.RegistryName {
case PublicRegistry:
if !validString(&o.Namespace) {
return ErrRequiredNamespace
}
case PrivateRegistry:
if validString(&o.Namespace) {
return ErrUnsupportedBothNamespaceAndPrivateRegistryName
}
case "":
// no-op: RegistryName is optional
// for all other string
default:
return ErrInvalidRegistryName
}
return nil
}

Expand Down
170 changes: 144 additions & 26 deletions registry_module_integration_test.go
Expand Up @@ -25,9 +25,9 @@ func TestRegistryModulesList(t *testing.T) {
orgTest, orgTestCleanup := createOrganization(t, client)
defer orgTestCleanup()

registryModuleTest1, registryModuleTest1Cleanup := createRegistryModule(t, client, orgTest)
registryModuleTest1, registryModuleTest1Cleanup := createRegistryModule(t, client, orgTest, PrivateRegistry)
defer registryModuleTest1Cleanup()
registryModuleTest2, registryModuleTest2Cleanup := createRegistryModule(t, client, orgTest)
registryModuleTest2, registryModuleTest2Cleanup := createRegistryModule(t, client, orgTest, PrivateRegistry)
defer registryModuleTest2Cleanup()

t.Run("with no list options", func(t *testing.T) {
Expand Down Expand Up @@ -71,29 +71,74 @@ 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) {
require.NotEmpty(t, registryModule.Permissions)
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) {
require.NotEmpty(t, registryModule.Organization)
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)
})
})

Expand Down Expand Up @@ -135,6 +180,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) {
Expand All @@ -155,7 +234,7 @@ func TestRegistryModulesCreateVersion(t *testing.T) {
orgTest, orgTestCleanup := createOrganization(t, client)
defer orgTestCleanup()

registryModuleTest, registryModuleTestCleanup := createRegistryModule(t, client, orgTest)
registryModuleTest, registryModuleTestCleanup := createRegistryModule(t, client, orgTest, PrivateRegistry)
defer registryModuleTestCleanup()

t.Run("with valid options", func(t *testing.T) {
Expand Down Expand Up @@ -393,9 +472,12 @@ func TestRegistryModulesRead(t *testing.T) {
orgTest, orgTestCleanup := createOrganization(t, client)
defer orgTestCleanup()

registryModuleTest, registryModuleTestCleanup := createRegistryModule(t, client, orgTest)
registryModuleTest, registryModuleTestCleanup := createRegistryModule(t, client, orgTest, PrivateRegistry)
defer registryModuleTestCleanup()

publicRegistryModuleTest, publicRegistryModuleTestCleanup := createRegistryModule(t, client, orgTest, PublicRegistry)
defer publicRegistryModuleTestCleanup()

t.Run("with valid name and provider", func(t *testing.T) {
rm, err := client.RegistryModules.Read(ctx, RegistryModuleID{
Organization: orgTest.Name,
Expand All @@ -417,7 +499,7 @@ func TestRegistryModulesRead(t *testing.T) {
})
})

t.Run("with complete registry module ID fields", func(t *testing.T) {
t.Run("with complete registry module ID fields for private module", func(t *testing.T) {
rm, err := client.RegistryModules.Read(ctx, RegistryModuleID{
Organization: orgTest.Name,
Name: registryModuleTest.Name,
Expand All @@ -442,6 +524,31 @@ func TestRegistryModulesRead(t *testing.T) {
})
})

t.Run("with complete registry module ID fields for public module", func(t *testing.T) {
rm, err := client.RegistryModules.Read(ctx, RegistryModuleID{
Organization: orgTest.Name,
Name: publicRegistryModuleTest.Name,
Provider: publicRegistryModuleTest.Provider,
Namespace: publicRegistryModuleTest.Namespace,
RegistryName: PublicRegistry,
})
require.NoError(t, err)
require.NotEmpty(t, rm)
assert.Equal(t, publicRegistryModuleTest.ID, rm.ID)

t.Run("permissions are properly decoded", func(t *testing.T) {
require.NotEmpty(t, rm.Permissions)
assert.True(t, rm.Permissions.CanDelete)
assert.True(t, rm.Permissions.CanResync)
assert.True(t, rm.Permissions.CanRetry)
})

t.Run("timestamps are properly decoded", func(t *testing.T) {
assert.NotEmpty(t, rm.CreatedAt)
assert.NotEmpty(t, rm.UpdatedAt)
})
})

t.Run("without a name", func(t *testing.T) {
rm, err := client.RegistryModules.Read(ctx, RegistryModuleID{
Organization: orgTest.Name,
Expand Down Expand Up @@ -504,6 +611,17 @@ func TestRegistryModulesRead(t *testing.T) {
assert.EqualError(t, err, ErrInvalidOrg.Error())
})

t.Run("without a valid namespace for public registry module", func(t *testing.T) {
rm, err := client.RegistryModules.Read(ctx, RegistryModuleID{
Organization: orgTest.Name,
Name: publicRegistryModuleTest.Name,
Provider: publicRegistryModuleTest.Provider,
RegistryName: PublicRegistry,
})
assert.Nil(t, rm)
assert.EqualError(t, err, ErrRequiredNamespace.Error())
})

t.Run("when the registry module does not exist", func(t *testing.T) {
rm, err := client.RegistryModules.Read(ctx, RegistryModuleID{
Organization: orgTest.Name,
Expand All @@ -522,7 +640,7 @@ func TestRegistryModulesDelete(t *testing.T) {
orgTest, orgTestCleanup := createOrganization(t, client)
defer orgTestCleanup()

registryModuleTest, _ := createRegistryModule(t, client, orgTest)
registryModuleTest, _ := createRegistryModule(t, client, orgTest, PrivateRegistry)

t.Run("with valid name", func(t *testing.T) {
err := client.RegistryModules.Delete(ctx, orgTest.Name, registryModuleTest.Name)
Expand Down Expand Up @@ -566,7 +684,7 @@ func TestRegistryModulesDeleteProvider(t *testing.T) {
orgTest, orgTestCleanup := createOrganization(t, client)
defer orgTestCleanup()

registryModuleTest, _ := createRegistryModule(t, client, orgTest)
registryModuleTest, _ := createRegistryModule(t, client, orgTest, PrivateRegistry)

t.Run("with valid name and provider", func(t *testing.T) {
err := client.RegistryModules.DeleteProvider(ctx, RegistryModuleID{
Expand Down Expand Up @@ -771,7 +889,7 @@ func TestRegistryModulesUpload(t *testing.T) {
orgTest, orgTestCleanup := createOrganization(t, client)
defer orgTestCleanup()

rm, _ := createRegistryModule(t, client, orgTest)
rm, _ := createRegistryModule(t, client, orgTest, PrivateRegistry)

optionsModuleVersion := RegistryModuleCreateVersionOptions{
Version: String("1.0.0"),
Expand Down Expand Up @@ -873,7 +991,7 @@ func TestRegistryModule_Unmarshal(t *testing.T) {
assert.Equal(t, rm.VersionStatuses[0].Error, "no error")
}

func TestRegistryCreateOptions_Marshal(t *testing.T) {
func TestRegistryCreateWithVCSOptions_Marshal(t *testing.T) {
// https://www.terraform.io/docs/cloud/api/modules.html#sample-payload
opts := RegistryModuleCreateWithVCSConnectionOptions{
VCSRepo: &RegistryModuleVCSRepoOptions{
Expand Down

0 comments on commit 392b639

Please sign in to comment.