mirror of
https://github.com/actions/download-artifact.git
synced 2025-07-27 00:48:28 +02:00
feat: implement waitTimeout
This commit is contained in:
parent
880511a8d8
commit
7825bea020
6 changed files with 135 additions and 18 deletions
33
.github/workflows/test.yml
vendored
33
.github/workflows/test.yml
vendored
|
@ -156,12 +156,14 @@ jobs:
|
||||||
- run: npm install
|
- run: npm install
|
||||||
|
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
|
|
||||||
# Test downloading a single artifact
|
# Test downloading a single artifact
|
||||||
- name: Download artifact A
|
- name: Download artifact A
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
name: 'Artifact-wait-${{ matrix.runs-on }}'
|
name: 'Artifact-wait-${{ matrix.runs-on }}'
|
||||||
path: some/new/path
|
path: some/new/path
|
||||||
|
waitTimeout: 600
|
||||||
|
|
||||||
# Test downloading an artifact using tilde expansion
|
# Test downloading an artifact using tilde expansion
|
||||||
- name: Download artifact A
|
- name: Download artifact A
|
||||||
|
@ -169,6 +171,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: 'Artifact-wait-${{ matrix.runs-on }}'
|
name: 'Artifact-wait-${{ matrix.runs-on }}'
|
||||||
path: ~/some/path/with/a/tilde
|
path: ~/some/path/with/a/tilde
|
||||||
|
# no need for a timeout here
|
||||||
|
|
||||||
- name: Verify successful download
|
- name: Verify successful download
|
||||||
run: |
|
run: |
|
||||||
|
@ -184,21 +187,43 @@ jobs:
|
||||||
}
|
}
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
# Test downloading both artifacts at once
|
# Test downloading all artifacts at once
|
||||||
|
test-wait-consumer-all:
|
||||||
|
name: 'Test: wait (consumer - all)'
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
runs-on: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
|
fail-fast: false
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.runs-on }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set Node.js 12.x
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 12.x
|
||||||
|
|
||||||
|
- run: npm install
|
||||||
|
|
||||||
|
- run: npm run build
|
||||||
|
|
||||||
- name: Download all Artifacts
|
- name: Download all Artifacts
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
path: some/other/path
|
path: some/other/path
|
||||||
|
waitTimeout: 600
|
||||||
|
|
||||||
- name: Verify successful download
|
- name: Verify successful download
|
||||||
run: |
|
run: |
|
||||||
$fileA = "some/other/path/Artifact-A/file-A.txt"
|
$fileA = "some/other/path/Artifact-A/file-A.txt"
|
||||||
$fileB = "some/other/path/Artifact-B/file-B.txt"
|
if(!(Test-Path -path $fileA))
|
||||||
if(!(Test-Path -path $fileA) -or !(Test-Path -path $fileB))
|
|
||||||
{
|
{
|
||||||
Write-Error "Expected files do not exist"
|
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"))
|
if(!(Get-Content $fileA) -ceq "Lorem ipsum dolor sit amet"))
|
||||||
{
|
{
|
||||||
Write-Error "File contents of downloaded artifacts are incorrect"
|
Write-Error "File contents of downloaded artifacts are incorrect"
|
||||||
}
|
}
|
||||||
|
|
39
README.md
39
README.md
|
@ -135,6 +135,45 @@ steps:
|
||||||
|
|
||||||
> Note: The `id` defined in the `download/artifact` step must match the `id` defined in the `echo` step (i.e `steps.[ID].outputs.download-path`)
|
> Note: The `id` defined in the `download/artifact` step must match the `id` defined in the `echo` step (i.e `steps.[ID].outputs.download-path`)
|
||||||
|
|
||||||
|
# Waiting for the artifact to be available
|
||||||
|
|
||||||
|
You can specify `waitTimeout` (seconds) to instruct the download/artifact to retry until the artifact is available.
|
||||||
|
This is useful if you want to launch the job before its dependency job has finished, e.g. if the dependant requires some time-consuming steps.
|
||||||
|
You can do this by removing the `needs` dependency and relying on the retry logic of download-artifact to fetch the artifact after it's uploaded.
|
||||||
|
|
||||||
|
Beware that GitHub actions come with a limited number of runners available, so if your workflow uses up the limt on the "dependant" jobs,
|
||||||
|
your artifact-source jobs may never be scheduled.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
producer-job:
|
||||||
|
name: This job produces an artifact
|
||||||
|
steps:
|
||||||
|
# ... do something
|
||||||
|
|
||||||
|
# ... then upload an artifact as usual
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: artifact-name
|
||||||
|
path: path/to/artifact
|
||||||
|
|
||||||
|
dependant-job:
|
||||||
|
name: This job has some long running preparation that can run before the artifact is necessary
|
||||||
|
steps:
|
||||||
|
# your long-running steps come first - they're run in parallel with the `producer-job` above (given you have enouth GH actions runners available)
|
||||||
|
run: # e.g. install some large SDK
|
||||||
|
|
||||||
|
# then when you finally need the artifact to be downloaded
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: artifact-name
|
||||||
|
path: output-path
|
||||||
|
# wait for 300 seconds
|
||||||
|
waitTimeout: 300
|
||||||
|
```
|
||||||
|
|
||||||
|
> Note: The `id` defined in the `download/artifact` step must match the `id` defined in the `echo` step (i.e `steps.[ID].outputs.download-path`)
|
||||||
|
|
||||||
# Limitations
|
# Limitations
|
||||||
|
|
||||||
### Permission Loss
|
### Permission Loss
|
||||||
|
|
|
@ -8,6 +8,9 @@ inputs:
|
||||||
path:
|
path:
|
||||||
description: 'Destination path'
|
description: 'Destination path'
|
||||||
required: false
|
required: false
|
||||||
|
waitTimeout:
|
||||||
|
description: 'Wait for the artifact to become available (timeout in seconds)'
|
||||||
|
required: false
|
||||||
runs:
|
runs:
|
||||||
using: 'node16'
|
using: 'node16'
|
||||||
main: 'dist/index.js'
|
main: 'dist/index.js'
|
||||||
|
|
29
dist/index.js
vendored
29
dist/index.js
vendored
|
@ -6954,6 +6954,7 @@ var Inputs;
|
||||||
(function (Inputs) {
|
(function (Inputs) {
|
||||||
Inputs["Name"] = "name";
|
Inputs["Name"] = "name";
|
||||||
Inputs["Path"] = "path";
|
Inputs["Path"] = "path";
|
||||||
|
Inputs["WaitTimeout"] = "waitTimeout";
|
||||||
})(Inputs = exports.Inputs || (exports.Inputs = {}));
|
})(Inputs = exports.Inputs || (exports.Inputs = {}));
|
||||||
var Outputs;
|
var Outputs;
|
||||||
(function (Outputs) {
|
(function (Outputs) {
|
||||||
|
@ -7009,6 +7010,28 @@ function run() {
|
||||||
try {
|
try {
|
||||||
const name = core.getInput(constants_1.Inputs.Name, { required: false });
|
const name = core.getInput(constants_1.Inputs.Name, { required: false });
|
||||||
const path = core.getInput(constants_1.Inputs.Path, { required: false });
|
const path = core.getInput(constants_1.Inputs.Path, { required: false });
|
||||||
|
const waitTimeoutStr = core.getInput(constants_1.Inputs.WaitTimeout, { required: false });
|
||||||
|
let runDownload;
|
||||||
|
// no retry allowed
|
||||||
|
if (waitTimeoutStr == '') {
|
||||||
|
runDownload = (action) => action();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const waitTimeoutSeconds = parseInt(waitTimeoutStr);
|
||||||
|
runDownload = (action) => {
|
||||||
|
const waitUntil = new Date().getSeconds() + waitTimeoutSeconds;
|
||||||
|
let lastError;
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
return action();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
lastError = e;
|
||||||
|
}
|
||||||
|
} while (new Date().getSeconds() < waitUntil);
|
||||||
|
throw Error('Timeout reached. Latest error: ' + lastError);
|
||||||
|
};
|
||||||
|
}
|
||||||
let resolvedPath;
|
let resolvedPath;
|
||||||
// resolve tilde expansions, path.replace only replaces the first occurrence of a pattern
|
// resolve tilde expansions, path.replace only replaces the first occurrence of a pattern
|
||||||
if (path.startsWith(`~`)) {
|
if (path.startsWith(`~`)) {
|
||||||
|
@ -7023,7 +7046,7 @@ function run() {
|
||||||
// download all artifacts
|
// download all artifacts
|
||||||
core.info('No artifact name specified, downloading all artifacts');
|
core.info('No artifact name specified, downloading all artifacts');
|
||||||
core.info('Creating an extra directory for each artifact that is being downloaded');
|
core.info('Creating an extra directory for each artifact that is being downloaded');
|
||||||
const downloadResponse = yield artifactClient.downloadAllArtifacts(resolvedPath);
|
const downloadResponse = yield runDownload(() => __awaiter(this, void 0, void 0, function* () { return yield artifactClient.downloadAllArtifacts(resolvedPath); }));
|
||||||
core.info(`There were ${downloadResponse.length} artifacts downloaded`);
|
core.info(`There were ${downloadResponse.length} artifacts downloaded`);
|
||||||
for (const artifact of downloadResponse) {
|
for (const artifact of downloadResponse) {
|
||||||
core.info(`Artifact ${artifact.artifactName} was downloaded to ${artifact.downloadPath}`);
|
core.info(`Artifact ${artifact.artifactName} was downloaded to ${artifact.downloadPath}`);
|
||||||
|
@ -7035,7 +7058,9 @@ function run() {
|
||||||
const downloadOptions = {
|
const downloadOptions = {
|
||||||
createArtifactFolder: false
|
createArtifactFolder: false
|
||||||
};
|
};
|
||||||
const downloadResponse = yield artifactClient.downloadArtifact(name, resolvedPath, downloadOptions);
|
const downloadResponse = yield runDownload(() => __awaiter(this, void 0, void 0, function* () {
|
||||||
|
return yield artifactClient.downloadArtifact(name, resolvedPath, downloadOptions);
|
||||||
|
}));
|
||||||
core.info(`Artifact ${downloadResponse.artifactName} was downloaded to ${downloadResponse.downloadPath}`);
|
core.info(`Artifact ${downloadResponse.artifactName} was downloaded to ${downloadResponse.downloadPath}`);
|
||||||
}
|
}
|
||||||
// output the directory that the artifact(s) was/were downloaded to
|
// output the directory that the artifact(s) was/were downloaded to
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
export enum Inputs {
|
export enum Inputs {
|
||||||
Name = 'name',
|
Name = 'name',
|
||||||
Path = 'path'
|
Path = 'path',
|
||||||
|
WaitTimeout = 'waitTimeout'
|
||||||
}
|
}
|
||||||
export enum Outputs {
|
export enum Outputs {
|
||||||
DownloadPath = 'download-path'
|
DownloadPath = 'download-path'
|
||||||
|
|
|
@ -8,6 +8,27 @@ async function run(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const name = core.getInput(Inputs.Name, {required: false})
|
const name = core.getInput(Inputs.Name, {required: false})
|
||||||
const path = core.getInput(Inputs.Path, {required: false})
|
const path = core.getInput(Inputs.Path, {required: false})
|
||||||
|
const waitTimeoutStr = core.getInput(Inputs.WaitTimeout, {required: false})
|
||||||
|
|
||||||
|
let runDownload: <T extends unknown>(action: () => T) => T
|
||||||
|
// no retry allowed
|
||||||
|
if (waitTimeoutStr == '') {
|
||||||
|
runDownload = <T extends unknown>(action: () => T) => action()
|
||||||
|
} else {
|
||||||
|
const waitTimeoutSeconds = parseInt(waitTimeoutStr)
|
||||||
|
runDownload = <T extends unknown>(action: () => T) => {
|
||||||
|
const waitUntil = new Date().getSeconds() + waitTimeoutSeconds
|
||||||
|
let lastError
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
return action()
|
||||||
|
} catch (e) {
|
||||||
|
lastError = e
|
||||||
|
}
|
||||||
|
} while (new Date().getSeconds() < waitUntil)
|
||||||
|
throw Error('Timeout reached. Latest error: ' + lastError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let resolvedPath
|
let resolvedPath
|
||||||
// resolve tilde expansions, path.replace only replaces the first occurrence of a pattern
|
// resolve tilde expansions, path.replace only replaces the first occurrence of a pattern
|
||||||
|
@ -25,8 +46,8 @@ async function run(): Promise<void> {
|
||||||
core.info(
|
core.info(
|
||||||
'Creating an extra directory for each artifact that is being downloaded'
|
'Creating an extra directory for each artifact that is being downloaded'
|
||||||
)
|
)
|
||||||
const downloadResponse = await artifactClient.downloadAllArtifacts(
|
const downloadResponse = await runDownload(
|
||||||
resolvedPath
|
async () => await artifactClient.downloadAllArtifacts(resolvedPath)
|
||||||
)
|
)
|
||||||
core.info(`There were ${downloadResponse.length} artifacts downloaded`)
|
core.info(`There were ${downloadResponse.length} artifacts downloaded`)
|
||||||
for (const artifact of downloadResponse) {
|
for (const artifact of downloadResponse) {
|
||||||
|
@ -40,11 +61,14 @@ async function run(): Promise<void> {
|
||||||
const downloadOptions = {
|
const downloadOptions = {
|
||||||
createArtifactFolder: false
|
createArtifactFolder: false
|
||||||
}
|
}
|
||||||
const downloadResponse = await artifactClient.downloadArtifact(
|
const downloadResponse = await runDownload(
|
||||||
|
async () =>
|
||||||
|
await artifactClient.downloadArtifact(
|
||||||
name,
|
name,
|
||||||
resolvedPath,
|
resolvedPath,
|
||||||
downloadOptions
|
downloadOptions
|
||||||
)
|
)
|
||||||
|
)
|
||||||
core.info(
|
core.info(
|
||||||
`Artifact ${downloadResponse.artifactName} was downloaded to ${downloadResponse.downloadPath}`
|
`Artifact ${downloadResponse.artifactName} was downloaded to ${downloadResponse.downloadPath}`
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue