Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for both public and private module creation without VCS #460

Merged
merged 4 commits into from Jul 20, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/CODEOWNERS

This file was deleted.

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)

# v1.5.0

Expand Down
3 changes: 1 addition & 2 deletions CODEOWNERS
Validating CODEOWNERS rules …
@@ -1,2 +1 @@
*.go @hashicorp/tf-cli
*.md @hashicorp/tf-cli
* @hashicorp/tf-cli
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll want to require Permissions to not be nil, otherwise this will panic if Permissions was not decoded properly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if my changes in #458 will be overwritten somehow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I will fix the conflicts once you merge the changes.

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here for Organization

})

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