| 
									
										
										
										
											2019-10-30 14:48:49 -04:00
										 |  |  | import * as core from "@actions/core"; | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  | import { HttpClient, HttpCodes } from "@actions/http-client"; | 
					
						
							| 
									
										
										
										
											2020-03-18 22:35:13 +09:00
										 |  |  | import { BearerCredentialHandler } from "@actions/http-client/auth"; | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  | import { | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |     IHttpClientResponse, | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     IRequestOptions, | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |     ITypedResponse | 
					
						
							|  |  |  | } from "@actions/http-client/interfaces"; | 
					
						
							| 
									
										
										
										
											2020-03-20 13:02:11 -07:00
										 |  |  | import * as crypto from "crypto"; | 
					
						
							| 
									
										
										
										
											2020-03-18 22:35:13 +09:00
										 |  |  | import * as fs from "fs"; | 
					
						
							| 
									
										
										
										
											2020-04-29 09:31:53 -04:00
										 |  |  | import * as stream from "stream"; | 
					
						
							|  |  |  | import * as util from "util"; | 
					
						
							| 
									
										
										
										
											2020-03-18 22:35:13 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-22 16:36:34 -04:00
										 |  |  | import { CompressionMethod, Inputs, SocketTimeout } from "./constants"; | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  | import { | 
					
						
							|  |  |  |     ArtifactCacheEntry, | 
					
						
							| 
									
										
										
										
											2020-04-22 16:36:34 -04:00
										 |  |  |     CacheOptions, | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     CommitCacheRequest, | 
					
						
							|  |  |  |     ReserveCacheRequest, | 
					
						
							|  |  |  |     ReserveCacheResponse | 
					
						
							|  |  |  | } from "./contracts"; | 
					
						
							|  |  |  | import * as utils from "./utils/actionUtils"; | 
					
						
							| 
									
										
										
										
											2019-10-30 14:48:49 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-20 13:02:11 -07:00
										 |  |  | const versionSalt = "1.0"; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  | function isSuccessStatusCode(statusCode?: number): boolean { | 
					
						
							|  |  |  |     if (!statusCode) { | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     return statusCode >= 200 && statusCode < 300; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  | function isRetryableStatusCode(statusCode?: number): boolean { | 
					
						
							|  |  |  |     if (!statusCode) { | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     const retryableStatusCodes = [ | 
					
						
							|  |  |  |         HttpCodes.BadGateway, | 
					
						
							|  |  |  |         HttpCodes.ServiceUnavailable, | 
					
						
							|  |  |  |         HttpCodes.GatewayTimeout | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  |     return retryableStatusCodes.includes(statusCode); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  | function getCacheApiUrl(resource: string): string { | 
					
						
							| 
									
										
										
										
											2019-11-13 06:48:02 +09:00
										 |  |  |     // Ideally we just use ACTIONS_CACHE_URL
 | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     const baseUrl: string = ( | 
					
						
							| 
									
										
										
										
											2019-11-13 06:48:02 +09:00
										 |  |  |         process.env["ACTIONS_CACHE_URL"] || | 
					
						
							|  |  |  |         process.env["ACTIONS_RUNTIME_URL"] || | 
					
						
							|  |  |  |         "" | 
					
						
							|  |  |  |     ).replace("pipelines", "artifactcache"); | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     if (!baseUrl) { | 
					
						
							| 
									
										
										
										
											2019-11-13 06:48:02 +09:00
										 |  |  |         throw new Error( | 
					
						
							|  |  |  |             "Cache Service Url not found, unable to restore cache." | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |     const url = `${baseUrl}_apis/artifactcache/${resource}`; | 
					
						
							|  |  |  |     core.debug(`Resource Url: ${url}`); | 
					
						
							|  |  |  |     return url; | 
					
						
							| 
									
										
										
										
											2019-11-13 06:48:02 +09:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function createAcceptHeader(type: string, apiVersion: string): string { | 
					
						
							|  |  |  |     return `${type};api-version=${apiVersion}`; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function getRequestOptions(): IRequestOptions { | 
					
						
							|  |  |  |     const requestOptions: IRequestOptions = { | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |         headers: { | 
					
						
							|  |  |  |             Accept: createAcceptHeader("application/json", "6.0-preview.1") | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-11-13 06:48:02 +09:00
										 |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return requestOptions; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  | function createHttpClient(): HttpClient { | 
					
						
							| 
									
										
										
										
											2019-10-30 14:48:49 -04:00
										 |  |  |     const token = process.env["ACTIONS_RUNTIME_TOKEN"] || ""; | 
					
						
							|  |  |  |     const bearerCredentialHandler = new BearerCredentialHandler(token); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |     return new HttpClient( | 
					
						
							|  |  |  |         "actions/cache", | 
					
						
							|  |  |  |         [bearerCredentialHandler], | 
					
						
							|  |  |  |         getRequestOptions() | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-22 16:36:34 -04:00
										 |  |  | export function getCacheVersion(compressionMethod?: CompressionMethod): string { | 
					
						
							|  |  |  |     const components = [core.getInput(Inputs.Path, { required: true })].concat( | 
					
						
							| 
									
										
										
										
											2020-04-30 15:28:04 -04:00
										 |  |  |         compressionMethod == CompressionMethod.Zstd ? [compressionMethod] : [] | 
					
						
							| 
									
										
										
										
											2020-04-22 16:36:34 -04:00
										 |  |  |     ); | 
					
						
							| 
									
										
										
										
											2020-03-20 13:02:11 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-30 15:28:04 -04:00
										 |  |  |     // Add salt to cache version to support breaking changes in cache entry
 | 
					
						
							|  |  |  |     components.push(versionSalt); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-20 13:02:11 -07:00
										 |  |  |     return crypto | 
					
						
							|  |  |  |         .createHash("sha256") | 
					
						
							|  |  |  |         .update(components.join("|")) | 
					
						
							|  |  |  |         .digest("hex"); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  | export async function getCacheEntry( | 
					
						
							| 
									
										
										
										
											2020-04-22 16:36:34 -04:00
										 |  |  |     keys: string[], | 
					
						
							|  |  |  |     options?: CacheOptions | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  | ): Promise<ArtifactCacheEntry | null> { | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |     const httpClient = createHttpClient(); | 
					
						
							| 
									
										
										
										
											2020-04-22 16:36:34 -04:00
										 |  |  |     const version = getCacheVersion(options?.compressionMethod); | 
					
						
							| 
									
										
										
										
											2020-03-20 13:02:11 -07:00
										 |  |  |     const resource = `cache?keys=${encodeURIComponent( | 
					
						
							|  |  |  |         keys.join(",") | 
					
						
							|  |  |  |     )}&version=${version}`;
 | 
					
						
							| 
									
										
										
										
											2019-10-30 14:48:49 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |     const response = await httpClient.getJson<ArtifactCacheEntry>( | 
					
						
							|  |  |  |         getCacheApiUrl(resource) | 
					
						
							| 
									
										
										
										
											2019-10-30 14:48:49 -04:00
										 |  |  |     ); | 
					
						
							|  |  |  |     if (response.statusCode === 204) { | 
					
						
							| 
									
										
										
										
											2019-11-04 10:03:18 -06:00
										 |  |  |         return null; | 
					
						
							| 
									
										
										
										
											2019-10-30 14:48:49 -04:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     if (!isSuccessStatusCode(response.statusCode)) { | 
					
						
							| 
									
										
										
										
											2019-10-30 14:48:49 -04:00
										 |  |  |         throw new Error(`Cache service responded with ${response.statusCode}`); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-30 14:48:49 -04:00
										 |  |  |     const cacheResult = response.result; | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     const cacheDownloadUrl = cacheResult?.archiveLocation; | 
					
						
							|  |  |  |     if (!cacheDownloadUrl) { | 
					
						
							| 
									
										
										
										
											2019-10-30 14:48:49 -04:00
										 |  |  |         throw new Error("Cache not found."); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     core.setSecret(cacheDownloadUrl); | 
					
						
							| 
									
										
										
										
											2019-11-21 14:37:32 -05:00
										 |  |  |     core.debug(`Cache Result:`); | 
					
						
							|  |  |  |     core.debug(JSON.stringify(cacheResult)); | 
					
						
							| 
									
										
										
										
											2019-10-30 14:48:49 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return cacheResult; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function pipeResponseToStream( | 
					
						
							|  |  |  |     response: IHttpClientResponse, | 
					
						
							| 
									
										
										
										
											2020-04-29 09:31:53 -04:00
										 |  |  |     output: NodeJS.WritableStream | 
					
						
							| 
									
										
										
										
											2019-10-30 14:48:49 -04:00
										 |  |  | ): Promise<void> { | 
					
						
							| 
									
										
										
										
											2020-04-29 09:31:53 -04:00
										 |  |  |     const pipeline = util.promisify(stream.pipeline); | 
					
						
							|  |  |  |     await pipeline(response.message, output); | 
					
						
							| 
									
										
										
										
											2019-10-30 14:48:49 -04:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-13 06:48:02 +09:00
										 |  |  | export async function downloadCache( | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     archiveLocation: string, | 
					
						
							| 
									
										
										
										
											2019-11-13 06:48:02 +09:00
										 |  |  |     archivePath: string | 
					
						
							|  |  |  | ): Promise<void> { | 
					
						
							|  |  |  |     const stream = fs.createWriteStream(archivePath); | 
					
						
							|  |  |  |     const httpClient = new HttpClient("actions/cache"); | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     const downloadResponse = await httpClient.get(archiveLocation); | 
					
						
							| 
									
										
										
										
											2020-04-22 18:23:41 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Abort download if no traffic received over the socket.
 | 
					
						
							|  |  |  |     downloadResponse.message.socket.setTimeout(SocketTimeout, () => { | 
					
						
							|  |  |  |         downloadResponse.message.destroy(); | 
					
						
							|  |  |  |         core.debug( | 
					
						
							|  |  |  |             `Aborting download, socket timed out after ${SocketTimeout} ms` | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-13 06:48:02 +09:00
										 |  |  |     await pipeResponseToStream(downloadResponse, stream); | 
					
						
							| 
									
										
										
										
											2020-04-22 18:23:41 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Validate download size.
 | 
					
						
							| 
									
										
										
										
											2020-04-22 18:35:16 -04:00
										 |  |  |     const contentLengthHeader = | 
					
						
							| 
									
										
										
										
											2020-04-22 18:23:41 -04:00
										 |  |  |         downloadResponse.message.headers["content-length"]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (contentLengthHeader) { | 
					
						
							|  |  |  |         const expectedLength = parseInt(contentLengthHeader); | 
					
						
							|  |  |  |         const actualLength = utils.getArchiveFileSize(archivePath); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (actualLength != expectedLength) { | 
					
						
							|  |  |  |             throw new Error( | 
					
						
							|  |  |  |                 `Incomplete download. Expected file size: ${expectedLength}, actual file size: ${actualLength}` | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         core.debug("Unable to validate download, no Content-Length header"); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-11-13 06:48:02 +09:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  | // Reserve Cache
 | 
					
						
							| 
									
										
										
										
											2020-04-22 16:36:34 -04:00
										 |  |  | export async function reserveCache( | 
					
						
							|  |  |  |     key: string, | 
					
						
							|  |  |  |     options?: CacheOptions | 
					
						
							|  |  |  | ): Promise<number> { | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |     const httpClient = createHttpClient(); | 
					
						
							| 
									
										
										
										
											2020-04-22 16:36:34 -04:00
										 |  |  |     const version = getCacheVersion(options?.compressionMethod); | 
					
						
							| 
									
										
										
										
											2019-11-14 17:14:16 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     const reserveCacheRequest: ReserveCacheRequest = { | 
					
						
							| 
									
										
										
										
											2020-03-20 13:02:11 -07:00
										 |  |  |         key, | 
					
						
							|  |  |  |         version | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     }; | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |     const response = await httpClient.postJson<ReserveCacheResponse>( | 
					
						
							|  |  |  |         getCacheApiUrl("caches"), | 
					
						
							|  |  |  |         reserveCacheRequest | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     ); | 
					
						
							|  |  |  |     return response?.result?.cacheId ?? -1; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-10-30 14:48:49 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  | function getContentRange(start: number, end: number): string { | 
					
						
							|  |  |  |     // Format: `bytes start-end/filesize
 | 
					
						
							|  |  |  |     // start and end are inclusive
 | 
					
						
							|  |  |  |     // filesize can be *
 | 
					
						
							|  |  |  |     // For a 200 byte chunk starting at byte 0:
 | 
					
						
							|  |  |  |     // Content-Range: bytes 0-199/*
 | 
					
						
							|  |  |  |     return `bytes ${start}-${end}/*`; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-10-30 14:48:49 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  | async function uploadChunk( | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |     httpClient: HttpClient, | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     resourceUrl: string, | 
					
						
							| 
									
										
										
										
											2020-05-11 10:49:48 -04:00
										 |  |  |     openStream: () => NodeJS.ReadableStream, | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     start: number, | 
					
						
							|  |  |  |     end: number | 
					
						
							|  |  |  | ): Promise<void> { | 
					
						
							|  |  |  |     core.debug( | 
					
						
							|  |  |  |         `Uploading chunk of size ${end - | 
					
						
							|  |  |  |             start + | 
					
						
							|  |  |  |             1} bytes at offset ${start} with content range: ${getContentRange( | 
					
						
							|  |  |  |             start, | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  |         )}`
 | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |     const additionalHeaders = { | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |         "Content-Type": "application/octet-stream", | 
					
						
							|  |  |  |         "Content-Range": getContentRange(start, end) | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |     const uploadChunkRequest = async (): Promise<IHttpClientResponse> => { | 
					
						
							|  |  |  |         return await httpClient.sendStream( | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |             "PATCH", | 
					
						
							|  |  |  |             resourceUrl, | 
					
						
							| 
									
										
										
										
											2020-05-11 10:49:48 -04:00
										 |  |  |             openStream(), | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |             additionalHeaders | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |         ); | 
					
						
							| 
									
										
										
										
											2019-10-30 14:48:49 -04:00
										 |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     const response = await uploadChunkRequest(); | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |     if (isSuccessStatusCode(response.message.statusCode)) { | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |     if (isRetryableStatusCode(response.message.statusCode)) { | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |         core.debug( | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |             `Received ${response.message.statusCode}, retrying chunk at offset ${start}.` | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |         ); | 
					
						
							|  |  |  |         const retryResponse = await uploadChunkRequest(); | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |         if (isSuccessStatusCode(retryResponse.message.statusCode)) { | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     throw new Error( | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |         `Cache service responded with ${response.message.statusCode} during chunk upload.` | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     ); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-06 14:06:24 -05:00
										 |  |  | function parseEnvNumber(key: string): number | undefined { | 
					
						
							|  |  |  |     const value = Number(process.env[key]); | 
					
						
							|  |  |  |     if (Number.isNaN(value) || value < 0) { | 
					
						
							|  |  |  |         return undefined; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return value; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  | async function uploadFile( | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |     httpClient: HttpClient, | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     cacheId: number, | 
					
						
							|  |  |  |     archivePath: string | 
					
						
							|  |  |  | ): Promise<void> { | 
					
						
							|  |  |  |     // Upload Chunks
 | 
					
						
							|  |  |  |     const fileSize = fs.statSync(archivePath).size; | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |     const resourceUrl = getCacheApiUrl(`caches/${cacheId.toString()}`); | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     const fd = fs.openSync(archivePath, "r"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-06 14:06:24 -05:00
										 |  |  |     const concurrency = parseEnvNumber("CACHE_UPLOAD_CONCURRENCY") ?? 4; // # of HTTP requests in parallel
 | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     const MAX_CHUNK_SIZE = | 
					
						
							| 
									
										
										
										
											2020-01-06 14:06:24 -05:00
										 |  |  |         parseEnvNumber("CACHE_UPLOAD_CHUNK_SIZE") ?? 32 * 1024 * 1024; // 32 MB Chunks
 | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     core.debug(`Concurrency: ${concurrency} and Chunk Size: ${MAX_CHUNK_SIZE}`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const parallelUploads = [...new Array(concurrency).keys()]; | 
					
						
							|  |  |  |     core.debug("Awaiting all uploads"); | 
					
						
							|  |  |  |     let offset = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |         await Promise.all( | 
					
						
							|  |  |  |             parallelUploads.map(async () => { | 
					
						
							|  |  |  |                 while (offset < fileSize) { | 
					
						
							|  |  |  |                     const chunkSize = Math.min( | 
					
						
							|  |  |  |                         fileSize - offset, | 
					
						
							|  |  |  |                         MAX_CHUNK_SIZE | 
					
						
							|  |  |  |                     ); | 
					
						
							|  |  |  |                     const start = offset; | 
					
						
							|  |  |  |                     const end = offset + chunkSize - 1; | 
					
						
							|  |  |  |                     offset += MAX_CHUNK_SIZE; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     await uploadChunk( | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |                         httpClient, | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |                         resourceUrl, | 
					
						
							| 
									
										
										
										
											2020-05-11 10:49:48 -04:00
										 |  |  |                         () => | 
					
						
							|  |  |  |                             fs.createReadStream(archivePath, { | 
					
						
							|  |  |  |                                 fd, | 
					
						
							|  |  |  |                                 start, | 
					
						
							|  |  |  |                                 end, | 
					
						
							|  |  |  |                                 autoClose: false | 
					
						
							|  |  |  |                             }), | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |                         start, | 
					
						
							|  |  |  |                         end | 
					
						
							|  |  |  |                     ); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |     } finally { | 
					
						
							|  |  |  |         fs.closeSync(fd); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function commitCache( | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |     httpClient: HttpClient, | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     cacheId: number, | 
					
						
							|  |  |  |     filesize: number | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  | ): Promise<ITypedResponse<null>> { | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |     const commitCacheRequest: CommitCacheRequest = { size: filesize }; | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |     return await httpClient.postJson<null>( | 
					
						
							|  |  |  |         getCacheApiUrl(`caches/${cacheId.toString()}`), | 
					
						
							|  |  |  |         commitCacheRequest | 
					
						
							| 
									
										
										
										
											2019-10-30 14:48:49 -04:00
										 |  |  |     ); | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export async function saveCache( | 
					
						
							|  |  |  |     cacheId: number, | 
					
						
							|  |  |  |     archivePath: string | 
					
						
							|  |  |  | ): Promise<void> { | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |     const httpClient = createHttpClient(); | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     core.debug("Upload cache"); | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |     await uploadFile(httpClient, cacheId, archivePath); | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Commit Cache
 | 
					
						
							|  |  |  |     core.debug("Commiting cache"); | 
					
						
							|  |  |  |     const cacheSize = utils.getArchiveFileSize(archivePath); | 
					
						
							|  |  |  |     const commitCacheResponse = await commitCache( | 
					
						
							| 
									
										
										
										
											2020-02-05 09:24:37 -05:00
										 |  |  |         httpClient, | 
					
						
							| 
									
										
										
										
											2020-01-06 13:05:50 -05:00
										 |  |  |         cacheId, | 
					
						
							|  |  |  |         cacheSize | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     if (!isSuccessStatusCode(commitCacheResponse.statusCode)) { | 
					
						
							|  |  |  |         throw new Error( | 
					
						
							|  |  |  |             `Cache service responded with ${commitCacheResponse.statusCode} during commit cache.` | 
					
						
							|  |  |  |         ); | 
					
						
							| 
									
										
										
										
											2019-10-30 14:48:49 -04:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     core.info("Cache saved successfully"); | 
					
						
							|  |  |  | } |