@@ -19,6 +19,7 @@ import (
19
19
"github.com/cli/safeexec"
20
20
"github.com/pkg/diff"
21
21
"github.com/pkg/diff/write"
22
+ "golang.org/x/exp/constraints"
22
23
"golang.org/x/exp/slices"
23
24
"golang.org/x/sync/errgroup"
24
25
"golang.org/x/xerrors"
@@ -51,6 +52,8 @@ type sshConfigOptions struct {
51
52
userHostPrefix string
52
53
sshOptions []string
53
54
disableAutostart bool
55
+ header []string
56
+ headerCommand string
54
57
}
55
58
56
59
// addOptions expects options in the form of "option=value" or "option value".
@@ -100,15 +103,25 @@ func (o *sshConfigOptions) addOption(option string) error {
100
103
}
101
104
102
105
func (o sshConfigOptions ) equal (other sshConfigOptions ) bool {
103
- // Compare without side-effects or regard to order.
104
- opt1 := slices .Clone (o .sshOptions )
105
- sort .Strings (opt1 )
106
- opt2 := slices .Clone (other .sshOptions )
107
- sort .Strings (opt2 )
108
- if ! slices .Equal (opt1 , opt2 ) {
106
+ if ! slicesSortedEqual (o .sshOptions , other .sshOptions ) {
109
107
return false
110
108
}
111
- return o .waitEnum == other .waitEnum && o .userHostPrefix == other .userHostPrefix && o .disableAutostart == other .disableAutostart
109
+ if ! slicesSortedEqual (o .header , other .header ) {
110
+ return false
111
+ }
112
+ return o .waitEnum == other .waitEnum && o .userHostPrefix == other .userHostPrefix && o .disableAutostart == other .disableAutostart && o .headerCommand == other .headerCommand
113
+ }
114
+
115
+ // slicesSortedEqual compares two slices without side-effects or regard to order.
116
+ func slicesSortedEqual [S ~ []E , E constraints.Ordered ](a , b S ) bool {
117
+ if len (a ) != len (b ) {
118
+ return false
119
+ }
120
+ a = slices .Clone (a )
121
+ slices .Sort (a )
122
+ b = slices .Clone (b )
123
+ slices .Sort (b )
124
+ return slices .Equal (a , b )
112
125
}
113
126
114
127
func (o sshConfigOptions ) asList () (list []string ) {
@@ -124,6 +137,13 @@ func (o sshConfigOptions) asList() (list []string) {
124
137
for _ , opt := range o .sshOptions {
125
138
list = append (list , fmt .Sprintf ("ssh-option: %s" , opt ))
126
139
}
140
+ for _ , h := range o .header {
141
+ list = append (list , fmt .Sprintf ("header: %s" , h ))
142
+ }
143
+ if o .headerCommand != "" {
144
+ list = append (list , fmt .Sprintf ("header-command: %s" , o .headerCommand ))
145
+ }
146
+
127
147
return list
128
148
}
129
149
@@ -230,6 +250,8 @@ func (r *RootCmd) configSSH() *clibase.Cmd {
230
250
// specifies skip-proxy-command, then wait cannot be applied.
231
251
return xerrors .Errorf ("cannot specify both --skip-proxy-command and --wait" )
232
252
}
253
+ sshConfigOpts .header = r .header
254
+ sshConfigOpts .headerCommand = r .headerCommand
233
255
234
256
recvWorkspaceConfigs := sshPrepareWorkspaceConfigs (inv .Context (), client )
235
257
@@ -393,6 +415,14 @@ func (r *RootCmd) configSSH() *clibase.Cmd {
393
415
}
394
416
395
417
if ! skipProxyCommand {
418
+ rootFlags := fmt .Sprintf ("--global-config %s" , escapedGlobalConfig )
419
+ for _ , h := range sshConfigOpts .header {
420
+ rootFlags += fmt .Sprintf (" --header %q" , h )
421
+ }
422
+ if sshConfigOpts .headerCommand != "" {
423
+ rootFlags += fmt .Sprintf (" --header-command %q" , sshConfigOpts .headerCommand )
424
+ }
425
+
396
426
flags := ""
397
427
if sshConfigOpts .waitEnum != "auto" {
398
428
flags += " --wait=" + sshConfigOpts .waitEnum
@@ -401,8 +431,8 @@ func (r *RootCmd) configSSH() *clibase.Cmd {
401
431
flags += " --disable-autostart=true"
402
432
}
403
433
defaultOptions = append (defaultOptions , fmt .Sprintf (
404
- "ProxyCommand %s --global-config %s ssh --stdio%s %s" ,
405
- escapedCoderBinary , escapedGlobalConfig , flags , workspaceHostname ,
434
+ "ProxyCommand %s %s ssh --stdio%s %s" ,
435
+ escapedCoderBinary , rootFlags , flags , workspaceHostname ,
406
436
))
407
437
}
408
438
@@ -623,6 +653,12 @@ func sshConfigWriteSectionHeader(w io.Writer, addNewline bool, o sshConfigOption
623
653
for _ , opt := range o .sshOptions {
624
654
_ , _ = fmt .Fprintf (& ow , "# :%s=%s\n " , "ssh-option" , opt )
625
655
}
656
+ for _ , h := range o .header {
657
+ _ , _ = fmt .Fprintf (& ow , "# :%s=%s\n " , "header" , h )
658
+ }
659
+ if o .headerCommand != "" {
660
+ _ , _ = fmt .Fprintf (& ow , "# :%s=%s\n " , "header-command" , o .headerCommand )
661
+ }
626
662
if ow .Len () > 0 {
627
663
_ , _ = fmt .Fprint (w , sshConfigOptionsHeader )
628
664
_ , _ = fmt .Fprint (w , ow .String ())
@@ -654,6 +690,10 @@ func sshConfigParseLastOptions(r io.Reader) (o sshConfigOptions) {
654
690
o .sshOptions = append (o .sshOptions , parts [1 ])
655
691
case "disable-autostart" :
656
692
o .disableAutostart , _ = strconv .ParseBool (parts [1 ])
693
+ case "header" :
694
+ o .header = append (o .header , parts [1 ])
695
+ case "header-command" :
696
+ o .headerCommand = parts [1 ]
657
697
default :
658
698
// Unknown option, ignore.
659
699
}
0 commit comments