Compare commits

..
Author SHA1 Message Date
Vallie Joseph b54065b5da making a promise array 2023-12-20 21:15:04 +00:00
srryan c33724abbd update to http client 2023-12-20 15:45:19 -05:00
Rob Herley d6f3ee93b8 reject don't throw 2023-12-20 14:37:13 -05:00
Rob Herley 34a411f3c0 add timeout in between data chunks 2023-12-20 13:59:31 -05:00
Rob Herley 2d6ba67518 retry the promise 2023-12-20 13:11:04 -05:00
srryan 78ed49ff88 update error handling abort 2023-12-19 12:46:58 -05:00
srryan c119fcd773 update optional settings for blob client 2023-12-19 12:02:10 -05:00
srryan 73babeabef add explicit options 2023-12-19 11:49:39 -05:00
Vallie Joseph bf93b54558 adding logger for blob client and response 2023-12-18 23:09:10 +00:00
srryan 0c0770ce57 cleanup 2023-12-18 17:52:55 -05:00
srryan 571bf222ee update to use blob client over http client 2023-12-18 17:11:14 -05:00
6 changed files with 75 additions and 165 deletions
+14 -81
View File
@@ -21,7 +21,6 @@
"archiver": "^5.3.1",
"crypto": "^1.0.1",
"jwt-decode": "^3.1.2",
"nock": "^13.4.0",
"twirp-ts": "^2.5.0",
"unzip-stream": "^0.3.1"
},
@@ -109,25 +108,6 @@
"node": ">=14.0.0"
}
},
"node_modules/@azure/core-http/node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/@azure/core-lro": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.4.tgz",
@@ -352,25 +332,6 @@
"once": "^1.4.0"
}
},
"node_modules/@octokit/request/node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/@octokit/types": {
"version": "6.41.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz",
@@ -795,22 +756,6 @@
"integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==",
"deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in."
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -966,11 +911,6 @@
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
},
"node_modules/json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="
},
"node_modules/jsonc-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
@@ -1125,11 +1065,6 @@
"mkdirp": "bin/cmd.js"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/neo-async": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
@@ -1145,17 +1080,23 @@
"tslib": "^2.0.3"
}
},
"node_modules/nock": {
"version": "13.4.0",
"resolved": "https://registry.npmjs.org/nock/-/nock-13.4.0.tgz",
"integrity": "sha512-W8NVHjO/LCTNA64yxAPHV/K47LpGYcVzgKd3Q0n6owhwvD0Dgoterc25R4rnZbckJEb6Loxz1f5QMuJpJnbSyQ==",
"node_modules/node-fetch": {
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
"integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==",
"dependencies": {
"debug": "^4.1.0",
"json-stringify-safe": "^5.0.1",
"propagate": "^2.0.0"
"whatwg-url": "^5.0.0"
},
"engines": {
"node": ">= 10.13"
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/normalize-path": {
@@ -1223,14 +1164,6 @@
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"node_modules/propagate": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz",
"integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==",
"engines": {
"node": ">= 8"
}
},
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
-1
View File
@@ -52,7 +52,6 @@
"archiver": "^5.3.1",
"crypto": "^1.0.1",
"jwt-decode": "^3.1.2",
"nock": "^13.4.0",
"twirp-ts": "^2.5.0",
"unzip-stream": "^0.3.1"
},
@@ -38,6 +38,36 @@ async function exists(path: string): Promise<boolean> {
}
async function streamExtract(url: string, directory: string): Promise<void> {
let retryCount = 0
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const promises: Promise<any>[] = []
while (retryCount < 5) {
const promise = new Promise(async () => {
try {
await streamExtractInternal(url, directory)
} catch (err) {
retryCount++
core.warning(`Failed to download artifact. Retrying in 5 seconds...`)
await new Promise(resolve => setTimeout(resolve, 5000))
}
})
promises.push(promise)
}
try {
await Promise.all(promises)
core.info('All Promises Returned')
} catch (error) {
throw new Error(`Artifact download failed after ${retryCount} retries.`)
}
// throw new Error(`Artifact download failed after ${retryCount} retries.`)
}
async function streamExtractInternal(
url: string,
directory: string
): Promise<void> {
const client = new httpClient.HttpClient(getUserAgentString())
const response = await client.get(url)
@@ -48,10 +78,34 @@ async function streamExtract(url: string, directory: string): Promise<void> {
}
return new Promise((resolve, reject) => {
response.message
.pipe(unzip.Extract({path: directory}))
.on('close', resolve)
.on('error', reject)
const zipStream = unzip.Extract({path: directory})
const timeout = 30 * 1000
const timerFn = (): void => {
zipStream.end()
reject(new Error(`Blob storage chunk did not respond in ${timeout}ms `))
}
let timer = setTimeout(timerFn, timeout)
try {
response.message
.on('data', () => {
clearTimeout(timer)
timer = setTimeout(timerFn, timeout)
})
.pipe(zipStream)
.on('close', () => {
core.debug(`zip stream: Artifact downloaded to: ${directory}`)
clearTimeout(timer)
resolve()
})
.on('error', reject)
} catch (error) {
zipStream.end()
reject(error)
} finally {
clearTimeout(timer)
}
})
}
@@ -45,21 +45,6 @@ export interface UploadArtifactOptions {
* For large files that are not easily compressed, a value of 0 is recommended for significantly faster uploads.
*/
compressionLevel?: number
/**
* The simulated network error we'll temporarily use to test the azure blob
* client behavior.
* The value can range from 0 to 6
* 0 - fetchError
* 1 - abortError
* 2 - networkError
* 3 - securityError
* 4 - notAllowedError
* 5 - quotaExceededError
* 6 - random
* 7 - none
*
*/
simulateError?: number
}
/**
@@ -5,17 +5,8 @@ import {getUploadChunkSize, getConcurrency} from '../shared/config'
import * as core from '@actions/core'
import * as crypto from 'crypto'
import * as stream from 'stream'
import nock from 'nock'
import {NetworkError} from '../shared/errors'
export const DEFAULT_ERROR_NUMBER = 7
export const ERROR_TYPES = [
'fetchError',
'abortError',
'securityError',
'notAllowedError',
'quotaExceededError'
]
export interface BlobUploadResponse {
/**
* The total reported upload size in bytes. Empty if the upload failed
@@ -27,58 +18,10 @@ export interface BlobUploadResponse {
*/
sha256Hash?: string
}
export async function sendSimulatedError(
simulatedError: number,
authenticatedUploadURL: string
): Promise<void> {
switch (simulatedError) {
case 0: {
nock(authenticatedUploadURL).get('/').replyWithError({
code: 'ECONNRESET',
message: 'socket hang up'
})
break
}
case 1: {
const controller = new AbortController()
controller.abort()
break
}
case 2: {
nock(authenticatedUploadURL).get('/').replyWithError({
code: 'ETIMEDOUT'
})
break
}
case 3: {
nock(authenticatedUploadURL).get('/').reply(403)
break
}
case 4: {
nock(authenticatedUploadURL).get('/').reply(405)
break
}
case 5: {
nock(authenticatedUploadURL).get('/').reply(429)
break
}
case 6: {
const rand = Math.floor(Math.random() * ERROR_TYPES.length)
sendSimulatedError(rand, authenticatedUploadURL)
break
}
case 7: {
core.info('no error selected')
break
}
default:
core.error('something went wrong')
}
}
export async function uploadZipToBlobStorage(
authenticatedUploadURL: string,
zipUploadStream: ZipUploadStream,
simulatedError: number = DEFAULT_ERROR_NUMBER
zipUploadStream: ZipUploadStream
): Promise<BlobUploadResponse> {
let uploadByteCount = 0
@@ -93,9 +36,6 @@ export async function uploadZipToBlobStorage(
const uploadCallback = (progress: TransferProgressEvent): void => {
core.info(`Uploaded bytes ${progress.loadedBytes}`)
if (progress.loadedBytes > 1) {
sendSimulatedError(simulatedError, authenticatedUploadURL)
}
uploadByteCount = progress.loadedBytes
}
@@ -76,8 +76,7 @@ export async function uploadArtifact(
// Upload zip to blob storage
const uploadResult = await uploadZipToBlobStorage(
createArtifactResp.signedUploadUrl,
zipUploadStream,
options?.simulateError
zipUploadStream
)
// finalize the artifact