diff --git a/packages/artifact/package-lock.json b/packages/artifact/package-lock.json index abe007be..d7d7c5ef 100644 --- a/packages/artifact/package-lock.json +++ b/packages/artifact/package-lock.json @@ -21,6 +21,7 @@ "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" }, @@ -108,6 +109,25 @@ "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", @@ -332,6 +352,25 @@ "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", @@ -756,6 +795,22 @@ "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", @@ -911,6 +966,11 @@ "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", @@ -1065,6 +1125,11 @@ "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", @@ -1080,23 +1145,17 @@ "tslib": "^2.0.3" } }, - "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==", + "node_modules/nock": { + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.4.0.tgz", + "integrity": "sha512-W8NVHjO/LCTNA64yxAPHV/K47LpGYcVzgKd3Q0n6owhwvD0Dgoterc25R4rnZbckJEb6Loxz1f5QMuJpJnbSyQ==", "dependencies": { - "whatwg-url": "^5.0.0" + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "node": ">= 10.13" } }, "node_modules/normalize-path": { @@ -1164,6 +1223,14 @@ "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", diff --git a/packages/artifact/package.json b/packages/artifact/package.json index c4f826c6..d6bad82c 100644 --- a/packages/artifact/package.json +++ b/packages/artifact/package.json @@ -52,6 +52,7 @@ "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" }, diff --git a/packages/artifact/src/internal/shared/interfaces.ts b/packages/artifact/src/internal/shared/interfaces.ts index c661721e..7bad675e 100644 --- a/packages/artifact/src/internal/shared/interfaces.ts +++ b/packages/artifact/src/internal/shared/interfaces.ts @@ -45,6 +45,21 @@ 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 } /** diff --git a/packages/artifact/src/internal/upload/blob-upload.ts b/packages/artifact/src/internal/upload/blob-upload.ts index 87bb7237..bc1783f0 100644 --- a/packages/artifact/src/internal/upload/blob-upload.ts +++ b/packages/artifact/src/internal/upload/blob-upload.ts @@ -5,8 +5,17 @@ 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 @@ -18,10 +27,58 @@ export interface BlobUploadResponse { */ sha256Hash?: string } - +export async function sendSimulatedError( + simulatedError: number, + authenticatedUploadURL: string +): Promise { + 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 + zipUploadStream: ZipUploadStream, + simulatedError: number = DEFAULT_ERROR_NUMBER ): Promise { let uploadByteCount = 0 @@ -36,6 +93,9 @@ 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 } diff --git a/packages/artifact/src/internal/upload/upload-artifact.ts b/packages/artifact/src/internal/upload/upload-artifact.ts index e880102f..c951731b 100644 --- a/packages/artifact/src/internal/upload/upload-artifact.ts +++ b/packages/artifact/src/internal/upload/upload-artifact.ts @@ -76,7 +76,8 @@ export async function uploadArtifact( // Upload zip to blob storage const uploadResult = await uploadZipToBlobStorage( createArtifactResp.signedUploadUrl, - zipUploadStream + zipUploadStream, + options?.simulateError ) // finalize the artifact