mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: run stop build before starting a workspace with a failed start (#22925)
This commit is contained in:
@@ -79,6 +79,29 @@ func (r *RootCmd) start() *serpent.Command {
|
||||
)
|
||||
build = workspace.LatestBuild
|
||||
default:
|
||||
// If the last build was a failed start, run a stop
|
||||
// first to clean up any partially-provisioned
|
||||
// resources.
|
||||
if workspace.LatestBuild.Status == codersdk.WorkspaceStatusFailed &&
|
||||
workspace.LatestBuild.Transition == codersdk.WorkspaceTransitionStart {
|
||||
_, _ = fmt.Fprintf(inv.Stdout, "The last start build failed. Cleaning up before retrying...\n")
|
||||
stopBuild, stopErr := client.CreateWorkspaceBuild(inv.Context(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
||||
Transition: codersdk.WorkspaceTransitionStop,
|
||||
})
|
||||
if stopErr != nil {
|
||||
return xerrors.Errorf("cleanup stop after failed start: %w", stopErr)
|
||||
}
|
||||
stopErr = cliui.WorkspaceBuild(inv.Context(), inv.Stdout, client, stopBuild.ID)
|
||||
if stopErr != nil {
|
||||
return xerrors.Errorf("wait for cleanup stop: %w", stopErr)
|
||||
}
|
||||
// Re-fetch workspace after stop completes so
|
||||
// startWorkspace sees the latest state.
|
||||
workspace, err = namedWorkspace(inv.Context(), client, inv.Args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
build, err = startWorkspace(inv, client, workspace, parameterFlags, bflags, WorkspaceStart)
|
||||
// It's possible for a workspace build to fail due to the template requiring starting
|
||||
// workspaces with the active version.
|
||||
|
||||
@@ -534,3 +534,55 @@ func TestStart_WithReason(t *testing.T) {
|
||||
workspace = coderdtest.MustWorkspace(t, member, workspace.ID)
|
||||
require.Equal(t, codersdk.BuildReasonCLI, workspace.LatestBuild.Reason)
|
||||
}
|
||||
|
||||
func TestStart_FailedStartCleansUp(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
store, ps := dbtestutil.NewDB(t)
|
||||
client := coderdtest.New(t, &coderdtest.Options{
|
||||
Database: store,
|
||||
Pubsub: ps,
|
||||
IncludeProvisionerDaemon: true,
|
||||
})
|
||||
owner := coderdtest.CreateFirstUser(t, client)
|
||||
memberClient, member := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
||||
|
||||
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
||||
workspace := coderdtest.CreateWorkspace(t, memberClient, template.ID)
|
||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
|
||||
|
||||
// Insert a failed start build directly into the database so that
|
||||
// the workspace's latest build is a failed "start" transition.
|
||||
dbfake.WorkspaceBuild(t, store, database.WorkspaceTable{
|
||||
ID: workspace.ID,
|
||||
OwnerID: member.ID,
|
||||
OrganizationID: owner.OrganizationID,
|
||||
TemplateID: template.ID,
|
||||
}).
|
||||
Seed(database.WorkspaceBuild{
|
||||
TemplateVersionID: version.ID,
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
BuildNumber: workspace.LatestBuild.BuildNumber + 1,
|
||||
}).
|
||||
Failed().
|
||||
Do()
|
||||
|
||||
inv, root := clitest.New(t, "start", workspace.Name)
|
||||
clitest.SetupConfig(t, memberClient, root)
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
doneChan := make(chan struct{})
|
||||
go func() {
|
||||
defer close(doneChan)
|
||||
err := inv.Run()
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
// The CLI should detect the failed start and clean up first.
|
||||
pty.ExpectMatch("Cleaning up before retrying")
|
||||
pty.ExpectMatch("workspace has been started")
|
||||
|
||||
_ = testutil.TryReceive(ctx, t, doneChan)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user