2024-06-13 10:16:59 +00:00
|
|
|
import * as core from '@actions/core'
|
|
|
|
import {
|
2024-09-24 10:17:44 +00:00
|
|
|
BlobClient,
|
2024-11-24 18:44:39 +00:00
|
|
|
BlobUploadCommonResponse,
|
2024-09-24 10:17:44 +00:00
|
|
|
BlockBlobClient,
|
2024-06-13 10:16:59 +00:00
|
|
|
BlockBlobParallelUploadOptions
|
|
|
|
} from '@azure/storage-blob'
|
2024-12-02 10:33:27 +00:00
|
|
|
import {TransferProgressEvent} from '@azure/ms-rest-js'
|
2024-11-28 11:53:34 +00:00
|
|
|
import {InvalidResponseError} from './shared/errors'
|
2024-11-28 15:22:01 +00:00
|
|
|
import {UploadOptions} from '../options'
|
2024-06-13 10:16:59 +00:00
|
|
|
|
2024-12-02 10:33:27 +00:00
|
|
|
/**
|
|
|
|
* Class for tracking the upload state and displaying stats.
|
|
|
|
*/
|
|
|
|
export class UploadProgress {
|
|
|
|
contentLength: number
|
|
|
|
sentBytes: number
|
|
|
|
startTime: number
|
|
|
|
displayedComplete: boolean
|
|
|
|
timeoutHandle?: ReturnType<typeof setTimeout>
|
|
|
|
|
|
|
|
constructor(contentLength: number) {
|
|
|
|
this.contentLength = contentLength
|
|
|
|
this.sentBytes = 0
|
|
|
|
this.displayedComplete = false
|
|
|
|
this.startTime = Date.now()
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the number of bytes sent
|
|
|
|
*
|
|
|
|
* @param sentBytes the number of bytes sent
|
|
|
|
*/
|
|
|
|
setSentBytes(sentBytes: number): void {
|
|
|
|
this.sentBytes = sentBytes
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the total number of bytes transferred.
|
|
|
|
*/
|
|
|
|
getTransferredBytes(): number {
|
|
|
|
return this.sentBytes
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if the upload is complete.
|
|
|
|
*/
|
|
|
|
isDone(): boolean {
|
|
|
|
return this.getTransferredBytes() === this.contentLength
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Prints the current upload stats. Once the upload completes, this will print one
|
|
|
|
* last line and then stop.
|
|
|
|
*/
|
|
|
|
display(): void {
|
|
|
|
if (this.displayedComplete) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const transferredBytes = this.sentBytes
|
|
|
|
const percentage = (100 * (transferredBytes / this.contentLength)).toFixed(
|
|
|
|
1
|
|
|
|
)
|
|
|
|
const elapsedTime = Date.now() - this.startTime
|
|
|
|
const uploadSpeed = (
|
|
|
|
transferredBytes /
|
|
|
|
(1024 * 1024) /
|
|
|
|
(elapsedTime / 1000)
|
|
|
|
).toFixed(1)
|
|
|
|
|
|
|
|
core.info(
|
|
|
|
`Sent ${transferredBytes} of ${this.contentLength} (${percentage}%), ${uploadSpeed} MBs/sec`
|
|
|
|
)
|
|
|
|
|
|
|
|
if (this.isDone()) {
|
|
|
|
this.displayedComplete = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a function used to handle TransferProgressEvents.
|
|
|
|
*/
|
|
|
|
onProgress(): (progress: TransferProgressEvent) => void {
|
|
|
|
return (progress: TransferProgressEvent) => {
|
|
|
|
this.setSentBytes(progress.loadedBytes)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Starts the timer that displays the stats.
|
|
|
|
*
|
|
|
|
* @param delayInMs the delay between each write
|
|
|
|
*/
|
|
|
|
startDisplayTimer(delayInMs = 1000): void {
|
|
|
|
const displayCallback = (): void => {
|
|
|
|
this.display()
|
|
|
|
|
|
|
|
if (!this.isDone()) {
|
|
|
|
this.timeoutHandle = setTimeout(displayCallback, delayInMs)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.timeoutHandle = setTimeout(displayCallback, delayInMs)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stops the timer that displays the stats. As this typically indicates the upload
|
|
|
|
* is complete, this will display one last line, unless the last line has already
|
|
|
|
* been written.
|
|
|
|
*/
|
|
|
|
stopDisplayTimer(): void {
|
|
|
|
if (this.timeoutHandle) {
|
|
|
|
clearTimeout(this.timeoutHandle)
|
|
|
|
this.timeoutHandle = undefined
|
|
|
|
}
|
|
|
|
|
|
|
|
this.display()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-02 12:08:21 +00:00
|
|
|
/**
|
|
|
|
* Uploads a cache archive directly to Azure Blob Storage using the Azure SDK.
|
|
|
|
* This function will display progress information to the console. Concurrency of the
|
|
|
|
* upload is determined by the calling functions.
|
|
|
|
*
|
|
|
|
* @param signedUploadURL
|
|
|
|
* @param archivePath
|
|
|
|
* @param options
|
|
|
|
* @returns
|
|
|
|
*/
|
2024-11-28 11:53:34 +00:00
|
|
|
export async function uploadCacheArchiveSDK(
|
|
|
|
signedUploadURL: string,
|
2024-11-28 15:22:01 +00:00
|
|
|
archivePath: string,
|
|
|
|
options?: UploadOptions
|
2024-11-28 11:53:34 +00:00
|
|
|
): Promise<BlobUploadCommonResponse> {
|
2024-12-02 10:33:27 +00:00
|
|
|
const blobClient: BlobClient = new BlobClient(signedUploadURL)
|
|
|
|
const blockBlobClient: BlockBlobClient = blobClient.getBlockBlobClient()
|
2024-12-02 11:55:57 +00:00
|
|
|
const uploadProgress = new UploadProgress(options?.archiveSizeBytes ?? 0)
|
2024-12-02 10:33:27 +00:00
|
|
|
|
2024-06-13 10:16:59 +00:00
|
|
|
// Specify data transfer options
|
|
|
|
const uploadOptions: BlockBlobParallelUploadOptions = {
|
2024-11-28 15:22:01 +00:00
|
|
|
blockSize: options?.uploadChunkSize,
|
|
|
|
concurrency: options?.uploadConcurrency, // maximum number of parallel transfer workers
|
2024-12-02 10:33:27 +00:00
|
|
|
maxSingleShotSize: 128 * 1024 * 1024, // 128 MiB initial transfer size
|
|
|
|
onProgress: uploadProgress.onProgress()
|
2024-11-14 11:22:03 +00:00
|
|
|
}
|
2024-06-13 10:16:59 +00:00
|
|
|
|
2024-12-02 11:35:20 +00:00
|
|
|
try {
|
|
|
|
uploadProgress.startDisplayTimer()
|
2024-06-13 10:16:59 +00:00
|
|
|
|
2024-12-02 11:35:20 +00:00
|
|
|
core.debug(
|
|
|
|
`BlobClient: ${blobClient.name}:${blobClient.accountName}:${blobClient.containerName}`
|
|
|
|
)
|
2024-12-02 10:33:27 +00:00
|
|
|
|
2024-12-02 11:35:20 +00:00
|
|
|
const response = await blockBlobClient.uploadFile(
|
|
|
|
archivePath,
|
|
|
|
uploadOptions
|
2024-12-02 11:08:05 +00:00
|
|
|
)
|
|
|
|
|
2024-12-02 11:35:20 +00:00
|
|
|
// TODO: better management of non-retryable errors
|
|
|
|
if (response._response.status >= 400) {
|
|
|
|
throw new InvalidResponseError(
|
2024-12-02 12:08:21 +00:00
|
|
|
`uploadCacheArchiveSDK: upload failed with status code ${response._response.status}`
|
2024-12-02 11:35:20 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return response
|
|
|
|
} catch (error) {
|
2024-12-02 11:55:57 +00:00
|
|
|
core.warning(
|
|
|
|
`uploadCacheArchiveSDK: internal error uploading cache archive: ${error.message}`
|
|
|
|
)
|
2024-12-02 11:35:20 +00:00
|
|
|
throw error
|
|
|
|
} finally {
|
|
|
|
uploadProgress.stopDisplayTimer()
|
|
|
|
}
|
2024-11-14 11:22:03 +00:00
|
|
|
}
|