Skip to content

Commit 8d0f72c

Browse files
committed
fix: Add Include to the top of config, verify semantically
1 parent c5f1983 commit 8d0f72c

File tree

1 file changed

+48
-12
lines changed

1 file changed

+48
-12
lines changed

cli/configssh.go

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"io/fs"
99
"os"
1010
"path/filepath"
11+
"regexp"
1112
"runtime"
1213
"sort"
1314
"strings"
@@ -26,8 +27,7 @@ import (
2627
)
2728

2829
const (
29-
sshDefaultConfigFileName = "~/.ssh/config"
30-
// TODO(mafredri): Consider moving to coder config dir, i.e. ~/.config/coderv2/ssh_config?
30+
sshDefaultConfigFileName = "~/.ssh/config"
3131
sshDefaultCoderConfigFileName = "~/.ssh/coder"
3232
sshCoderConfigHeader = `# This file is managed by coder. DO NOT EDIT.
3333
#
@@ -42,6 +42,16 @@ const (
4242
sshConfigIncludeStatement = "Include coder"
4343
)
4444

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+
4555
func configSSH() *cobra.Command {
4656
var (
4757
sshConfigFile string
@@ -132,15 +142,11 @@ func configSSH() *cobra.Command {
132142
changes = append(changes, fmt.Sprintf("Remove old config block (START-CODER/END-CODER) from %s", sshConfigFileOrig))
133143
}
134144

135-
if found := bytes.Index(configModified, []byte(sshConfigIncludeStatement)); found == -1 || (found > 0 && configModified[found-1] != '\n') {
136-
changes = append(changes, fmt.Sprintf("Add 'Include coder' to %s", sshConfigFileOrig))
137-
// Separate Include statement from user content with an empty newline.
138-
configModified = bytes.TrimRight(configModified, "\n")
139-
sep := "\n\n"
140-
if len(configModified) == 0 {
141-
sep = ""
142-
}
143-
configModified = append(configModified, []byte(sep+sshConfigIncludeStatement+"\n")...)
145+
// Check for the presence of the coder Include
146+
// statement is present and add if missing.
147+
configModified, ok = sshConfigAddCoderInclude(configModified)
148+
if ok {
149+
changes = append(changes, fmt.Sprintf("Add %q to %s", "Include coder", sshConfigFileOrig))
144150
}
145151

146152
root := createConfig(cmd)
@@ -278,7 +284,7 @@ func configSSH() *cobra.Command {
278284
_, _ = fmt.Fprint(out, "\n")
279285

280286
if !bytes.Equal(configRaw, configModified) {
281-
err = writeWithTempFileAndMove(sshConfigFile, bytes.NewReader(configRaw))
287+
err = writeWithTempFileAndMove(sshConfigFile, bytes.NewReader(configModified))
282288
if err != nil {
283289
return xerrors.Errorf("write ssh config failed: %w", err)
284290
}
@@ -311,6 +317,36 @@ func configSSH() *cobra.Command {
311317
return cmd
312318
}
313319

320+
// sshConfigAddCoderInclude checks for the coder Include statement and
321+
// returns modified = true if it was added.
322+
func sshConfigAddCoderInclude(data []byte) (modifiedData []byte, modified bool) {
323+
found := false
324+
firstHost := sshHostRe.FindIndex(data)
325+
coderInclude := sshCoderIncludedRe.FindIndex(data)
326+
if firstHost != nil && coderInclude != nil {
327+
// If the Coder Include statement exists
328+
// before a Host entry, we're good.
329+
found = coderInclude[1] < firstHost[0]
330+
} else if coderInclude != nil {
331+
found = true
332+
}
333+
if found {
334+
return data, false
335+
}
336+
337+
// Add Include statement to the top of SSH config.
338+
// The user is allowed to move it as long as it
339+
// stays above the first Host (or Match) statement.
340+
sep := "\n\n"
341+
if len(data) == 0 {
342+
// If SSH config is empty, a single newline will suffice.
343+
sep = "\n"
344+
}
345+
data = append([]byte(sshConfigIncludeStatement+sep), data...)
346+
347+
return data, true
348+
}
349+
314350
// writeWithTempFileAndMove writes to a temporary file in the same
315351
// directory as path and renames the temp file to the file provided in
316352
// path. This ensure we avoid trashing the file we are writing due to

0 commit comments

Comments
 (0)