mirror of
https://github.com/actions/upload-artifact.git
synced 2025-06-13 23:37:12 +02:00
Compare commits
30 Commits
v4.3.1
...
joshmgross
Author | SHA1 | Date | |
---|---|---|---|
f626d210e8 | |||
fa37431cef | |||
3412bb46a4 | |||
834a144ee9 | |||
134dcf33c0 | |||
73a0b9c954 | |||
89ef406dd8 | |||
23d796df36 | |||
e445c64bc2 | |||
0b2256b8c0 | |||
488dcefb9b | |||
04c51f5766 | |||
32a9e276a8 | |||
552bf3722c | |||
79616d2ded | |||
65462800fd | |||
c004fb4bf6 | |||
90aba496fc | |||
b06cde36fc | |||
1746f4ab65 | |||
31685d04a0 | |||
18bf333cd2 | |||
dac413befa | |||
bb3b4a3cdb | |||
3e3da837d2 | |||
e35774f165 | |||
e63ea677fb | |||
ef09cdac3e | |||
00e36f94d8 | |||
4c0ff1c489 |
@ -22,7 +22,7 @@ jobs:
|
||||
steps:
|
||||
- name: Update the ${{ env.TAG_NAME }} tag
|
||||
id: update-major-tag
|
||||
uses: actions/publish-action@v0.2.1
|
||||
uses: actions/publish-action@v0.3.0
|
||||
with:
|
||||
source-tag: ${{ env.TAG_NAME }}
|
||||
slack-webhook: ${{ secrets.SLACK_WEBHOOK }}
|
||||
|
2
.licenses/npm/@actions/artifact.dep.yml
generated
2
.licenses/npm/@actions/artifact.dep.yml
generated
@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@actions/artifact"
|
||||
version: 2.1.1
|
||||
version: 2.1.8
|
||||
type: npm
|
||||
summary:
|
||||
homepage:
|
||||
|
2
.licenses/npm/@actions/core.dep.yml
generated
2
.licenses/npm/@actions/core.dep.yml
generated
@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@actions/core"
|
||||
version: 1.10.0
|
||||
version: 1.10.1
|
||||
type: npm
|
||||
summary:
|
||||
homepage:
|
||||
|
32
README.md
32
README.md
@ -1,5 +1,11 @@
|
||||
# `@actions/upload-artifact`
|
||||
|
||||
> [!WARNING]
|
||||
> actions/upload-artifact@v3 is scheduled for deprecation on **November 30, 2024**. [Learn more.](https://github.blog/changelog/2024-04-16-deprecation-notice-v3-of-the-artifact-actions/)
|
||||
> Similarly, v1/v2 are scheduled for deprecation on **June 30, 2024**.
|
||||
> Please update your workflow to use v4 of the artifact actions.
|
||||
> This deprecation will not impact any existing versions of GitHub Enterprise Server being used by customers.
|
||||
|
||||
Upload [Actions Artifacts](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts) from your Workflow Runs. Internally powered by [@actions/artifact](https://github.com/actions/toolkit/tree/main/packages/artifact) package.
|
||||
|
||||
See also [download-artifact](https://github.com/actions/download-artifact).
|
||||
@ -10,6 +16,7 @@ See also [download-artifact](https://github.com/actions/download-artifact).
|
||||
- [Breaking Changes](#breaking-changes)
|
||||
- [Usage](#usage)
|
||||
- [Inputs](#inputs)
|
||||
- [Uploading the `.git` directory](#uploading-the-git-directory)
|
||||
- [Outputs](#outputs)
|
||||
- [Examples](#examples)
|
||||
- [Upload an Individual File](#upload-an-individual-file)
|
||||
@ -58,6 +65,7 @@ There is also a new sub-action, `actions/upload-artifact/merge`. For more info,
|
||||
Due to how Artifacts are created in this new version, it is no longer possible to upload to the same named Artifact multiple times. You must either split the uploads into multiple Artifacts with different names, or only upload once. Otherwise you _will_ encounter an error.
|
||||
|
||||
3. Limit of Artifacts for an individual job. Each job in a workflow run now has a limit of 500 artifacts.
|
||||
4. With `v4.4` and later, the `.git` directory is excluded by default.
|
||||
|
||||
For assistance with breaking changes, see [MIGRATION.md](docs/MIGRATION.md).
|
||||
|
||||
@ -103,6 +111,30 @@ For assistance with breaking changes, see [MIGRATION.md](docs/MIGRATION.md).
|
||||
overwrite:
|
||||
```
|
||||
|
||||
#### Uploading the `.git` directory
|
||||
|
||||
By default, files in a `.git` directory are ignored in the uploaded artifact.
|
||||
This is intended to prevent accidentally uploading Git credentials into an artifact that could then
|
||||
be extracted.
|
||||
If files in the `.git` directory are needed, ensure that `actions/checkout` is being used with
|
||||
`persist-credentials: false`.
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false # Ensure credentials are not saved in `.git/config`
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: .
|
||||
include-git-directory: true
|
||||
```
|
||||
|
||||
### Outputs
|
||||
|
||||
| Name | Description | Example |
|
||||
|
@ -61,6 +61,12 @@ const lonelyFilePath = path.join(
|
||||
'lonely-file.txt'
|
||||
)
|
||||
|
||||
const gitConfigPath = path.join(root, '.git', 'config')
|
||||
const gitHeadPath = path.join(root, '.git', 'HEAD')
|
||||
|
||||
const nestedGitConfigPath = path.join(root, 'repository-name', '.git', 'config')
|
||||
const nestedGitHeadPath = path.join(root, 'repository-name', '.git', 'HEAD')
|
||||
|
||||
describe('Search', () => {
|
||||
beforeAll(async () => {
|
||||
// mock all output so that there is less noise when running tests
|
||||
@ -93,6 +99,11 @@ describe('Search', () => {
|
||||
recursive: true
|
||||
})
|
||||
|
||||
await fs.mkdir(path.join(root, '.git'))
|
||||
await fs.mkdir(path.join(root, 'repository-name', '.git'), {
|
||||
recursive: true
|
||||
})
|
||||
|
||||
await fs.writeFile(searchItem1Path, 'search item1 file')
|
||||
await fs.writeFile(searchItem2Path, 'search item2 file')
|
||||
await fs.writeFile(searchItem3Path, 'search item3 file')
|
||||
@ -110,9 +121,17 @@ describe('Search', () => {
|
||||
await fs.writeFile(amazingFileInFolderHPath, 'amazing file')
|
||||
|
||||
await fs.writeFile(lonelyFilePath, 'all by itself')
|
||||
|
||||
await fs.writeFile(gitConfigPath, 'git config file')
|
||||
await fs.writeFile(gitHeadPath, 'git head file')
|
||||
await fs.writeFile(nestedGitConfigPath, 'nested git config file')
|
||||
await fs.writeFile(nestedGitHeadPath, 'nested git head file')
|
||||
/*
|
||||
Directory structure of files that get created:
|
||||
root/
|
||||
.git/
|
||||
config
|
||||
HEAD
|
||||
folder-a/
|
||||
folder-b/
|
||||
folder-c/
|
||||
@ -136,6 +155,10 @@ describe('Search', () => {
|
||||
folder-j/
|
||||
folder-k/
|
||||
lonely-file.txt
|
||||
repository-name/
|
||||
.git/
|
||||
config
|
||||
HEAD
|
||||
search-item5.txt
|
||||
*/
|
||||
})
|
||||
@ -352,4 +375,18 @@ describe('Search', () => {
|
||||
)
|
||||
expect(searchResult.filesToUpload.includes(lonelyFilePath)).toEqual(true)
|
||||
})
|
||||
|
||||
it('Excludes .git directory by default', async () => {
|
||||
const searchResult = await findFilesToUpload(root)
|
||||
expect(searchResult.filesToUpload.length).toEqual(13)
|
||||
expect(searchResult.filesToUpload).not.toContain(gitConfigPath)
|
||||
})
|
||||
|
||||
it('Includes .git directory when includeGitDirectory is true', async () => {
|
||||
const searchResult = await findFilesToUpload(root, {
|
||||
includeGitDirectory: true
|
||||
})
|
||||
expect(searchResult.filesToUpload.length).toEqual(17)
|
||||
expect(searchResult.filesToUpload).toContain(gitConfigPath)
|
||||
})
|
||||
})
|
||||
|
@ -40,6 +40,9 @@ inputs:
|
||||
If false, the action will fail if an artifact for the given name already exists.
|
||||
Does not fail if the artifact does not exist.
|
||||
default: 'false'
|
||||
include-git-directory:
|
||||
description: 'Include files in the .git directory in the artifact.'
|
||||
default: 'false'
|
||||
|
||||
outputs:
|
||||
artifact-id:
|
||||
|
34186
dist/merge/index.js
vendored
34186
dist/merge/index.js
vendored
File diff suppressed because it is too large
Load Diff
36040
dist/upload/index.js
vendored
36040
dist/upload/index.js
vendored
File diff suppressed because it is too large
Load Diff
@ -189,7 +189,8 @@ jobs:
|
||||
- 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@v3
|
||||
+ uses: actions/upload-artifact@v4
|
||||
with:
|
||||
- name: all-my-files
|
||||
+ name: my-artifact-${{ matrix.runs-on }}
|
||||
@ -206,3 +207,41 @@ 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](../merge/README.md).
|
||||
|
||||
## `.git` Directory
|
||||
|
||||
By default, files in the `.git` directory are ignored to avoid unintentionally uploading
|
||||
credentials.
|
||||
|
||||
In versions of this action before `v4.4.0`, files in the `.git` directory were included by default.
|
||||
If this directory is required, ensure credentials are not saved in `.git/config` and then
|
||||
enable the `include-git-directory` input.
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: .
|
||||
```
|
||||
|
||||
|
||||
```diff
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
+ with:
|
||||
+ persist-credentials: false
|
||||
- name: Upload Artifact
|
||||
- uses: actions/upload-artifact@v3
|
||||
+ uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: .
|
||||
+ include-git-directory: true
|
||||
```
|
@ -5,6 +5,7 @@ Merge multiple [Actions Artifacts](https://docs.github.com/en/actions/using-work
|
||||
- [`@actions/upload-artifact/merge`](#actionsupload-artifactmerge)
|
||||
- [Usage](#usage)
|
||||
- [Inputs](#inputs)
|
||||
- [Uploading the `.git` directory](#uploading-the-git-directory)
|
||||
- [Outputs](#outputs)
|
||||
- [Examples](#examples)
|
||||
- [Combining all artifacts in a workflow run](#combining-all-artifacts-in-a-workflow-run)
|
||||
@ -59,6 +60,44 @@ For most cases, this may not be the most efficient solution. See [the migration
|
||||
compression-level:
|
||||
```
|
||||
|
||||
#### Uploading the `.git` directory
|
||||
|
||||
By default, files in a `.git` directory are ignored in the merged artifact.
|
||||
This is intended to prevent accidentally uploading Git credentials into an artifact that could then
|
||||
be extracted.
|
||||
If files in the `.git` directory are needed, ensure that `actions/checkout` is being used with
|
||||
`persist-credentials: false`.
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
foo: [a, b, c]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false # Ensure credentials are not saved in `.git/config`
|
||||
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: my-artifact-${{ matrix.foo }}
|
||||
path: .
|
||||
include-git-directory: true
|
||||
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/upload-artifact/merge@v4
|
||||
with:
|
||||
include-git-directory: true
|
||||
```
|
||||
|
||||
### Outputs
|
||||
|
||||
| Name | Description | Example |
|
||||
|
@ -36,6 +36,9 @@ inputs:
|
||||
If true, the artifacts that were merged will be deleted.
|
||||
If false, the artifacts will still exist.
|
||||
default: 'false'
|
||||
include-git-directory:
|
||||
description: 'Include files in the .git directory in the merged artifact.'
|
||||
default: 'false'
|
||||
|
||||
outputs:
|
||||
artifact-id:
|
||||
|
1172
package-lock.json
generated
1172
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "upload-artifact",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"description": "Upload an Actions Artifact in a workflow run",
|
||||
"main": "dist/upload/index.js",
|
||||
"scripts": {
|
||||
@ -29,8 +29,8 @@
|
||||
},
|
||||
"homepage": "https://github.com/actions/upload-artifact#readme",
|
||||
"dependencies": {
|
||||
"@actions/artifact": "^2.1.1",
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/artifact": "2.1.8",
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/github": "^6.0.0",
|
||||
"@actions/glob": "^0.3.0",
|
||||
"@actions/io": "^1.1.2",
|
||||
|
@ -5,5 +5,6 @@ export enum Inputs {
|
||||
SeparateDirectories = 'separate-directories',
|
||||
RetentionDays = 'retention-days',
|
||||
CompressionLevel = 'compression-level',
|
||||
DeleteMerged = 'delete-merged'
|
||||
DeleteMerged = 'delete-merged',
|
||||
IncludeGitDirectory = 'include-git-directory'
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ export function getInputs(): MergeInputs {
|
||||
const pattern = core.getInput(Inputs.Pattern, {required: true})
|
||||
const separateDirectories = core.getBooleanInput(Inputs.SeparateDirectories)
|
||||
const deleteMerged = core.getBooleanInput(Inputs.DeleteMerged)
|
||||
const includeGitDirectory = core.getBooleanInput(Inputs.IncludeGitDirectory)
|
||||
|
||||
const inputs = {
|
||||
name,
|
||||
@ -17,7 +18,8 @@ export function getInputs(): MergeInputs {
|
||||
separateDirectories,
|
||||
deleteMerged,
|
||||
retentionDays: 0,
|
||||
compressionLevel: 6
|
||||
compressionLevel: 6,
|
||||
includeGitDirectory
|
||||
} as MergeInputs
|
||||
|
||||
const retentionDaysStr = core.getInput(Inputs.RetentionDays)
|
||||
|
@ -62,7 +62,9 @@ export async function run(): Promise<void> {
|
||||
options.compressionLevel = inputs.compressionLevel
|
||||
}
|
||||
|
||||
const searchResult = await findFilesToUpload(tmpDir)
|
||||
const searchResult = await findFilesToUpload(tmpDir, {
|
||||
includeGitDirectory: inputs.includeGitDirectory
|
||||
})
|
||||
|
||||
await uploadArtifact(
|
||||
inputs.name,
|
||||
|
@ -30,4 +30,9 @@ export interface MergeInputs {
|
||||
* If false, the artifacts will be merged into the root of the destination.
|
||||
*/
|
||||
separateDirectories: boolean
|
||||
|
||||
/**
|
||||
* Include files in the `.git` directory in the artifact
|
||||
*/
|
||||
includeGitDirectory: boolean
|
||||
}
|
||||
|
@ -78,15 +78,21 @@ function getMultiPathLCA(searchPaths: string[]): string {
|
||||
return path.join(...commonPaths)
|
||||
}
|
||||
|
||||
export interface SearchOptions {
|
||||
/**
|
||||
* Indicates whether files in the .git directory should be included in the artifact
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
includeGitDirectory: boolean
|
||||
}
|
||||
|
||||
export async function findFilesToUpload(
|
||||
searchPath: string,
|
||||
globOptions?: glob.GlobOptions
|
||||
searchOptions?: SearchOptions
|
||||
): Promise<SearchResult> {
|
||||
const searchResults: string[] = []
|
||||
const globber = await glob.create(
|
||||
searchPath,
|
||||
globOptions || getDefaultGlobOptions()
|
||||
)
|
||||
const globber = await glob.create(searchPath, getDefaultGlobOptions())
|
||||
const rawSearchResults: string[] = await globber.glob()
|
||||
|
||||
/*
|
||||
@ -104,6 +110,12 @@ export async function findFilesToUpload(
|
||||
// isDirectory() returns false for symlinks if using fs.lstat(), make sure to use fs.stat() instead
|
||||
if (!fileStats.isDirectory()) {
|
||||
debug(`File:${searchResult} was found using the provided searchPath`)
|
||||
|
||||
if (!searchOptions?.includeGitDirectory && inGitDirectory(searchResult)) {
|
||||
debug(`Ignoring ${searchResult} because it is in the .git directory`)
|
||||
continue
|
||||
}
|
||||
|
||||
searchResults.push(searchResult)
|
||||
|
||||
// detect any files that would be overwritten because of case insensitivity
|
||||
@ -155,3 +167,15 @@ export async function findFilesToUpload(
|
||||
rootDirectory: searchPaths[0]
|
||||
}
|
||||
}
|
||||
|
||||
function inGitDirectory(filePath: string): boolean {
|
||||
// The .git directory is a directory, so we need to check if the file path is a directory
|
||||
// and if it is a .git directory
|
||||
for (const part of filePath.split(path.sep)) {
|
||||
if (part === '.git') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
@ -5,7 +5,8 @@ export enum Inputs {
|
||||
IfNoFilesFound = 'if-no-files-found',
|
||||
RetentionDays = 'retention-days',
|
||||
CompressionLevel = 'compression-level',
|
||||
Overwrite = 'overwrite'
|
||||
Overwrite = 'overwrite',
|
||||
IncludeGitDirectory = 'include-git-directory'
|
||||
}
|
||||
|
||||
export enum NoFileOptions {
|
||||
|
@ -9,6 +9,7 @@ export function getInputs(): UploadInputs {
|
||||
const name = core.getInput(Inputs.Name)
|
||||
const path = core.getInput(Inputs.Path, {required: true})
|
||||
const overwrite = core.getBooleanInput(Inputs.Overwrite)
|
||||
const includeGitDirectory = core.getBooleanInput(Inputs.IncludeGitDirectory)
|
||||
|
||||
const ifNoFilesFound = core.getInput(Inputs.IfNoFilesFound)
|
||||
const noFileBehavior: NoFileOptions = NoFileOptions[ifNoFilesFound]
|
||||
@ -27,7 +28,8 @@ export function getInputs(): UploadInputs {
|
||||
artifactName: name,
|
||||
searchPath: path,
|
||||
ifNoFilesFound: noFileBehavior,
|
||||
overwrite: overwrite
|
||||
overwrite: overwrite,
|
||||
includeGitDirectory: includeGitDirectory
|
||||
} as UploadInputs
|
||||
|
||||
const retentionDaysStr = core.getInput(Inputs.RetentionDays)
|
||||
|
@ -24,7 +24,9 @@ async function deleteArtifactIfExists(artifactName: string): Promise<void> {
|
||||
|
||||
export async function run(): Promise<void> {
|
||||
const inputs = getInputs()
|
||||
const searchResult = await findFilesToUpload(inputs.searchPath)
|
||||
const searchResult = await findFilesToUpload(inputs.searchPath, {
|
||||
includeGitDirectory: inputs.includeGitDirectory
|
||||
})
|
||||
if (searchResult.filesToUpload.length === 0) {
|
||||
// No files were found, different use cases warrant different types of behavior if nothing is found
|
||||
switch (inputs.ifNoFilesFound) {
|
||||
|
@ -30,4 +30,9 @@ export interface UploadInputs {
|
||||
* Whether or not to replace an existing artifact with the same name
|
||||
*/
|
||||
overwrite: boolean
|
||||
|
||||
/**
|
||||
* Include files in the `.git` directory in the artifact
|
||||
*/
|
||||
includeGitDirectory: boolean
|
||||
}
|
||||
|
Reference in New Issue
Block a user