Skip to content

Commit 8436f1c

Browse files
committed
cli: stop before starting on update
1 parent 2cc3a0d commit 8436f1c

File tree

2 files changed

+83
-0
lines changed

2 files changed

+83
-0
lines changed

cli/update.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55

66
"golang.org/x/xerrors"
77

8+
"github.com/coder/coder/v2/cli/cliui"
9+
"github.com/coder/coder/v2/cli/cliutil"
810
"github.com/coder/coder/v2/codersdk"
911
"github.com/coder/serpent"
1012
)
@@ -34,6 +36,29 @@ func (r *RootCmd) update() *serpent.Command {
3436
return nil
3537
}
3638

39+
// #17840: If the workspace is already running, we will stop it before
40+
// updating. Simply performing a new start transition may not work if the
41+
// template specifies ignore_changes.
42+
if workspace.LatestBuild.Transition == codersdk.WorkspaceTransitionStart {
43+
_, _ = fmt.Fprintf(inv.Stdout, "Stopping workspace %s before updating.\n", workspace.Name)
44+
wbr := codersdk.CreateWorkspaceBuildRequest{
45+
Transition: codersdk.WorkspaceTransitionStop,
46+
TemplateVersionID: workspace.LatestBuild.TemplateVersionID,
47+
}
48+
if bflags.provisionerLogDebug {
49+
wbr.LogLevel = codersdk.ProvisionerLogLevelDebug
50+
}
51+
build, err := client.CreateWorkspaceBuild(inv.Context(), workspace.ID, wbr)
52+
if err != nil {
53+
return xerrors.Errorf("stop workspace: %w", err)
54+
}
55+
cliutil.WarnMatchedProvisioners(inv.Stderr, build.MatchedProvisioners, build.Job)
56+
// Wait for the stop to complete.
57+
if err := cliui.WorkspaceBuild(inv.Context(), inv.Stdout, client, build.ID); err != nil {
58+
return xerrors.Errorf("wait for stop: %w", err)
59+
}
60+
}
61+
3762
build, err := startWorkspace(inv, client, workspace, parameterFlags, bflags, WorkspaceUpdate)
3863
if err != nil {
3964
return xerrors.Errorf("start workspace: %w", err)

cli/update_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,64 @@ func TestUpdate(t *testing.T) {
9696
require.Equal(t, codersdk.WorkspaceTransitionStop, prevBuild.Transition, "previous build must be a stop transition")
9797
require.Equal(t, version1.ID.String(), prevBuild.TemplateVersionID.String(), "previous build must have the old template version")
9898
})
99+
100+
t.Run("Stopped", func(t *testing.T) {
101+
t.Parallel()
102+
103+
// Given: a workspace exists on the latest template version.
104+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
105+
owner := coderdtest.CreateFirstUser(t, client)
106+
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
107+
version1 := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
108+
109+
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version1.ID)
110+
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version1.ID)
111+
112+
ws := coderdtest.CreateWorkspace(t, member, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
113+
cwr.Name = "my-workspace"
114+
})
115+
require.False(t, ws.Outdated, "newly created workspace with active template version must not be outdated")
116+
117+
// Given: the template version is updated
118+
version2 := coderdtest.UpdateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
119+
Parse: echo.ParseComplete,
120+
ProvisionApply: echo.ApplyComplete,
121+
ProvisionPlan: echo.PlanComplete,
122+
}, template.ID)
123+
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version2.ID)
124+
125+
ctx := testutil.Context(t, testutil.WaitShort)
126+
err := client.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{
127+
ID: version2.ID,
128+
})
129+
require.NoError(t, err, "failed to update active template version")
130+
131+
// Given: the workspace is in a stopped state.
132+
coderdtest.MustTransitionWorkspace(t, member, ws.ID, codersdk.WorkspaceTransitionStart, codersdk.WorkspaceTransitionStop)
133+
134+
// Then: the workspace is marked as 'outdated'
135+
ws, err = member.WorkspaceByOwnerAndName(ctx, codersdk.Me, "my-workspace", codersdk.WorkspaceOptions{})
136+
require.NoError(t, err, "member failed to get workspace they themselves own")
137+
require.True(t, ws.Outdated, "workspace must be outdated after template version update")
138+
139+
// When: the workspace is updated
140+
inv, root := clitest.New(t, "update", ws.Name)
141+
clitest.SetupConfig(t, member, root)
142+
143+
err = inv.Run()
144+
require.NoError(t, err, "update command failed")
145+
146+
// Then: the workspace is no longer 'outdated'
147+
ws, err = member.WorkspaceByOwnerAndName(ctx, codersdk.Me, "my-workspace", codersdk.WorkspaceOptions{})
148+
require.NoError(t, err, "member failed to get workspace they themselves own after update")
149+
require.Equal(t, version2.ID.String(), ws.LatestBuild.TemplateVersionID.String(), "workspace must have latest template version after update")
150+
require.False(t, ws.Outdated, "workspace must not be outdated after update")
151+
152+
// Then: the workspace must have been started with the new template version
153+
require.Equal(t, codersdk.WorkspaceTransitionStart, ws.LatestBuild.Transition, "latest build must be a start transition")
154+
// Then: we expect 3 builds, as we manually stopped the workspace.
155+
require.Equal(t, int32(3), ws.LatestBuild.BuildNumber, "workspace must have 3 builds after update")
156+
})
99157
}
100158

101159
func TestUpdateWithRichParameters(t *testing.T) {

0 commit comments

Comments
 (0)