@@ -14,6 +14,7 @@ import {
1414 CliDownloadsDisabledError ,
1515 CliFallbackDeclinedError ,
1616 CliTelemetry ,
17+ type CliConfigureTrace ,
1718 type CliDownloadAction ,
1819 type CliDownloadReason ,
1920 type CliVersionCheckOutcome ,
@@ -55,16 +56,6 @@ type SingleVerifyResult =
5556 | { kind : "bypassed" }
5657 | { kind : "sig_unavailable" ; status : number } ;
5758
58- function getDownloadAction (
59- downloadsEnabled : boolean ,
60- hasExistingBinary : boolean ,
61- ) : CliDownloadAction {
62- if ( downloadsEnabled ) {
63- return "download" ;
64- }
65- return hasExistingBinary ? "fallback" : "blocked" ;
66- }
67-
6859export class CliManager {
6960 private readonly binaryLock : BinaryLock ;
7061 private readonly cliTelemetry : CliTelemetry ;
@@ -142,73 +133,82 @@ export class CliManager {
142133 * unable to download a working binary, whether because of network issues or
143134 * downloads being disabled.
144135 */
145- public async fetchBinary ( restClient : Api ) : Promise < string > {
146- return this . cliTelemetry . resolve ( async ( trace ) => {
147- const baseUrl = restClient . getAxiosInstance ( ) . defaults . baseURL ;
148- if ( ! baseUrl ) {
149- trace . setFailure ( "unknown" ) ;
150- throw new Error ( "REST client has no base URL configured" ) ;
151- }
152- const safeHostname = toSafeHost ( baseUrl ) ;
153- const cfg = vscode . workspace . getConfiguration ( "coder" ) ;
154- // Settings can be undefined when set to their defaults (true in this
155- // case), so explicitly check against false.
156- const enableDownloads = cfg . get ( "enableDownloads" ) !== false ;
157- this . output . debug (
158- "Downloads are" ,
159- enableDownloads ? "enabled" : "disabled" ,
160- ) ;
136+ public fetchBinary ( restClient : Api ) : Promise < string > {
137+ return this . cliTelemetry . resolve ( ( trace ) =>
138+ this . resolveBinary ( restClient , trace ) ,
139+ ) ;
140+ }
161141
162- const resolved = await trace . cacheLookup ( ( ) =>
163- this . lookupBinary ( safeHostname ) ,
164- ) ;
165- const { buildInfo, parsedVersion, existingVersion, downloadReason } =
166- await trace . versionCheck ( ( ) =>
167- this . checkResolvedBinary ( restClient , resolved ) ,
168- ) ;
142+ private async resolveBinary (
143+ restClient : Api ,
144+ trace : CliResolveTrace ,
145+ ) : Promise < string > {
146+ const baseUrl = restClient . getAxiosInstance ( ) . defaults . baseURL ;
147+ if ( ! baseUrl ) {
148+ trace . setFailure ( "unknown" ) ;
149+ throw new Error ( "REST client has no base URL configured" ) ;
150+ }
151+ const safeHostname = toSafeHost ( baseUrl ) ;
152+ const cfg = vscode . workspace . getConfiguration ( "coder" ) ;
153+ // Settings can be undefined when set to their defaults (true in this
154+ // case), so explicitly check against false.
155+ const enableDownloads = cfg . get ( "enableDownloads" ) !== false ;
156+ this . output . debug (
157+ "Downloads are" ,
158+ enableDownloads ? "enabled" : "disabled" ,
159+ ) ;
169160
170- if ( existingVersion === buildInfo . version ) {
171- this . output . debug ( "Existing binary matches server version" ) ;
172- trace . setOutcome ( "cache_hit" ) ;
173- return resolved . binPath ;
174- }
161+ const resolved = await trace . cacheLookup ( ( ) =>
162+ this . lookupBinary ( safeHostname ) ,
163+ ) ;
164+ const { buildInfo, parsedVersion, existingVersion, downloadReason } =
165+ await trace . versionCheck ( ( ) =>
166+ this . checkResolvedBinary ( restClient , resolved ) ,
167+ ) ;
175168
176- await trace . recordDownloadDecision ( {
177- reason : downloadReason ,
178- action : getDownloadAction ( enableDownloads , existingVersion !== null ) ,
179- } ) ;
169+ if ( existingVersion === buildInfo . version ) {
170+ this . output . debug ( "Existing binary matches server version" ) ;
171+ trace . setOutcome ( "cache_hit" ) ;
172+ return resolved . binPath ;
173+ }
180174
181- if ( ! enableDownloads ) {
182- if ( existingVersion ) {
183- this . output . info (
184- "Using existing binary despite version mismatch because downloads are disabled" ,
185- ) ;
186- trace . setOutcome ( "download_disabled_fallback" ) ;
187- return resolved . binPath ;
188- }
189- this . output . warn (
190- "Unable to download CLI because downloads are disabled" ,
191- ) ;
192- const error = new CliDownloadsDisabledError ( ) ;
193- trace . setFailure ( "downloads_disabled" ) ;
194- throw error ;
195- }
175+ let action : CliDownloadAction ;
176+ if ( enableDownloads ) {
177+ action = "download" ;
178+ } else {
179+ action = existingVersion !== null ? "fallback" : "blocked" ;
180+ }
181+ await trace . downloadDecision ( downloadReason , action ) ;
196182
183+ if ( ! enableDownloads ) {
197184 if ( existingVersion ) {
198185 this . output . info (
199- "Downloading since existing binary does not match the server version " ,
186+ "Using existing binary despite version mismatch because downloads are disabled " ,
200187 ) ;
188+ trace . setOutcome ( "download_disabled_fallback" ) ;
189+ return resolved . binPath ;
201190 }
191+ this . output . warn ( "Unable to download CLI because downloads are disabled" ) ;
192+ trace . setFailure ( "downloads_disabled" ) ;
193+ throw new CliDownloadsDisabledError ( ) ;
194+ }
195+
196+ if ( existingVersion ) {
197+ this . output . info (
198+ "Downloading since existing binary does not match the server version" ,
199+ ) ;
200+ }
202201
203- return this . downloadBinary (
204- restClient ,
205- trace ,
202+ return this . downloadBinary (
203+ restClient ,
204+ {
206205 resolved,
207206 parsedVersion,
208- buildInfo . version ,
207+ serverVersion : buildInfo . version ,
209208 downloadReason,
210- ) ;
211- } ) ;
209+ } ,
210+ trace ,
211+ ) ;
212212 }
213213
214214 private async lookupBinary ( safeHostname : string ) : Promise < ResolvedBinary > {
@@ -282,12 +282,15 @@ export class CliManager {
282282
283283 private async downloadBinary (
284284 restClient : Api ,
285+ options : {
286+ resolved : ResolvedBinary ;
287+ parsedVersion : semver . SemVer ;
288+ serverVersion : string ;
289+ downloadReason : CliDownloadReason ;
290+ } ,
285291 trace : CliResolveTrace ,
286- resolved : ResolvedBinary ,
287- parsedVersion : semver . SemVer ,
288- serverVersion : string ,
289- downloadReason : CliDownloadReason ,
290292 ) : Promise < string > {
293+ const { resolved, parsedVersion, serverVersion, downloadReason } = options ;
291294 // Always download using the platform-specific name.
292295 const downloadBinPath = path . join (
293296 path . dirname ( resolved . binPath ) ,
@@ -308,7 +311,7 @@ export class CliManager {
308311 this . output . debug ( "Acquired download lock" ) ;
309312
310313 if ( lockResult . waited ) {
311- const waitResult = await trace . lockWaitRecheck ( ( ) =>
314+ const waitResult = await trace . lockRecheck ( ( ) =>
312315 this . recheckBinaryAfterWait ( restClient , downloadBinPath ) ,
313316 ) ;
314317 if ( waitResult . matches ) {
@@ -324,9 +327,11 @@ export class CliManager {
324327 async ( span ) => {
325328 const downloadedBinPath = await this . performBinaryDownload (
326329 restClient ,
327- latestVersion ,
328- downloadBinPath ,
329- progressLogPath ,
330+ {
331+ parsedVersion : latestVersion ,
332+ binPath : downloadBinPath ,
333+ progressLogPath,
334+ } ,
330335 span ,
331336 ) ;
332337 return this . renameToFinalPath ( resolved , downloadedBinPath ) ;
@@ -564,11 +569,14 @@ export class CliManager {
564569
565570 private async performBinaryDownload (
566571 restClient : Api ,
567- parsedVersion : semver . SemVer ,
568- binPath : string ,
569- progressLogPath : string ,
572+ options : {
573+ parsedVersion : semver . SemVer ;
574+ binPath : string ;
575+ progressLogPath : string ;
576+ } ,
570577 downloadSpan : Span ,
571578 ) : Promise < string > {
579+ const { parsedVersion, binPath, progressLogPath } = options ;
572580 const cfg = vscode . workspace . getConfiguration ( "coder" ) ;
573581 const tempFile = tempFilePath ( binPath , "temp" ) ;
574582
@@ -1010,42 +1018,46 @@ export class CliManager {
10101018 silent,
10111019 credentialSource : token === "" ? "empty_token" : "session_token" ,
10121020 } ,
1013- async ( trace ) => {
1014- const configs = vscode . workspace . getConfiguration ( ) ;
1015-
1016- if ( silent ) {
1017- try {
1018- await this . cliCredentialManager . storeToken ( url , token , configs ) ;
1019- } catch ( error ) {
1020- trace . failed ( error ) ;
1021- this . handleStoreError ( error ) ;
1022- }
1023- return ;
1024- }
1021+ ( trace ) => this . storeCredentials ( { url, token, silent } , trace ) ,
1022+ ) ;
1023+ }
10251024
1026- const result = await withCancellableProgress (
1027- ( { signal } ) =>
1028- this . cliCredentialManager . storeToken ( url , token , configs , {
1029- signal,
1030- } ) ,
1031- {
1032- location : vscode . ProgressLocation . Notification ,
1033- title : `Storing credentials for ${ url } ` ,
1034- cancellable : true ,
1035- } ,
1036- ) ;
1037- if ( result . ok ) {
1038- return ;
1039- }
1040- if ( result . cancelled ) {
1041- this . output . info ( "Credential storage cancelled by user" ) ;
1042- trace . cancelled ( ) ;
1043- return ;
1044- }
1045- trace . failed ( result . error ) ;
1046- this . handleStoreError ( result . error ) ;
1025+ private async storeCredentials (
1026+ options : { url : string ; token : string ; silent : boolean } ,
1027+ trace : CliConfigureTrace ,
1028+ ) : Promise < void > {
1029+ const { url, token, silent } = options ;
1030+ const configs = vscode . workspace . getConfiguration ( ) ;
1031+
1032+ if ( silent ) {
1033+ try {
1034+ await this . cliCredentialManager . storeToken ( url , token , configs ) ;
1035+ } catch ( error ) {
1036+ trace . failed ( error ) ;
1037+ this . handleStoreError ( error ) ;
1038+ }
1039+ return ;
1040+ }
1041+
1042+ const result = await withCancellableProgress (
1043+ ( { signal } ) =>
1044+ this . cliCredentialManager . storeToken ( url , token , configs , { signal } ) ,
1045+ {
1046+ location : vscode . ProgressLocation . Notification ,
1047+ title : `Storing credentials for ${ url } ` ,
1048+ cancellable : true ,
10471049 } ,
10481050 ) ;
1051+ if ( result . ok ) {
1052+ return ;
1053+ }
1054+ if ( result . cancelled ) {
1055+ this . output . info ( "Credential storage cancelled by user" ) ;
1056+ trace . cancelled ( ) ;
1057+ return ;
1058+ }
1059+ trace . failed ( result . error ) ;
1060+ this . handleStoreError ( result . error ) ;
10491061 }
10501062
10511063 /**
0 commit comments