Add support for specifying whether to skip decompressing
This commit is contained in:
@@ -815,5 +815,31 @@ describe('download-artifact', () => {
|
||||
// Verify files were extracted
|
||||
await expectExtractedArchive(fixtures.workspaceDir)
|
||||
})
|
||||
|
||||
it('should skip decompression when skipDecompress option is true even for zip content-type', async () => {
|
||||
const mockHttpClient = (HttpClient as jest.Mock).mockImplementation(
|
||||
() => {
|
||||
return {
|
||||
get: mockGetArtifactSuccess
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
await streamExtractExternal(
|
||||
fixtures.blobStorageUrl,
|
||||
fixtures.workspaceDir,
|
||||
{skipDecompress: true}
|
||||
)
|
||||
|
||||
expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString())
|
||||
// Verify zip was saved as-is, not extracted
|
||||
// When skipDecompress is true, the file should be saved with default name 'artifact'
|
||||
const savedFilePath = path.join(fixtures.workspaceDir, 'artifact')
|
||||
expect(fs.existsSync(savedFilePath)).toBe(true)
|
||||
// The saved file should be the raw zip content
|
||||
const savedContent = fs.readFileSync(savedFilePath)
|
||||
const originalZipContent = fs.readFileSync(fixtures.exampleArtifact.path)
|
||||
expect(savedContent).toEqual(originalZipContent)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -45,12 +45,13 @@ async function exists(path: string): Promise<boolean> {
|
||||
|
||||
async function streamExtract(
|
||||
url: string,
|
||||
directory: string
|
||||
directory: string,
|
||||
skipDecompress?: boolean
|
||||
): Promise<StreamExtractResponse> {
|
||||
let retryCount = 0
|
||||
while (retryCount < 5) {
|
||||
try {
|
||||
return await streamExtractExternal(url, directory)
|
||||
return await streamExtractExternal(url, directory, {skipDecompress})
|
||||
} catch (error) {
|
||||
retryCount++
|
||||
core.debug(
|
||||
@@ -67,8 +68,9 @@ async function streamExtract(
|
||||
export async function streamExtractExternal(
|
||||
url: string,
|
||||
directory: string,
|
||||
opts: {timeout: number} = {timeout: 30 * 1000}
|
||||
opts: {timeout?: number; skipDecompress?: boolean} = {}
|
||||
): Promise<StreamExtractResponse> {
|
||||
const {timeout = 30 * 1000, skipDecompress = false} = opts
|
||||
const client = new httpClient.HttpClient(getUserAgentString())
|
||||
const response = await client.get(url)
|
||||
if (response.message.statusCode !== 200) {
|
||||
@@ -94,7 +96,7 @@ export async function streamExtractExternal(
|
||||
fileName = decodeURIComponent(filenameMatch[1].trim())
|
||||
}
|
||||
|
||||
core.debug(`Content-Type: ${contentType}, isZip: ${isZip}`)
|
||||
core.debug(`Content-Type: ${contentType}, isZip: ${isZip}, skipDecompress: ${skipDecompress}`)
|
||||
core.debug(`Content-Disposition: ${contentDisposition}, fileName: ${fileName}`)
|
||||
|
||||
let sha256Digest: string | undefined = undefined
|
||||
@@ -102,12 +104,12 @@ export async function streamExtractExternal(
|
||||
return new Promise((resolve, reject) => {
|
||||
const timerFn = (): void => {
|
||||
const timeoutError = new Error(
|
||||
`Blob storage chunk did not respond in ${opts.timeout}ms`
|
||||
`Blob storage chunk did not respond in ${timeout}ms`
|
||||
)
|
||||
response.message.destroy(timeoutError)
|
||||
reject(timeoutError)
|
||||
}
|
||||
const timer = setTimeout(timerFn, opts.timeout)
|
||||
const timer = setTimeout(timerFn, timeout)
|
||||
|
||||
const onError = (error: Error): void => {
|
||||
core.debug(
|
||||
@@ -137,7 +139,7 @@ export async function streamExtractExternal(
|
||||
resolve({sha256Digest: `sha256:${sha256Digest}`})
|
||||
}
|
||||
|
||||
if (isZip) {
|
||||
if (isZip && !skipDecompress) {
|
||||
// Extract zip file
|
||||
passThrough.pipe(unzip.Extract({path: directory})).on('close', onClose).on('error', onError)
|
||||
} else {
|
||||
@@ -193,7 +195,11 @@ export async function downloadArtifactPublic(
|
||||
|
||||
try {
|
||||
core.info(`Starting download of artifact to: ${downloadPath}`)
|
||||
const extractResponse = await streamExtract(location, downloadPath)
|
||||
const extractResponse = await streamExtract(
|
||||
location,
|
||||
downloadPath,
|
||||
options?.skipDecompress
|
||||
)
|
||||
core.info(`Artifact download completed successfully.`)
|
||||
if (options?.expectedHash) {
|
||||
if (options?.expectedHash !== extractResponse.sha256Digest) {
|
||||
@@ -254,7 +260,11 @@ export async function downloadArtifactInternal(
|
||||
|
||||
try {
|
||||
core.info(`Starting download of artifact to: ${downloadPath}`)
|
||||
const extractResponse = await streamExtract(signedUrl, downloadPath)
|
||||
const extractResponse = await streamExtract(
|
||||
signedUrl,
|
||||
downloadPath,
|
||||
options?.skipDecompress
|
||||
)
|
||||
core.info(`Artifact download completed successfully.`)
|
||||
if (options?.expectedHash) {
|
||||
if (options?.expectedHash !== extractResponse.sha256Digest) {
|
||||
|
||||
@@ -113,6 +113,12 @@ export interface DownloadArtifactOptions {
|
||||
* matches the expected hash.
|
||||
*/
|
||||
expectedHash?: string
|
||||
|
||||
/**
|
||||
* If true, the downloaded artifact will not be automatically extracted/decompressed.
|
||||
* The artifact will be saved as-is to the destination path.
|
||||
*/
|
||||
skipDecompress?: boolean
|
||||
}
|
||||
|
||||
export interface StreamExtractResponse {
|
||||
|
||||
Reference in New Issue
Block a user