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,6 +31,12 @@ const defaultFileSystem: FileSystem = {
30
31
writeFile,
31
32
}
32
33
34
+ export const defaultSSHConfigResponse : SSHConfigResponse = {
35
+ ssh_config_options : { } ,
36
+ // The prefix is not used by the vscode-extension
37
+ hostname_prefix : "coder." ,
38
+ }
39
+
33
40
export class SSHConfig {
34
41
private filePath : string
35
42
private fileSystem : FileSystem
@@ -51,15 +58,15 @@ export class SSHConfig {
51
58
}
52
59
}
53
60
54
- async update ( values : SSHValues ) {
61
+ async update ( values : SSHValues , overrides : SSHConfigResponse = defaultSSHConfigResponse ) {
55
62
// We should remove this in March 2023 because there is not going to have
56
63
// old configs
57
64
this . cleanUpOldConfig ( )
58
65
const block = this . getBlock ( )
59
66
if ( block ) {
60
67
this . eraseBlock ( block )
61
68
}
62
- this . appendBlock ( values )
69
+ this . appendBlock ( values , overrides . ssh_config_options )
63
70
await this . save ( )
64
71
}
65
72
@@ -102,12 +109,59 @@ export class SSHConfig {
102
109
this . raw = this . getRaw ( ) . replace ( block . raw , "" )
103
110
}
104
111
105
- private appendBlock ( { Host, ...otherValues } : SSHValues ) {
112
+ /**
113
+ *
114
+ * appendBlock builds the ssh config block. The order of the keys is determinstic based on the input.
115
+ * Expected values are always in a consistent order followed by any additional overrides in sorted order.
116
+ *
117
+ * @param param0 - SSHValues are the expected SSH values for using ssh with coder.
118
+ * @param overrides - Overrides typically come from the deployment api and are used to override the default values.
119
+ * The overrides are given as key:value pairs where the key is the ssh config file key.
120
+ * If the key matches an expected value, the expected value is overridden. If it does not
121
+ * match an expected value, it is appended to the end of the block.
122
+ */
123
+ private appendBlock ( { Host, ...otherValues } : SSHValues , overrides : Record < string , string > ) {
106
124
const lines = [ this . startBlockComment , `Host ${ Host } ` ]
125
+ // We need to do a case insensitive match for the overrides as ssh config keys are case insensitive.
126
+ // To get the correct key:value, use:
127
+ // key = caseInsensitiveOverrides[key.toLowerCase()]
128
+ // value = overrides[key]
129
+ const caseInsensitiveOverrides : Record < string , string > = { }
130
+ Object . keys ( overrides ) . forEach ( ( key ) => {
131
+ caseInsensitiveOverrides [ key . toLowerCase ( ) ] = key
132
+ } )
133
+
107
134
const keys = Object . keys ( otherValues ) as Array < keyof typeof otherValues >
108
135
keys . forEach ( ( key ) => {
136
+ const lower = key . toLowerCase ( )
137
+ if ( caseInsensitiveOverrides [ lower ] ) {
138
+ const correctCaseKey = caseInsensitiveOverrides [ lower ]
139
+ const value = overrides [ correctCaseKey ]
140
+ // Remove the key from the overrides so we don't write it again.
141
+ delete caseInsensitiveOverrides [ lower ]
142
+ if ( value === "" ) {
143
+ // If the value is empty, don't write it. Prevent writing the default
144
+ // value as well.
145
+ return
146
+ }
147
+ // If the key is in overrides, use the override value.
148
+ // Doing it this way maintains the default order of the keys.
149
+ lines . push ( this . withIndentation ( `${ key } ${ value } ` ) )
150
+ return
151
+ }
109
152
lines . push ( this . withIndentation ( `${ key } ${ otherValues [ key ] } ` ) )
110
153
} )
154
+ // Write remaining overrides that have not been written yet. Sort to maintain deterministic order.
155
+ const remainingKeys = ( Object . keys ( caseInsensitiveOverrides ) as Array < keyof typeof caseInsensitiveOverrides > ) . sort ( )
156
+ remainingKeys . forEach ( ( key ) => {
157
+ const correctKey = caseInsensitiveOverrides [ key ]
158
+ const value = overrides [ correctKey ]
159
+ // Only write the value if it is not empty.
160
+ if ( value !== "" ) {
161
+ lines . push ( this . withIndentation ( `${ correctKey } ${ value } ` ) )
162
+ }
163
+ } )
164
+
111
165
lines . push ( this . endBlockComment )
112
166
const raw = this . getRaw ( )
113
167
0 commit comments