@@ -67,7 +67,7 @@ func (r *RootCmd) ssh() *serpent.Command {
67
67
stdio bool
68
68
hostPrefix string
69
69
hostnameSuffix string
70
- forceTunnel bool
70
+ forceNewTunnel bool
71
71
forwardAgent bool
72
72
forwardGPG bool
73
73
identityAgent string
@@ -278,27 +278,38 @@ func (r *RootCmd) ssh() *serpent.Command {
278
278
return err
279
279
}
280
280
281
- // See if we can use the Coder Connect tunnel
282
- if ! forceTunnel {
281
+ // If we're in stdio mode, check to see if we can use Coder Connect.
282
+ // We don't support Coder Connect over non-stdio coder ssh yet.
283
+ if stdio && ! forceNewTunnel {
283
284
connInfo , err := wsClient .AgentConnectionInfoGeneric (ctx )
284
285
if err != nil {
285
286
return xerrors .Errorf ("get agent connection info: %w" , err )
286
287
}
287
-
288
288
coderConnectHost := fmt .Sprintf ("%s.%s.%s.%s" ,
289
289
workspaceAgent .Name , workspace .Name , workspace .OwnerName , connInfo .HostnameSuffix )
290
290
exists , _ := workspacesdk .ExistsViaCoderConnect (ctx , coderConnectHost )
291
291
if exists {
292
292
_ , _ = fmt .Fprintln (inv .Stderr , "Connecting to workspace via Coder Connect..." )
293
293
defer cancel ()
294
- addr := fmt . Sprintf ( "%s:22" , coderConnectHost )
295
- if stdio {
294
+
295
+ if networkInfoDir != "" {
296
296
if err := writeCoderConnectNetInfo (ctx , networkInfoDir ); err != nil {
297
297
logger .Error (ctx , "failed to write coder connect net info file" , slog .Error (err ))
298
298
}
299
- return runCoderConnectStdio (ctx , addr , stdioReader , stdioWriter , stack )
300
299
}
301
- return runCoderConnectPTY (ctx , addr , inv .Stdin , inv .Stdout , inv .Stderr , stack )
300
+
301
+ stopPolling := tryPollWorkspaceAutostop (ctx , client , workspace )
302
+ defer stopPolling ()
303
+
304
+ usageAppName := getUsageAppName (usageApp )
305
+ if usageAppName != "" {
306
+ closeUsage := client .UpdateWorkspaceUsageWithBodyContext (ctx , workspace .ID , codersdk.PostWorkspaceUsageRequest {
307
+ AgentID : workspaceAgent .ID ,
308
+ AppName : usageAppName ,
309
+ })
310
+ defer closeUsage ()
311
+ }
312
+ return runCoderConnectStdio (ctx , fmt .Sprintf ("%s:22" , coderConnectHost ), stdioReader , stdioWriter , stack )
302
313
}
303
314
}
304
315
@@ -481,11 +492,36 @@ func (r *RootCmd) ssh() *serpent.Command {
481
492
stdinFile , validIn := inv .Stdin .(* os.File )
482
493
stdoutFile , validOut := inv .Stdout .(* os.File )
483
494
if validIn && validOut && isatty .IsTerminal (stdinFile .Fd ()) && isatty .IsTerminal (stdoutFile .Fd ()) {
484
- restorePtyFn , err := configurePTY (ctx , stdinFile , stdoutFile , sshSession )
485
- defer restorePtyFn ()
495
+ inState , err := pty .MakeInputRaw (stdinFile .Fd ())
496
+ if err != nil {
497
+ return err
498
+ }
499
+ defer func () {
500
+ _ = pty .RestoreTerminal (stdinFile .Fd (), inState )
501
+ }()
502
+ outState , err := pty .MakeOutputRaw (stdoutFile .Fd ())
486
503
if err != nil {
487
- return xerrors . Errorf ( "configure pty: %w" , err )
504
+ return err
488
505
}
506
+ defer func () {
507
+ _ = pty .RestoreTerminal (stdoutFile .Fd (), outState )
508
+ }()
509
+
510
+ windowChange := listenWindowSize (ctx )
511
+ go func () {
512
+ for {
513
+ select {
514
+ case <- ctx .Done ():
515
+ return
516
+ case <- windowChange :
517
+ }
518
+ width , height , err := term .GetSize (int (stdoutFile .Fd ()))
519
+ if err != nil {
520
+ continue
521
+ }
522
+ _ = sshSession .WindowChange (height , width )
523
+ }
524
+ }()
489
525
}
490
526
491
527
for _ , kv := range parsedEnv {
@@ -667,48 +703,14 @@ func (r *RootCmd) ssh() *serpent.Command {
667
703
{
668
704
Flag : "force-new-tunnel" ,
669
705
Description : "Force the creation of a new tunnel to the workspace, even if the Coder Connect tunnel is available." ,
670
- Value : serpent .BoolOf (& forceTunnel ),
706
+ Value : serpent .BoolOf (& forceNewTunnel ),
707
+ Hidden : true ,
671
708
},
672
709
sshDisableAutostartOption (serpent .BoolOf (& disableAutostart )),
673
710
}
674
711
return cmd
675
712
}
676
713
677
- func configurePTY (ctx context.Context , stdinFile * os.File , stdoutFile * os.File , sshSession * gossh.Session ) (restoreFn func (), err error ) {
678
- inState , err := pty .MakeInputRaw (stdinFile .Fd ())
679
- if err != nil {
680
- return restoreFn , err
681
- }
682
- restoreFn = func () {
683
- _ = pty .RestoreTerminal (stdinFile .Fd (), inState )
684
- }
685
- outState , err := pty .MakeOutputRaw (stdoutFile .Fd ())
686
- if err != nil {
687
- return restoreFn , err
688
- }
689
- restoreFn = func () {
690
- _ = pty .RestoreTerminal (stdinFile .Fd (), inState )
691
- _ = pty .RestoreTerminal (stdoutFile .Fd (), outState )
692
- }
693
-
694
- windowChange := listenWindowSize (ctx )
695
- go func () {
696
- for {
697
- select {
698
- case <- ctx .Done ():
699
- return
700
- case <- windowChange :
701
- }
702
- width , height , err := term .GetSize (int (stdoutFile .Fd ()))
703
- if err != nil {
704
- continue
705
- }
706
- _ = sshSession .WindowChange (height , width )
707
- }
708
- }()
709
- return restoreFn , nil
710
- }
711
-
712
714
// findWorkspaceAndAgentByHostname parses the hostname from the commandline and finds the workspace and agent it
713
715
// corresponds to, taking into account any name prefixes or suffixes configured (e.g. myworkspace.coder, or
714
716
// vscode-coder--myusername--myworkspace).
@@ -1502,87 +1504,14 @@ func runCoderConnectStdio(ctx context.Context, addr string, stdin io.Reader, std
1502
1504
return err
1503
1505
}
1504
1506
1505
- agentssh .Bicopy (ctx , conn , & cliutil.StdioConn {
1507
+ agentssh .Bicopy (ctx , conn , & cliutil.ReaderWriterConn {
1506
1508
Reader : stdin ,
1507
1509
Writer : stdout ,
1508
1510
})
1509
1511
1510
1512
return nil
1511
1513
}
1512
1514
1513
- func runCoderConnectPTY (ctx context.Context , addr string , stdin io.Reader , stdout io.Writer , stderr io.Writer , stack * closerStack ) error {
1514
- client , err := gossh .Dial ("tcp" , addr , & gossh.ClientConfig {
1515
- // We've already checked the agent's address
1516
- // is within the Coder service prefix.
1517
- // #nosec
1518
- HostKeyCallback : gossh .InsecureIgnoreHostKey (),
1519
- })
1520
- if err != nil {
1521
- return xerrors .Errorf ("dial coder connect host: %w" , err )
1522
- }
1523
- if err := stack .push ("ssh client" , client ); err != nil {
1524
- return err
1525
- }
1526
-
1527
- session , err := client .NewSession ()
1528
- if err != nil {
1529
- return xerrors .Errorf ("create ssh session: %w" , err )
1530
- }
1531
- if err := stack .push ("ssh session" , session ); err != nil {
1532
- return err
1533
- }
1534
-
1535
- stdinFile , validIn := stdin .(* os.File )
1536
- stdoutFile , validOut := stdout .(* os.File )
1537
- if validIn && validOut && isatty .IsTerminal (stdinFile .Fd ()) && isatty .IsTerminal (stdoutFile .Fd ()) {
1538
- restorePtyFn , err := configurePTY (ctx , stdinFile , stdoutFile , session )
1539
- defer restorePtyFn ()
1540
- if err != nil {
1541
- return xerrors .Errorf ("configure pty: %w" , err )
1542
- }
1543
- }
1544
-
1545
- session .Stdin = stdin
1546
- session .Stdout = stdout
1547
- session .Stderr = stderr
1548
-
1549
- err = session .RequestPty ("xterm-256color" , 80 , 24 , gossh.TerminalModes {})
1550
- if err != nil {
1551
- return xerrors .Errorf ("request pty: %w" , err )
1552
- }
1553
-
1554
- err = session .Shell ()
1555
- if err != nil {
1556
- return xerrors .Errorf ("start shell: %w" , err )
1557
- }
1558
-
1559
- if validOut {
1560
- // Set initial window size.
1561
- width , height , err := term .GetSize (int (stdoutFile .Fd ()))
1562
- if err == nil {
1563
- _ = session .WindowChange (height , width )
1564
- }
1565
- }
1566
-
1567
- err = session .Wait ()
1568
- if err != nil {
1569
- if exitErr := (& gossh.ExitError {}); errors .As (err , & exitErr ) {
1570
- // Clear the error since it's not useful beyond
1571
- // reporting status.
1572
- return ExitError (exitErr .ExitStatus (), nil )
1573
- }
1574
- // If the connection drops unexpectedly, we get an
1575
- // ExitMissingError but no other error details, so try to at
1576
- // least give the user a better message
1577
- if errors .Is (err , & gossh.ExitMissingError {}) {
1578
- return ExitError (255 , xerrors .New ("SSH connection ended unexpectedly" ))
1579
- }
1580
- return xerrors .Errorf ("session ended: %w" , err )
1581
- }
1582
-
1583
- return nil
1584
- }
1585
-
1586
1515
func writeCoderConnectNetInfo (ctx context.Context , networkInfoDir string ) error {
1587
1516
fs , ok := ctx .Value ("fs" ).(afero.Fs )
1588
1517
if ! ok {
0 commit comments