feat: support downloading multiple artifacts to different paths

There is a few use cases where we want to download a few different artifacts but
not all of them. This commit implements this support, without breaking backward
compatibility. Two build test cases were also added to the pipeline.
This commit is contained in:
Diogo Kiss 2022-10-13 13:54:16 +02:00
parent fbb8edeffa
commit 569e039f2a
4 changed files with 2294 additions and 120 deletions

View File

@ -38,7 +38,7 @@ jobs:
run: npm run lint run: npm run lint
- name: Format - name: Format
run: npm run format-check run: npm run format-check
# Test end-to-end by uploading two artifacts and then downloading them # Test end-to-end by uploading two artifacts and then downloading them
# Once upload-artifact v2 is out of preview, switch over # Once upload-artifact v2 is out of preview, switch over
@ -48,7 +48,7 @@ jobs:
mkdir -p path/to/artifact-B mkdir -p path/to/artifact-B
echo "Lorem ipsum dolor sit amet" > path/to/artifact-A/file-A.txt echo "Lorem ipsum dolor sit amet" > path/to/artifact-A/file-A.txt
echo "Hello world from file B" > path/to/artifact-B/file-B.txt echo "Hello world from file B" > path/to/artifact-B/file-B.txt
- name: Upload artifact A - name: Upload artifact A
uses: actions/upload-artifact@v1 uses: actions/upload-artifact@v1
with: with:
@ -89,7 +89,56 @@ jobs:
} }
shell: pwsh shell: pwsh
# Test downloading both artifacts at once # Test downloading multiple artifacts to the same path
- name: Download artifacts A and B to the same path
uses: ./
with:
name: |
Artifact-A
Artifact-B
path: some/path/for/multiple/files
- name: Verify successful download
run: |
$fileA = "some/path/for/multiple/files/file-A.txt"
$fileB = "some/path/for/multiple/files/file-B.txt"
if(!(Test-Path -path $fileA) -or !(Test-Path -path $fileB))
{
Write-Error "Expected files do not exist"
}
if(!((Get-Content $fileA) -ceq "Lorem ipsum dolor sit amet") -or !((Get-Content $fileB) -ceq "Hello world from file B"))
{
Write-Error "File contents of downloaded artifacts are incorrect"
}
shell: pwsh
# Test downloading multiple artifacts to different paths
- name: Download artifacts A and B to different paths
uses: ./
with:
name: |
Artifact-A
Artifact-B
path: |
some/path/for/a
some/path/for/b
- name: Verify successful download
run: |
$fileA = "some/path/for/a/file-A.txt"
$fileB = "some/path/for/b/file-B.txt"
if(!(Test-Path -path $fileA) -or !(Test-Path -path $fileB))
{
Write-Error "Expected files do not exist"
}
if(!((Get-Content $fileA) -ceq "Lorem ipsum dolor sit amet") -or !((Get-Content $fileB) -ceq "Hello world from file B"))
{
Write-Error "File contents of downloaded artifacts are incorrect"
}
shell: pwsh
# Test downloading all artifacts
- name: Download all Artifacts - name: Download all Artifacts
uses: ./ uses: ./
with: with:
@ -108,5 +157,3 @@ jobs:
Write-Error "File contents of downloaded artifacts are incorrect" Write-Error "File contents of downloaded artifacts are incorrect"
} }
shell: pwsh shell: pwsh

2204
dist/index.js vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
export enum Inputs { export enum Inputs {
Name = 'name', Names = 'name',
Path = 'path' Paths = 'path'
} }
export enum Outputs { export enum Outputs {
DownloadPath = 'download-path' DownloadPaths = 'download-path'
} }

View File

