@@ -21,7 +21,7 @@ import * as vscode from "vscode"
21
21
import * as ws from "ws"
22
22
import { z } from "zod"
23
23
import { SSHConfig , SSHValues , defaultSSHConfigResponse , mergeSSHConfigValues } from "./sshConfig"
24
- import { sshSupportsSetEnv } from "./sshSupport"
24
+ import { computeSSHProperties , sshSupportsSetEnv } from "./sshSupport"
25
25
import { Storage } from "./storage"
26
26
27
27
export class Remote {
@@ -123,7 +123,9 @@ export class Remote {
123
123
124
124
const disposables : vscode . Disposable [ ] = [ ]
125
125
// Register before connection so the label still displays!
126
- disposables . push ( this . registerLabelFormatter ( `${ this . storage . workspace . owner_name } /${ this . storage . workspace . name } ` ) )
126
+ disposables . push (
127
+ this . registerLabelFormatter ( remoteAuthority , this . storage . workspace . owner_name , this . storage . workspace . name ) ,
128
+ )
127
129
128
130
let buildComplete : undefined | ( ( ) => void )
129
131
if ( this . storage . workspace . latest_build . status === "stopped" ) {
@@ -409,7 +411,7 @@ export class Remote {
409
411
//
410
412
// If we didn't write to the SSH config file, connecting would fail with
411
413
// "Host not found".
412
- await this . updateSSHConfig ( )
414
+ await this . updateSSHConfig ( authorityParts [ 1 ] )
413
415
414
416
this . findSSHProcessID ( ) . then ( ( pid ) => {
415
417
if ( ! pid ) {
@@ -420,14 +422,11 @@ export class Remote {
420
422
} )
421
423
422
424
// Register the label formatter again because SSH overrides it!
423
- let label = `${ this . storage . workspace . owner_name } /${ this . storage . workspace . name } `
424
- if ( agents . length > 1 ) {
425
- label += `/${ agent . name } `
426
- }
427
-
425
+ const workspace = this . storage . workspace
426
+ const agentName = agents . length > 1 ? agent . name : undefined
428
427
disposables . push (
429
428
vscode . extensions . onDidChange ( ( ) => {
430
- disposables . push ( this . registerLabelFormatter ( label ) )
429
+ disposables . push ( this . registerLabelFormatter ( remoteAuthority , workspace . owner_name , workspace . name , agentName ) )
431
430
} ) ,
432
431
)
433
432
@@ -506,7 +505,7 @@ export class Remote {
506
505
507
506
// updateSSHConfig updates the SSH configuration with a wildcard that handles
508
507
// all Coder entries.
509
- private async updateSSHConfig ( ) {
508
+ private async updateSSHConfig ( hostName : string ) {
510
509
let deploymentSSHConfig = defaultSSHConfigResponse
511
510
try {
512
511
const deploymentConfig = await getDeploymentSSHConfig ( )
@@ -594,6 +593,34 @@ export class Remote {
594
593
}
595
594
596
595
await sshConfig . update ( sshValues , sshConfigOverrides )
596
+
597
+ // A user can provide a "Host *" entry in their SSH config to add options
598
+ // to all hosts. We need to ensure that the options we set are not
599
+ // overridden by the user's config.
600
+ const computedProperties = computeSSHProperties ( hostName , sshConfig . getRaw ( ) )
601
+ const keysToMatch : Array < keyof SSHValues > = [ "ProxyCommand" , "UserKnownHostsFile" , "StrictHostKeyChecking" ]
602
+ for ( let i = 0 ; i < keysToMatch . length ; i ++ ) {
603
+ const key = keysToMatch [ i ]
604
+ if ( computedProperties [ key ] === sshValues [ key ] ) {
605
+ continue
606
+ }
607
+
608
+ const result = await this . vscodeProposed . window . showErrorMessage (
609
+ "Unexpected SSH Config Option" ,
610
+ {
611
+ useCustom : true ,
612
+ modal : true ,
613
+ detail : `Your SSH config is overriding the "${ key } " property to "${ computedProperties [ key ] } " when it expected "${ sshValues [ key ] } " for the "${ hostName } " host. Please fix this and try again!` ,
614
+ } ,
615
+ "Reload Window" ,
616
+ )
617
+ if ( result === "Reload Window" ) {
618
+ await this . reloadWindow ( )
619
+ }
620
+ await this . closeRemote ( )
621
+ }
622
+
623
+ return sshConfig . getRaw ( )
597
624
}
598
625
599
626
// showNetworkUpdates finds the SSH process ID that is being used by this
@@ -744,14 +771,34 @@ export class Remote {
744
771
await vscode . commands . executeCommand ( "workbench.action.reloadWindow" )
745
772
}
746
773
747
- private registerLabelFormatter ( suffix : string ) : vscode . Disposable {
774
+ private registerLabelFormatter (
775
+ remoteAuthority : string ,
776
+ owner : string ,
777
+ workspace : string ,
778
+ agent ?: string ,
779
+ ) : vscode . Disposable {
780
+ // VS Code splits based on the separator when displaying the label
781
+ // in a recently opened dialog. If the workspace suffix contains /,
782
+ // then it'll visually display weird:
783
+ // "/home/kyle [Coder: kyle/workspace]" displays as "workspace] /home/kyle [Coder: kyle"
784
+ // For this reason, we use a different / that visually appears the
785
+ // same on non-monospace fonts "∕".
786
+ let suffix = `Coder: ${ owner } ∕${ workspace } `
787
+ if ( agent ) {
788
+ suffix += `∕${ agent } `
789
+ }
790
+ // VS Code caches resource label formatters in it's global storage SQLite database
791
+ // under the key "memento/cachedResourceLabelFormatters2".
748
792
return this . vscodeProposed . workspace . registerResourceLabelFormatter ( {
749
793
scheme : "vscode-remote" ,
794
+ // authority is optional but VS Code prefers formatters that most
795
+ // accurately match the requested authority, so we include it.
796
+ authority : remoteAuthority ,
750
797
formatting : {
751
798
label : "${path}" ,
752
799
separator : "/" ,
753
800
tildify : true ,
754
- workspaceSuffix : `Coder: ${ suffix } ` ,
801
+ workspaceSuffix : suffix ,
755
802
} ,
756
803
} )
757
804
}
0 commit comments