mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
feat: restart stopped workspaces on ssh command (#11050)
* feat: autostart workspaces on ssh & port forward This is opt out by default. VScode ssh does not have this behavior
This commit is contained in:
70
cli/ssh.go
70
cli/ssh.go
@ -14,6 +14,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coder/retry"
|
||||
"github.com/gen2brain/beeep"
|
||||
"github.com/gofrs/flock"
|
||||
"github.com/google/uuid"
|
||||
@ -34,7 +35,6 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/util/ptr"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/cryptorand"
|
||||
"github.com/coder/retry"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -44,15 +44,16 @@ var (
|
||||
|
||||
func (r *RootCmd) ssh() *clibase.Cmd {
|
||||
var (
|
||||
stdio bool
|
||||
forwardAgent bool
|
||||
forwardGPG bool
|
||||
identityAgent string
|
||||
wsPollInterval time.Duration
|
||||
waitEnum string
|
||||
noWait bool
|
||||
logDirPath string
|
||||
remoteForward string
|
||||
stdio bool
|
||||
forwardAgent bool
|
||||
forwardGPG bool
|
||||
identityAgent string
|
||||
wsPollInterval time.Duration
|
||||
waitEnum string
|
||||
noWait bool
|
||||
logDirPath string
|
||||
remoteForward string
|
||||
disableAutostart bool
|
||||
)
|
||||
client := new(codersdk.Client)
|
||||
cmd := &clibase.Cmd{
|
||||
@ -143,7 +144,7 @@ func (r *RootCmd) ssh() *clibase.Cmd {
|
||||
}
|
||||
}
|
||||
|
||||
workspace, workspaceAgent, err := getWorkspaceAndAgent(ctx, inv, client, codersdk.Me, inv.Args[0])
|
||||
workspace, workspaceAgent, err := getWorkspaceAndAgent(ctx, inv, client, !disableAutostart, codersdk.Me, inv.Args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -459,6 +460,7 @@ func (r *RootCmd) ssh() *clibase.Cmd {
|
||||
FlagShorthand: "R",
|
||||
Value: clibase.StringOf(&remoteForward),
|
||||
},
|
||||
sshDisableAutostartOption(clibase.BoolOf(&disableAutostart)),
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
@ -530,9 +532,9 @@ startWatchLoop:
|
||||
}
|
||||
|
||||
// getWorkspaceAgent returns the workspace and agent selected using either the
|
||||
// `<workspace>[.<agent>]` syntax via `in` or picks a random workspace and agent
|
||||
// if `shuffle` is true.
|
||||
func getWorkspaceAndAgent(ctx context.Context, inv *clibase.Invocation, client *codersdk.Client, userID string, in string) (codersdk.Workspace, codersdk.WorkspaceAgent, error) { //nolint:revive
|
||||
// `<workspace>[.<agent>]` syntax via `in`.
|
||||
// If autoStart is true, the workspace will be started if it is not already running.
|
||||
func getWorkspaceAndAgent(ctx context.Context, inv *clibase.Invocation, client *codersdk.Client, autostart bool, userID string, in string) (codersdk.Workspace, codersdk.WorkspaceAgent, error) { //nolint:revive
|
||||
var (
|
||||
workspace codersdk.Workspace
|
||||
workspaceParts = strings.Split(in, ".")
|
||||
@ -545,7 +547,35 @@ func getWorkspaceAndAgent(ctx context.Context, inv *clibase.Invocation, client *
|
||||
}
|
||||
|
||||
if workspace.LatestBuild.Transition != codersdk.WorkspaceTransitionStart {
|
||||
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, xerrors.New("workspace must be in start transition to ssh")
|
||||
if !autostart {
|
||||
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, xerrors.New("workspace must be in start transition to ssh")
|
||||
}
|
||||
// Autostart the workspace for the user.
|
||||
// For some failure modes, return a better message.
|
||||
if workspace.LatestBuild.Transition == codersdk.WorkspaceTransitionDelete {
|
||||
// Any sort of deleting status, we should reject with a nicer error.
|
||||
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, xerrors.Errorf("workspace %q is deleted", workspace.Name)
|
||||
}
|
||||
if workspace.LatestBuild.Job.Status == codersdk.ProvisionerJobFailed {
|
||||
return codersdk.Workspace{}, codersdk.WorkspaceAgent{},
|
||||
xerrors.Errorf("workspace %q is in failed state, unable to autostart the workspace", workspace.Name)
|
||||
}
|
||||
// The workspace needs to be stopped before we can start it.
|
||||
// It cannot be in any pending or failed state.
|
||||
if workspace.LatestBuild.Status != codersdk.WorkspaceStatusStopped {
|
||||
return codersdk.Workspace{}, codersdk.WorkspaceAgent{},
|
||||
xerrors.Errorf("workspace must be in start transition to ssh, was unable to autostart as the last build job is %q, expected %q",
|
||||
workspace.LatestBuild.Status,
|
||||
codersdk.WorkspaceStatusStopped,
|
||||
)
|
||||
}
|
||||
// startWorkspace based on the last build parameters.
|
||||
_, _ = fmt.Fprintf(inv.Stderr, "Workspace was stopped, starting workspace to allow connecting to %q...\n", workspace.Name)
|
||||
build, err := startWorkspace(inv, client, workspace, workspaceParameterFlags{}, WorkspaceStart)
|
||||
if err != nil {
|
||||
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, xerrors.Errorf("unable to start workspace: %w", err)
|
||||
}
|
||||
workspace.LatestBuild = build
|
||||
}
|
||||
if workspace.LatestBuild.Job.CompletedAt == nil {
|
||||
err := cliui.WorkspaceBuild(ctx, inv.Stderr, client, workspace.LatestBuild.ID)
|
||||
@ -915,3 +945,13 @@ func (c *rawSSHCopier) Close() error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func sshDisableAutostartOption(src *clibase.Bool) clibase.Option {
|
||||
return clibase.Option{
|
||||
Flag: "disable-autostart",
|
||||
Description: "Disable starting the workspace automatically when connecting via SSH.",
|
||||
Env: "CODER_SSH_DISABLE_AUTOSTART",
|
||||
Value: src,
|
||||
Default: "false",
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user