1
+ import { SSHConfigResponse } from "coder/site/src/api/typesGenerated"
1
2
import { writeFile , readFile } from "fs/promises"
2
3
import { ensureDir } from "fs-extra"
3
4
import path from "path"
@@ -30,16 +31,27 @@ const defaultFileSystem: FileSystem = {
30
31
writeFile,
31
32
}
32
33
34
+ const defaultSSHConfigResponse : SSHConfigResponse = {
35
+ ssh_config_options : { } ,
36
+ hostname_prefix : "coder." ,
37
+ }
38
+
33
39
export class SSHConfig {
34
40
private filePath : string
35
41
private fileSystem : FileSystem
42
+ private deploymentConfig : SSHConfigResponse
36
43
private raw : string | undefined
37
44
private startBlockComment = "# --- START CODER VSCODE ---"
38
45
private endBlockComment = "# --- END CODER VSCODE ---"
39
46
40
- constructor ( filePath : string , fileSystem : FileSystem = defaultFileSystem ) {
47
+ constructor (
48
+ filePath : string ,
49
+ fileSystem : FileSystem = defaultFileSystem ,
50
+ sshConfig : SSHConfigResponse = defaultSSHConfigResponse ,
51
+ ) {
41
52
this . filePath = filePath
42
53
this . fileSystem = fileSystem
54
+ this . deploymentConfig = sshConfig
43
55
}
44
56
45
57
async load ( ) {
@@ -59,7 +71,7 @@ export class SSHConfig {
59
71
if ( block ) {
60
72
this . eraseBlock ( block )
61
73
}
62
- this . appendBlock ( values )
74
+ this . appendBlock ( values , this . deploymentConfig . ssh_config_options )
63
75
await this . save ( )
64
76
}
65
77
@@ -102,12 +114,49 @@ export class SSHConfig {
102
114
this . raw = this . getRaw ( ) . replace ( block . raw , "" )
103
115
}
104
116
105
- private appendBlock ( { Host, ...otherValues } : SSHValues ) {
117
+ /**
118
+ *
119
+ * appendBlock builds the ssh config block. The order of the keys is determinstic based on the input.
120
+ * Expected values are always in a consistent order followed by any additional overrides in sorted order.
121
+ *
122
+ * @param param0 - SSHValues are the expected SSH values for using ssh with coder.
123
+ * @param overrides - Overrides typically come from the deployment api and are used to override the default values.
124
+ * The overrides are given as key:value pairs where the key is the ssh config file key.
125
+ * If the key matches an expected value, the expected value is overridden. If it does not
126
+ * match an expected value, it is appended to the end of the block.
127
+ */
128
+ private appendBlock ( { Host, ...otherValues } : SSHValues , overrides : Record < string , string > ) {
106
129
const lines = [ this . startBlockComment , `Host ${ Host } ` ]
130
+ // We need to do a case insensitive match for the overrides as ssh config keys are case insensitive.
131
+ // To get the correct key:value, use:
132
+ // key = caseInsensitiveOverrides[key.toLowerCase()]
133
+ // value = overrides[key]
134
+ const caseInsensitiveOverrides : Record < string , string > = { }
135
+ Object . keys ( overrides ) . forEach ( ( key ) => {
136
+ caseInsensitiveOverrides [ key . toLowerCase ( ) ] = key
137
+ } )
138
+
107
139
const keys = Object . keys ( otherValues ) as Array < keyof typeof otherValues >
108
140
keys . forEach ( ( key ) => {
141
+ const lower = key . toLowerCase ( )
142
+ if ( caseInsensitiveOverrides [ lower ] ) {
143
+ const correctCaseKey = caseInsensitiveOverrides [ lower ]
144
+ // If the key is in overrides, use the override value.
145
+ // Doing it this way maintains the default order of the keys.
146
+ lines . push ( this . withIndentation ( `${ key } ${ overrides [ correctCaseKey ] } ` ) )
147
+ // Remove the key from the overrides so we don't write it again.
148
+ delete caseInsensitiveOverrides [ lower ]
149
+ return
150
+ }
109
151
lines . push ( this . withIndentation ( `${ key } ${ otherValues [ key ] } ` ) )
110
152
} )
153
+ // Write remaining overrides that have not been written yet. Sort to maintain deterministic order.
154
+ const remainingKeys = ( Object . keys ( caseInsensitiveOverrides ) as Array < keyof typeof caseInsensitiveOverrides > ) . sort ( )
155
+ remainingKeys . forEach ( ( key ) => {
156
+ const correctKey = caseInsensitiveOverrides [ key ]
157
+ lines . push ( this . withIndentation ( `${ key } ${ overrides [ correctKey ] } ` ) )
158
+ } )
159
+
111
160
lines . push ( this . endBlockComment )
112
161
const raw = this . getRaw ( )
113
162
0 commit comments