Skip to content

Commit 77a4792

Browse files
authored
fix(cli): ssh: auto-update workspace (#11773)
1 parent 369821e commit 77a4792

File tree

5 files changed

+97
-8
lines changed

5 files changed

+97
-8
lines changed

cli/restart.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func (r *RootCmd) restart() *clibase.Cmd {
6464
// It's possible for a workspace build to fail due to the template requiring starting
6565
// workspaces with the active version.
6666
if cerr, ok := codersdk.AsError(err); ok && cerr.StatusCode() == http.StatusForbidden {
67-
_, _ = fmt.Fprintln(inv.Stdout, "Failed to restart with the template version from your last build. Policy may require you to restart with the current active template version.")
67+
_, _ = fmt.Fprintln(inv.Stdout, "Unable to restart the workspace with the template version from the last build. Policy may require you to restart with the current active template version.")
6868
build, err = startWorkspace(inv, client, workspace, parameterFlags, WorkspaceUpdate)
6969
if err != nil {
7070
return xerrors.Errorf("start workspace with active template version: %w", err)

cli/ssh.go

+19-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"fmt"
88
"io"
9+
"net/http"
910
"net/url"
1011
"os"
1112
"os/exec"
@@ -575,13 +576,27 @@ func getWorkspaceAndAgent(ctx context.Context, inv *clibase.Invocation, client *
575576
codersdk.WorkspaceStatusStopped,
576577
)
577578
}
578-
// startWorkspace based on the last build parameters.
579+
580+
// Start workspace based on the last build parameters.
581+
// It's possible for a workspace build to fail due to the template requiring starting
582+
// workspaces with the active version.
579583
_, _ = fmt.Fprintf(inv.Stderr, "Workspace was stopped, starting workspace to allow connecting to %q...\n", workspace.Name)
580-
build, err := startWorkspace(inv, client, workspace, workspaceParameterFlags{}, WorkspaceStart)
584+
_, err = startWorkspace(inv, client, workspace, workspaceParameterFlags{}, WorkspaceStart)
585+
if cerr, ok := codersdk.AsError(err); ok && cerr.StatusCode() == http.StatusForbidden {
586+
_, _ = fmt.Fprintln(inv.Stdout, "Unable to start the workspace with template version from last build. The auto-update policy may require you to restart with the current active template version.")
587+
_, err = startWorkspace(inv, client, workspace, workspaceParameterFlags{}, WorkspaceUpdate)
588+
if err != nil {
589+
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, xerrors.Errorf("start workspace with active template version: %w", err)
590+
}
591+
} else if err != nil {
592+
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, xerrors.Errorf("start workspace with current template version: %w", err)
593+
}
594+
595+
// Refresh workspace state so that `outdated`, `build`,`template_*` fields are up-to-date.
596+
workspace, err = namedWorkspace(ctx, client, workspaceParts[0])
581597
if err != nil {
582-
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, xerrors.Errorf("unable to start workspace: %w", err)
598+
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, err
583599
}
584-
workspace.LatestBuild = build
585600
}
586601
if workspace.LatestBuild.Job.CompletedAt == nil {
587602
err := cliui.WorkspaceBuild(ctx, inv.Stderr, client, workspace.LatestBuild.ID)

cli/ssh_test.go

+65
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,71 @@ func TestSSH(t *testing.T) {
133133
pty.WriteLine("exit")
134134
<-cmdDone
135135
})
136+
t.Run("RequireActiveVersion", func(t *testing.T) {
137+
t.Parallel()
138+
139+
authToken := uuid.NewString()
140+
ownerClient := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
141+
owner := coderdtest.CreateFirstUser(t, ownerClient)
142+
client, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleMember())
143+
144+
echoResponses := &echo.Responses{
145+
Parse: echo.ParseComplete,
146+
ProvisionPlan: echo.PlanComplete,
147+
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
148+
}
149+
150+
version := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, echoResponses)
151+
coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version.ID)
152+
template := coderdtest.CreateTemplate(t, ownerClient, owner.OrganizationID, version.ID)
153+
154+
workspace := coderdtest.CreateWorkspace(t, client, owner.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
155+
cwr.AutomaticUpdates = codersdk.AutomaticUpdatesAlways
156+
})
157+
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
158+
159+
// Stop the workspace
160+
workspaceBuild := coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStop)
161+
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspaceBuild.ID)
162+
163+
// Update template version
164+
version = coderdtest.UpdateTemplateVersion(t, ownerClient, owner.OrganizationID, echoResponses, template.ID)
165+
coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version.ID)
166+
err := ownerClient.UpdateActiveTemplateVersion(context.Background(), template.ID, codersdk.UpdateActiveTemplateVersion{
167+
ID: version.ID,
168+
})
169+
require.NoError(t, err)
170+
171+
// SSH to the workspace which should auto-update and autostart it
172+
inv, root := clitest.New(t, "ssh", workspace.Name)
173+
clitest.SetupConfig(t, client, root)
174+
pty := ptytest.New(t).Attach(inv)
175+
176+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
177+
defer cancel()
178+
179+
cmdDone := tGo(t, func() {
180+
err := inv.WithContext(ctx).Run()
181+
assert.NoError(t, err)
182+
})
183+
184+
// When the agent connects, the workspace was started, and we should
185+
// have access to the shell.
186+
_ = agenttest.New(t, client.URL, authToken)
187+
coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
188+
189+
// Shells on Mac, Windows, and Linux all exit shells with the "exit" command.
190+
pty.WriteLine("exit")
191+
<-cmdDone
192+
193+
// Double-check if workspace's template version is up-to-date
194+
workspace, err = client.Workspace(context.Background(), workspace.ID)
195+
require.NoError(t, err)
196+
assert.Equal(t, version.ID, workspace.TemplateActiveVersionID)
197+
assert.Equal(t, workspace.TemplateActiveVersionID, workspace.LatestBuild.TemplateVersionID)
198+
assert.False(t, workspace.Outdated)
199+
})
200+
136201
t.Run("ShowTroubleshootingURLAfterTimeout", func(t *testing.T) {
137202
t.Parallel()
138203

cli/start.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func (r *RootCmd) start() *clibase.Cmd {
4949
// It's possible for a workspace build to fail due to the template requiring starting
5050
// workspaces with the active version.
5151
if cerr, ok := codersdk.AsError(err); ok && cerr.StatusCode() == http.StatusForbidden {
52-
_, _ = fmt.Fprintln(inv.Stdout, "Failed to restart with the template version from your last build. Policy may require you to restart with the current active template version.")
52+
_, _ = fmt.Fprintln(inv.Stdout, "Unable to start the workspace with the template version from the last build. Policy may require you to restart with the current active template version.")
5353
build, err = startWorkspace(inv, client, workspace, parameterFlags, WorkspaceUpdate)
5454
if err != nil {
5555
return xerrors.Errorf("start workspace with active template version: %w", err)
@@ -79,6 +79,7 @@ func (r *RootCmd) start() *clibase.Cmd {
7979

8080
func buildWorkspaceStartRequest(inv *clibase.Invocation, client *codersdk.Client, workspace codersdk.Workspace, parameterFlags workspaceParameterFlags, action WorkspaceCLIAction) (codersdk.CreateWorkspaceBuildRequest, error) {
8181
version := workspace.LatestBuild.TemplateVersionID
82+
8283
if workspace.AutomaticUpdates == codersdk.AutomaticUpdatesAlways || action == WorkspaceUpdate {
8384
version = workspace.TemplateActiveVersionID
8485
if version != workspace.LatestBuild.TemplateVersionID {

enterprise/cli/start_test.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,16 @@ func TestStart(t *testing.T) {
159159

160160
ws = coderdtest.MustWorkspace(t, c.Client, ws.ID)
161161
require.Equal(t, c.ExpectedVersion, ws.LatestBuild.TemplateVersionID)
162-
if initialTemplateVersion != ws.LatestBuild.TemplateVersionID {
163-
require.Contains(t, buf.String(), "Failed to restart with the template version from your last build. Policy may require you to restart with the current active template version.")
162+
if initialTemplateVersion == ws.LatestBuild.TemplateVersionID {
163+
return
164+
}
165+
166+
if cmd == "start" {
167+
require.Contains(t, buf.String(), "Unable to start the workspace with the template version from the last build")
168+
}
169+
170+
if cmd == "restart" {
171+
require.Contains(t, buf.String(), "Unable to restart the workspace with the template version from the last build")
164172
}
165173
})
166174
}

0 commit comments

Comments
 (0)