@@ -45,7 +45,10 @@ const (
45
45
// sshConfigOptions represents options that can be stored and read
46
46
// from the coder config in ~/.ssh/coder.
47
47
type sshConfigOptions struct {
48
- sshOptions []string
48
+ wait bool
49
+ noWait bool
50
+ userHostPrefix string
51
+ sshOptions []string
49
52
}
50
53
51
54
// addOptions expects options in the form of "option=value" or "option value".
@@ -100,10 +103,22 @@ func (o sshConfigOptions) equal(other sshConfigOptions) bool {
100
103
sort .Strings (opt1 )
101
104
opt2 := slices .Clone (other .sshOptions )
102
105
sort .Strings (opt2 )
103
- return slices .Equal (opt1 , opt2 )
106
+ if ! slices .Equal (opt1 , opt2 ) {
107
+ return false
108
+ }
109
+ return o .wait == other .wait && o .noWait == other .noWait && o .userHostPrefix == other .userHostPrefix
104
110
}
105
111
106
112
func (o sshConfigOptions ) asList () (list []string ) {
113
+ if o .wait {
114
+ list = append (list , "wait" )
115
+ }
116
+ if o .noWait {
117
+ list = append (list , "no-wait" )
118
+ }
119
+ if o .userHostPrefix != "" {
120
+ list = append (list , fmt .Sprintf ("ssh-host-prefix: %s" , o .userHostPrefix ))
121
+ }
107
122
for _ , opt := range o .sshOptions {
108
123
list = append (list , fmt .Sprintf ("ssh-option: %s" , opt ))
109
124
}
@@ -178,14 +193,14 @@ func sshPrepareWorkspaceConfigs(ctx context.Context, client *codersdk.Client) (r
178
193
}
179
194
}
180
195
196
+ //nolint:gocyclo
181
197
func (r * RootCmd ) configSSH () * clibase.Cmd {
182
198
var (
183
199
sshConfigFile string
184
200
sshConfigOpts sshConfigOptions
185
201
usePreviousOpts bool
186
202
dryRun bool
187
203
skipProxyCommand bool
188
- userHostPrefix string
189
204
)
190
205
client := new (codersdk.Client )
191
206
cmd := & clibase.Cmd {
@@ -207,6 +222,13 @@ func (r *RootCmd) configSSH() *clibase.Cmd {
207
222
r .InitClient (client ),
208
223
),
209
224
Handler : func (inv * clibase.Invocation ) error {
225
+ if sshConfigOpts .wait && sshConfigOpts .noWait {
226
+ return xerrors .Errorf ("cannot specify both --wait and --no-wait" )
227
+ }
228
+ if skipProxyCommand && (sshConfigOpts .wait || sshConfigOpts .noWait ) {
229
+ return xerrors .Errorf ("cannot specify --skip-proxy-command with --wait or --no-wait" )
230
+ }
231
+
210
232
recvWorkspaceConfigs := sshPrepareWorkspaceConfigs (inv .Context (), client )
211
233
212
234
out := inv .Stdout
@@ -295,7 +317,7 @@ func (r *RootCmd) configSSH() *clibase.Cmd {
295
317
// Selecting "no" will use the last config.
296
318
sshConfigOpts = * lastConfig
297
319
} else {
298
- changes = append (changes , "Use new SSH options" )
320
+ changes = append (changes , "Use new options" )
299
321
}
300
322
// Only print when prompts are shown.
301
323
if yes , _ := inv .ParsedFlags ().GetBool ("yes" ); ! yes {
@@ -336,9 +358,9 @@ func (r *RootCmd) configSSH() *clibase.Cmd {
336
358
coderdConfig .HostnamePrefix = "coder."
337
359
}
338
360
339
- if userHostPrefix != "" {
361
+ if sshConfigOpts . userHostPrefix != "" {
340
362
// Override with user flag.
341
- coderdConfig .HostnamePrefix = userHostPrefix
363
+ coderdConfig .HostnamePrefix = sshConfigOpts . userHostPrefix
342
364
}
343
365
344
366
// Ensure stable sorting of output.
@@ -363,13 +385,22 @@ func (r *RootCmd) configSSH() *clibase.Cmd {
363
385
}
364
386
365
387
if ! skipProxyCommand {
388
+ flags := ""
389
+ if sshConfigOpts .wait {
390
+ flags += " --wait"
391
+ } else if sshConfigOpts .noWait {
392
+ flags += " --no-wait"
393
+ }
366
394
defaultOptions = append (defaultOptions , fmt .Sprintf (
367
- "ProxyCommand %s --global-config %s ssh --stdio %s" ,
368
- escapedCoderBinary , escapedGlobalConfig , workspaceHostname ,
395
+ "ProxyCommand %s --global-config %s ssh --stdio%s %s" ,
396
+ escapedCoderBinary , escapedGlobalConfig , flags , workspaceHostname ,
369
397
))
370
398
}
371
399
372
- var configOptions sshConfigOptions
400
+ // Create a copy of the options so we can modify them.
401
+ configOptions := sshConfigOpts
402
+ configOptions .sshOptions = nil
403
+
373
404
// Add standard options.
374
405
err := configOptions .addOptions (defaultOptions ... )
375
406
if err != nil {
@@ -507,7 +538,19 @@ func (r *RootCmd) configSSH() *clibase.Cmd {
507
538
Flag : "ssh-host-prefix" ,
508
539
Env : "" ,
509
540
Description : "Override the default host prefix." ,
510
- Value : clibase .StringOf (& userHostPrefix ),
541
+ Value : clibase .StringOf (& sshConfigOpts .userHostPrefix ),
542
+ },
543
+ {
544
+ Flag : "wait" ,
545
+ Env : "CODER_CONFIGSSH_WAIT" , // Not to be mixed with CODER_SSH_WAIT.
546
+ Description : "Wait for the the startup script to finish executing. This is the default if the template has configured the agent startup script behavior as blocking. Can not be used together with --no-wait." ,
547
+ Value : clibase .BoolOf (& sshConfigOpts .wait ),
548
+ },
549
+ {
550
+ Flag : "no-wait" ,
551
+ Env : "CODER_CONFIGSSH_NO_WAIT" , // Not to be mixed with CODER_SSH_NO_WAIT.
552
+ Description : "Enter workspace immediately after the agent has connected. This is the default if the template has configured the agent startup script behavior as non-blocking. Can not be used together with --wait." ,
553
+ Value : clibase .BoolOf (& sshConfigOpts .noWait ),
511
554
},
512
555
cliui .SkipPromptOption (),
513
556
}
@@ -524,12 +567,25 @@ func sshConfigWriteSectionHeader(w io.Writer, addNewline bool, o sshConfigOption
524
567
_ , _ = fmt .Fprint (w , nl + sshStartToken + "\n " )
525
568
_ , _ = fmt .Fprint (w , sshConfigSectionHeader )
526
569
_ , _ = fmt .Fprint (w , sshConfigDocsHeader )
527
- if len (o .sshOptions ) > 0 {
570
+
571
+ var ow strings.Builder
572
+ if o .wait {
573
+ _ , _ = fmt .Fprintf (& ow , "# :%s\n " , "wait" )
574
+ }
575
+ if o .noWait {
576
+ _ , _ = fmt .Fprintf (& ow , "# :%s\n " , "no-wait" )
577
+ }
578
+ if o .userHostPrefix != "" {
579
+ _ , _ = fmt .Fprintf (& ow , "# :%s=%s\n " , "ssh-host-prefix" , o .userHostPrefix )
580
+ }
581
+ for _ , opt := range o .sshOptions {
582
+ _ , _ = fmt .Fprintf (& ow , "# :%s=%s\n " , "ssh-option" , opt )
583
+ }
584
+ if ow .Len () > 0 {
528
585
_ , _ = fmt .Fprint (w , sshConfigOptionsHeader )
529
- for _ , opt := range o .sshOptions {
530
- _ , _ = fmt .Fprintf (w , "# :%s=%s\n " , "ssh-option" , opt )
531
- }
586
+ _ , _ = fmt .Fprint (w , ow .String ())
532
587
}
588
+
533
589
_ , _ = fmt .Fprint (w , "#\n " )
534
590
}
535
591
@@ -545,6 +601,12 @@ func sshConfigParseLastOptions(r io.Reader) (o sshConfigOptions) {
545
601
line = strings .TrimPrefix (line , "# :" )
546
602
parts := strings .SplitN (line , "=" , 2 )
547
603
switch parts [0 ] {
604
+ case "wait" :
605
+ o .wait = true
606
+ case "no-wait" :
607
+ o .noWait = true
608
+ case "user-host-prefix" :
609
+ o .userHostPrefix = parts [1 ]
548
610
case "ssh-option" :
549
611
o .sshOptions = append (o .sshOptions , parts [1 ])
550
612
default :
0 commit comments