8
8
"io/fs"
9
9
"os"
10
10
"path/filepath"
11
+ "regexp"
11
12
"runtime"
12
13
"sort"
13
14
"strings"
@@ -26,8 +27,7 @@ import (
26
27
)
27
28
28
29
const (
29
- sshDefaultConfigFileName = "~/.ssh/config"
30
- // TODO(mafredri): Consider moving to coder config dir, i.e. ~/.config/coderv2/ssh_config?
30
+ sshDefaultConfigFileName = "~/.ssh/config"
31
31
sshDefaultCoderConfigFileName = "~/.ssh/coder"
32
32
sshCoderConfigHeader = `# This file is managed by coder. DO NOT EDIT.
33
33
#
@@ -42,6 +42,16 @@ const (
42
42
sshConfigIncludeStatement = "Include coder"
43
43
)
44
44
45
+ // Regular expressions are used because SSH configs do not have
46
+ // meaningful indentation and keywords are case-insensitive.
47
+ var (
48
+ // Find the first Host and Match statement as these restrict the
49
+ // following declarations to be used conditionally.
50
+ sshHostRe = regexp .MustCompile (`^\s*((?i)Host|Match)` )
51
+ // Find the semantically correct include statement.
52
+ sshCoderIncludedRe = regexp .MustCompile (`^\s*((?i)Include) coder(\s|$)` )
53
+ )
54
+
45
55
func configSSH () * cobra.Command {
46
56
var (
47
57
sshConfigFile string
@@ -134,15 +144,11 @@ func configSSH() *cobra.Command {
134
144
changes = append (changes , fmt .Sprintf ("Remove old config block (START-CODER/END-CODER) from %s" , sshConfigFileOrig ))
135
145
}
136
146
137
- if found := bytes .Index (configModified , []byte (sshConfigIncludeStatement )); found == - 1 || (found > 0 && configModified [found - 1 ] != '\n' ) {
138
- changes = append (changes , fmt .Sprintf ("Add 'Include coder' to %s" , sshConfigFileOrig ))
139
- // Separate Include statement from user content with an empty newline.
140
- configModified = bytes .TrimRight (configModified , "\n " )
141
- sep := "\n \n "
142
- if len (configModified ) == 0 {
143
- sep = ""
144
- }
145
- configModified = append (configModified , []byte (sep + sshConfigIncludeStatement + "\n " )... )
147
+ // Check for the presence of the coder Include
148
+ // statement is present and add if missing.
149
+ configModified , ok = sshConfigAddCoderInclude (configModified )
150
+ if ok {
151
+ changes = append (changes , fmt .Sprintf ("Add %q to %s" , "Include coder" , sshConfigFileOrig ))
146
152
}
147
153
148
154
root := createConfig (cmd )
@@ -280,7 +286,7 @@ func configSSH() *cobra.Command {
280
286
_ , _ = fmt .Fprint (out , "\n " )
281
287
282
288
if ! bytes .Equal (configRaw , configModified ) {
283
- err = writeWithTempFileAndMove (sshConfigFile , bytes .NewReader (configRaw ))
289
+ err = writeWithTempFileAndMove (sshConfigFile , bytes .NewReader (configModified ))
284
290
if err != nil {
285
291
return xerrors .Errorf ("write ssh config failed: %w" , err )
286
292
}
@@ -313,6 +319,36 @@ func configSSH() *cobra.Command {
313
319
return cmd
314
320
}
315
321
322
+ // sshConfigAddCoderInclude checks for the coder Include statement and
323
+ // returns modified = true if it was added.
324
+ func sshConfigAddCoderInclude (data []byte ) (modifiedData []byte , modified bool ) {
325
+ found := false
326
+ firstHost := sshHostRe .FindIndex (data )
327
+ coderInclude := sshCoderIncludedRe .FindIndex (data )
328
+ if firstHost != nil && coderInclude != nil {
329
+ // If the Coder Include statement exists
330
+ // before a Host entry, we're good.
331
+ found = coderInclude [1 ] < firstHost [0 ]
332
+ } else if coderInclude != nil {
333
+ found = true
334
+ }
335
+ if found {
336
+ return data , false
337
+ }
338
+
339
+ // Add Include statement to the top of SSH config.
340
+ // The user is allowed to move it as long as it
341
+ // stays above the first Host (or Match) statement.
342
+ sep := "\n \n "
343
+ if len (data ) == 0 {
344
+ // If SSH config is empty, a single newline will suffice.
345
+ sep = "\n "
346
+ }
347
+ data = append ([]byte (sshConfigIncludeStatement + sep ), data ... )
348
+
349
+ return data , true
350
+ }
351
+
316
352
// writeWithTempFileAndMove writes to a temporary file in the same
317
353
// directory as path and renames the temp file to the file provided in
318
354
// path. This ensure we avoid trashing the file we are writing due to
0 commit comments