1
1
import axios from "axios"
2
2
import { execFile } from "child_process"
3
3
import { getBuildInfo } from "coder/site/src/api/api"
4
+ import * as crypto from "crypto"
4
5
import { createWriteStream } from "fs"
5
6
import { ensureDir } from "fs-extra"
6
7
import fs from "fs/promises"
@@ -81,31 +82,7 @@ export class Storage {
81
82
82
83
const buildInfo = await getBuildInfo ( )
83
84
const binPath = this . binaryPath ( )
84
- const exists = await fs
85
- . stat ( binPath )
86
- . then ( ( ) => true )
87
- . catch ( ( ) => false )
88
- if ( exists ) {
89
- // Even if the file exists, it could be corrupted.
90
- // We run `coder version` to ensure the binary can be executed.
91
- this . output . appendLine ( `Using cached binary: ${ binPath } ` )
92
- const valid = await new Promise < boolean > ( ( resolve ) => {
93
- try {
94
- execFile ( binPath , [ "version" ] , ( err ) => {
95
- if ( err ) {
96
- this . output . appendLine ( "Check for binary corruption: " + err )
97
- }
98
- resolve ( err === null )
99
- } )
100
- } catch ( ex ) {
101
- this . output . appendLine ( "The cached binary cannot be executed: " + ex )
102
- resolve ( false )
103
- }
104
- } )
105
- if ( valid ) {
106
- return binPath
107
- }
108
- }
85
+ const exists = await this . checkBinaryExists ( binPath )
109
86
const os = goos ( )
110
87
const arch = goarch ( )
111
88
let binName = `coder-${ os } -${ arch } `
@@ -114,6 +91,23 @@ export class Storage {
114
91
binName += ".exe"
115
92
}
116
93
const controller = new AbortController ( )
94
+
95
+ if ( exists ) {
96
+ this . output . appendLine ( `Checking if binary outdated...` )
97
+ const outdated = await this . checkBinaryOutdated ( binName , baseURL , controller )
98
+ // If it's outdated, we fall through to the download logic.
99
+ if ( outdated ) {
100
+ this . output . appendLine ( `Found outdated version.` )
101
+ } else {
102
+ // Even if the file exists, it could be corrupted.
103
+ // We run `coder version` to ensure the binary can be executed.
104
+ this . output . appendLine ( `Using existing binary: ${ binPath } ` )
105
+ const valid = await this . checkBinaryValid ( binPath )
106
+ if ( valid ) {
107
+ return binPath
108
+ }
109
+ }
110
+ }
117
111
const resp = await axios . get ( "/bin/" + binName , {
118
112
signal : controller . signal ,
119
113
baseURL : baseURL ,
@@ -240,6 +234,10 @@ export class Storage {
240
234
return path . join ( this . globalStorageUri . fsPath , "url" )
241
235
}
242
236
237
+ public getBinaryETag ( ) : string {
238
+ return crypto . createHash ( "sha1" ) . update ( this . binaryPath ( ) ) . digest ( "hex" )
239
+ }
240
+
243
241
private appDataDir ( ) : string {
244
242
switch ( process . platform ) {
245
243
case "darwin" :
@@ -274,6 +272,47 @@ export class Storage {
274
272
return binPath
275
273
}
276
274
275
+ private async checkBinaryExists ( binPath : string ) : Promise < boolean > {
276
+ return await fs
277
+ . stat ( binPath )
278
+ . then ( ( ) => true )
279
+ . catch ( ( ) => false )
280
+ }
281
+
282
+ private async checkBinaryValid ( binPath : string ) : Promise < boolean > {
283
+ return await new Promise < boolean > ( ( resolve ) => {
284
+ try {
285
+ execFile ( binPath , [ "version" ] , ( err ) => {
286
+ if ( err ) {
287
+ this . output . appendLine ( "Check for binary corruption: " + err )
288
+ }
289
+ resolve ( err === null )
290
+ } )
291
+ } catch ( ex ) {
292
+ this . output . appendLine ( "The cached binary cannot be executed: " + ex )
293
+ resolve ( false )
294
+ }
295
+ } )
296
+ }
297
+
298
+ private async checkBinaryOutdated ( binName : string , baseURL : string , controller : AbortController ) : Promise < boolean > {
299
+ const resp = await axios . get ( "/bin/" + binName , {
300
+ signal : controller . signal ,
301
+ baseURL : baseURL ,
302
+ headers : {
303
+ "If-None-Match" : this . getBinaryETag ( ) ,
304
+ } ,
305
+ } )
306
+
307
+ switch ( resp . status ) {
308
+ case 200 :
309
+ return true
310
+ case 304 :
311
+ default :
312
+ return false
313
+ }
314
+ }
315
+
277
316
private async updateSessionToken ( ) {
278
317
const token = await this . getSessionToken ( )
279
318
if ( token ) {
0 commit comments