From 6dcc99c28052864769d9497d6612cfcfcc76666d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Hern=C3=A1ndez?= Date: Tue, 15 Nov 2022 12:58:06 +0100 Subject: [PATCH] no code provisioning in registry modules (#562) * add update operations for registry modules * add no code properties to registry modules creation * add changelog entry --- CHANGELOG.md | 1 + mocks/registry_module_mocks.go | 15 ++++++ registry_module.go | 56 ++++++++++++++++++++++ registry_module_integration_test.go | 73 +++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61ddd683f..2f9693b63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Add OPA support to the Policy Set APIs by @mrinalirao [#575](https://github.com/hashicorp/go-tfe/pull/575) * Add OPA support to the Policy APIs by @mrinalirao [#579](https://github.com/hashicorp/go-tfe/pull/579) +* Add support for enabling no-code provisioning in an existing or new `RegistryModule` by @miguelhrocha [#562](https://github.com/hashicorp/go-tfe/pull/562) * Add Policy Evaluation and Policy Set Outcome APIs by @mrinalirao [#583](https://github.com/hashicorp/go-tfe/pull/583) * Add OPA support to Task Stage APIs by @mrinalirao [#584](https://github.com/hashicorp/go-tfe/pull/584) diff --git a/mocks/registry_module_mocks.go b/mocks/registry_module_mocks.go index 185e75f25..de9564551 100644 --- a/mocks/registry_module_mocks.go +++ b/mocks/registry_module_mocks.go @@ -152,6 +152,21 @@ func (mr *MockRegistryModulesMockRecorder) Read(ctx, moduleID interface{}) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockRegistryModules)(nil).Read), ctx, moduleID) } +// Update mocks base method. +func (m *MockRegistryModules) Update(ctx context.Context, moduleID tfe.RegistryModuleID, options tfe.RegistryModuleUpdateOptions) (*tfe.RegistryModule, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Update", ctx, moduleID, options) + ret0, _ := ret[0].(*tfe.RegistryModule) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Update indicates an expected call of Update. +func (mr *MockRegistryModulesMockRecorder) Update(ctx, moduleID, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockRegistryModules)(nil).Update), ctx, moduleID, options) +} + // Upload mocks base method. func (m *MockRegistryModules) Upload(ctx context.Context, rmv tfe.RegistryModuleVersion, path string) error { m.ctrl.T.Helper() diff --git a/registry_module.go b/registry_module.go index 4a75bc3e4..ee8698ff2 100644 --- a/registry_module.go +++ b/registry_module.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "net/http" "net/url" "strings" ) @@ -40,6 +41,9 @@ type RegistryModules interface { // Delete a specific registry module version DeleteVersion(ctx context.Context, moduleID RegistryModuleID, version string) error + // Update properties of a registry module + Update(ctx context.Context, moduleID RegistryModuleID, options RegistryModuleUpdateOptions) (*RegistryModule, error) + // Upload Terraform configuration files for the provided registry module version. It // requires a path to the configuration files on disk, which will be packaged by // hashicorp/go-slug before being uploaded. @@ -104,6 +108,7 @@ type RegistryModule struct { Provider string `jsonapi:"attr,provider"` RegistryName RegistryName `jsonapi:"attr,registry-name"` Namespace string `jsonapi:"attr,namespace"` + NoCode bool `jsonapi:"attr,no-code"` Permissions *RegistryModulePermissions `jsonapi:"attr,permissions"` Status RegistryModuleStatus `jsonapi:"attr,status"` VCSRepo *VCSRepo `jsonapi:"attr,vcs-repo"` @@ -164,6 +169,9 @@ type RegistryModuleCreateOptions struct { RegistryName RegistryName `jsonapi:"attr,registry-name,omitempty"` // Optional: The namespace of this module. Required for public modules only. Namespace string `jsonapi:"attr,namespace,omitempty"` + // Optional: If set to true the module is enabled for no-code provisioning. + // **Note: This field is still in BETA and subject to change.** + NoCode bool `jsonapi:"attr,no-code"` } // RegistryModuleCreateVersionOptions is used when creating a registry module version @@ -189,6 +197,19 @@ type RegistryModuleCreateWithVCSConnectionOptions struct { VCSRepo *RegistryModuleVCSRepoOptions `jsonapi:"attr,vcs-repo"` } +// RegistryModuleCreateVersionOptions is used when updating a registry module +type RegistryModuleUpdateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-updating + Type string `jsonapi:"primary,registry-modules"` + + // Optional: Flag to enable no-code provisioning for the whole module. + // **Note: This field is still in BETA and subject to change.** + NoCode *bool `jsonapi:"attr,no-code,omitempty"` +} + type RegistryModuleVCSRepoOptions struct { Identifier *string `json:"identifier"` // Required OAuthTokenID *string `json:"oauth-token-id"` // Required @@ -265,6 +286,41 @@ func (r *registryModules) Create(ctx context.Context, organization string, optio return rm, nil } +func (r *registryModules) Update(ctx context.Context, moduleID RegistryModuleID, options RegistryModuleUpdateOptions) (*RegistryModule, error) { + if err := moduleID.valid(); err != nil { + return nil, err + } + + if moduleID.RegistryName == "" { + log.Println("[WARN] Support for using the RegistryModuleID without RegistryName is deprecated as of release 1.5.0 and may be removed in a future version. The preferred method is to include the RegistryName in RegistryModuleID.") + moduleID.RegistryName = PrivateRegistry + } + + if moduleID.RegistryName == PrivateRegistry && strings.TrimSpace(moduleID.Namespace) == "" { + log.Println("[WARN] Support for using the RegistryModuleID without Namespace is deprecated as of release 1.5.0 and may be removed in a future version. The preferred method is to include the Namespace in RegistryModuleID.") + moduleID.Namespace = moduleID.Organization + } + + org := url.QueryEscape(moduleID.Organization) + registryName := url.QueryEscape(string(moduleID.RegistryName)) + namespace := url.QueryEscape(moduleID.Namespace) + name := url.QueryEscape(moduleID.Name) + provider := url.QueryEscape(moduleID.Provider) + url := fmt.Sprintf("organizations/%s/registry-modules/%s/%s/%s/%s", org, registryName, namespace, name, provider) + + req, err := r.client.NewRequest(http.MethodPatch, url, &options) + if err != nil { + return nil, err + } + + rm := &RegistryModule{} + if err := req.Do(ctx, rm); err != nil { + return nil, err + } + + return rm, nil +} + // CreateVersion creates a new registry module version func (r *registryModules) CreateVersion(ctx context.Context, moduleID RegistryModuleID, options RegistryModuleCreateVersionOptions) (*RegistryModuleVersion, error) { if err := moduleID.valid(); err != nil { diff --git a/registry_module_integration_test.go b/registry_module_integration_test.go index d488bac1d..6ba3dabe1 100644 --- a/registry_module_integration_test.go +++ b/registry_module_integration_test.go @@ -99,6 +99,7 @@ func TestRegistryModulesCreate(t *testing.T) { assert.Equal(t, *options.Provider, rm.Provider) assert.Equal(t, PrivateRegistry, rm.RegistryName) assert.Equal(t, orgTest.Name, rm.Namespace) + assert.False(t, rm.NoCode, "no-code module attribute should be false by default") assertRegistryModuleAttributes(t, rm) }) @@ -116,6 +117,7 @@ func TestRegistryModulesCreate(t *testing.T) { assert.Equal(t, *options.Provider, rm.Provider) assert.Equal(t, options.RegistryName, rm.RegistryName) assert.Equal(t, orgTest.Name, rm.Namespace) + assert.False(t, rm.NoCode, "no-code module attribute should be false by default") assertRegistryModuleAttributes(t, rm) }) @@ -134,6 +136,27 @@ func TestRegistryModulesCreate(t *testing.T) { assert.Equal(t, *options.Provider, rm.Provider) assert.Equal(t, options.RegistryName, rm.RegistryName) assert.Equal(t, options.Namespace, rm.Namespace) + assert.False(t, rm.NoCode, "no-code module attribute should be false by default") + + assertRegistryModuleAttributes(t, rm) + }) + + t.Run("with no-code attribute", func(t *testing.T) { + skipIfBeta(t) + options := RegistryModuleCreateOptions{ + Name: String("iam"), + Provider: String("aws"), + NoCode: true, + 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) + assert.Equal(t, options.NoCode, rm.NoCode) assertRegistryModuleAttributes(t, rm) }) @@ -224,6 +247,56 @@ func TestRegistryModulesCreate(t *testing.T) { }) } +func TestRegistryModuleUpdate(t *testing.T) { + skipIfBeta(t) + client := testClient(t) + ctx := context.Background() + + orgTest, orgTestCleanup := createOrganization(t, client) + defer orgTestCleanup() + + options := RegistryModuleCreateOptions{ + Name: String("vault"), + Provider: String("aws"), + RegistryName: PublicRegistry, + Namespace: "hashicorp", + } + rm, err := client.RegistryModules.Create(ctx, orgTest.Name, options) + require.NoError(t, err) + assert.NotEmpty(t, rm.ID) + + t.Run("enable no-code", func(t *testing.T) { + options := RegistryModuleUpdateOptions{ + NoCode: Bool(true), + } + rm, err := client.RegistryModules.Update(ctx, RegistryModuleID{ + Organization: orgTest.Name, + Name: "vault", + Provider: "aws", + Namespace: "hashicorp", + RegistryName: PublicRegistry, + }, options) + require.NoError(t, err) + assert.True(t, rm.NoCode) + }) + + t.Run("disable no-code", func(t *testing.T) { + options := RegistryModuleUpdateOptions{ + NoCode: Bool(false), + } + rm, err := client.RegistryModules.Update(ctx, RegistryModuleID{ + Organization: orgTest.Name, + Name: "vault", + Provider: "aws", + Namespace: "hashicorp", + RegistryName: PublicRegistry, + }, options) + require.NoError(t, err) + assert.False(t, rm.NoCode) + }) + +} + func TestRegistryModulesCreateVersion(t *testing.T) { client := testClient(t) ctx := context.Background()