mirror of
https://github.com/coder/coder.git
synced 2026-10-16 14:48:56 +08:00
Add named template version selection to `coder start` and `coder stop`, and independent stop/start selection to `coder update` with `--stop-template-version` and `--template-version`. Stop still defaults to the workspace's current version, and update starts with the active version by default. Explicit selections use the selected version for parameter preparation and builds, keep server permissions authoritative, and never silently fall back to the active version. Classic stop builds retain previous parameter values omitted by the selected stop version, so a later start can reuse them across separate commands or an interrupted update. Declared parameters still follow normal validation, and start builds still discard removed parameters. Update now returns an error and retry guidance when a start apply fails after stopping, including unflagged updates that previously returned success. Closes #29100 [PLAT-616](https://linear.app/codercom/issue/PLAT-616/cli-allow-specifying-a-template-version-for-stopstart-builds-and-both) --------- Co-authored-by: Nick Vigilante <nickvigilante@users.noreply.github.com>
107 lines
3.1 KiB
Go
107 lines
3.1 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/coder/coder/v2/cli/cliui"
|
|
"github.com/coder/coder/v2/cli/cliutil"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
"github.com/coder/serpent"
|
|
)
|
|
|
|
func (r *RootCmd) stop() *serpent.Command {
|
|
var bflags buildFlags
|
|
var templateVersion string
|
|
cmd := &serpent.Command{
|
|
Annotations: serpent.Annotations(workspaceCommand).Mark(annotationClientSessionID, "").Mark(annotationFlightRecorder, ""),
|
|
Use: "stop <workspace>",
|
|
Short: "Stop a workspace",
|
|
Middleware: serpent.Chain(
|
|
serpent.RequireNArgs(1),
|
|
),
|
|
Options: serpent.OptionSet{
|
|
{
|
|
Flag: "template-version",
|
|
Description: "Stop with a named version of the workspace template. Defaults to the workspace's current version.",
|
|
Value: serpent.StringOf(&templateVersion),
|
|
},
|
|
cliui.SkipPromptOption(),
|
|
},
|
|
Handler: func(inv *serpent.Invocation) error {
|
|
client, err := r.InitClient(inv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// The invocation logger records debug detail and emits it to stderr
|
|
// only if the command fails (see flightRecorderMiddleware).
|
|
client.SetLogger(inv.Logger)
|
|
|
|
_, err = cliui.Prompt(inv, cliui.PromptOptions{
|
|
Text: "Confirm stop workspace?",
|
|
IsConfirm: true,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
workspace, err := client.ResolveWorkspace(inv.Context(), inv.Args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
versionID, err := resolveTemplateVersionID(inv.Context(), client, workspace.TemplateID, templateVersion)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
build, err := stopWorkspace(inv, client, workspace, bflags, versionID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = cliui.WorkspaceBuild(inv.Context(), inv.Stdout, client, build.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, _ = fmt.Fprintf(
|
|
inv.Stdout,
|
|
"\nThe %s workspace has been stopped at %s!\n",
|
|
cliui.Keyword(workspace.Name),
|
|
cliui.Timestamp(time.Now()),
|
|
)
|
|
return nil
|
|
},
|
|
}
|
|
cmd.Options = append(cmd.Options, bflags.cliOptions()...)
|
|
|
|
return cmd
|
|
}
|
|
|
|
func stopWorkspace(inv *serpent.Invocation, client *codersdk.Client, workspace codersdk.Workspace, bflags buildFlags, templateVersionID uuid.UUID) (codersdk.WorkspaceBuild, error) {
|
|
if workspace.LatestBuild.Job.Status == codersdk.ProvisionerJobPending {
|
|
// cliutil.WarnMatchedProvisioners also checks if the job is pending
|
|
// but we still want to avoid users spamming multiple builds that will
|
|
// not be picked up.
|
|
cliui.Warn(inv.Stderr, "The workspace is already stopping!")
|
|
cliutil.WarnMatchedProvisioners(inv.Stderr, workspace.LatestBuild.MatchedProvisioners, workspace.LatestBuild.Job)
|
|
if _, err := cliui.Prompt(inv, cliui.PromptOptions{
|
|
Text: "Enqueue another stop?",
|
|
IsConfirm: true,
|
|
Default: cliui.ConfirmNo,
|
|
}); err != nil {
|
|
return codersdk.WorkspaceBuild{}, err
|
|
}
|
|
}
|
|
wbr := codersdk.CreateWorkspaceBuildRequest{
|
|
Transition: codersdk.WorkspaceTransitionStop,
|
|
TemplateVersionID: templateVersionID,
|
|
}
|
|
if bflags.provisionerLogDebug {
|
|
wbr.LogLevel = codersdk.ProvisionerLogLevelDebug
|
|
}
|
|
return client.CreateWorkspaceBuild(inv.Context(), workspace.ID, wbr)
|
|
}
|