mirror of https://github.com/actions/toolkit
consistent promise behavior for upload artifact
parent
8ac8bf1d3d
commit
75a3586061
|
@ -7,6 +7,7 @@ import {Timestamp, ArtifactServiceClientJSON} from '../src/generated'
|
||||||
import * as blobUpload from '../src/internal/upload/blob-upload'
|
import * as blobUpload from '../src/internal/upload/blob-upload'
|
||||||
import {uploadArtifact} from '../src/internal/upload/upload-artifact'
|
import {uploadArtifact} from '../src/internal/upload/upload-artifact'
|
||||||
import {noopLogs} from './common'
|
import {noopLogs} from './common'
|
||||||
|
import {FilesNotFoundError} from '../src/internal/shared/errors'
|
||||||
|
|
||||||
describe('upload-artifact', () => {
|
describe('upload-artifact', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -59,7 +60,6 @@ describe('upload-artifact', () => {
|
||||||
)
|
)
|
||||||
jest.spyOn(blobUpload, 'uploadZipToBlobStorage').mockReturnValue(
|
jest.spyOn(blobUpload, 'uploadZipToBlobStorage').mockReturnValue(
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
isSuccess: true,
|
|
||||||
uploadSize: 1234,
|
uploadSize: 1234,
|
||||||
sha256Hash: 'test-sha256-hash'
|
sha256Hash: 'test-sha256-hash'
|
||||||
})
|
})
|
||||||
|
@ -84,7 +84,7 @@ describe('upload-artifact', () => {
|
||||||
'/home/user/files/plz-upload'
|
'/home/user/files/plz-upload'
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(uploadResp).resolves.toEqual({success: true, size: 1234, id: 1})
|
expect(uploadResp).resolves.toEqual({size: 1234, id: 1})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw an error if the root directory is invalid', () => {
|
it('should throw an error if the root directory is invalid', () => {
|
||||||
|
@ -107,7 +107,7 @@ describe('upload-artifact', () => {
|
||||||
expect(uploadResp).rejects.toThrow('Invalid root directory')
|
expect(uploadResp).rejects.toThrow('Invalid root directory')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return false if there are no files to upload', () => {
|
it('should reject if there are no files to upload', () => {
|
||||||
jest
|
jest
|
||||||
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
||||||
.mockReturnValue()
|
.mockReturnValue()
|
||||||
|
@ -124,7 +124,7 @@ describe('upload-artifact', () => {
|
||||||
],
|
],
|
||||||
'/home/user/files/plz-upload'
|
'/home/user/files/plz-upload'
|
||||||
)
|
)
|
||||||
expect(uploadResp).resolves.toEqual({success: false})
|
expect(uploadResp).rejects.toThrowError(FilesNotFoundError)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should reject if no backend IDs are found', () => {
|
it('should reject if no backend IDs are found', () => {
|
||||||
|
@ -217,7 +217,7 @@ describe('upload-artifact', () => {
|
||||||
'/home/user/files/plz-upload'
|
'/home/user/files/plz-upload'
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(uploadResp).resolves.toEqual({success: false})
|
expect(uploadResp).rejects.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return false if blob storage upload is unsuccessful', () => {
|
it('should return false if blob storage upload is unsuccessful', () => {
|
||||||
|
@ -262,7 +262,7 @@ describe('upload-artifact', () => {
|
||||||
)
|
)
|
||||||
jest
|
jest
|
||||||
.spyOn(blobUpload, 'uploadZipToBlobStorage')
|
.spyOn(blobUpload, 'uploadZipToBlobStorage')
|
||||||
.mockReturnValue(Promise.resolve({isSuccess: false}))
|
.mockReturnValue(Promise.reject(new Error('boom')))
|
||||||
|
|
||||||
// ArtifactHttpClient mocks
|
// ArtifactHttpClient mocks
|
||||||
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
||||||
|
@ -280,10 +280,10 @@ describe('upload-artifact', () => {
|
||||||
'/home/user/files/plz-upload'
|
'/home/user/files/plz-upload'
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(uploadResp).resolves.toEqual({success: false})
|
expect(uploadResp).rejects.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return false if finalize artifact fails', () => {
|
it('should reject if finalize artifact fails', () => {
|
||||||
const mockDate = new Date('2020-01-01')
|
const mockDate = new Date('2020-01-01')
|
||||||
jest
|
jest
|
||||||
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
||||||
|
@ -325,7 +325,6 @@ describe('upload-artifact', () => {
|
||||||
)
|
)
|
||||||
jest.spyOn(blobUpload, 'uploadZipToBlobStorage').mockReturnValue(
|
jest.spyOn(blobUpload, 'uploadZipToBlobStorage').mockReturnValue(
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
isSuccess: true,
|
|
||||||
uploadSize: 1234,
|
uploadSize: 1234,
|
||||||
sha256Hash: 'test-sha256-hash'
|
sha256Hash: 'test-sha256-hash'
|
||||||
})
|
})
|
||||||
|
@ -350,6 +349,6 @@ describe('upload-artifact', () => {
|
||||||
'/home/user/files/plz-upload'
|
'/home/user/files/plz-upload'
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(uploadResp).resolves.toEqual({success: false})
|
expect(uploadResp).rejects.toThrow()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {ArtifactClient, Client} from './internal/client'
|
||||||
* Exported functionality that we want to expose for any users of @actions/artifact
|
* Exported functionality that we want to expose for any users of @actions/artifact
|
||||||
*/
|
*/
|
||||||
export * from './internal/shared/interfaces'
|
export * from './internal/shared/interfaces'
|
||||||
|
export * from './internal/shared/errors'
|
||||||
export {ArtifactClient}
|
export {ArtifactClient}
|
||||||
|
|
||||||
export function create(): ArtifactClient {
|
export function create(): ArtifactClient {
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
export class FilesNotFoundError extends Error {
|
||||||
|
files: string[]
|
||||||
|
|
||||||
|
constructor(files: string[] = []) {
|
||||||
|
let message = 'No files were found to upload'
|
||||||
|
if (files.length > 0) {
|
||||||
|
message += `: ${files.join(', ')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
super(message)
|
||||||
|
this.files = files
|
||||||
|
this.name = 'FilesNotFoundError'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InvalidResponseError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message)
|
||||||
|
this.name = 'InvalidResponseError'
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,11 +4,6 @@
|
||||||
* *
|
* *
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
export interface UploadArtifactResponse {
|
export interface UploadArtifactResponse {
|
||||||
/**
|
|
||||||
* Denotes if an artifact was successfully uploaded
|
|
||||||
*/
|
|
||||||
success: boolean
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Total size of the artifact in bytes. Not provided if no artifact was uploaded
|
* Total size of the artifact in bytes. Not provided if no artifact was uploaded
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -7,11 +7,6 @@ import * as crypto from 'crypto'
|
||||||
import * as stream from 'stream'
|
import * as stream from 'stream'
|
||||||
|
|
||||||
export interface BlobUploadResponse {
|
export interface BlobUploadResponse {
|
||||||
/**
|
|
||||||
* If the upload was successful or not
|
|
||||||
*/
|
|
||||||
isSuccess: boolean
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The total reported upload size in bytes. Empty if the upload failed
|
* The total reported upload size in bytes. Empty if the upload failed
|
||||||
*/
|
*/
|
||||||
|
@ -55,41 +50,28 @@ export async function uploadZipToBlobStorage(
|
||||||
zipUploadStream.pipe(uploadStream) // This stream is used for the upload
|
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
|
zipUploadStream.pipe(hashStream).setEncoding('hex') // This stream is used to compute a hash of the zip content that gets used. Integrity check
|
||||||
|
|
||||||
try {
|
core.info('Beginning upload of artifact content to blob storage')
|
||||||
core.info('Beginning upload of artifact content to blob storage')
|
|
||||||
|
|
||||||
await blockBlobClient.uploadStream(
|
await blockBlobClient.uploadStream(
|
||||||
uploadStream,
|
uploadStream,
|
||||||
bufferSize,
|
bufferSize,
|
||||||
maxConcurrency,
|
maxConcurrency,
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
|
|
||||||
core.info('Finished uploading artifact content to blob storage!')
|
core.info('Finished uploading artifact content to blob storage!')
|
||||||
|
|
||||||
hashStream.end()
|
hashStream.end()
|
||||||
sha256Hash = hashStream.read() as string
|
sha256Hash = hashStream.read() as string
|
||||||
core.info(`SHA256 hash of uploaded artifact zip is ${sha256Hash}`)
|
core.info(`SHA256 hash of uploaded artifact zip is ${sha256Hash}`)
|
||||||
} catch (error) {
|
|
||||||
core.warning(
|
|
||||||
`Failed to upload artifact zip to blob storage, error: ${error}`
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
isSuccess: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uploadByteCount === 0) {
|
if (uploadByteCount === 0) {
|
||||||
core.warning(
|
core.warning(
|
||||||
`No data was uploaded to blob storage. Reported upload byte count is 0`
|
`No data was uploaded to blob storage. Reported upload byte count is 0.`
|
||||||
)
|
)
|
||||||
return {
|
|
||||||
isSuccess: false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isSuccess: true,
|
|
||||||
uploadSize: uploadByteCount,
|
uploadSize: uploadByteCount,
|
||||||
sha256Hash
|
sha256Hash
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import {
|
||||||
FinalizeArtifactRequest,
|
FinalizeArtifactRequest,
|
||||||
StringValue
|
StringValue
|
||||||
} from '../../generated'
|
} from '../../generated'
|
||||||
|
import {FilesNotFoundError, InvalidResponseError} from '../shared/errors'
|
||||||
|
|
||||||
export async function uploadArtifact(
|
export async function uploadArtifact(
|
||||||
name: string,
|
name: string,
|
||||||
|
@ -34,10 +35,9 @@ export async function uploadArtifact(
|
||||||
rootDirectory
|
rootDirectory
|
||||||
)
|
)
|
||||||
if (zipSpecification.length === 0) {
|
if (zipSpecification.length === 0) {
|
||||||
core.warning(`No files were found to upload`)
|
throw new FilesNotFoundError(
|
||||||
return {
|
zipSpecification.flatMap(s => (s.sourcePath ? [s.sourcePath] : []))
|
||||||
success: false
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const zipUploadStream = await createZipUploadStream(
|
const zipUploadStream = await createZipUploadStream(
|
||||||
|
@ -68,10 +68,9 @@ export async function uploadArtifact(
|
||||||
const createArtifactResp =
|
const createArtifactResp =
|
||||||
await artifactClient.CreateArtifact(createArtifactReq)
|
await artifactClient.CreateArtifact(createArtifactReq)
|
||||||
if (!createArtifactResp.ok) {
|
if (!createArtifactResp.ok) {
|
||||||
core.warning(`Failed to create artifact`)
|
throw new InvalidResponseError(
|
||||||
return {
|
'CreateArtifact: response from backend was not ok'
|
||||||
success: false
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload zip to blob storage
|
// Upload zip to blob storage
|
||||||
|
@ -79,11 +78,6 @@ export async function uploadArtifact(
|
||||||
createArtifactResp.signedUploadUrl,
|
createArtifactResp.signedUploadUrl,
|
||||||
zipUploadStream
|
zipUploadStream
|
||||||
)
|
)
|
||||||
if (uploadResult.isSuccess === false) {
|
|
||||||
return {
|
|
||||||
success: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// finalize the artifact
|
// finalize the artifact
|
||||||
const finalizeArtifactReq: FinalizeArtifactRequest = {
|
const finalizeArtifactReq: FinalizeArtifactRequest = {
|
||||||
|
@ -104,10 +98,9 @@ export async function uploadArtifact(
|
||||||
const finalizeArtifactResp =
|
const finalizeArtifactResp =
|
||||||
await artifactClient.FinalizeArtifact(finalizeArtifactReq)
|
await artifactClient.FinalizeArtifact(finalizeArtifactReq)
|
||||||
if (!finalizeArtifactResp.ok) {
|
if (!finalizeArtifactResp.ok) {
|
||||||
core.warning(`Failed to finalize artifact`)
|
throw new InvalidResponseError(
|
||||||
return {
|
'FinalizeArtifact: response from backend was not ok'
|
||||||
success: false
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const artifactId = BigInt(finalizeArtifactResp.artifactId)
|
const artifactId = BigInt(finalizeArtifactResp.artifactId)
|
||||||
|
@ -116,7 +109,6 @@ export async function uploadArtifact(
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
|
||||||
size: uploadResult.uploadSize,
|
size: uploadResult.uploadSize,
|
||||||
id: Number(artifactId)
|
id: Number(artifactId)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue