From e142f48b3b900ce0b008a0f76c3eb1971b751aa4 Mon Sep 17 00:00:00 2001 From: Sebastian Rivera Date: Fri, 15 Jul 2022 09:51:30 -0400 Subject: [PATCH 1/4] 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/4] 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/4] 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 From 44786dd8c99d56112d73a9c130ea93595ff96f3d Mon Sep 17 00:00:00 2001 From: Lauren Date: Fri, 22 Jul 2022 15:45:16 -0400 Subject: [PATCH 4/4] update CONTRIBUTING.md to include mockgen link, fix typo --- docs/CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index ed89ede0b..d99a8be3d 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -18,7 +18,7 @@ There are instances where several new resources being added (i.e Workspace Run T ## Running the Linters Locally -1. Ensure you have have [installed golangci-lint](https://golangci-lint.run/usage/install/#local-installation) +1. Ensure you have [installed golangci-lint](https://golangci-lint.run/usage/install/#local-installation) 2. From the CLI, run `golangci-lint run` ## Writing Tests @@ -30,6 +30,7 @@ The test suite contains many acceptance tests that are run against the latest ve We've included VSCode settings to assist with configuring the go extension. For other editors that integrate with the [Go Language Server](https://github.com/golang/tools/tree/master/gopls), the main thing to do is to add the `integration` build tags so that the test files are found by the language server. See `.vscode/settings.json` for more details. ## Generating Mocks +Ensure you have installed the [mockgen](https://github.com/golang/mock) tool. You'll need to generate mocks if an existing endpoint method is modified or a new method is added. To generate mocks, simply run `./generate_mocks.sh` If you're adding a new API resource to go-tfe, you'll need to add the command to `generate_mocks.sh`. For example if someone creates `example_resource.go`, you'll add: