mirror of
https://github.com/actions/download-artifact.git
synced 2025-04-23 23:16:44 +08:00
Merge pull request #401 from actions/download-by-id
feat: implement new `artifact-ids` input
This commit is contained in:
commit
8ea3c2c174
32
README.md
32
README.md
@ -13,6 +13,7 @@ See also [upload-artifact](https://github.com/actions/upload-artifact).
|
|||||||
- [Outputs](#outputs)
|
- [Outputs](#outputs)
|
||||||
- [Examples](#examples)
|
- [Examples](#examples)
|
||||||
- [Download Single Artifact](#download-single-artifact)
|
- [Download Single Artifact](#download-single-artifact)
|
||||||
|
- [Download Artifacts by ID](#download-artifacts-by-id)
|
||||||
- [Download All Artifacts](#download-all-artifacts)
|
- [Download All Artifacts](#download-all-artifacts)
|
||||||
- [Download multiple (filtered) Artifacts to the same directory](#download-multiple-filtered-artifacts-to-the-same-directory)
|
- [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)
|
- [Download Artifacts from other Workflow Runs or Repositories](#download-artifacts-from-other-workflow-runs-or-repositories)
|
||||||
@ -53,6 +54,11 @@ For assistance with breaking changes, see [MIGRATION.md](docs/MIGRATION.md).
|
|||||||
# Optional.
|
# Optional.
|
||||||
name:
|
name:
|
||||||
|
|
||||||
|
# IDs of the artifacts to download, comma-separated.
|
||||||
|
# Either inputs `artifact-ids` or `name` can be used, but not both.
|
||||||
|
# Optional.
|
||||||
|
artifact-ids:
|
||||||
|
|
||||||
# Destination path. Supports basic tilde expansion.
|
# Destination path. Supports basic tilde expansion.
|
||||||
# Optional. Default is $GITHUB_WORKSPACE
|
# Optional. Default is $GITHUB_WORKSPACE
|
||||||
path:
|
path:
|
||||||
@ -117,6 +123,32 @@ steps:
|
|||||||
run: ls -R your/destination/dir
|
run: ls -R your/destination/dir
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Download Artifacts by ID
|
||||||
|
|
||||||
|
The `artifact-ids` input allows downloading artifacts using their unique ID rather than name. This is particularly useful when working with immutable artifacts from `actions/upload-artifact@v4` which assigns a unique ID to each artifact.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
artifact-ids: 12345
|
||||||
|
- name: Display structure of downloaded files
|
||||||
|
run: ls -R
|
||||||
|
```
|
||||||
|
|
||||||
|
Multiple artifacts can be downloaded by providing a comma-separated list of IDs:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
artifact-ids: 12345,67890
|
||||||
|
path: path/to/artifacts
|
||||||
|
- name: Display structure of downloaded files
|
||||||
|
run: ls -R path/to/artifacts
|
||||||
|
```
|
||||||
|
|
||||||
|
This will download multiple artifacts to separate directories (similar to downloading multiple artifacts by name).
|
||||||
|
|
||||||
### Download All Artifacts
|
### Download All Artifacts
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ describe('download', () => {
|
|||||||
await run()
|
await run()
|
||||||
|
|
||||||
expect(core.info).toHaveBeenCalledWith(
|
expect(core.info).toHaveBeenCalledWith(
|
||||||
'No input name or pattern filtered specified, downloading all artifacts'
|
'No input name, artifact-ids or pattern filtered specified, downloading all artifacts'
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(core.info).toHaveBeenCalledWith('Total of 2 artifact(s) downloaded')
|
expect(core.info).toHaveBeenCalledWith('Total of 2 artifact(s) downloaded')
|
||||||
@ -221,4 +221,154 @@ describe('download', () => {
|
|||||||
expect.stringContaining('digest validation failed')
|
expect.stringContaining('digest validation failed')
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('downloads a single artifact by ID', async () => {
|
||||||
|
const mockArtifact = {
|
||||||
|
id: 456,
|
||||||
|
name: 'artifact-by-id',
|
||||||
|
size: 1024,
|
||||||
|
digest: 'def456'
|
||||||
|
}
|
||||||
|
|
||||||
|
mockInputs({
|
||||||
|
[Inputs.Name]: '',
|
||||||
|
[Inputs.Pattern]: '',
|
||||||
|
[Inputs.ArtifactIds]: '456'
|
||||||
|
})
|
||||||
|
|
||||||
|
jest.spyOn(artifact, 'listArtifacts').mockImplementation(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
artifacts: [mockArtifact]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
await run()
|
||||||
|
|
||||||
|
expect(core.info).toHaveBeenCalledWith('Downloading artifacts by ID')
|
||||||
|
expect(core.debug).toHaveBeenCalledWith('Parsed artifact IDs: ["456"]')
|
||||||
|
expect(artifact.downloadArtifact).toHaveBeenCalledTimes(1)
|
||||||
|
expect(artifact.downloadArtifact).toHaveBeenCalledWith(
|
||||||
|
456,
|
||||||
|
expect.objectContaining({
|
||||||
|
expectedHash: mockArtifact.digest
|
||||||
|
})
|
||||||
|
)
|
||||||
|
expect(core.info).toHaveBeenCalledWith('Total of 1 artifact(s) downloaded')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('downloads multiple artifacts by ID', async () => {
|
||||||
|
const mockArtifacts = [
|
||||||
|
{id: 123, name: 'first-artifact', size: 1024, digest: 'abc123'},
|
||||||
|
{id: 456, name: 'second-artifact', size: 2048, digest: 'def456'},
|
||||||
|
{id: 789, name: 'third-artifact', size: 3072, digest: 'ghi789'}
|
||||||
|
]
|
||||||
|
|
||||||
|
mockInputs({
|
||||||
|
[Inputs.Name]: '',
|
||||||
|
[Inputs.Pattern]: '',
|
||||||
|
[Inputs.ArtifactIds]: '123, 456, 789'
|
||||||
|
})
|
||||||
|
|
||||||
|
jest.spyOn(artifact, 'listArtifacts').mockImplementation(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
artifacts: mockArtifacts
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
await run()
|
||||||
|
|
||||||
|
expect(core.info).toHaveBeenCalledWith('Downloading artifacts by ID')
|
||||||
|
expect(core.debug).toHaveBeenCalledWith(
|
||||||
|
'Parsed artifact IDs: ["123","456","789"]'
|
||||||
|
)
|
||||||
|
expect(artifact.downloadArtifact).toHaveBeenCalledTimes(3)
|
||||||
|
mockArtifacts.forEach(mockArtifact => {
|
||||||
|
expect(artifact.downloadArtifact).toHaveBeenCalledWith(
|
||||||
|
mockArtifact.id,
|
||||||
|
expect.objectContaining({
|
||||||
|
expectedHash: mockArtifact.digest
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
expect(core.info).toHaveBeenCalledWith('Total of 3 artifact(s) downloaded')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('warns when some artifact IDs are not found', async () => {
|
||||||
|
const mockArtifacts = [
|
||||||
|
{id: 123, name: 'found-artifact', size: 1024, digest: 'abc123'}
|
||||||
|
]
|
||||||
|
|
||||||
|
mockInputs({
|
||||||
|
[Inputs.Name]: '',
|
||||||
|
[Inputs.Pattern]: '',
|
||||||
|
[Inputs.ArtifactIds]: '123, 456, 789'
|
||||||
|
})
|
||||||
|
|
||||||
|
jest.spyOn(artifact, 'listArtifacts').mockImplementation(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
artifacts: mockArtifacts
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
await run()
|
||||||
|
|
||||||
|
expect(core.warning).toHaveBeenCalledWith(
|
||||||
|
'Could not find the following artifact IDs: 456, 789'
|
||||||
|
)
|
||||||
|
expect(core.debug).toHaveBeenCalledWith('Found 1 artifacts by ID')
|
||||||
|
expect(artifact.downloadArtifact).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('throws error when no artifacts with requested IDs are found', async () => {
|
||||||
|
mockInputs({
|
||||||
|
[Inputs.Name]: '',
|
||||||
|
[Inputs.Pattern]: '',
|
||||||
|
[Inputs.ArtifactIds]: '123, 456'
|
||||||
|
})
|
||||||
|
|
||||||
|
jest.spyOn(artifact, 'listArtifacts').mockImplementation(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
artifacts: []
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
await expect(run()).rejects.toThrow(
|
||||||
|
'None of the provided artifact IDs were found'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('throws error when artifact-ids input is empty', async () => {
|
||||||
|
mockInputs({
|
||||||
|
[Inputs.Name]: '',
|
||||||
|
[Inputs.Pattern]: '',
|
||||||
|
[Inputs.ArtifactIds]: ' '
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(run()).rejects.toThrow(
|
||||||
|
"No valid artifact IDs provided in 'artifact-ids' input"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('throws error when some artifact IDs are not valid numbers', async () => {
|
||||||
|
mockInputs({
|
||||||
|
[Inputs.Name]: '',
|
||||||
|
[Inputs.Pattern]: '',
|
||||||
|
[Inputs.ArtifactIds]: '123, abc, 456'
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(run()).rejects.toThrow(
|
||||||
|
"Invalid artifact ID: 'abc'. Must be a number."
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('throws error when both name and artifact-ids are provided', async () => {
|
||||||
|
mockInputs({
|
||||||
|
[Inputs.Name]: 'some-artifact',
|
||||||
|
[Inputs.ArtifactIds]: '123'
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(run()).rejects.toThrow(
|
||||||
|
"Inputs 'name' and 'artifact-ids' cannot be used together. Please specify only one."
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -5,6 +5,9 @@ inputs:
|
|||||||
name:
|
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
|
required: false
|
||||||
|
artifact-ids:
|
||||||
|
description: 'IDs of the artifacts to download, comma-separated. Either inputs `artifact-ids` or `name` can be used, but not both.'
|
||||||
|
required: false
|
||||||
path:
|
path:
|
||||||
description: 'Destination path. Supports basic tilde expansion. Defaults to $GITHUB_WORKSPACE'
|
description: 'Destination path. Supports basic tilde expansion. Defaults to $GITHUB_WORKSPACE'
|
||||||
required: false
|
required: false
|
||||||
|
45
dist/index.js
vendored
45
dist/index.js
vendored
@ -118710,6 +118710,7 @@ var Inputs;
|
|||||||
Inputs["RunID"] = "run-id";
|
Inputs["RunID"] = "run-id";
|
||||||
Inputs["Pattern"] = "pattern";
|
Inputs["Pattern"] = "pattern";
|
||||||
Inputs["MergeMultiple"] = "merge-multiple";
|
Inputs["MergeMultiple"] = "merge-multiple";
|
||||||
|
Inputs["ArtifactIds"] = "artifact-ids";
|
||||||
})(Inputs || (exports.Inputs = Inputs = {}));
|
})(Inputs || (exports.Inputs = Inputs = {}));
|
||||||
var Outputs;
|
var Outputs;
|
||||||
(function (Outputs) {
|
(function (Outputs) {
|
||||||
@ -118783,7 +118784,10 @@ function run() {
|
|||||||
repository: core.getInput(constants_1.Inputs.Repository, { required: false }),
|
repository: core.getInput(constants_1.Inputs.Repository, { required: false }),
|
||||||
runID: parseInt(core.getInput(constants_1.Inputs.RunID, { required: false })),
|
runID: parseInt(core.getInput(constants_1.Inputs.RunID, { required: false })),
|
||||||
pattern: core.getInput(constants_1.Inputs.Pattern, { required: false }),
|
pattern: core.getInput(constants_1.Inputs.Pattern, { required: false }),
|
||||||
mergeMultiple: core.getBooleanInput(constants_1.Inputs.MergeMultiple, { required: false })
|
mergeMultiple: core.getBooleanInput(constants_1.Inputs.MergeMultiple, {
|
||||||
|
required: false
|
||||||
|
}),
|
||||||
|
artifactIds: core.getInput(constants_1.Inputs.ArtifactIds, { required: false })
|
||||||
};
|
};
|
||||||
if (!inputs.path) {
|
if (!inputs.path) {
|
||||||
inputs.path = process.env['GITHUB_WORKSPACE'] || process.cwd();
|
inputs.path = process.env['GITHUB_WORKSPACE'] || process.cwd();
|
||||||
@ -118791,7 +118795,12 @@ function run() {
|
|||||||
if (inputs.path.startsWith(`~`)) {
|
if (inputs.path.startsWith(`~`)) {
|
||||||
inputs.path = inputs.path.replace('~', os.homedir());
|
inputs.path = inputs.path.replace('~', os.homedir());
|
||||||
}
|
}
|
||||||
|
// Check for mutually exclusive inputs
|
||||||
|
if (inputs.name && inputs.artifactIds) {
|
||||||
|
throw new Error(`Inputs 'name' and 'artifact-ids' cannot be used together. Please specify only one.`);
|
||||||
|
}
|
||||||
const isSingleArtifactDownload = !!inputs.name;
|
const isSingleArtifactDownload = !!inputs.name;
|
||||||
|
const isDownloadByIds = !!inputs.artifactIds;
|
||||||
const resolvedPath = path.resolve(inputs.path);
|
const resolvedPath = path.resolve(inputs.path);
|
||||||
core.debug(`Resolved path is ${resolvedPath}`);
|
core.debug(`Resolved path is ${resolvedPath}`);
|
||||||
const options = {};
|
const options = {};
|
||||||
@ -118808,6 +118817,7 @@ function run() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
let artifacts = [];
|
let artifacts = [];
|
||||||
|
let artifactIds = [];
|
||||||
if (isSingleArtifactDownload) {
|
if (isSingleArtifactDownload) {
|
||||||
core.info(`Downloading single artifact`);
|
core.info(`Downloading single artifact`);
|
||||||
const { artifact: targetArtifact } = yield artifact_1.default.getArtifact(inputs.name, options);
|
const { artifact: targetArtifact } = yield artifact_1.default.getArtifact(inputs.name, options);
|
||||||
@ -118817,6 +118827,37 @@ function run() {
|
|||||||
core.debug(`Found named artifact '${inputs.name}' (ID: ${targetArtifact.id}, Size: ${targetArtifact.size})`);
|
core.debug(`Found named artifact '${inputs.name}' (ID: ${targetArtifact.id}, Size: ${targetArtifact.size})`);
|
||||||
artifacts = [targetArtifact];
|
artifacts = [targetArtifact];
|
||||||
}
|
}
|
||||||
|
else if (isDownloadByIds) {
|
||||||
|
core.info(`Downloading artifacts by ID`);
|
||||||
|
const artifactIdList = inputs.artifactIds
|
||||||
|
.split(',')
|
||||||
|
.map(id => id.trim())
|
||||||
|
.filter(id => id !== '');
|
||||||
|
if (artifactIdList.length === 0) {
|
||||||
|
throw new Error(`No valid artifact IDs provided in 'artifact-ids' input`);
|
||||||
|
}
|
||||||
|
core.debug(`Parsed artifact IDs: ${JSON.stringify(artifactIdList)}`);
|
||||||
|
// Parse the artifact IDs
|
||||||
|
artifactIds = artifactIdList.map(id => {
|
||||||
|
const numericId = parseInt(id, 10);
|
||||||
|
if (isNaN(numericId)) {
|
||||||
|
throw new Error(`Invalid artifact ID: '${id}'. Must be a number.`);
|
||||||
|
}
|
||||||
|
return numericId;
|
||||||
|
});
|
||||||
|
// We need to fetch all artifacts to get metadata for the specified IDs
|
||||||
|
const listArtifactResponse = yield artifact_1.default.listArtifacts(Object.assign({ latest: true }, options));
|
||||||
|
artifacts = listArtifactResponse.artifacts.filter(artifact => artifactIds.includes(artifact.id));
|
||||||
|
if (artifacts.length === 0) {
|
||||||
|
throw new Error(`None of the provided artifact IDs were found`);
|
||||||
|
}
|
||||||
|
if (artifacts.length < artifactIds.length) {
|
||||||
|
const foundIds = artifacts.map(a => a.id);
|
||||||
|
const missingIds = artifactIds.filter(id => !foundIds.includes(id));
|
||||||
|
core.warning(`Could not find the following artifact IDs: ${missingIds.join(', ')}`);
|
||||||
|
}
|
||||||
|
core.debug(`Found ${artifacts.length} artifacts by ID`);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
const listArtifactResponse = yield artifact_1.default.listArtifacts(Object.assign({ latest: true }, options));
|
const listArtifactResponse = yield artifact_1.default.listArtifacts(Object.assign({ latest: true }, options));
|
||||||
artifacts = listArtifactResponse.artifacts;
|
artifacts = listArtifactResponse.artifacts;
|
||||||
@ -118828,7 +118869,7 @@ function run() {
|
|||||||
core.debug(`Filtered from ${listArtifactResponse.artifacts.length} to ${artifacts.length} artifacts`);
|
core.debug(`Filtered from ${listArtifactResponse.artifacts.length} to ${artifacts.length} artifacts`);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
core.info('No input name or pattern filtered specified, downloading all artifacts');
|
core.info('No input name, artifact-ids or pattern filtered specified, downloading all artifacts');
|
||||||
if (!inputs.mergeMultiple) {
|
if (!inputs.mergeMultiple) {
|
||||||
core.info('An extra directory with the artifact name will be created for each download');
|
core.info('An extra directory with the artifact name will be created for each download');
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
- [Multiple uploads to the same named Artifact](#multiple-uploads-to-the-same-named-artifact)
|
- [Multiple uploads to the same named Artifact](#multiple-uploads-to-the-same-named-artifact)
|
||||||
- [Overwriting an Artifact](#overwriting-an-artifact)
|
- [Overwriting an Artifact](#overwriting-an-artifact)
|
||||||
- [Merging multiple artifacts](#merging-multiple-artifacts)
|
- [Merging multiple artifacts](#merging-multiple-artifacts)
|
||||||
|
- [Working with Immutable Artifacts](#working-with-immutable-artifacts)
|
||||||
|
|
||||||
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`.
|
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`.
|
||||||
|
|
||||||
@ -207,3 +208,38 @@ jobs:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Note that this will download all artifacts to a temporary directory and reupload them as a single artifact. For more information on inputs and other use cases for `actions/upload-artifact/merge@v4`, see [the action documentation](https://github.com/actions/upload-artifact/blob/main/merge/README.md).
|
Note that this will download all artifacts to a temporary directory and reupload them as a single artifact. For more information on inputs and other use cases for `actions/upload-artifact/merge@v4`, see [the action documentation](https://github.com/actions/upload-artifact/blob/main/merge/README.md).
|
||||||
|
|
||||||
|
## Working with Immutable Artifacts
|
||||||
|
|
||||||
|
In `v4`, artifacts are immutable by default and each artifact gets a unique ID when uploaded. When an artifact with the same name is uploaded again (with or without `overwrite: true`), it gets a new artifact ID.
|
||||||
|
|
||||||
|
To take advantage of this immutability for security purposes (to avoid potential TOCTOU issues where an artifact might be replaced between upload and download), the new `artifact-ids` input allows you to download artifacts by their specific ID rather than by name:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
upload:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Create a file
|
||||||
|
run: echo "hello world" > my-file.txt
|
||||||
|
- name: Upload Artifact
|
||||||
|
id: upload
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: my-artifact
|
||||||
|
path: my-file.txt
|
||||||
|
# The upload step outputs the artifact ID
|
||||||
|
- name: Print Artifact ID
|
||||||
|
run: echo "Artifact ID is ${{ steps.upload.outputs.artifact-id }}"
|
||||||
|
download:
|
||||||
|
needs: upload
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download Artifact by ID
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
# Use the artifact ID directly, not the name, to ensure you get exactly the artifact you expect
|
||||||
|
artifact-ids: ${{ needs.upload.outputs.artifact-id }}
|
||||||
|
```
|
||||||
|
|
||||||
|
This approach provides stronger guarantees about which artifact version you're downloading compared to using just the artifact name.
|
||||||
|
@ -5,7 +5,8 @@ export enum Inputs {
|
|||||||
Repository = 'repository',
|
Repository = 'repository',
|
||||||
RunID = 'run-id',
|
RunID = 'run-id',
|
||||||
Pattern = 'pattern',
|
Pattern = 'pattern',
|
||||||
MergeMultiple = 'merge-multiple'
|
MergeMultiple = 'merge-multiple',
|
||||||
|
ArtifactIds = 'artifact-ids'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Outputs {
|
export enum Outputs {
|
||||||
|
@ -23,7 +23,10 @@ export async function run(): Promise<void> {
|
|||||||
repository: core.getInput(Inputs.Repository, {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}),
|
pattern: core.getInput(Inputs.Pattern, {required: false}),
|
||||||
mergeMultiple: core.getBooleanInput(Inputs.MergeMultiple, {required: false})
|
mergeMultiple: core.getBooleanInput(Inputs.MergeMultiple, {
|
||||||
|
required: false
|
||||||
|
}),
|
||||||
|
artifactIds: core.getInput(Inputs.ArtifactIds, {required: false})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!inputs.path) {
|
if (!inputs.path) {
|
||||||
@ -34,7 +37,15 @@ export async function run(): Promise<void> {
|
|||||||
inputs.path = inputs.path.replace('~', os.homedir())
|
inputs.path = inputs.path.replace('~', os.homedir())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for mutually exclusive inputs
|
||||||
|
if (inputs.name && inputs.artifactIds) {
|
||||||
|
throw new Error(
|
||||||
|
`Inputs 'name' and 'artifact-ids' cannot be used together. Please specify only one.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const isSingleArtifactDownload = !!inputs.name
|
const isSingleArtifactDownload = !!inputs.name
|
||||||
|
const isDownloadByIds = !!inputs.artifactIds
|
||||||
const resolvedPath = path.resolve(inputs.path)
|
const resolvedPath = path.resolve(inputs.path)
|
||||||
core.debug(`Resolved path is ${resolvedPath}`)
|
core.debug(`Resolved path is ${resolvedPath}`)
|
||||||
|
|
||||||
@ -56,6 +67,7 @@ export async function run(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let artifacts: Artifact[] = []
|
let artifacts: Artifact[] = []
|
||||||
|
let artifactIds: number[] = []
|
||||||
|
|
||||||
if (isSingleArtifactDownload) {
|
if (isSingleArtifactDownload) {
|
||||||
core.info(`Downloading single artifact`)
|
core.info(`Downloading single artifact`)
|
||||||
@ -74,6 +86,52 @@ export async function run(): Promise<void> {
|
|||||||
)
|
)
|
||||||
|
|
||||||
artifacts = [targetArtifact]
|
artifacts = [targetArtifact]
|
||||||
|
} else if (isDownloadByIds) {
|
||||||
|
core.info(`Downloading artifacts by ID`)
|
||||||
|
|
||||||
|
const artifactIdList = inputs.artifactIds
|
||||||
|
.split(',')
|
||||||
|
.map(id => id.trim())
|
||||||
|
.filter(id => id !== '')
|
||||||
|
|
||||||
|
if (artifactIdList.length === 0) {
|
||||||
|
throw new Error(`No valid artifact IDs provided in 'artifact-ids' input`)
|
||||||
|
}
|
||||||
|
|
||||||
|
core.debug(`Parsed artifact IDs: ${JSON.stringify(artifactIdList)}`)
|
||||||
|
|
||||||
|
// Parse the artifact IDs
|
||||||
|
artifactIds = artifactIdList.map(id => {
|
||||||
|
const numericId = parseInt(id, 10)
|
||||||
|
if (isNaN(numericId)) {
|
||||||
|
throw new Error(`Invalid artifact ID: '${id}'. Must be a number.`)
|
||||||
|
}
|
||||||
|
return numericId
|
||||||
|
})
|
||||||
|
|
||||||
|
// We need to fetch all artifacts to get metadata for the specified IDs
|
||||||
|
const listArtifactResponse = await artifactClient.listArtifacts({
|
||||||
|
latest: true,
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
|
||||||
|
artifacts = listArtifactResponse.artifacts.filter(artifact =>
|
||||||
|
artifactIds.includes(artifact.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (artifacts.length === 0) {
|
||||||
|
throw new Error(`None of the provided artifact IDs were found`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (artifacts.length < artifactIds.length) {
|
||||||
|
const foundIds = artifacts.map(a => a.id)
|
||||||
|
const missingIds = artifactIds.filter(id => !foundIds.includes(id))
|
||||||
|
core.warning(
|
||||||
|
`Could not find the following artifact IDs: ${missingIds.join(', ')}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
core.debug(`Found ${artifacts.length} artifacts by ID`)
|
||||||
} else {
|
} else {
|
||||||
const listArtifactResponse = await artifactClient.listArtifacts({
|
const listArtifactResponse = await artifactClient.listArtifacts({
|
||||||
latest: true,
|
latest: true,
|
||||||
@ -92,7 +150,7 @@ export async function run(): Promise<void> {
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
core.info(
|
core.info(
|
||||||
'No input name or pattern filtered specified, downloading all artifacts'
|
'No input name, artifact-ids or pattern filtered specified, downloading all artifacts'
|
||||||
)
|
)
|
||||||
if (!inputs.mergeMultiple) {
|
if (!inputs.mergeMultiple) {
|
||||||
core.info(
|
core.info(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user