diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f814fc00..55984a6f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Enhancements * Adds support for creating no-code workspaces by @paladin-devops [#927](https://github.com/hashicorp/go-tfe/pull/927) +* Adds support for upgrading no-code workspaces by @paladin-devops [#935](https://github.com/hashicorp/go-tfe/pull/935) # v1.60.0 diff --git a/mocks/registry_no_code_module_mocks.go b/mocks/registry_no_code_module_mocks.go index 4aea68fbb..b63b3bf4e 100644 --- a/mocks/registry_no_code_module_mocks.go +++ b/mocks/registry_no_code_module_mocks.go @@ -113,3 +113,18 @@ func (mr *MockRegistryNoCodeModulesMockRecorder) Update(ctx, noCodeModuleID, opt mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockRegistryNoCodeModules)(nil).Update), ctx, noCodeModuleID, options) } + +// UpgradeWorkspace mocks base method. +func (m *MockRegistryNoCodeModules) UpgradeWorkspace(ctx context.Context, noCodeModuleID, workspaceID string, options *tfe.RegistryNoCodeModuleUpgradeWorkspaceOptions) (*tfe.RegistryNoCodeModuleWorkspace, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpgradeWorkspace", ctx, noCodeModuleID, workspaceID, options) + ret0, _ := ret[0].(*tfe.RegistryNoCodeModuleWorkspace) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpgradeWorkspace indicates an expected call of UpgradeWorkspace. +func (mr *MockRegistryNoCodeModulesMockRecorder) UpgradeWorkspace(ctx, noCodeModuleID, workspaceID, options any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpgradeWorkspace", reflect.TypeOf((*MockRegistryNoCodeModules)(nil).UpgradeWorkspace), ctx, noCodeModuleID, workspaceID, options) +} diff --git a/registry_no_code_module.go b/registry_no_code_module.go index 9cf393ff0..2e785be4e 100644 --- a/registry_no_code_module.go +++ b/registry_no_code_module.go @@ -36,6 +36,9 @@ type RegistryNoCodeModules interface { // CreateWorkspace creates a workspace using a no-code module. CreateWorkspace(ctx context.Context, noCodeModuleID string, options *RegistryNoCodeModuleCreateWorkspaceOptions) (*RegistryNoCodeModuleWorkspace, error) + + // UpgradeWorkspace initiates an upgrade of an existing no-code module workspace. + UpgradeWorkspace(ctx context.Context, noCodeModuleID string, workspaceID string, options *RegistryNoCodeModuleUpgradeWorkspaceOptions) (*RegistryNoCodeModuleWorkspace, error) } type RegistryNoCodeModuleCreateWorkspaceOptions struct { @@ -67,6 +70,14 @@ type RegistryNoCodeModuleCreateWorkspaceOptions struct { SourceURL *string `jsonapi:"attr,source-url,omitempty"` } +type RegistryNoCodeModuleUpgradeWorkspaceOptions struct { + Type string `jsonapi:"primary,no-code-module-workspace"` + + // Variables is the slice of variables to be configured for the no-code + // workspace. + Variables []*Variable `jsonapi:"relation,vars,omitempty"` +} + type RegistryNoCodeModuleWorkspace struct { Workspace } @@ -284,6 +295,35 @@ func (r *registryNoCodeModules) CreateWorkspace( return w, nil } +// UpgradeWorkspace initiates an upgrade of an existing no-code module workspace. +func (r *registryNoCodeModules) UpgradeWorkspace( + ctx context.Context, + noCodeModuleID string, + workspaceID string, + options *RegistryNoCodeModuleUpgradeWorkspaceOptions, +) (*RegistryNoCodeModuleWorkspace, error) { + if err := options.valid(); err != nil { + return nil, err + } + + u := fmt.Sprintf("no-code-modules/%s/workspaces/%s/upgrade", + url.QueryEscape(noCodeModuleID), + workspaceID, + ) + req, err := r.client.NewRequest("POST", u, options) + if err != nil { + return nil, err + } + + w := &RegistryNoCodeModuleWorkspace{} + err = req.Do(ctx, w) + if err != nil { + return nil, err + } + + return w, nil +} + func (o RegistryNoCodeModuleCreateOptions) valid() error { if o.RegistryModule == nil || o.RegistryModule.ID == "" { return ErrRequiredRegistryModule @@ -315,3 +355,7 @@ func (o *RegistryNoCodeModuleCreateWorkspaceOptions) valid() error { return nil } + +func (o *RegistryNoCodeModuleUpgradeWorkspaceOptions) valid() error { + return nil +} diff --git a/registry_no_code_module_integration_test.go b/registry_no_code_module_integration_test.go index e68fcfb24..5b2c5f5d1 100644 --- a/registry_no_code_module_integration_test.go +++ b/registry_no_code_module_integration_test.go @@ -385,3 +385,118 @@ func TestRegistryNoCodeModulesCreateWorkspace(t *testing.T) { r.Error(err) }) } + +func TestRegistryNoCodeModuleWorkspaceUpgrade(t *testing.T) { + skipUnlessBeta(t) + + client := testClient(t) + ctx := context.Background() + r := require.New(t) + + orgTest, orgTestCleanup := createOrganization(t, client) + defer orgTestCleanup() + + org, err := client.Organizations.Read(ctx, orgTest.Name) + r.NoError(err) + r.NotNil(org) + + githubIdentifier := os.Getenv("GITHUB_REGISTRY_NO_CODE_MODULE_IDENTIFIER") + if githubIdentifier == "" { + t.Skip("Export a valid GITHUB_REGISTRY_NO_CODE_MODULE_IDENTIFIER before running this test") + } + + token, cleanupToken := createOAuthToken(t, client, org) + defer cleanupToken() + + rmOpts := RegistryModuleCreateWithVCSConnectionOptions{ + VCSRepo: &RegistryModuleVCSRepoOptions{ + OrganizationName: String(org.Name), + Identifier: String(githubIdentifier), + Tags: Bool(true), + OAuthTokenID: String(token.ID), + DisplayIdentifier: String(githubIdentifier), + }, + InitialVersion: String("1.0.0"), + } + + // create the module + rm, err := client.RegistryModules.CreateWithVCSConnection(ctx, rmOpts) + r.NoError(err) + + // create the no-code module + ncm, err := client.RegistryNoCodeModules.Create(ctx, org.Name, RegistryNoCodeModuleCreateOptions{ + RegistryModule: rm, + Enabled: Bool(true), + VariableOptions: nil, + }) + r.NoError(err) + r.NotNil(ncm) + + // We sleep for 10 seconds to let the module finish getting ready + time.Sleep(time.Second * 10) + + // update the module's pinned version to be 1.0.0 + // NOTE: This is done here as an update instead of at create time, because + // that results in the following error: + // Validation failed: Provided version pin is not equal to latest or provided + // string does not represent an existing version of the module. + uncm, err := client.RegistryNoCodeModules.Update(ctx, ncm.ID, RegistryNoCodeModuleUpdateOptions{ + RegistryModule: rm, + VersionPin: "1.0.0", + }) + r.NoError(err) + r.NotNil(uncm) + + // create a workspace, which will be attempted to be updated during the test + wn := fmt.Sprintf("foo-%s", randomString(t)) + sn := "my-app" + su := "http://my-app.com" + w, err := client.RegistryNoCodeModules.CreateWorkspace( + ctx, + uncm.ID, + &RegistryNoCodeModuleCreateWorkspaceOptions{ + Name: wn, + SourceName: String(sn), + SourceURL: String(su), + }, + ) + r.NoError(err) + r.NotNil(w) + + // update the module's pinned version + uncm, err = client.RegistryNoCodeModules.Update(ctx, ncm.ID, RegistryNoCodeModuleUpdateOptions{ + VersionPin: "1.0.1", + }) + r.NoError(err) + r.NotNil(uncm) + + t.Run("test upgrading a workspace via a no-code module", func(t *testing.T) { + _, err = client.RegistryNoCodeModules.UpgradeWorkspace( + ctx, + ncm.ID, + w.ID, + &RegistryNoCodeModuleUpgradeWorkspaceOptions{}, + ) + r.NoError(err) + }) + + t.Run("fail to upgrade workspace with invalid no-code module", func(t *testing.T) { + _, err = client.RegistryNoCodeModules.UpgradeWorkspace( + ctx, + ncm.ID+"-invalid", + w.ID, + &RegistryNoCodeModuleUpgradeWorkspaceOptions{}, + ) + r.Error(err) + }) + + t.Run("fail to upgrade workspace with invalid workspace ID", func(t *testing.T) { + _, err = client.RegistryNoCodeModules.UpgradeWorkspace( + ctx, + ncm.ID, + w.ID+"-invalid", + &RegistryNoCodeModuleUpgradeWorkspaceOptions{}, + ) + r.Error(err) + }) +}