1
0
Fork 0
mirror of https://github.com/actions/toolkit synced 2025-05-09 00:22:56 +00:00

Merge branch 'actions:main' into main

This commit is contained in:
RockySpain 2024-11-10 00:08:04 -08:00 committed by GitHub
commit 664d7c70c9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 1207 additions and 956 deletions

View file

@ -11,7 +11,7 @@ on:
jobs: jobs:
test: test:
runs-on: macos-latest runs-on: macos-latest-large
steps: steps:
- name: setup repo - name: setup repo
@ -48,7 +48,7 @@ jobs:
path: packages/${{ github.event.inputs.package }}/*.tgz path: packages/${{ github.event.inputs.package }}/*.tgz
publish: publish:
runs-on: macos-latest runs-on: macos-latest-large
needs: test needs: test
environment: npm-publish environment: npm-publish
permissions: permissions:

View file

@ -16,7 +16,11 @@ jobs:
strategy: strategy:
matrix: matrix:
runs-on: [ubuntu-latest, macos-latest, windows-latest] runs-on: [ubuntu-latest, macos-latest-large, windows-latest]
# Node 18 is the current default Node version in hosted runners, so users may still use the toolkit with it when running tests (see https://github.com/actions/toolkit/issues/1841)
# Node 20 is the currently support Node version for actions - https://docs.github.com/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#runsusing-for-javascript-actions
node-version: [18.x, 20.x]
fail-fast: false fail-fast: false
runs-on: ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }}
@ -25,10 +29,10 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set Node.js 20.x - name: Set up Node ${{ matrix.node-version }}
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20.x node-version: ${{ matrix.node-version }}
- name: npm install - name: npm install
run: npm install run: npm install

View file

@ -1,5 +1,15 @@
# @actions/artifact Releases # @actions/artifact Releases
### 2.1.11
- Fixed a bug with relative symlinks resolution [#1844](https://github.com/actions/toolkit/pull/1844)
- Use native `crypto` [#1815](https://github.com/actions/toolkit/pull/1815)
### 2.1.10
- Fixed a regression with symlinks not being automatically resolved [#1830](https://github.com/actions/toolkit/pull/1830)
- Fixed a regression with chunk timeout [#1786](https://github.com/actions/toolkit/pull/1786)
### 2.1.9 ### 2.1.9
- Fixed artifact upload chunk timeout logic [#1774](https://github.com/actions/toolkit/pull/1774) - Fixed artifact upload chunk timeout logic [#1774](https://github.com/actions/toolkit/pull/1774)

View file

@ -10,6 +10,7 @@ import {FilesNotFoundError} from '../src/internal/shared/errors'
import {BlockBlobUploadStreamOptions} from '@azure/storage-blob' import {BlockBlobUploadStreamOptions} from '@azure/storage-blob'
import * as fs from 'fs' import * as fs from 'fs'
import * as path from 'path' import * as path from 'path'
import unzip from 'unzip-stream'
const uploadStreamMock = jest.fn() const uploadStreamMock = jest.fn()
const blockBlobClientMock = jest.fn().mockImplementation(() => ({ const blockBlobClientMock = jest.fn().mockImplementation(() => ({
@ -27,9 +28,25 @@ jest.mock('@azure/storage-blob', () => ({
const fixtures = { const fixtures = {
uploadDirectory: path.join(__dirname, '_temp', 'plz-upload'), uploadDirectory: path.join(__dirname, '_temp', 'plz-upload'),
files: [ files: [
['file1.txt', 'test 1 file content'], {name: 'file1.txt', content: 'test 1 file content'},
['file2.txt', 'test 2 file content'], {name: 'file2.txt', content: 'test 2 file content'},
['file3.txt', 'test 3 file content'] {name: 'file3.txt', content: 'test 3 file content'},
{
name: 'real.txt',
content: 'from a symlink'
},
{
name: 'relative.txt',
content: 'from a symlink',
symlink: 'real.txt',
relative: true
},
{
name: 'absolute.txt',
content: 'from a symlink',
symlink: 'real.txt',
relative: false
}
], ],
backendIDs: { backendIDs: {
workflowRunBackendId: '67dbcc20-e851-4452-a7c3-2cc0d2e0ec67', workflowRunBackendId: '67dbcc20-e851-4452-a7c3-2cc0d2e0ec67',
@ -50,12 +67,30 @@ const fixtures = {
describe('upload-artifact', () => { describe('upload-artifact', () => {
beforeAll(() => { beforeAll(() => {
if (!fs.existsSync(fixtures.uploadDirectory)) { fs.mkdirSync(fixtures.uploadDirectory, {
fs.mkdirSync(fixtures.uploadDirectory, {recursive: true}) recursive: true
} })
for (const [file, content] of fixtures.files) { for (const file of fixtures.files) {
fs.writeFileSync(path.join(fixtures.uploadDirectory, file), content) if (file.symlink) {
let symlinkPath = file.symlink
if (!file.relative) {
symlinkPath = path.join(fixtures.uploadDirectory, file.symlink)
}
if (!fs.existsSync(path.join(fixtures.uploadDirectory, file.name))) {
fs.symlinkSync(
symlinkPath,
path.join(fixtures.uploadDirectory, file.name),
'file'
)
}
} else {
fs.writeFileSync(
path.join(fixtures.uploadDirectory, file.name),
file.content
)
}
} }
}) })
@ -71,8 +106,9 @@ describe('upload-artifact', () => {
.spyOn(uploadZipSpecification, 'getUploadZipSpecification') .spyOn(uploadZipSpecification, 'getUploadZipSpecification')
.mockReturnValue( .mockReturnValue(
fixtures.files.map(file => ({ fixtures.files.map(file => ({
sourcePath: path.join(fixtures.uploadDirectory, file[0]), sourcePath: path.join(fixtures.uploadDirectory, file.name),
destinationPath: file[0] destinationPath: file.name,
stats: new fs.Stats()
})) }))
) )
jest.spyOn(config, 'getRuntimeToken').mockReturnValue(fixtures.runtimeToken) jest.spyOn(config, 'getRuntimeToken').mockReturnValue(fixtures.runtimeToken)
@ -185,6 +221,10 @@ describe('upload-artifact', () => {
}) })
it('should successfully upload an artifact', async () => { it('should successfully upload an artifact', async () => {
jest
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
.mockRestore()
jest jest
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact') .spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
.mockReturnValue( .mockReturnValue(
@ -202,6 +242,12 @@ describe('upload-artifact', () => {
}) })
) )
let loadedBytes = 0
const uploadedZip = path.join(
fixtures.uploadDirectory,
'..',
'uploaded.zip'
)
uploadStreamMock.mockImplementation( uploadStreamMock.mockImplementation(
async ( async (
stream: NodeJS.ReadableStream, stream: NodeJS.ReadableStream,
@ -209,31 +255,69 @@ describe('upload-artifact', () => {
maxConcurrency?: number, maxConcurrency?: number,
options?: BlockBlobUploadStreamOptions options?: BlockBlobUploadStreamOptions
) => { ) => {
const {onProgress, abortSignal} = options || {} const {onProgress} = options || {}
if (fs.existsSync(uploadedZip)) {
fs.unlinkSync(uploadedZip)
}
const uploadedZipStream = fs.createWriteStream(uploadedZip)
onProgress?.({loadedBytes: 0}) onProgress?.({loadedBytes: 0})
return new Promise((resolve, reject) => {
return new Promise(resolve => { stream.on('data', chunk => {
const timerId = setTimeout(() => { loadedBytes += chunk.length
onProgress?.({loadedBytes: 256}) uploadedZipStream.write(chunk)
resolve({}) onProgress?.({loadedBytes})
}, 1_000) })
abortSignal?.addEventListener('abort', () => { stream.on('end', () => {
clearTimeout(timerId) onProgress?.({loadedBytes})
uploadedZipStream.end()
resolve({}) resolve({})
}) })
stream.on('error', err => {
reject(err)
})
}) })
} }
) )
const {id, size} = await uploadArtifact( const {id, size} = await uploadArtifact(
fixtures.inputs.artifactName, fixtures.inputs.artifactName,
fixtures.inputs.files, fixtures.files.map(file =>
fixtures.inputs.rootDirectory path.join(fixtures.uploadDirectory, file.name)
),
fixtures.uploadDirectory
) )
expect(id).toBe(1) expect(id).toBe(1)
expect(size).toBe(256) expect(size).toBe(loadedBytes)
const extractedDirectory = path.join(
fixtures.uploadDirectory,
'..',
'extracted'
)
if (fs.existsSync(extractedDirectory)) {
fs.rmdirSync(extractedDirectory, {recursive: true})
}
const extract = new Promise((resolve, reject) => {
fs.createReadStream(uploadedZip)
.pipe(unzip.Extract({path: extractedDirectory}))
.on('close', () => {
resolve(true)
})
.on('error', err => {
reject(err)
})
})
await expect(extract).resolves.toBe(true)
for (const file of fixtures.files) {
const filePath = path.join(extractedDirectory, file.name)
expect(fs.existsSync(filePath)).toBe(true)
expect(fs.readFileSync(filePath, 'utf8')).toBe(file.content)
}
}) })
it('should throw an error uploading blob chunks get delayed', async () => { it('should throw an error uploading blob chunks get delayed', async () => {

View file

@ -305,4 +305,22 @@ describe('Search', () => {
} }
} }
}) })
it('Upload Specification - Includes symlinks', async () => {
const targetPath = path.join(root, 'link-dir', 'symlink-me.txt')
await fs.mkdir(path.dirname(targetPath), {recursive: true})
await fs.writeFile(targetPath, 'symlink file content')
const uploadPath = path.join(root, 'upload-dir', 'symlink.txt')
await fs.mkdir(path.dirname(uploadPath), {recursive: true})
await fs.symlink(targetPath, uploadPath, 'file')
const specifications = getUploadZipSpecification([uploadPath], root)
expect(specifications.length).toEqual(1)
expect(specifications[0].sourcePath).toEqual(uploadPath)
expect(specifications[0].destinationPath).toEqual(
path.join('/upload-dir', 'symlink.txt')
)
expect(specifications[0].stats.isSymbolicLink()).toBe(true)
})
}) })

View file

@ -1,12 +1,12 @@
{ {
"name": "@actions/artifact", "name": "@actions/artifact",
"version": "2.1.9", "version": "2.1.11",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@actions/artifact", "name": "@actions/artifact",
"version": "2.1.9", "version": "2.1.11",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.10.0", "@actions/core": "^1.10.0",
@ -19,7 +19,6 @@
"@octokit/request-error": "^5.0.0", "@octokit/request-error": "^5.0.0",
"@protobuf-ts/plugin": "^2.2.3-alpha.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1",
"archiver": "^7.0.1", "archiver": "^7.0.1",
"crypto": "^1.0.1",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"twirp-ts": "^2.5.0", "twirp-ts": "^2.5.0",
"unzip-stream": "^0.3.1" "unzip-stream": "^0.3.1"
@ -852,12 +851,6 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/crypto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
"integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==",
"deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in."
},
"node_modules/delayed-stream": { "node_modules/delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@ -1315,9 +1308,9 @@
} }
}, },
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "6.2.1", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
"integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "2.8.8", "version": "2.8.8",

View file

@ -1,6 +1,6 @@
{ {
"name": "@actions/artifact", "name": "@actions/artifact",
"version": "2.1.9", "version": "2.1.11",
"preview": true, "preview": true,
"description": "Actions artifact lib", "description": "Actions artifact lib",
"keywords": [ "keywords": [
@ -50,7 +50,6 @@
"@octokit/request-error": "^5.0.0", "@octokit/request-error": "^5.0.0",
"@protobuf-ts/plugin": "^2.2.3-alpha.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1",
"archiver": "^7.0.1", "archiver": "^7.0.1",
"crypto": "^1.0.1",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"twirp-ts": "^2.5.0", "twirp-ts": "^2.5.0",
"unzip-stream": "^0.3.1" "unzip-stream": "^0.3.1"

View file

@ -13,6 +13,12 @@ export interface UploadZipSpecification {
* The destination path in a zip for a file * The destination path in a zip for a file
*/ */
destinationPath: string destinationPath: string
/**
* Information about the file
* https://nodejs.org/api/fs.html#class-fsstats
*/
stats: fs.Stats
} }
/** /**
@ -75,10 +81,11 @@ export function getUploadZipSpecification(
- file3.txt - file3.txt
*/ */
for (let file of filesToZip) { for (let file of filesToZip) {
if (!fs.existsSync(file)) { const stats = fs.lstatSync(file, {throwIfNoEntry: false})
if (!stats) {
throw new Error(`File ${file} does not exist`) throw new Error(`File ${file} does not exist`)
} }
if (!fs.statSync(file).isDirectory()) { if (!stats.isDirectory()) {
// Normalize and resolve, this allows for either absolute or relative paths to be used // Normalize and resolve, this allows for either absolute or relative paths to be used
file = normalize(file) file = normalize(file)
file = resolve(file) file = resolve(file)
@ -94,7 +101,8 @@ export function getUploadZipSpecification(
specification.push({ specification.push({
sourcePath: file, sourcePath: file,
destinationPath: uploadPath destinationPath: uploadPath,
stats
}) })
} else { } else {
// Empty directory // Empty directory
@ -103,7 +111,8 @@ export function getUploadZipSpecification(
specification.push({ specification.push({
sourcePath: null, sourcePath: null,
destinationPath: directoryPath destinationPath: directoryPath,
stats
}) })
} }
} }

View file

@ -1,4 +1,5 @@
import * as stream from 'stream' import * as stream from 'stream'
import {realpath} from 'fs/promises'
import * as archiver from 'archiver' import * as archiver from 'archiver'
import * as core from '@actions/core' import * as core from '@actions/core'
import {UploadZipSpecification} from './upload-zip-specification' import {UploadZipSpecification} from './upload-zip-specification'
@ -42,8 +43,14 @@ export async function createZipUploadStream(
for (const file of uploadSpecification) { for (const file of uploadSpecification) {
if (file.sourcePath !== null) { if (file.sourcePath !== null) {
// Add a normal file to the zip // Check if symlink and resolve the source path
zip.file(file.sourcePath, { let sourcePath = file.sourcePath
if (file.stats.isSymbolicLink()) {
sourcePath = await realpath(file.sourcePath)
}
// Add the file to the zip
zip.file(sourcePath, {
name: file.destinationPath name: file.destinationPath
}) })
} else { } else {

View file

@ -32,8 +32,7 @@ async function run() {
const ghToken = core.getInput('gh-token'); const ghToken = core.getInput('gh-token');
const attestation = await attest({ const attestation = await attest({
subjectName: 'my-artifact-name', subjects: [{name: 'my-artifact-name', digest: { 'sha256': '36ab4667...'}}],
subjectDigest: { 'sha256': '36ab4667...'},
predicateType: 'https://in-toto.io/attestation/release', predicateType: 'https://in-toto.io/attestation/release',
predicate: { . . . }, predicate: { . . . },
token: ghToken token: ghToken
@ -49,11 +48,12 @@ The `attest` function supports the following options:
```typescript ```typescript
export type AttestOptions = { export type AttestOptions = {
// The name of the subject to be attested. // Deprecated. Use 'subjects' instead.
subjectName: string subjectName?: string
// The digest of the subject to be attested. Should be a map of digest // Deprecated. Use 'subjects' instead.
// algorithms to their hex-encoded values. subjectDigest?: Record<string, string>
subjectDigest: Record<string, string> // Collection of subjects to be attested
subjects?: Subject[]
// URI identifying the content type of the predicate being attested. // URI identifying the content type of the predicate being attested.
predicateType: string predicateType: string
// Predicate to be attested. // Predicate to be attested.
@ -68,6 +68,13 @@ export type AttestOptions = {
// Whether to skip writing the attestation to the GH attestations API. // Whether to skip writing the attestation to the GH attestations API.
skipWrite?: boolean skipWrite?: boolean
} }
export type Subject = {
// Name of the subject.
name: string
// Digests of the subject. Should be a map of digest algorithms to their hex-encoded values.
digest: Record<string, string>
}
``` ```
### `attestProvenance` ### `attestProvenance`
@ -105,12 +112,13 @@ The `attestProvenance` function supports the following options:
```typescript ```typescript
export type AttestProvenanceOptions = { export type AttestProvenanceOptions = {
// The name of the subject to be attested. // Deprecated. Use 'subjects' instead.
subjectName: string subjectName?: string
// The digest of the subject to be attested. Should be a map of digest // Deprecated. Use 'subjects' instead.
// algorithms to their hex-encoded values. subjectDigest?: Record<string, string>
subjectDigest: Record<string, string> // Collection of subjects to be attested
// GitHub token for writing attestations. subjects?: Subject[]
// URI identifying the content type of the predicate being attested.
token: string token: string
// Sigstore instance to use for signing. Must be one of "public-good" or // Sigstore instance to use for signing. Must be one of "public-good" or
// "github". // "github".

View file

@ -1,5 +1,17 @@
# @actions/attest Releases # @actions/attest Releases
### 1.5.0
- Bump @actions/core from 1.10.1 to 1.11.1 [#1847](https://github.com/actions/toolkit/pull/1847)
- Bump @sigstore/bundle from 2.3.2 to 3.0.0 [#1846](https://github.com/actions/toolkit/pull/1846)
- Bump @sigstore/sign from 2.3.2 to 3.0.0 [#1846](https://github.com/actions/toolkit/pull/1846)
- Support for generating multi-subject attestations [#1864](https://github.com/actions/toolkit/pull/1865)
- Fix bug in `buildSLSAProvenancePredicate` related to `workflow_ref` OIDC token claims containing the "@" symbol in the tag name [#1863](https://github.com/actions/toolkit/pull/1863)
### 1.4.2
- Fix bug in `buildSLSAProvenancePredicate`/`attestProvenance` when generating provenance statement for enterprise account using customized OIDC issuer value [#1823](https://github.com/actions/toolkit/pull/1823)
### 1.4.1 ### 1.4.1
- Bump @actions/http-client from 2.2.1 to 2.2.3 [#1805](https://github.com/actions/toolkit/pull/1805) - Bump @actions/http-client from 2.2.1 to 2.2.3 [#1805](https://github.com/actions/toolkit/pull/1805)
@ -8,7 +20,6 @@
- Add new `headers` parameter to the `attest` and `attestProvenance` functions [#1790](https://github.com/actions/toolkit/pull/1790) - Add new `headers` parameter to the `attest` and `attestProvenance` functions [#1790](https://github.com/actions/toolkit/pull/1790)
- Update `buildSLSAProvenancePredicate`/`attestProvenance` to automatically derive default OIDC issuer URL from current execution context [#1796](https://github.com/actions/toolkit/pull/1796) - Update `buildSLSAProvenancePredicate`/`attestProvenance` to automatically derive default OIDC issuer URL from current execution context [#1796](https://github.com/actions/toolkit/pull/1796)
### 1.3.1 ### 1.3.1
- Fix bug with proxy support when retrieving JWKS for OIDC issuer [#1776](https://github.com/actions/toolkit/pull/1776) - Fix bug with proxy support when retrieving JWKS for OIDC issuer [#1776](https://github.com/actions/toolkit/pull/1776)

View file

@ -1,5 +1,47 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`provenance functions buildSLSAProvenancePredicate handle tags including "@" character 1`] = `
{
"params": {
"buildDefinition": {
"buildType": "https://actions.github.io/buildtypes/workflow/v1",
"externalParameters": {
"workflow": {
"path": ".github/workflows/main.yml",
"ref": "foo@1.0.0",
"repository": "https://foo.ghe.com/owner/repo",
},
},
"internalParameters": {
"github": {
"event_name": "push",
"repository_id": "repo-id",
"repository_owner_id": "owner-id",
"runner_environment": "github-hosted",
},
},
"resolvedDependencies": [
{
"digest": {
"gitCommit": "babca52ab0c93ae16539e5923cb0d7403b9a093b",
},
"uri": "git+https://foo.ghe.com/owner/repo@refs/heads/main",
},
],
},
"runDetails": {
"builder": {
"id": "https://foo.ghe.com/owner/workflows/.github/workflows/publish.yml@main",
},
"metadata": {
"invocationId": "https://foo.ghe.com/owner/repo/actions/runs/run-id/attempts/run-attempt",
},
},
},
"type": "https://slsa.dev/provenance/v1",
}
`;
exports[`provenance functions buildSLSAProvenancePredicate returns a provenance hydrated from an OIDC token 1`] = ` exports[`provenance functions buildSLSAProvenancePredicate returns a provenance hydrated from an OIDC token 1`] = `
{ {
"params": { "params": {

View file

@ -0,0 +1,16 @@
import {attest} from '../src/attest'
describe('attest', () => {
describe('when no subject information is provided', () => {
it('throws an error', async () => {
const options = {
predicateType: 'foo',
predicate: {bar: 'baz'},
token: 'token'
}
expect(attest(options)).rejects.toThrowError(
'Must provide either subjectName and subjectDigest or subjects'
)
})
})
})

View file

@ -17,7 +17,7 @@ describe('buildIntotoStatement', () => {
} }
it('returns an intoto statement', () => { it('returns an intoto statement', () => {
const statement = buildIntotoStatement(subject, predicate) const statement = buildIntotoStatement([subject], predicate)
expect(statement).toMatchSnapshot() expect(statement).toMatchSnapshot()
}) })
}) })

View file

@ -68,6 +68,55 @@ describe('getIDTokenClaims', () => {
}) })
}) })
describe('when ID token is valid (w/ enterprise slug)', () => {
const claims = {
iss: `${issuer}/foo-bar`,
aud: audience,
ref: 'ref',
sha: 'sha',
repository: 'repo',
event_name: 'push',
job_workflow_ref: 'job_workflow_ref',
workflow_ref: 'workflow',
repository_id: '1',
repository_owner_id: '1',
runner_environment: 'github-hosted',
run_id: '1',
run_attempt: '1'
}
beforeEach(async () => {
const jwt = await new jose.SignJWT(claims)
.setProtectedHeader({alg: 'PS256'})
.sign(key.privateKey)
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
})
it('returns the ID token claims', async () => {
const result = await getIDTokenClaims(issuer)
expect(result).toEqual(claims)
})
})
describe('when ID token is missing the "iss" claim', () => {
const claims = {
aud: audience
}
beforeEach(async () => {
const jwt = await new jose.SignJWT(claims)
.setProtectedHeader({alg: 'PS256'})
.sign(key.privateKey)
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
})
it('throws an error', async () => {
await expect(getIDTokenClaims(issuer)).rejects.toThrow(/missing "iss"/i)
})
})
describe('when ID token is missing required claims', () => { describe('when ID token is missing required claims', () => {
const claims = { const claims = {
iss: issuer, iss: issuer,
@ -99,7 +148,9 @@ describe('getIDTokenClaims', () => {
}) })
it('throws an error', async () => { it('throws an error', async () => {
await expect(getIDTokenClaims(issuer)).rejects.toThrow(/unexpected "iss"/) await expect(getIDTokenClaims(issuer)).rejects.toThrow(
/unexpected "iss"/i
)
}) })
}) })

View file

@ -33,15 +33,7 @@ describe('provenance functions', () => {
runner_environment: 'github-hosted' runner_environment: 'github-hosted'
} }
beforeEach(async () => { const mockIssuer = async (claims: jose.JWTPayload): Promise<void> => {
process.env = {
...originalEnv,
ACTIONS_ID_TOKEN_REQUEST_URL: `${issuer}${tokenPath}?`,
ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token',
GITHUB_SERVER_URL: 'https://foo.ghe.com',
GITHUB_REPOSITORY: claims.repository
}
// Generate JWT signing key // Generate JWT signing key
const key = await jose.generateKeyPair('PS256') const key = await jose.generateKeyPair('PS256')
@ -60,6 +52,18 @@ describe('provenance functions', () => {
// Mock OIDC token endpoint for populating the provenance // Mock OIDC token endpoint for populating the provenance
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt}) nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
}
beforeEach(async () => {
process.env = {
...originalEnv,
ACTIONS_ID_TOKEN_REQUEST_URL: `${issuer}${tokenPath}?`,
ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token',
GITHUB_SERVER_URL: 'https://foo.ghe.com',
GITHUB_REPOSITORY: claims.repository
}
await mockIssuer(claims)
}) })
afterEach(() => { afterEach(() => {
@ -71,6 +75,16 @@ describe('provenance functions', () => {
const predicate = await buildSLSAProvenancePredicate() const predicate = await buildSLSAProvenancePredicate()
expect(predicate).toMatchSnapshot() expect(predicate).toMatchSnapshot()
}) })
it('handle tags including "@" character', async () => {
nock.cleanAll()
await mockIssuer({
...claims,
workflow_ref: 'owner/repo/.github/workflows/main.yml@foo@1.0.0'
})
const predicate = await buildSLSAProvenancePredicate()
expect(predicate).toMatchSnapshot()
})
}) })
describe('attestProvenance', () => { describe('attestProvenance', () => {
@ -115,8 +129,7 @@ describe('provenance functions', () => {
describe('when the sigstore instance is explicitly set', () => { describe('when the sigstore instance is explicitly set', () => {
it('attests provenance', async () => { it('attests provenance', async () => {
const attestation = await attestProvenance({ const attestation = await attestProvenance({
subjectName, subjects: [{name: subjectName, digest: subjectDigest}],
subjectDigest,
token: 'token', token: 'token',
sigstore: 'github' sigstore: 'github'
}) })
@ -143,8 +156,7 @@ describe('provenance functions', () => {
it('attests provenance', async () => { it('attests provenance', async () => {
const attestation = await attestProvenance({ const attestation = await attestProvenance({
subjectName, subjects: [{name: subjectName, digest: subjectDigest}],
subjectDigest,
token: 'token' token: 'token'
}) })
@ -178,8 +190,7 @@ describe('provenance functions', () => {
describe('when the sigstore instance is explicitly set', () => { describe('when the sigstore instance is explicitly set', () => {
it('attests provenance', async () => { it('attests provenance', async () => {
const attestation = await attestProvenance({ const attestation = await attestProvenance({
subjectName, subjects: [{name: subjectName, digest: subjectDigest}],
subjectDigest,
token: 'token', token: 'token',
sigstore: 'public-good' sigstore: 'public-good'
}) })
@ -206,8 +217,7 @@ describe('provenance functions', () => {
it('attests provenance', async () => { it('attests provenance', async () => {
const attestation = await attestProvenance({ const attestation = await attestProvenance({
subjectName, subjects: [{name: subjectName, digest: subjectDigest}],
subjectDigest,
token: 'token' token: 'token'
}) })

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "@actions/attest", "name": "@actions/attest",
"version": "1.4.1", "version": "1.5.0",
"description": "Actions attestation lib", "description": "Actions attestation lib",
"keywords": [ "keywords": [
"github", "github",
@ -35,19 +35,19 @@
"url": "https://github.com/actions/toolkit/issues" "url": "https://github.com/actions/toolkit/issues"
}, },
"devDependencies": { "devDependencies": {
"@sigstore/mock": "^0.7.4", "@sigstore/mock": "^0.8.0",
"@sigstore/rekor-types": "^2.0.0", "@sigstore/rekor-types": "^3.0.0",
"@types/jsonwebtoken": "^9.0.6", "@types/jsonwebtoken": "^9.0.6",
"nock": "^13.5.1", "nock": "^13.5.1",
"undici": "^5.28.4" "undici": "^5.28.4"
}, },
"dependencies": { "dependencies": {
"@actions/core": "^1.10.1", "@actions/core": "^1.11.1",
"@actions/github": "^6.0.0", "@actions/github": "^6.0.0",
"@actions/http-client": "^2.2.3", "@actions/http-client": "^2.2.3",
"@octokit/plugin-retry": "^6.0.1", "@octokit/plugin-retry": "^6.0.1",
"@sigstore/bundle": "^2.3.2", "@sigstore/bundle": "^3.0.0",
"@sigstore/sign": "^2.3.2", "@sigstore/sign": "^3.0.0",
"jose": "^5.2.3" "jose": "^5.2.3"
}, },
"overrides": { "overrides": {

View file

@ -14,11 +14,16 @@ const INTOTO_PAYLOAD_TYPE = 'application/vnd.in-toto+json'
* Options for attesting a subject / predicate. * Options for attesting a subject / predicate.
*/ */
export type AttestOptions = { export type AttestOptions = {
// The name of the subject to be attested. /**
subjectName: string * @deprecated Use `subjects` instead.
// The digest of the subject to be attested. Should be a map of digest **/
// algorithms to their hex-encoded values. subjectName?: string
subjectDigest: Record<string, string> /**
* @deprecated Use `subjects` instead.
**/
subjectDigest?: Record<string, string>
// Subjects to be attested.
subjects?: Subject[]
// Content type of the predicate being attested. // Content type of the predicate being attested.
predicateType: string predicateType: string
// Predicate to be attested. // Predicate to be attested.
@ -42,15 +47,24 @@ export type AttestOptions = {
* @returns A promise that resolves to the attestation. * @returns A promise that resolves to the attestation.
*/ */
export async function attest(options: AttestOptions): Promise<Attestation> { export async function attest(options: AttestOptions): Promise<Attestation> {
const subject: Subject = { let subjects: Subject[]
name: options.subjectName,
digest: options.subjectDigest if (options.subjects) {
subjects = options.subjects
} else if (options.subjectName && options.subjectDigest) {
subjects = [{name: options.subjectName, digest: options.subjectDigest}]
} else {
throw new Error(
'Must provide either subjectName and subjectDigest or subjects'
)
} }
const predicate: Predicate = { const predicate: Predicate = {
type: options.predicateType, type: options.predicateType,
params: options.predicate params: options.predicate
} }
const statement = buildIntotoStatement(subject, predicate)
const statement = buildIntotoStatement(subjects, predicate)
// Sign the provenance statement // Sign the provenance statement
const payload: Payload = { const payload: Payload = {

View file

@ -20,12 +20,12 @@ export type InTotoStatement = {
* @returns The constructed in-toto statement. * @returns The constructed in-toto statement.
*/ */
export const buildIntotoStatement = ( export const buildIntotoStatement = (
subject: Subject, subjects: Subject[],
predicate: Predicate predicate: Predicate
): InTotoStatement => { ): InTotoStatement => {
return { return {
_type: INTOTO_STATEMENT_V1_TYPE, _type: INTOTO_STATEMENT_V1_TYPE,
subject: [subject], subject: subjects,
predicateType: predicate.type, predicateType: predicate.type,
predicate: predicate.params predicate: predicate.params
} }

View file

@ -49,10 +49,19 @@ const decodeOIDCToken = async (
// Verify and decode token // Verify and decode token
const jwks = jose.createLocalJWKSet(await getJWKS(issuer)) const jwks = jose.createLocalJWKSet(await getJWKS(issuer))
const {payload} = await jose.jwtVerify(token, jwks, { const {payload} = await jose.jwtVerify(token, jwks, {
audience: OIDC_AUDIENCE, audience: OIDC_AUDIENCE
issuer
}) })
if (!payload.iss) {
throw new Error('Missing "iss" claim')
}
// Check that the issuer STARTS WITH the expected issuer URL to account for
// the fact that the value may include an enterprise-specific slug
if (!payload.iss.startsWith(issuer)) {
throw new Error(`Unexpected "iss" claim: ${payload.iss}`)
}
return payload return payload
} }

View file

@ -30,9 +30,11 @@ export const buildSLSAProvenancePredicate = async (
// Split just the path and ref from the workflow string. // Split just the path and ref from the workflow string.
// owner/repo/.github/workflows/main.yml@main => // owner/repo/.github/workflows/main.yml@main =>
// .github/workflows/main.yml, main // .github/workflows/main.yml, main
const [workflowPath, workflowRef] = claims.workflow_ref const [workflowPath, ...workflowRefChunks] = claims.workflow_ref
.replace(`${claims.repository}/`, '') .replace(`${claims.repository}/`, '')
.split('@') .split('@')
// Handle case where tag contains `@` (e.g: when using changesets in a monorepo context),
const workflowRef = workflowRefChunks.join('@')
return { return {
type: SLSA_PREDICATE_V1_TYPE, type: SLSA_PREDICATE_V1_TYPE,

View file

@ -86,7 +86,6 @@ const initBundleBuilder = (opts: SignOptions): BundleBuilder => {
witnesses.push( witnesses.push(
new RekorWitness({ new RekorWitness({
rekorBaseURL: opts.rekorURL, rekorBaseURL: opts.rekorURL,
entryType: 'dsse',
fetchOnConflict: true, fetchOnConflict: true,
timeout, timeout,
retry retry
@ -106,5 +105,5 @@ const initBundleBuilder = (opts: SignOptions): BundleBuilder => {
// Build the bundle with the singleCertificate option which will // Build the bundle with the singleCertificate option which will
// trigger the creation of v0.3 DSSE bundles // trigger the creation of v0.3 DSSE bundles
return new DSSEBundleBuilder({signer, witnesses, singleCertificate: true}) return new DSSEBundleBuilder({signer, witnesses})
} }

View file

@ -1,5 +1,9 @@
# @actions/cache Releases # @actions/cache Releases
### 3.3.0
- Update `@actions/core` to `1.11.1`
- Remove dependency on `uuid` package [#1824](https://github.com/actions/toolkit/pull/1824), [#1842](https://github.com/actions/toolkit/pull/1842)
### 3.2.4 ### 3.2.4
- Updated `isGhes` check to include `.ghe.com` and `.ghe.localhost` as accepted hosts - Updated `isGhes` check to include `.ghe.com` and `.ghe.localhost` as accepted hosts

71
packages/cache/package-lock.json generated vendored
View file

@ -1,15 +1,15 @@
{ {
"name": "@actions/cache", "name": "@actions/cache",
"version": "3.2.4", "version": "3.3.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@actions/cache", "name": "@actions/cache",
"version": "3.2.4", "version": "3.3.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.10.0", "@actions/core": "^1.11.1",
"@actions/exec": "^1.0.1", "@actions/exec": "^1.0.1",
"@actions/glob": "^0.1.0", "@actions/glob": "^0.1.0",
"@actions/http-client": "^2.1.1", "@actions/http-client": "^2.1.1",
@ -17,30 +17,20 @@
"@azure/abort-controller": "^1.1.0", "@azure/abort-controller": "^1.1.0",
"@azure/ms-rest-js": "^2.6.0", "@azure/ms-rest-js": "^2.6.0",
"@azure/storage-blob": "^12.13.0", "@azure/storage-blob": "^12.13.0",
"semver": "^6.3.1", "semver": "^6.3.1"
"uuid": "^3.3.3"
}, },
"devDependencies": { "devDependencies": {
"@types/semver": "^6.0.0", "@types/semver": "^6.0.0",
"@types/uuid": "^3.4.5",
"typescript": "^5.2.2" "typescript": "^5.2.2"
} }
}, },
"node_modules/@actions/core": { "node_modules/@actions/core": {
"version": "1.10.0", "version": "1.11.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz", "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==", "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
"dependencies": { "dependencies": {
"@actions/http-client": "^2.0.1", "@actions/exec": "^1.1.1",
"uuid": "^8.3.2" "@actions/http-client": "^2.0.1"
}
},
"node_modules/@actions/core/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
} }
}, },
"node_modules/@actions/exec": { "node_modules/@actions/exec": {
@ -296,12 +286,6 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/uuid": {
"version": "3.4.10",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.10.tgz",
"integrity": "sha512-BgeaZuElf7DEYZhWYDTc/XcLZXdVgFkVSTa13BqKvbnmUrxr3TJFKofUxCtDO9UQOdhnV+HPOESdHiHKZOJV1A==",
"dev": true
},
"node_modules/abort-controller": { "node_modules/abort-controller": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@ -486,15 +470,6 @@
"node": ">=14.17" "node": ">=14.17"
} }
}, },
"node_modules/uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
"bin": {
"uuid": "bin/uuid"
}
},
"node_modules/webidl-conversions": { "node_modules/webidl-conversions": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@ -532,19 +507,12 @@
}, },
"dependencies": { "dependencies": {
"@actions/core": { "@actions/core": {
"version": "1.10.0", "version": "1.11.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz", "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==", "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
"requires": { "requires": {
"@actions/http-client": "^2.0.1", "@actions/exec": "^1.1.1",
"uuid": "^8.3.2" "@actions/http-client": "^2.0.1"
},
"dependencies": {
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
}
} }
}, },
"@actions/exec": { "@actions/exec": {
@ -764,12 +732,6 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/uuid": {
"version": "3.4.10",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.10.tgz",
"integrity": "sha512-BgeaZuElf7DEYZhWYDTc/XcLZXdVgFkVSTa13BqKvbnmUrxr3TJFKofUxCtDO9UQOdhnV+HPOESdHiHKZOJV1A==",
"dev": true
},
"abort-controller": { "abort-controller": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@ -900,11 +862,6 @@
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"dev": true "dev": true
}, },
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
},
"webidl-conversions": { "webidl-conversions": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",

View file

@ -1,6 +1,6 @@
{ {
"name": "@actions/cache", "name": "@actions/cache",
"version": "3.2.4", "version": "3.3.0",
"preview": true, "preview": true,
"description": "Actions cache lib", "description": "Actions cache lib",
"keywords": [ "keywords": [
@ -37,7 +37,7 @@
"url": "https://github.com/actions/toolkit/issues" "url": "https://github.com/actions/toolkit/issues"
}, },
"dependencies": { "dependencies": {
"@actions/core": "^1.10.0", "@actions/core": "^1.11.1",
"@actions/exec": "^1.0.1", "@actions/exec": "^1.0.1",
"@actions/glob": "^0.1.0", "@actions/glob": "^0.1.0",
"@actions/http-client": "^2.1.1", "@actions/http-client": "^2.1.1",
@ -45,12 +45,10 @@
"@azure/abort-controller": "^1.1.0", "@azure/abort-controller": "^1.1.0",
"@azure/ms-rest-js": "^2.6.0", "@azure/ms-rest-js": "^2.6.0",
"@azure/storage-blob": "^12.13.0", "@azure/storage-blob": "^12.13.0",
"semver": "^6.3.1", "semver": "^6.3.1"
"uuid": "^3.3.3"
}, },
"devDependencies": { "devDependencies": {
"@types/semver": "^6.0.0", "@types/semver": "^6.0.0",
"@types/uuid": "^3.4.5",
"typescript": "^5.2.2" "typescript": "^5.2.2"
} }
} }

View file

@ -2,11 +2,11 @@ import * as core from '@actions/core'
import * as exec from '@actions/exec' import * as exec from '@actions/exec'
import * as glob from '@actions/glob' import * as glob from '@actions/glob'
import * as io from '@actions/io' import * as io from '@actions/io'
import * as crypto from 'crypto'
import * as fs from 'fs' import * as fs from 'fs'
import * as path from 'path' import * as path from 'path'
import * as semver from 'semver' import * as semver from 'semver'
import * as util from 'util' import * as util from 'util'
import {v4 as uuidV4} from 'uuid'
import { import {
CacheFilename, CacheFilename,
CompressionMethod, CompressionMethod,
@ -34,7 +34,7 @@ export async function createTempDirectory(): Promise<string> {
tempDirectory = path.join(baseLocation, 'actions', 'temp') tempDirectory = path.join(baseLocation, 'actions', 'temp')
} }
const dest = path.join(tempDirectory, uuidV4()) const dest = path.join(tempDirectory, crypto.randomUUID())
await io.mkdirP(dest) await io.mkdirP(dest)
return dest return dest
} }

View file

@ -1,5 +1,12 @@
# @actions/core Releases # @actions/core Releases
### 1.11.1
- Fix uses of `crypto.randomUUID` on Node 18 and earlier [#1842](https://github.com/actions/toolkit/pull/1842)
### 1.11.0
- Add platform info utilities [#1551](https://github.com/actions/toolkit/pull/1551)
- Remove dependency on `uuid` package [#1824](https://github.com/actions/toolkit/pull/1824)
### 1.10.1 ### 1.10.1
- Fix error message reference in oidc utils [#1511](https://github.com/actions/toolkit/pull/1511) - Fix error message reference in oidc utils [#1511](https://github.com/actions/toolkit/pull/1511)

View file

@ -4,9 +4,6 @@ import * as path from 'path'
import * as core from '../src/core' import * as core from '../src/core'
import {HttpClient} from '@actions/http-client' import {HttpClient} from '@actions/http-client'
import {toCommandProperties} from '../src/utils' import {toCommandProperties} from '../src/utils'
import * as uuid from 'uuid'
jest.mock('uuid')
/* eslint-disable @typescript-eslint/unbound-method */ /* eslint-disable @typescript-eslint/unbound-method */
@ -49,11 +46,23 @@ const testEnvVars = {
const UUID = '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d' const UUID = '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'
const DELIMITER = `ghadelimiter_${UUID}` const DELIMITER = `ghadelimiter_${UUID}`
jest.mock('crypto', () => ({
...jest.requireActual('crypto'),
randomUUID: jest.fn(() => UUID)
}))
const TEMP_DIR = path.join(__dirname, '_temp')
describe('@actions/core', () => { describe('@actions/core', () => {
beforeAll(() => { beforeAll(() => {
const filePath = path.join(__dirname, `test`) const filePath = TEMP_DIR
if (!fs.existsSync(filePath)) { if (!fs.existsSync(filePath)) {
fs.mkdirSync(filePath) fs.mkdirSync(filePath)
} else {
// Clear out the temp directory
for (const file of fs.readdirSync(filePath)) {
fs.unlinkSync(path.join(filePath, file))
}
} }
}) })
@ -62,10 +71,6 @@ describe('@actions/core', () => {
process.env[key] = testEnvVars[key as keyof typeof testEnvVars] process.env[key] = testEnvVars[key as keyof typeof testEnvVars]
} }
process.stdout.write = jest.fn() process.stdout.write = jest.fn()
jest.spyOn(uuid, 'v4').mockImplementation(() => {
return UUID
})
}) })
afterEach(() => { afterEach(() => {
@ -141,7 +146,7 @@ describe('@actions/core', () => {
`Unexpected input: value should not contain the delimiter "${DELIMITER}"` `Unexpected input: value should not contain the delimiter "${DELIMITER}"`
) )
const filePath = path.join(__dirname, `test/${command}`) const filePath = path.join(TEMP_DIR, command)
fs.unlinkSync(filePath) fs.unlinkSync(filePath)
}) })
@ -155,7 +160,7 @@ describe('@actions/core', () => {
`Unexpected input: name should not contain the delimiter "${DELIMITER}"` `Unexpected input: name should not contain the delimiter "${DELIMITER}"`
) )
const filePath = path.join(__dirname, `test/${command}`) const filePath = path.join(TEMP_DIR, command)
fs.unlinkSync(filePath) fs.unlinkSync(filePath)
}) })
@ -347,7 +352,7 @@ describe('@actions/core', () => {
`Unexpected input: value should not contain the delimiter "${DELIMITER}"` `Unexpected input: value should not contain the delimiter "${DELIMITER}"`
) )
const filePath = path.join(__dirname, `test/${command}`) const filePath = path.join(TEMP_DIR, command)
fs.unlinkSync(filePath) fs.unlinkSync(filePath)
}) })
@ -361,7 +366,7 @@ describe('@actions/core', () => {
`Unexpected input: name should not contain the delimiter "${DELIMITER}"` `Unexpected input: name should not contain the delimiter "${DELIMITER}"`
) )
const filePath = path.join(__dirname, `test/${command}`) const filePath = path.join(TEMP_DIR, command)
fs.unlinkSync(filePath) fs.unlinkSync(filePath)
}) })
@ -585,7 +590,7 @@ describe('@actions/core', () => {
`Unexpected input: value should not contain the delimiter "${DELIMITER}"` `Unexpected input: value should not contain the delimiter "${DELIMITER}"`
) )
const filePath = path.join(__dirname, `test/${command}`) const filePath = path.join(TEMP_DIR, command)
fs.unlinkSync(filePath) fs.unlinkSync(filePath)
}) })
@ -599,7 +604,7 @@ describe('@actions/core', () => {
`Unexpected input: name should not contain the delimiter "${DELIMITER}"` `Unexpected input: name should not contain the delimiter "${DELIMITER}"`
) )
const filePath = path.join(__dirname, `test/${command}`) const filePath = path.join(TEMP_DIR, command)
fs.unlinkSync(filePath) fs.unlinkSync(filePath)
}) })
@ -641,7 +646,7 @@ function assertWriteCalls(calls: string[]): void {
} }
function createFileCommandFile(command: string): void { function createFileCommandFile(command: string): void {
const filePath = path.join(__dirname, `test/${command}`) const filePath = path.join(__dirname, `_temp/${command}`)
process.env[`GITHUB_${command}`] = filePath process.env[`GITHUB_${command}`] = filePath
fs.appendFileSync(filePath, '', { fs.appendFileSync(filePath, '', {
encoding: 'utf8' encoding: 'utf8'
@ -649,7 +654,7 @@ function createFileCommandFile(command: string): void {
} }
function verifyFileCommand(command: string, expectedContents: string): void { function verifyFileCommand(command: string, expectedContents: string): void {
const filePath = path.join(__dirname, `test/${command}`) const filePath = path.join(__dirname, `_temp/${command}`)
const contents = fs.readFileSync(filePath, 'utf8') const contents = fs.readFileSync(filePath, 'utf8')
try { try {
expect(contents).toEqual(expectedContents) expect(contents).toEqual(expectedContents)

View file

@ -1,21 +1,19 @@
{ {
"name": "@actions/core", "name": "@actions/core",
"version": "1.10.1", "version": "1.11.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@actions/core", "name": "@actions/core",
"version": "1.10.1", "version": "1.11.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/exec": "^1.1.1", "@actions/exec": "^1.1.1",
"@actions/http-client": "^2.0.1", "@actions/http-client": "^2.0.1"
"uuid": "^8.3.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^12.0.2", "@types/node": "^16.18.112"
"@types/uuid": "^8.3.4"
} }
}, },
"node_modules/@actions/exec": { "node_modules/@actions/exec": {
@ -40,15 +38,9 @@
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==" "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "12.0.2", "version": "16.18.112",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.112.tgz",
"integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==", "integrity": "sha512-EKrbKUGJROm17+dY/gMi31aJlGLJ75e1IkTojt9n6u+hnaTBDs+M1bIdOawpk2m6YUAXq/R2W0SxCng1tndHCg==",
"dev": true
},
"node_modules/@types/uuid": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
"dev": true "dev": true
}, },
"node_modules/tunnel": { "node_modules/tunnel": {
@ -58,14 +50,6 @@
"engines": { "engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3" "node": ">=0.6.11 <=0.7.0 || >=0.7.3"
} }
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
} }
}, },
"dependencies": { "dependencies": {
@ -91,26 +75,15 @@
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==" "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
}, },
"@types/node": { "@types/node": {
"version": "12.0.2", "version": "16.18.112",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.112.tgz",
"integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==", "integrity": "sha512-EKrbKUGJROm17+dY/gMi31aJlGLJ75e1IkTojt9n6u+hnaTBDs+M1bIdOawpk2m6YUAXq/R2W0SxCng1tndHCg==",
"dev": true
},
"@types/uuid": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
"dev": true "dev": true
}, },
"tunnel": { "tunnel": {
"version": "0.0.6", "version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
} }
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@actions/core", "name": "@actions/core",
"version": "1.10.1", "version": "1.11.1",
"description": "Actions core lib", "description": "Actions core lib",
"keywords": [ "keywords": [
"github", "github",
@ -37,11 +37,9 @@
}, },
"dependencies": { "dependencies": {
"@actions/exec": "^1.1.1", "@actions/exec": "^1.1.1",
"@actions/http-client": "^2.0.1", "@actions/http-client": "^2.0.1"
"uuid": "^8.3.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^12.0.2", "@types/node": "^16.18.112"
"@types/uuid": "^8.3.4"
} }
} }

View file

@ -3,9 +3,9 @@
// We use any as a valid input type // We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import * as crypto from 'crypto'
import * as fs from 'fs' import * as fs from 'fs'
import * as os from 'os' import * as os from 'os'
import {v4 as uuidv4} from 'uuid'
import {toCommandValue} from './utils' import {toCommandValue} from './utils'
export function issueFileCommand(command: string, message: any): void { export function issueFileCommand(command: string, message: any): void {
@ -25,7 +25,7 @@ export function issueFileCommand(command: string, message: any): void {
} }
export function prepareKeyValueMessage(key: string, value: any): string { export function prepareKeyValueMessage(key: string, value: any): string {
const delimiter = `ghadelimiter_${uuidv4()}` const delimiter = `ghadelimiter_${crypto.randomUUID()}`
const convertedValue = toCommandValue(value) const convertedValue = toCommandValue(value)
// These should realistically never happen, but just in case someone finds a // These should realistically never happen, but just in case someone finds a

View file

@ -1,5 +1,8 @@
# @actions/tool-cache Releases # @actions/tool-cache Releases
### Unreleased
- Remove dependency on `uuid` package [#1824](https://github.com/actions/toolkit/pull/1824), [#1842](https://github.com/actions/toolkit/pull/1842)
### 2.0.1 ### 2.0.1
- Update to v2.0.1 of `@actions/http-client` [#1087](https://github.com/actions/toolkit/pull/1087) - Update to v2.0.1 of `@actions/http-client` [#1087](https://github.com/actions/toolkit/pull/1087)

View file

@ -13,13 +13,11 @@
"@actions/exec": "^1.0.0", "@actions/exec": "^1.0.0",
"@actions/http-client": "^2.0.1", "@actions/http-client": "^2.0.1",
"@actions/io": "^1.1.1", "@actions/io": "^1.1.1",
"semver": "^6.1.0", "semver": "^6.1.0"
"uuid": "^3.3.2"
}, },
"devDependencies": { "devDependencies": {
"@types/nock": "^11.1.0", "@types/nock": "^11.1.0",
"@types/semver": "^6.0.0", "@types/semver": "^6.0.0",
"@types/uuid": "^3.4.4",
"nock": "^13.2.9" "nock": "^13.2.9"
} }
}, },
@ -71,27 +69,12 @@
"nock": "*" "nock": "*"
} }
}, },
"node_modules/@types/node": {
"version": "12.7.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.0.tgz",
"integrity": "sha512-vqcj1MVm2Sla4PpMfYKh1MyDN4D2f/mPIZD7RdAGqEsbE+JxfeqQHHVbRDQ0Nqn8i73gJa1HQ1Pu3+nH4Q0Yiw==",
"dev": true
},
"node_modules/@types/semver": { "node_modules/@types/semver": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.0.1.tgz",
"integrity": "sha512-ffCdcrEE5h8DqVxinQjo+2d1q+FV5z7iNtPofw3JsrltSoSVlOGaW0rY8XxtO9XukdTn8TaCGWmk2VFGhI70mg==", "integrity": "sha512-ffCdcrEE5h8DqVxinQjo+2d1q+FV5z7iNtPofw3JsrltSoSVlOGaW0rY8XxtO9XukdTn8TaCGWmk2VFGhI70mg==",
"dev": true "dev": true
}, },
"node_modules/@types/uuid": {
"version": "3.4.5",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.5.tgz",
"integrity": "sha512-MNL15wC3EKyw1VLF+RoVO4hJJdk9t/Hlv3rt1OL65Qvuadm4BYo6g9ZJQqoq7X8NBFSsQXgAujWciovh2lpVjA==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@ -166,15 +149,6 @@
"engines": { "engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3" "node": ">=0.6.11 <=0.7.0 || >=0.7.3"
} }
},
"node_modules/uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
"bin": {
"uuid": "bin/uuid"
}
} }
}, },
"dependencies": { "dependencies": {
@ -224,27 +198,12 @@
"nock": "*" "nock": "*"
} }
}, },
"@types/node": {
"version": "12.7.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.0.tgz",
"integrity": "sha512-vqcj1MVm2Sla4PpMfYKh1MyDN4D2f/mPIZD7RdAGqEsbE+JxfeqQHHVbRDQ0Nqn8i73gJa1HQ1Pu3+nH4Q0Yiw==",
"dev": true
},
"@types/semver": { "@types/semver": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.0.1.tgz",
"integrity": "sha512-ffCdcrEE5h8DqVxinQjo+2d1q+FV5z7iNtPofw3JsrltSoSVlOGaW0rY8XxtO9XukdTn8TaCGWmk2VFGhI70mg==", "integrity": "sha512-ffCdcrEE5h8DqVxinQjo+2d1q+FV5z7iNtPofw3JsrltSoSVlOGaW0rY8XxtO9XukdTn8TaCGWmk2VFGhI70mg==",
"dev": true "dev": true
}, },
"@types/uuid": {
"version": "3.4.5",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.5.tgz",
"integrity": "sha512-MNL15wC3EKyw1VLF+RoVO4hJJdk9t/Hlv3rt1OL65Qvuadm4BYo6g9ZJQqoq7X8NBFSsQXgAujWciovh2lpVjA==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"debug": { "debug": {
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@ -299,11 +258,6 @@
"version": "0.0.6", "version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
},
"uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
} }
} }
} }

View file

@ -40,13 +40,11 @@
"@actions/exec": "^1.0.0", "@actions/exec": "^1.0.0",
"@actions/http-client": "^2.0.1", "@actions/http-client": "^2.0.1",
"@actions/io": "^1.1.1", "@actions/io": "^1.1.1",
"semver": "^6.1.0", "semver": "^6.1.0"
"uuid": "^3.3.2"
}, },
"devDependencies": { "devDependencies": {
"@types/nock": "^11.1.0", "@types/nock": "^11.1.0",
"@types/semver": "^6.0.0", "@types/semver": "^6.0.0",
"@types/uuid": "^3.4.4",
"nock": "^13.2.9" "nock": "^13.2.9"
} }
} }

View file

@ -1,5 +1,6 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as io from '@actions/io' import * as io from '@actions/io'
import * as crypto from 'crypto'
import * as fs from 'fs' import * as fs from 'fs'
import * as mm from './manifest' import * as mm from './manifest'
import * as os from 'os' import * as os from 'os'
@ -10,7 +11,6 @@ import * as stream from 'stream'
import * as util from 'util' import * as util from 'util'
import {ok} from 'assert' import {ok} from 'assert'
import {OutgoingHttpHeaders} from 'http' import {OutgoingHttpHeaders} from 'http'
import uuidV4 from 'uuid/v4'
import {exec} from '@actions/exec/lib/exec' import {exec} from '@actions/exec/lib/exec'
import {ExecOptions} from '@actions/exec/lib/interfaces' import {ExecOptions} from '@actions/exec/lib/interfaces'
import {RetryHelper} from './retry-helper' import {RetryHelper} from './retry-helper'
@ -41,7 +41,7 @@ export async function downloadTool(
auth?: string, auth?: string,
headers?: OutgoingHttpHeaders headers?: OutgoingHttpHeaders
): Promise<string> { ): Promise<string> {
dest = dest || path.join(_getTempDirectory(), uuidV4()) dest = dest || path.join(_getTempDirectory(), crypto.randomUUID())
await io.mkdirP(path.dirname(dest)) await io.mkdirP(path.dirname(dest))
core.debug(`Downloading ${url}`) core.debug(`Downloading ${url}`)
core.debug(`Destination ${dest}`) core.debug(`Destination ${dest}`)
@ -651,7 +651,7 @@ export async function findFromManifest(
async function _createExtractFolder(dest?: string): Promise<string> { async function _createExtractFolder(dest?: string): Promise<string> {
if (!dest) { if (!dest) {
// create a temp dir // create a temp dir
dest = path.join(_getTempDirectory(), uuidV4()) dest = path.join(_getTempDirectory(), crypto.randomUUID())
} }
await io.mkdirP(dest) await io.mkdirP(dest)
return dest return dest