1
- import { $ , CryptoHasher , file , write } from "bun" ;
1
+ import { $ , CryptoHasher , file , sleep , write } from "bun" ;
2
2
import { extract } from "tar" ;
3
3
4
4
import stream from "node:stream" ;
@@ -71,7 +71,7 @@ if (!(await file(tarFile).exists())) {
71
71
72
72
await mkdir ( imagePath ) ;
73
73
74
- const result = await extract ( {
74
+ await extract ( {
75
75
file : tarFile ,
76
76
cwd : imagePath ,
77
77
} ) ;
@@ -251,7 +251,6 @@ async function pushLayer(layerDigest: string, readableStream: ReadableStream, to
251
251
throw new Error ( `oci-chunk-max-length header is malformed (not a number)` ) ;
252
252
}
253
253
254
- const reader = readableStream . getReader ( ) ;
255
254
const uploadId = createUploadResponse . headers . get ( "docker-upload-uuid" ) ;
256
255
if ( uploadId === null ) {
257
256
throw new Error ( "Docker-Upload-UUID not defined in headers" ) ;
@@ -271,9 +270,13 @@ async function pushLayer(layerDigest: string, readableStream: ReadableStream, to
271
270
let written = 0 ;
272
271
let previousReadable : ReadableLimiter | undefined ;
273
272
let totalLayerSizeLeft = totalLayerSize ;
273
+ const reader = readableStream . getReader ( ) ;
274
+ let fail = "true" ;
275
+ let failures = 0 ;
274
276
while ( totalLayerSizeLeft > 0 ) {
275
277
const range = `0-${ Math . min ( end , totalLayerSize ) - 1 } ` ;
276
278
const current = new ReadableLimiter ( reader as ReadableStreamDefaultReader , maxToWrite , previousReadable ) ;
279
+ await current . init ( ) ;
277
280
const patchChunkUploadURL = parseLocation ( location ) ;
278
281
// we have to do fetchNode because Bun doesn't allow setting custom Content-Length.
279
282
// https://github.com/oven-sh/bun/issues/10507
@@ -284,14 +287,19 @@ async function pushLayer(layerDigest: string, readableStream: ReadableStream, to
284
287
"range" : range ,
285
288
"authorization" : cred ,
286
289
"content-length" : `${ Math . min ( totalLayerSizeLeft , maxToWrite ) } ` ,
290
+ "x-fail" : fail ,
287
291
} ) ,
288
292
} ) ;
289
293
if ( ! patchChunkResult . ok ) {
290
- throw new Error (
291
- `uploading chunk ${ patchChunkUploadURL } returned ${ patchChunkResult . status } : ${ await patchChunkResult . text ( ) } ` ,
292
- ) ;
294
+ previousReadable = current ;
295
+ console . error ( `${ layerDigest } : Pushing ${ range } failed with ${ patchChunkResult . status } , retrying` ) ;
296
+ await sleep ( 500 ) ;
297
+ if ( failures ++ >= 2 ) fail = "false" ;
298
+ continue ;
293
299
}
294
300
301
+ fail = "true" ;
302
+ current . ok ( ) ;
295
303
const rangeResponse = patchChunkResult . headers . get ( "range" ) ;
296
304
if ( rangeResponse !== range ) {
297
305
throw new Error ( `unexpected Range header ${ rangeResponse } , expected ${ range } ` ) ;
@@ -308,18 +316,24 @@ async function pushLayer(layerDigest: string, readableStream: ReadableStream, to
308
316
const range = `0-${ written - 1 } ` ;
309
317
const uploadURL = new URL ( parseLocation ( location ) ) ;
310
318
uploadURL . searchParams . append ( "digest" , layerDigest ) ;
311
- const response = await fetch ( uploadURL . toString ( ) , {
312
- method : "PUT" ,
313
- headers : new Headers ( {
314
- Range : range ,
315
- Authorization : cred ,
316
- } ) ,
317
- } ) ;
318
- if ( ! response . ok ) {
319
- throw new Error ( `${ uploadURL . toString ( ) } failed with ${ response . status } : ${ await response . text ( ) } ` ) ;
319
+ for ( let tries = 0 ; tries < 3 ; tries ++ ) {
320
+ const response = await fetch ( uploadURL . toString ( ) , {
321
+ method : "PUT" ,
322
+ headers : new Headers ( {
323
+ Range : range ,
324
+ Authorization : cred ,
325
+ } ) ,
326
+ } ) ;
327
+ if ( ! response . ok ) {
328
+ console . error ( `${ layerDigest } : Finishing ${ range } failed with ${ response . status } , retrying` ) ;
329
+ continue ;
330
+ }
331
+
332
+ console . log ( "Pushed" , layerDigest ) ;
333
+ return ;
320
334
}
321
335
322
- console . log ( "Pushed" , layerDigest ) ;
336
+ throw new Error ( `Could not push after multiple tries` ) ;
323
337
}
324
338
325
339
const layersManifest = [ ] as {
0 commit comments