@ -4,55 +4,118 @@ import * as os from 'os'
import {resolve} from 'path' import {resolve} from 'path'
import {Inputs, Outputs} from './constants' import {Inputs, Outputs} from './constants'
async function downloadArtifact(name: string, path: string): Promise<string> {
let resolvedPath
// resolve tilde expansions, path.replace only replaces the first occurrence of a pattern
if (path.startsWith(`~`)) {
resolvedPath = resolve(path.replace('~', os.homedir()))
} else {
resolvedPath = resolve(path)
}
core.debug(`Resolved path is ${resolvedPath}`)
const artifactClient = artifact.create()
if (!name) {
// download all artifacts
core.info('No artifact name specified, downloading all artifacts')
core.info(
'Creating an extra directory for each artifact that is being downloaded'
)
const downloadResponse = await artifactClient.downloadAllArtifacts(
resolvedPath
)
core.info(`There were ${downloadResponse.length} artifacts downloaded`)
for (const artifact of downloadResponse) {
core.info(
`Artifact ${artifact.artifactName} was downloaded to ${artifact.downloadPath}`
)
}
} else {
// download a single artifact
core.info(`Starting download for ${name}`)
const downloadOptions = {
createArtifactFolder: false
}
const downloadResponse = await artifactClient.downloadArtifact(
name,
resolvedPath,
downloadOptions
)
core.info(
`Artifact ${downloadResponse.artifactName} was downloaded to ${downloadResponse.downloadPath}`
)
}
// output the directory that the artifact(s) was/were downloaded to
// if no path is provided, an empty string resolves to the current working directory
core.info('Artifact download has finished successfully')
return resolvedPath
}
async function run(): Promise<void> { async function run(): Promise<void> {
try { try {
const name = core.getInput(Inputs.Name, {required: false}) const names = core.getMultilineInput(Inputs.Names, {required: false})
const path = core.getInput(Inputs.Path, {required: false}) const paths = core.getMultilineInput(Inputs.Paths, {required: false})
let resolvedPath core.info(`names: '${JSON.stringify(names)}' | length: ${names.length}`)
// resolve tilde expansions, path.replace only replaces the first occurrence of a pattern core.info(`paths: '${JSON.stringify(paths)}' | length: ${paths.length}`)
if (path.startsWith(`~`)) {
resolvedPath = resolve(path.replace('~', os.homedir()))
} else {
resolvedPath = resolve(path)
}
core.debug(`Resolved path is ${resolvedPath}`)
const artifactClient = artifact.create() let downloadPaths: string[] = []
if (!name) {
// download all artifacts // Names is set and has fewer entries than Paths
core.info('No artifact name specified, downloading all artifacts') if (names.length !== 0 && paths.length > names.length) {
core.info( throw Error(
'Creating an extra directory for each artifact that is being downloaded' `The input 'path' cannot have more entries than 'name', if 'name' is set.`
)
const downloadResponse = await artifactClient.downloadAllArtifacts(
resolvedPath
)
core.info(`There were ${downloadResponse.length} artifacts downloaded`)
for (const artifact of downloadResponse) {
core.info(
`Artifact ${artifact.artifactName} was downloaded to ${artifact.downloadPath}`
)
}
} else {
// download a single artifact
core.info(`Starting download for ${name}`)
const downloadOptions = {
createArtifactFolder: false
}
const downloadResponse = await artifactClient.downloadArtifact(
name,
resolvedPath,
downloadOptions
)
core.info(
`Artifact ${downloadResponse.artifactName} was downloaded to ${downloadResponse.downloadPath}`
) )
} }
// Names is NOT set and Paths has more than 1 entry
else if (names.length === 0 && paths.length > 1) {
throw Error(
`The input 'path' cannot have more than one entry, if 'name' is not set.`
)
}
// Names is NOT set and path has at max 1 entry: download all artifacts
else if (names.length === 0 && paths.length <= 1) {
const name = names.toString() // ''
const path = paths.toString() // '' or 'some/path'
const downloadPath = await downloadArtifact(name, path)
downloadPaths.push(downloadPath)
}
// Names has one or more entries and Paths has at max 1 entry
else if (names.length >= 1 && paths.length <= 1) {
const path = paths.toString() // '' or 'some/path'
names.forEach(async name => {
const downloadPath = await downloadArtifact(name, path)
downloadPaths.push(downloadPath)
})
}
// Names and Paths have the same numbers of entries (more than 1)
else if (
names.length > 1 &&
paths.length > 1 &&
names.length === paths.length
) {
names.forEach(async (name, index) => {
const path = paths[index]
const downloadPath = await downloadArtifact(name, path)
downloadPaths.push(downloadPath)
})
}
// Unhandled exception
else {
throw Error(
`Unhandled scenario. This shouldn't happen. It's very likely a bug. :-()`
)
}
// Remove duplicates and empty strings
downloadPaths = [...new Set(downloadPaths.filter(path => path !== ''))]
// Returns a newline-separated list of paths
const output = downloadPaths.join('\n')
// output the directory that the artifact(s) was/were downloaded to // output the directory that the artifact(s) was/were downloaded to
// if no path is provided, an empty string resolves to the current working directory core.setOutput(Outputs.DownloadPaths, output)
core.setOutput(Outputs.DownloadPath, resolvedPath)
core.info('Artifact download has finished successfully')
} catch (err) { } catch (err) {
core.setFailed(err.message) core.setFailed(err.message)
} }