mirror of
https://github.com/actions/download-artifact.git
synced 2024-11-09 15:12:44 +00:00
Merge pull request #259 from actions/robherley/glob-downloads
Support pattern matching to filter artifacts & merge to same directory
This commit is contained in:
commit
f44cd7b40b
12 changed files with 9511 additions and 4739 deletions
|
@ -9,8 +9,7 @@
|
|||
"plugin:import/errors",
|
||||
"plugin:import/warnings",
|
||||
"plugin:import/typescript",
|
||||
"plugin:prettier/recommended",
|
||||
"prettier/@typescript-eslint"
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"plugins": ["@typescript-eslint"]
|
||||
}
|
22
.github/workflows/test.yml
vendored
22
.github/workflows/test.yml
vendored
|
@ -106,3 +106,25 @@ jobs:
|
|||
Write-Error "File contents of downloaded artifacts are incorrect"
|
||||
}
|
||||
shell: pwsh
|
||||
|
||||
# Test glob downloading both artifacts to same directory
|
||||
- name: Download all Artifacts
|
||||
uses: ./
|
||||
with:
|
||||
pattern: Artifact-*
|
||||
path: single/directory
|
||||
merge-multiple: true
|
||||
|
||||
- name: Verify successful download
|
||||
run: |
|
||||
$fileA = "single/directory/file-A.txt"
|
||||
$fileB = "single/directory/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
|
||||
|
|
26
.licenses/npm/minimatch-9.0.3.dep.yml
generated
Normal file
26
.licenses/npm/minimatch-9.0.3.dep.yml
generated
Normal file
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
name: minimatch
|
||||
version: 9.0.3
|
||||
type: npm
|
||||
summary: a glob matcher in javascript
|
||||
homepage:
|
||||
license: isc
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The ISC License
|
||||
|
||||
Copyright (c) 2011-2023 Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
notices: []
|
26
.licenses/npm/minimatch.dep.yml
generated
Normal file
26
.licenses/npm/minimatch.dep.yml
generated
Normal file
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
name: minimatch
|
||||
version: 9.0.3
|
||||
type: npm
|
||||
summary:
|
||||
homepage:
|
||||
license: isc
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The ISC License
|
||||
|
||||
Copyright (c) 2011-2023 Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
notices: []
|
82
README.md
82
README.md
|
@ -14,6 +14,7 @@ See also [upload-artifact](https://github.com/actions/upload-artifact).
|
|||
- [Examples](#examples)
|
||||
- [Download Single Artifact](#download-single-artifact)
|
||||
- [Download All Artifacts](#download-all-artifacts)
|
||||
- [Download multiple (filtered) Artifacts to the same directory](#download-multiple-filtered-artifacts-to-the-same-directory)
|
||||
- [Download Artifacts from other Workflow Runs or Repositories](#download-artifacts-from-other-workflow-runs-or-repositories)
|
||||
- [Limitations](#limitations)
|
||||
- [Permission Loss](#permission-loss)
|
||||
|
@ -38,6 +39,8 @@ For more information, see the [`@actions/artifact`](https://github.com/actions/t
|
|||
1. On self hosted runners, additional [firewall rules](https://github.com/actions/toolkit/tree/main/packages/artifact#breaking-changes) may be required.
|
||||
2. Downloading artifacts that were created from `action/upload-artifact@v3` and below are not supported.
|
||||
|
||||
For assistance with breaking changes, see [MIGRATION.md](docs/MIGRATION.md).
|
||||
|
||||
## Usage
|
||||
|
||||
### Inputs
|
||||
|
@ -46,13 +49,25 @@ For more information, see the [`@actions/artifact`](https://github.com/actions/t
|
|||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
# Name of the artifact to download.
|
||||
# Optional. If unspecified, all artifacts for the run are downloaded.
|
||||
# If unspecified, all artifacts for the run are downloaded.
|
||||
# Optional.
|
||||
name:
|
||||
|
||||
# Destination path. Supports basic tilde expansion.
|
||||
# Optional. Defaults is $GITHUB_WORKSPACE
|
||||
# Optional. Default is $GITHUB_WORKSPACE
|
||||
path:
|
||||
|
||||
# A glob pattern to the artifacts that should be downloaded.
|
||||
# Ignored if name is specified.
|
||||
# Optional.
|
||||
pattern:
|
||||
|
||||
# When multiple artifacts are matched, this changes the behavior of the destination directories.
|
||||
# If true, the downloaded artifacts will be in the same directory specified by path.
|
||||
# If false, the downloaded artifacts will be extracted into individual named directories within the specified path.
|
||||
# Optional. Default is 'false'
|
||||
merge-multiple:
|
||||
|
||||
# The GitHub token used to authenticate with the GitHub API.
|
||||
# This is required when downloading artifacts from a different repository or from a different workflow run.
|
||||
# Optional. If unspecified, the action will download artifacts from the current repo and the current workflow run.
|
||||
|
@ -105,7 +120,7 @@ steps:
|
|||
|
||||
### Download All Artifacts
|
||||
|
||||
If the `name` input parameter is not provided, all artifacts will be downloaded. **To differentiate between downloaded artifacts, a directory denoted by the artifacts name will be created for each individual artifact.**
|
||||
If the `name` input parameter is not provided, all artifacts will be downloaded. To differentiate between downloaded artifacts, by default a directory denoted by the artifacts name will be created for each individual artifact. This behavior can be changed with the `merge-multiple` input parameter.
|
||||
|
||||
Example, if there are two artifacts `Artifact-A` and `Artifact-B`, and the directory is `etc/usr/artifacts/`, the directory structure will look like this:
|
||||
|
||||
|
@ -137,6 +152,67 @@ steps:
|
|||
run: ls -R path/to/artifacts
|
||||
```
|
||||
|
||||
To download them to the _same_ directory:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: path/to/artifacts
|
||||
merge-multiple: true
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R path/to/artifacts
|
||||
```
|
||||
|
||||
Which will result in:
|
||||
|
||||
```
|
||||
path/to/artifacts/
|
||||
... contents of Artifact-A
|
||||
... contents of Artifact-B
|
||||
```
|
||||
|
||||
### Download multiple (filtered) Artifacts to the same directory
|
||||
|
||||
In multiple arch/os scenarios, you may have Artifacts built in different jobs. To download all Artifacts to the same directory (or matching a glob pattern), you can use the `pattern` and `merge-multiple` inputs.
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
upload:
|
||||
strategy:
|
||||
matrix:
|
||||
runs-on: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
steps:
|
||||
- name: Create a File
|
||||
run: echo "hello from ${{ matrix.runs-on }}" > file-${{ matrix.runs-on }}.txt
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: my-artifact-${{ matrix.runs-on }}
|
||||
path: file-${{ matrix.runs-on }}.txt
|
||||
download:
|
||||
needs: upload
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download All Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: my-artifact
|
||||
pattern: my-artifact-*
|
||||
merge-multiple: true
|
||||
- run: ls -R my-artifact
|
||||
```
|
||||
|
||||
This results in a directory like so:
|
||||
|
||||
```
|
||||
my-artifact/
|
||||
file-macos-latest.txt
|
||||
file-ubuntu-latest.txt
|
||||
file-windows-latest.txt
|
||||
```
|
||||
|
||||
### Download Artifacts from other Workflow Runs or Repositories
|
||||
|
||||
It may be useful to download Artifacts from other workflow runs, or even other repositories. By default, the permissions are scoped so they can only download Artifacts within the current workflow run. To elevate permissions for this scenario, you can specify a `github-token` along with other repository and run identifiers:
|
||||
|
|
11
action.yml
11
action.yml
|
@ -3,11 +3,20 @@ description: 'Download a build artifact that was previously uploaded in the work
|
|||
author: 'GitHub'
|
||||
inputs:
|
||||
name:
|
||||
description: 'Name of the artifact to download. If unspecified, all artifacts for the run are downloaded'
|
||||
description: 'Name of the artifact to download. If unspecified, all artifacts for the run are downloaded.'
|
||||
required: false
|
||||
path:
|
||||
description: 'Destination path. Supports basic tilde expansion. Defaults to $GITHUB_WORKSPACE'
|
||||
required: false
|
||||
pattern:
|
||||
description: 'A glob pattern matching the artifacts that should be downloaded. Ignored if name is specified.'
|
||||
required: false
|
||||
merge-multiple:
|
||||
description: 'When multiple artifacts are matched, this changes the behavior of the destination directories.
|
||||
If true, the downloaded artifacts will be in the same directory specified by path.
|
||||
If false, the downloaded artifacts will be extracted into individual named directories within the specified path.'
|
||||
required: false
|
||||
default: 'false'
|
||||
github-token:
|
||||
description: 'The GitHub token used to authenticate with the GitHub API.
|
||||
This is required when downloading artifacts from a different repository or from a different workflow run.
|
||||
|
|
7761
dist/index.js
vendored
7761
dist/index.js
vendored
File diff suppressed because one or more lines are too long
80
docs/MIGRATION.md
Normal file
80
docs/MIGRATION.md
Normal file
|
@ -0,0 +1,80 @@
|
|||
# Migration
|
||||
|
||||
- [Migration](#migration)
|
||||
- [Multiple uploads to the same named Artifact](#multiple-uploads-to-the-same-named-artifact)
|
||||
|
||||
Several behavioral differences exist between Artifact actions `v3` and below vs `v4`. This document outlines common scenarios in `v3`, and how they would be handled in `v4`.
|
||||
|
||||
## Multiple uploads to the same named Artifact
|
||||
|
||||
In `v3`, Artifacts are _mutable_ so it's possible to write workflow scenarios where multiple jobs upload to the same Artifact like so:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
upload:
|
||||
strategy:
|
||||
matrix:
|
||||
runs-on: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
steps:
|
||||
- name: Create a File
|
||||
run: echo "hello from ${{ matrix.runs-on }}" > file-${{ matrix.runs-on }}.txt
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: my-artifact # NOTE: same artifact name
|
||||
path: file-${{ matrix.runs-on }}.txt
|
||||
download:
|
||||
needs: upload
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download All Artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: my-artifact
|
||||
- run: ls -R my-artifact
|
||||
```
|
||||
|
||||
This results in a directory like so:
|
||||
|
||||
```
|
||||
my-artifact/
|
||||
file-macos-latest.txt
|
||||
file-ubuntu-latest.txt
|
||||
file-windows-latest.txt
|
||||
```
|
||||
|
||||
In v4, Artifacts are immutable (unless deleted). So you must change each of the uploaded Artifacts to have a different name and filter the downloads by name to achieve the same effect:
|
||||
|
||||
```diff
|
||||
jobs:
|
||||
upload:
|
||||
strategy:
|
||||
matrix:
|
||||
runs-on: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
steps:
|
||||
- name: Create a File
|
||||
run: echo "hello from ${{ matrix.runs-on }}" > file-${{ matrix.runs-on }}.txt
|
||||
- name: Upload Artifact
|
||||
- uses: actions/upload-artifact@v3
|
||||
+ uses: actions/upload-artifact@v4
|
||||
with:
|
||||
- name: my-artifact
|
||||
+ name: my-artifact-${{ matrix.runs-on }}
|
||||
path: file-${{ matrix.runs-on }}.txt
|
||||
download:
|
||||
needs: upload
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download All Artifacts
|
||||
- uses: actions/download-artifact@v3
|
||||
+ uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: my-artifact
|
||||
+ pattern: my-artifact-*
|
||||
+ merge-multiple: true
|
||||
- run: ls -R my-artifact
|
||||
```
|
||||
|
||||
In `v4`, the new `pattern:` input will filter the downloaded Artifacts to match the name specified. The new `merge-multiple:` input will support downloading multiple Artifacts to the same directory. If the files within the Artifacts have the same name, the last writer wins.
|
6185
package-lock.json
generated
6185
package-lock.json
generated
File diff suppressed because it is too large
Load diff
14
package.json
14
package.json
|
@ -30,16 +30,18 @@
|
|||
"dependencies": {
|
||||
"@actions/artifact": "^2.0.0",
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/github": "^5.1.1"
|
||||
"@actions/github": "^5.1.1",
|
||||
"minimatch": "^9.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^12.12.6",
|
||||
"@typescript-eslint/eslint-plugin": "^5.40.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||
"@vercel/ncc": "^0.33.4",
|
||||
"concurrently": "^5.2.0",
|
||||
"eslint": "^7.4.0",
|
||||
"eslint-plugin-github": "^4.1.1",
|
||||
"prettier": "^2.0.5",
|
||||
"typescript": "^3.8.3"
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-plugin-github": "^4.10.1",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"prettier": "^3.1.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@ export enum Inputs {
|
|||
Path = 'path',
|
||||
GitHubToken = 'github-token',
|
||||
Repository = 'repository',
|
||||
RunID = 'run-id'
|
||||
RunID = 'run-id',
|
||||
Pattern = 'pattern',
|
||||
MergeMultiple = 'merge-multiple'
|
||||
}
|
||||
|
||||
export enum Outputs {
|
||||
|
|
|
@ -3,6 +3,7 @@ import * as path from 'path'
|
|||
import * as core from '@actions/core'
|
||||
import artifactClient from '@actions/artifact'
|
||||
import type {Artifact, FindOptions} from '@actions/artifact'
|
||||
import {Minimatch} from 'minimatch'
|
||||
import {Inputs, Outputs} from './constants'
|
||||
|
||||
const PARALLEL_DOWNLOADS = 5
|
||||
|
@ -20,7 +21,9 @@ async function run(): Promise<void> {
|
|||
path: core.getInput(Inputs.Path, {required: false}),
|
||||
token: core.getInput(Inputs.GitHubToken, {required: false}),
|
||||
repository: core.getInput(Inputs.Repository, {required: false}),
|
||||
runID: parseInt(core.getInput(Inputs.RunID, {required: false}))
|
||||
runID: parseInt(core.getInput(Inputs.RunID, {required: false})),
|
||||
pattern: core.getInput(Inputs.Pattern, {required: false}),
|
||||
mergeMultiple: core.getBooleanInput(Inputs.MergeMultiple, {required: false})
|
||||
}
|
||||
|
||||
if (!inputs.path) {
|
||||
|
@ -80,23 +83,36 @@ async function run(): Promise<void> {
|
|||
latest: true,
|
||||
...options
|
||||
})
|
||||
artifacts = listArtifactResponse.artifacts
|
||||
|
||||
if (listArtifactResponse.artifacts.length === 0) {
|
||||
throw new Error(
|
||||
`No artifacts found for run '${inputs.runID}' in '${inputs.repository}'`
|
||||
core.debug(`Found ${artifacts.length} artifacts in run`)
|
||||
|
||||
if (inputs.pattern) {
|
||||
core.info(`Filtering artifacts by pattern '${inputs.pattern}'`)
|
||||
const matcher = new Minimatch(inputs.pattern)
|
||||
artifacts = artifacts.filter(artifact => matcher.match(artifact.name))
|
||||
core.debug(
|
||||
`Filtered from ${listArtifactResponse.artifacts.length} to ${artifacts.length} artifacts`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
core.debug(`Found ${listArtifactResponse.artifacts.length} artifacts`)
|
||||
artifacts = listArtifactResponse.artifacts
|
||||
if (artifacts.length) {
|
||||
core.info(`Preparing to download the following artifacts:`)
|
||||
artifacts.forEach(artifact => {
|
||||
core.info(
|
||||
`- ${artifact.name} (ID: ${artifact.id}, Size: ${artifact.size})`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const downloadPromises = artifacts.map(artifact =>
|
||||
artifactClient.downloadArtifact(artifact.id, {
|
||||
...options,
|
||||
path: isSingleArtifactDownload
|
||||
? resolvedPath
|
||||
: path.join(resolvedPath, artifact.name)
|
||||
path:
|
||||
isSingleArtifactDownload || inputs.mergeMultiple
|
||||
? resolvedPath
|
||||
: path.join(resolvedPath, artifact.name)
|
||||
})
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in a new issue