import {BlobClient, BlockBlobUploadStreamOptions} from '@azure/storage-blob' import {TransferProgressEvent} from '@azure/core-http' import {ZipUploadStream} from './zip' import {getUploadChunkSize, getConcurrency} from '../shared/config' import * as core from '@actions/core' import * as crypto from 'crypto' import * as stream from 'stream' import {NetworkError} from '../shared/errors' export interface BlobUploadResponse { /** * The total reported upload size in bytes. Empty if the upload failed */ uploadSize?: number /** * The SHA256 hash of the uploaded file. Empty if the upload failed */ sha256Hash?: string } export async function uploadZipToBlobStorage( authenticatedUploadURL: string, zipUploadStream: ZipUploadStream ): Promise { let uploadByteCount = 0 const maxConcurrency = getConcurrency() const bufferSize = getUploadChunkSize() const blobClient = new BlobClient(authenticatedUploadURL) const blockBlobClient = blobClient.getBlockBlobClient() core.debug( `Uploading artifact zip to blob storage with maxConcurrency: ${maxConcurrency}, bufferSize: ${bufferSize}` ) const uploadCallback = (progress: TransferProgressEvent): void => { core.info(`Uploaded bytes ${progress.loadedBytes}`) uploadByteCount = progress.loadedBytes } const options: BlockBlobUploadStreamOptions = { blobHTTPHeaders: {blobContentType: 'zip'}, onProgress: uploadCallback } let sha256Hash: string | undefined = undefined const uploadStream = new stream.PassThrough() const hashStream = crypto.createHash('sha256') zipUploadStream.pipe(uploadStream) // This stream is used for the upload zipUploadStream.pipe(hashStream).setEncoding('hex') // This stream is used to compute a hash of the zip content that gets used. Integrity check core.info('Beginning upload of artifact content to blob storage') core.info(`Is the zipUploadStream readable? ${zipUploadStream.readable}`) // it is readable, that's good core.info(`Is the zipUploadStream writable now? ${zipUploadStream.writable}`) // it is readable, that's good core.info(`Is the zipUploadStream closed? ${zipUploadStream.closed}`) // it is not closed, that's good core.info(`Is the buffer size appropriate? ${bufferSize}`) // it is not closed, that's good core.info(`is the upload stream closed? ${uploadStream.closed}`) core.info(`is the upload stream readable? ${uploadStream.readable}`) core.info(`is the upload stream writable? ${uploadStream.writable}`) core.info(`are we exceeding the max concurrency? ${maxConcurrency}`) // is the blob client ready? core.info(`blob client props: ${blockBlobClient.url}`) try { core.info( '1 Even more beginning upload of artifact content to blob storage' ) await blockBlobClient .uploadStream(uploadStream, bufferSize, maxConcurrency, options) .catch(error => { core.info(`Upload stream error: ${error}`) }) .then(response => { core.info(`Upload stream response: ${response}`) }) core.info( '2 Even more beginning upload of artifact content to blob storage' ) } catch (error) { if (NetworkError.isNetworkErrorCode(error?.code)) { throw new NetworkError(error?.code) } throw error } core.info('Finished uploading artifact content to blob storage!') hashStream.end() sha256Hash = hashStream.read() as string core.info(`SHA256 hash of uploaded artifact zip is ${sha256Hash}`) if (uploadByteCount === 0) { core.warning( `No data was uploaded to blob storage. Reported upload byte count is 0.` ) } return { uploadSize: uploadByteCount, sha256Hash } }