1+ import { SSHConfigResponse } from "coder/site/src/api/typesGenerated"
12import { writeFile , readFile } from "fs/promises"
23import { ensureDir } from "fs-extra"
34import path from "path"
@@ -30,6 +31,12 @@ const defaultFileSystem: FileSystem = {
3031 writeFile,
3132}
3233
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+
3340export class SSHConfig {
3441 private filePath : string
3542 private fileSystem : FileSystem
@@ -51,15 +58,15 @@ export class SSHConfig {
5158 }
5259 }
5360
54- async update ( values : SSHValues ) {
61+ async update ( values : SSHValues , overrides : SSHConfigResponse = defaultSSHConfigResponse ) {
5562 // We should remove this in March 2023 because there is not going to have
5663 // old configs
5764 this . cleanUpOldConfig ( )
5865 const block = this . getBlock ( )
5966 if ( block ) {
6067 this . eraseBlock ( block )
6168 }
62- this . appendBlock ( values )
69+ this . appendBlock ( values , overrides . ssh_config_options )
6370 await this . save ( )
6471 }
6572
@@ -102,12 +109,59 @@ export class SSHConfig {
102109 this . raw = this . getRaw ( ) . replace ( block . raw , "" )
103110 }
104111
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 > ) {
106124 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+
107134 const keys = Object . keys ( otherValues ) as Array < keyof typeof otherValues >
108135 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+ }
109152 lines . push ( this . withIndentation ( `${ key } ${ otherValues [ key ] } ` ) )
110153 } )
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+
111165 lines . push ( this . endBlockComment )
112166 const raw = this . getRaw ( )
113167
0 commit comments