mirror of
https://github.com/actions/toolkit.git
synced 2026-08-21 00:00:16 +02:00
Add support for specifying whether to skip decompressing
This commit is contained in:
@@ -815,5 +815,31 @@ describe('download-artifact', () => {
|
|||||||
// Verify files were extracted
|
// Verify files were extracted
|
||||||
await expectExtractedArchive(fixtures.workspaceDir)
|
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(
|
async function streamExtract(
|
||||||
url: string,
|
url: string,
|
||||||
directory: string
|
directory: string,
|
||||||
|
skipDecompress?: boolean
|
||||||
): Promise<StreamExtractResponse> {
|
): Promise<StreamExtractResponse> {
|
||||||
let retryCount = 0
|
let retryCount = 0
|
||||||
while (retryCount < 5) {
|
while (retryCount < 5) {
|
||||||
try {
|
try {
|
||||||
return await streamExtractExternal(url, directory)
|
return await streamExtractExternal(url, directory, {skipDecompress})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
retryCount++
|
retryCount++
|
||||||
core.debug(
|
core.debug(
|
||||||
@@ -67,8 +68,9 @@ async function streamExtract(
|
|||||||
export async function streamExtractExternal(
|
export async function streamExtractExternal(
|
||||||
url: string,
|
url: string,
|
||||||
directory: string,
|
directory: string,
|
||||||
opts: {timeout: number} = {timeout: 30 * 1000}
|
opts: {timeout?: number; skipDecompress?: boolean} = {}
|
||||||
): Promise<StreamExtractResponse> {
|
): Promise<StreamExtractResponse> {
|
||||||
|
const {timeout = 30 * 1000, skipDecompress = false} = opts
|
||||||
const client = new httpClient.HttpClient(getUserAgentString())
|
const client = new httpClient.HttpClient(getUserAgentString())
|
||||||
const response = await client.get(url)
|
const response = await client.get(url)
|
||||||
if (response.message.statusCode !== 200) {
|
if (response.message.statusCode !== 200) {
|
||||||
@@ -94,7 +96,7 @@ export async function streamExtractExternal(
|
|||||||
fileName = decodeURIComponent(filenameMatch[1].trim())
|
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}`)
|
core.debug(`Content-Disposition: ${contentDisposition}, fileName: ${fileName}`)
|
||||||
|
|
||||||
let sha256Digest: string | undefined = undefined
|
let sha256Digest: string | undefined = undefined
|
||||||
@@ -102,12 +104,12 @@ export async function streamExtractExternal(
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const timerFn = (): void => {
|
const timerFn = (): void => {
|
||||||
const timeoutError = new Error(
|
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)
|
response.message.destroy(timeoutError)
|
||||||
reject(timeoutError)
|
reject(timeoutError)
|
||||||
}
|
}
|
||||||
const timer = setTimeout(timerFn, opts.timeout)
|
const timer = setTimeout(timerFn, timeout)
|
||||||
|
|
||||||
const onError = (error: Error): void => {
|
const onError = (error: Error): void => {
|
||||||
core.debug(
|
core.debug(
|
||||||
@@ -137,7 +139,7 @@ export async function streamExtractExternal(
|
|||||||
resolve({sha256Digest: `sha256:${sha256Digest}`})
|
resolve({sha256Digest: `sha256:${sha256Digest}`})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isZip) {
|
if (isZip && !skipDecompress) {
|
||||||
// Extract zip file
|
// Extract zip file
|
||||||
passThrough.pipe(unzip.Extract({path: directory})).on('close', onClose).on('error', onError)
|
passThrough.pipe(unzip.Extract({path: directory})).on('close', onClose).on('error', onError)
|
||||||
} else {
|
} else {
|
||||||
@@ -193,7 +195,11 @@ export async function downloadArtifactPublic(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
core.info(`Starting download of artifact to: ${downloadPath}`)
|
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.`)
|
core.info(`Artifact download completed successfully.`)
|
||||||
if (options?.expectedHash) {
|
if (options?.expectedHash) {
|
||||||
if (options?.expectedHash !== extractResponse.sha256Digest) {
|
if (options?.expectedHash !== extractResponse.sha256Digest) {
|
||||||
@@ -254,7 +260,11 @@ export async function downloadArtifactInternal(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
core.info(`Starting download of artifact to: ${downloadPath}`)
|
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.`)
|
core.info(`Artifact download completed successfully.`)
|
||||||
if (options?.expectedHash) {
|
if (options?.expectedHash) {
|
||||||
if (options?.expectedHash !== extractResponse.sha256Digest) {
|
if (options?.expectedHash !== extractResponse.sha256Digest) {
|
||||||
|
|||||||
@@ -113,6 +113,12 @@ export interface DownloadArtifactOptions {
|
|||||||
* matches the expected hash.
|
* matches the expected hash.
|
||||||
*/
|
*/
|
||||||
expectedHash?: string
|
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 {
|
export interface StreamExtractResponse {
|
||||||
|
|||||||
Reference in New Issue
Block a user