From e142f48b3b900ce0b008a0f76c3eb1971b751aa4 Mon Sep 17 00:00:00 2001 From: Sebastian Rivera Date: Fri, 15 Jul 2022 09:51:30 -0400 Subject: [PATCH 1/3] Modify codeowners to have wildcard access, removed duplicate CODEOWNERS file --- .github/CODEOWNERS | 3 --- CODEOWNERS | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 653fd5654..000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,3 +0,0 @@ - -# This whitelists the whole repo -* @hashicorp/tf-practitioner diff --git a/CODEOWNERS b/CODEOWNERS index 989a818a8..0ff5bf358 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,2 +1 @@ -*.go @hashicorp/tf-cli -*.md @hashicorp/tf-cli +* @hashicorp/tf-cli From 4350dd84bdcc03fbef93bc9da00ff74a27ef31ec Mon Sep 17 00:00:00 2001 From: uk1288 Date: Mon, 11 Jul 2022 11:06:57 -0400 Subject: [PATCH 2/3] add support for both public and private module creation without VCS --- CHANGELOG.md | 2 + errors.go | 2 + helper_test.go | 12 +- registry_module.go | 21 ++++ registry_module_integration_test.go | 170 +++++++++++++++++++++++----- 5 files changed, 178 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 393cae96a..b61421570 100644 --- a/CHANGELOG.md +++ b/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` without VCS by @Uk1288 [#460](https://github.com/hashicorp/go-tfe/pull/460) # v1.5.0 diff --git a/errors.go b/errors.go index 7fe3b9157..e344b80ef 100644 --- a/errors.go +++ b/errors.go @@ -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 diff --git a/helper_test.go b/helper_test.go index 3c2d15df9..025ac3778 100644 --- a/helper_test.go +++ b/helper_test.go @@ -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 { @@ -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) diff --git a/registry_module.go b/registry_module.go index 08923cdf5..464ed1810 100644 --- a/registry_module.go +++ b/registry_module.go @@ -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 @@ -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 } diff --git a/registry_module_integration_test.go b/registry_module_integration_test.go index f38922370..bd485dba3 100644 --- a/registry_module_integration_test.go +++ b/registry_module_integration_test.go @@ -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) { @@ -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) }) }) @@ -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) { @@ -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) { @@ -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, @@ -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, @@ -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, @@ -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, @@ -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) @@ -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{ @@ -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"), @@ -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{ From 599ef7c83c02c7d8a662aa1a3e6a80c1c24c8dd2 Mon Sep 17 00:00:00 2001 From: uk1288 Date: Tue, 19 Jul 2022 10:36:48 -0400 Subject: [PATCH 3/3] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b61421570..e911ae3ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ # 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` without VCS by @Uk1288 [#460](https://github.com/hashicorp/go-tfe/pull/460) +* 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