@@ -14,6 +14,7 @@ import (
14
14
"sync"
15
15
"time"
16
16
17
+ "github.com/coder/retry"
17
18
"github.com/gen2brain/beeep"
18
19
"github.com/gofrs/flock"
19
20
"github.com/google/uuid"
@@ -34,7 +35,6 @@ import (
34
35
"github.com/coder/coder/v2/coderd/util/ptr"
35
36
"github.com/coder/coder/v2/codersdk"
36
37
"github.com/coder/coder/v2/cryptorand"
37
- "github.com/coder/retry"
38
38
)
39
39
40
40
var (
@@ -44,15 +44,16 @@ var (
44
44
45
45
func (r * RootCmd ) ssh () * clibase.Cmd {
46
46
var (
47
- stdio bool
48
- forwardAgent bool
49
- forwardGPG bool
50
- identityAgent string
51
- wsPollInterval time.Duration
52
- waitEnum string
53
- noWait bool
54
- logDirPath string
55
- remoteForward string
47
+ stdio bool
48
+ forwardAgent bool
49
+ forwardGPG bool
50
+ identityAgent string
51
+ wsPollInterval time.Duration
52
+ waitEnum string
53
+ noWait bool
54
+ logDirPath string
55
+ remoteForward string
56
+ disableAutostart bool
56
57
)
57
58
client := new (codersdk.Client )
58
59
cmd := & clibase.Cmd {
@@ -143,7 +144,7 @@ func (r *RootCmd) ssh() *clibase.Cmd {
143
144
}
144
145
}
145
146
146
- workspace , workspaceAgent , err := getWorkspaceAndAgent (ctx , inv , client , codersdk .Me , inv .Args [0 ])
147
+ workspace , workspaceAgent , err := getWorkspaceAndAgent (ctx , inv , client , ! disableAutostart , codersdk .Me , inv .Args [0 ])
147
148
if err != nil {
148
149
return err
149
150
}
@@ -459,6 +460,7 @@ func (r *RootCmd) ssh() *clibase.Cmd {
459
460
FlagShorthand : "R" ,
460
461
Value : clibase .StringOf (& remoteForward ),
461
462
},
463
+ sshDisableAutostartOption (clibase .BoolOf (& disableAutostart )),
462
464
}
463
465
return cmd
464
466
}
@@ -530,9 +532,9 @@ startWatchLoop:
530
532
}
531
533
532
534
// getWorkspaceAgent returns the workspace and agent selected using either the
533
- // `<workspace>[.<agent>]` syntax via `in` or picks a random workspace and agent
534
- // if `shuffle` is true.
535
- func getWorkspaceAndAgent (ctx context.Context , inv * clibase.Invocation , client * codersdk.Client , userID string , in string ) (codersdk.Workspace , codersdk.WorkspaceAgent , error ) { //nolint:revive
535
+ // `<workspace>[.<agent>]` syntax via `in`.
536
+ // If autoStart is true, the workspace will be started if it is not already running .
537
+ func getWorkspaceAndAgent (ctx context.Context , inv * clibase.Invocation , client * codersdk.Client , autostart bool , userID string , in string ) (codersdk.Workspace , codersdk.WorkspaceAgent , error ) { //nolint:revive
536
538
var (
537
539
workspace codersdk.Workspace
538
540
workspaceParts = strings .Split (in , "." )
@@ -545,7 +547,35 @@ func getWorkspaceAndAgent(ctx context.Context, inv *clibase.Invocation, client *
545
547
}
546
548
547
549
if workspace .LatestBuild .Transition != codersdk .WorkspaceTransitionStart {
548
- return codersdk.Workspace {}, codersdk.WorkspaceAgent {}, xerrors .New ("workspace must be in start transition to ssh" )
550
+ if ! autostart {
551
+ return codersdk.Workspace {}, codersdk.WorkspaceAgent {}, xerrors .New ("workspace must be in start transition to ssh" )
552
+ }
553
+ // Autostart the workspace for the user.
554
+ // For some failure modes, return a better message.
555
+ if workspace .LatestBuild .Transition == codersdk .WorkspaceTransitionDelete {
556
+ // Any sort of deleting status, we should reject with a nicer error.
557
+ return codersdk.Workspace {}, codersdk.WorkspaceAgent {}, xerrors .Errorf ("workspace %q is deleted" , workspace .Name )
558
+ }
559
+ if workspace .LatestBuild .Job .Status == codersdk .ProvisionerJobFailed {
560
+ return codersdk.Workspace {}, codersdk.WorkspaceAgent {},
561
+ xerrors .Errorf ("workspace %q is in failed state, unable to autostart the workspace" , workspace .Name )
562
+ }
563
+ // The workspace needs to be stopped before we can start it.
564
+ // It cannot be in any pending or failed state.
565
+ if workspace .LatestBuild .Status != codersdk .WorkspaceStatusStopped {
566
+ return codersdk.Workspace {}, codersdk.WorkspaceAgent {},
567
+ xerrors .Errorf ("workspace must be in start transition to ssh, was unable to autostart as the last build job is %q, expected %q" ,
568
+ workspace .LatestBuild .Status ,
569
+ codersdk .WorkspaceStatusStopped ,
570
+ )
571
+ }
572
+ // startWorkspace based on the last build parameters.
573
+ _ , _ = fmt .Fprintf (inv .Stderr , "Workspace was stopped, starting workspace to allow connecting to %q...\n " , workspace .Name )
574
+ build , err := startWorkspace (inv , client , workspace , workspaceParameterFlags {}, WorkspaceStart )
575
+ if err != nil {
576
+ return codersdk.Workspace {}, codersdk.WorkspaceAgent {}, xerrors .Errorf ("unable to start workspace: %w" , err )
577
+ }
578
+ workspace .LatestBuild = build
549
579
}
550
580
if workspace .LatestBuild .Job .CompletedAt == nil {
551
581
err := cliui .WorkspaceBuild (ctx , inv .Stderr , client , workspace .LatestBuild .ID )
@@ -915,3 +945,13 @@ func (c *rawSSHCopier) Close() error {
915
945
}
916
946
return err
917
947
}
948
+
949
+ func sshDisableAutostartOption (src * clibase.Bool ) clibase.Option {
950
+ return clibase.Option {
951
+ Flag : "disable-autostart" ,
952
+ Description : "Disable starting the workspace automatically when connecting via SSH." ,
953
+ Env : "CODER_SSH_DISABLE_AUTOSTART" ,
954
+ Value : src ,
955
+ Default : "false" ,
956
+ }
957
+ }
0 commit comments