mirror of
https://code.forgejo.org/actions/download-artifact.git
synced 2024-12-23 12:36:01 +01:00
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:
parent
fbb8edeffa
commit
569e039f2a
4 changed files with 2294 additions and 120 deletions
57
.github/workflows/test.yml
vendored
57
.github/workflows/test.yml
vendored
|
@ -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
2204
dist/index.js
vendored
File diff suppressed because it is too large
Load diff
|
@ -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'
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue