"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.digestForStream = exports.sleep = exports.getProperRetention = exports.rmFile = exports.getFileSize = exports.createEmptyFilesForArtifact = exports.createDirectoriesForArtifact = exports.displayHttpDiagnostics = exports.getArtifactUrl = exports.createHttpClient = exports.getUploadHeaders = exports.getDownloadHeaders = exports.getContentRange = exports.tryGetRetryAfterValueTimeInMilliseconds = exports.isThrottledStatusCode = exports.isRetryableStatusCode = exports.isForbiddenStatusCode = exports.isSuccessStatusCode = exports.getApiVersion = exports.parseEnvNumber = exports.getExponentialRetryTimeInMilliseconds = void 0; const crypto_1 = __importDefault(require("crypto")); const fs_1 = require("fs"); const core_1 = require("@actions/core"); const http_client_1 = require("@actions/http-client"); const auth_1 = require("@actions/http-client/lib/auth"); const config_variables_1 = require("./config-variables"); const crc64_1 = __importDefault(require("./crc64")); /** * Returns a retry time in milliseconds that exponentially gets larger * depending on the amount of retries that have been attempted */ function getExponentialRetryTimeInMilliseconds(retryCount) { if (retryCount < 0) { throw new Error('RetryCount should not be negative'); } else if (retryCount === 0) { return (0, config_variables_1.getInitialRetryIntervalInMilliseconds)(); } const minTime = (0, config_variables_1.getInitialRetryIntervalInMilliseconds)() * (0, config_variables_1.getRetryMultiplier)() * retryCount; const maxTime = minTime * (0, config_variables_1.getRetryMultiplier)(); // returns a random number between the minTime (inclusive) and the maxTime (exclusive) return Math.trunc(Math.random() * (maxTime - minTime) + minTime); } exports.getExponentialRetryTimeInMilliseconds = getExponentialRetryTimeInMilliseconds; /** * Parses a env variable that is a number */ function parseEnvNumber(key) { const value = Number(process.env[key]); if (Number.isNaN(value) || value < 0) { return undefined; } return value; } exports.parseEnvNumber = parseEnvNumber; /** * Various utility functions to help with the necessary API calls */ function getApiVersion() { return '6.0-preview'; } exports.getApiVersion = getApiVersion; function isSuccessStatusCode(statusCode) { if (!statusCode) { return false; } return statusCode >= 200 && statusCode < 300; } exports.isSuccessStatusCode = isSuccessStatusCode; function isForbiddenStatusCode(statusCode) { if (!statusCode) { return false; } return statusCode === http_client_1.HttpCodes.Forbidden; } exports.isForbiddenStatusCode = isForbiddenStatusCode; function isRetryableStatusCode(statusCode) { if (!statusCode) { return false; } const retryableStatusCodes = [ http_client_1.HttpCodes.BadGateway, http_client_1.HttpCodes.GatewayTimeout, http_client_1.HttpCodes.InternalServerError, http_client_1.HttpCodes.ServiceUnavailable, http_client_1.HttpCodes.TooManyRequests, 413 // Payload Too Large ]; return retryableStatusCodes.includes(statusCode); } exports.isRetryableStatusCode = isRetryableStatusCode; function isThrottledStatusCode(statusCode) { if (!statusCode) { return false; } return statusCode === http_client_1.HttpCodes.TooManyRequests; } exports.isThrottledStatusCode = isThrottledStatusCode; /** * Attempts to get the retry-after value from a set of http headers. The retry time * is originally denoted in seconds, so if present, it is converted to milliseconds * @param headers all the headers received when making an http call */ function tryGetRetryAfterValueTimeInMilliseconds(headers) { if (headers['retry-after']) { const retryTime = Number(headers['retry-after']); if (!isNaN(retryTime)) { (0, core_1.info)(`Retry-After header is present with a value of ${retryTime}`); return retryTime * 1000; } (0, core_1.info)(`Returned retry-after header value: ${retryTime} is non-numeric and cannot be used`); return undefined; } (0, core_1.info)(`No retry-after header was found. Dumping all headers for diagnostic purposes`); // eslint-disable-next-line no-console console.log(headers); return undefined; } exports.tryGetRetryAfterValueTimeInMilliseconds = tryGetRetryAfterValueTimeInMilliseconds; function getContentRange(start, end, total) { // Format: `bytes start-end/fileSize // start and end are inclusive // For a 200 byte chunk starting at byte 0: // Content-Range: bytes 0-199/200 return `bytes ${start}-${end}/${total}`; } exports.getContentRange = getContentRange; /** * Sets all the necessary headers when downloading an artifact * @param {string} contentType the type of content being uploaded * @param {boolean} isKeepAlive is the same connection being used to make multiple calls * @param {boolean} acceptGzip can we accept a gzip encoded response * @param {string} acceptType the type of content that we can accept * @returns appropriate headers to make a specific http call during artifact download */ function getDownloadHeaders(contentType, isKeepAlive, acceptGzip) { const requestOptions = {}; if (contentType) { requestOptions['Content-Type'] = contentType; } if (isKeepAlive) { requestOptions['Connection'] = 'Keep-Alive'; // keep alive for at least 10 seconds before closing the connection requestOptions['Keep-Alive'] = '10'; } if (acceptGzip) { // if we are expecting a response with gzip encoding, it should be using an octet-stream in the accept header requestOptions['Accept-Encoding'] = 'gzip'; requestOptions['Accept'] = `application/octet-stream;api-version=${getApiVersion()}`; } else { // default to application/json if we are not working with gzip content requestOptions['Accept'] = `application/json;api-version=${getApiVersion()}`; } return requestOptions; } exports.getDownloadHeaders = getDownloadHeaders; /** * Sets all the necessary headers when uploading an artifact * @param {string} contentType the type of content being uploaded * @param {boolean} isKeepAlive is the same connection being used to make multiple calls * @param {boolean} isGzip is the connection being used to upload GZip compressed content * @param {number} uncompressedLength the original size of the content if something is being uploaded that has been compressed * @param {number} contentLength the length of the content that is being uploaded * @param {string} contentRange the range of the content that is being uploaded * @returns appropriate headers to make a specific http call during artifact upload */ function getUploadHeaders(contentType, isKeepAlive, isGzip, uncompressedLength, contentLength, contentRange, digest) { const requestOptions = {}; requestOptions['Accept'] = `application/json;api-version=${getApiVersion()}`; if (contentType) { requestOptions['Content-Type'] = contentType; } if (isKeepAlive) { requestOptions['Connection'] = 'Keep-Alive'; // keep alive for at least 10 seconds before closing the connection requestOptions['Keep-Alive'] = '10'; } if (isGzip) { requestOptions['Content-Encoding'] = 'gzip'; requestOptions['x-tfs-filelength'] = uncompressedLength; } if (contentLength) { requestOptions['Content-Length'] = contentLength; } if (contentRange) { requestOptions['Content-Range'] = contentRange; } if (digest) { requestOptions['x-actions-results-crc64'] = digest.crc64; requestOptions['x-actions-results-md5'] = digest.md5; } return requestOptions; } exports.getUploadHeaders = getUploadHeaders; function createHttpClient(userAgent) { return new http_client_1.HttpClient(userAgent, [ new auth_1.BearerCredentialHandler((0, config_variables_1.getRuntimeToken)()) ]); } exports.createHttpClient = createHttpClient; function getArtifactUrl() { const artifactUrl = `${(0, config_variables_1.getRuntimeUrl)()}_apis/pipelines/workflows/${(0, config_variables_1.getWorkFlowRunId)()}/artifacts?api-version=${getApiVersion()}`; (0, core_1.debug)(`Artifact Url: ${artifactUrl}`); return artifactUrl; } exports.getArtifactUrl = getArtifactUrl; /** * Uh oh! Something might have gone wrong during either upload or download. The IHtttpClientResponse object contains information * about the http call that was made by the actions http client. This information might be useful to display for diagnostic purposes, but * this entire object is really big and most of the information is not really useful. This function takes the response object and displays only * the information that we want. * * Certain information such as the TLSSocket and the Readable state are not really useful for diagnostic purposes so they can be avoided. * Other information such as the headers, the response code and message might be useful, so this is displayed. */ function displayHttpDiagnostics(response) { (0, core_1.info)(`##### Begin Diagnostic HTTP information ##### Status Code: ${response.message.statusCode} Status Message: ${response.message.statusMessage} Header Information: ${JSON.stringify(response.message.headers, undefined, 2)} ###### End Diagnostic HTTP information ######`); } exports.displayHttpDiagnostics = displayHttpDiagnostics; function createDirectoriesForArtifact(directories) { return __awaiter(this, void 0, void 0, function* () { for (const directory of directories) { yield fs_1.promises.mkdir(directory, { recursive: true }); } }); } exports.createDirectoriesForArtifact = createDirectoriesForArtifact; function createEmptyFilesForArtifact(emptyFilesToCreate) { return __awaiter(this, void 0, void 0, function* () { for (const filePath of emptyFilesToCreate) { yield (yield fs_1.promises.open(filePath, 'w')).close(); } }); } exports.createEmptyFilesForArtifact = createEmptyFilesForArtifact; function getFileSize(filePath) { return __awaiter(this, void 0, void 0, function* () { const stats = yield fs_1.promises.stat(filePath); (0, core_1.debug)(`${filePath} size:(${stats.size}) blksize:(${stats.blksize}) blocks:(${stats.blocks})`); return stats.size; }); } exports.getFileSize = getFileSize; function rmFile(filePath) { return __awaiter(this, void 0, void 0, function* () { yield fs_1.promises.unlink(filePath); }); } exports.rmFile = rmFile; function getProperRetention(retentionInput, retentionSetting) { if (retentionInput < 0) { throw new Error('Invalid retention, minimum value is 1.'); } let retention = retentionInput; if (retentionSetting) { const maxRetention = parseInt(retentionSetting); if (!isNaN(maxRetention) && maxRetention < retention) { (0, core_1.warning)(`Retention days is greater than the max value allowed by the repository setting, reduce retention to ${maxRetention} days`); retention = maxRetention; } } return retention; } exports.getProperRetention = getProperRetention; function sleep(milliseconds) { return __awaiter(this, void 0, void 0, function* () { return new Promise(resolve => setTimeout(resolve, milliseconds)); }); } exports.sleep = sleep; function digestForStream(stream) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { const crc64 = new crc64_1.default(); const md5 = crypto_1.default.createHash('md5'); stream .on('data', data => { crc64.update(data); md5.update(data); }) .on('end', () => resolve({ crc64: crc64.digest('base64'), md5: md5.digest('base64') })) .on('error', reject); }); }); } exports.digestForStream = digestForStream; //# sourceMappingURL=utils.js.map