Compare commits

...

3 Commits

Author SHA1 Message Date
Brian DeHamer 63d2e98e26 add note about gh plans supporting attestations (#182)
Signed-off-by: Brian DeHamer <bdehamer@github.com>
2024-12-09 10:46:52 -08:00
Brian DeHamer 94d0d43131 add attestation-id and attestation-url outputs (#181)
Signed-off-by: Brian DeHamer <bdehamer@github.com>
2024-12-09 09:56:33 -08:00
Brian DeHamer 65e34a8aa7 deduplicate subjects before adding to statement (#180)
Signed-off-by: Brian DeHamer <bdehamer@github.com>
2024-12-06 07:14:14 -08:00
9 changed files with 95 additions and 15 deletions
+22 -10
View File
@@ -24,6 +24,16 @@ CLI][5].
See [Using artifact attestations to establish provenance for builds][9] for more
information on artifact attestations.
<!-- prettier-ignore-start -->
> [!NOTE]
> Artifact attestations are available in public repositories for all
> current GitHub plans. They are not available on legacy plans, such as Bronze,
> Silver, or Gold. If you are on a GitHub Free, GitHub Pro, or GitHub Team plan,
> artifact attestations are only available for public repositories. To use
> artifact attestations in private or internal repositories, you must be on a
> GitHub Enterprise Cloud plan.
<!-- prettier-ignore-end -->
## Usage
Within the GitHub Actions workflow which builds some artifact you would like to
@@ -44,7 +54,7 @@ attest:
1. Add the following to your workflow after your artifact has been built:
```yaml
- uses: actions/attest@v1
- uses: actions/attest@v2
with:
subject-path: '<PATH TO ARTIFACT>'
predicate-type: '<PREDICATE URI>'
@@ -61,7 +71,7 @@ attest:
See [action.yml](action.yml)
```yaml
- uses: actions/attest@v1
- uses: actions/attest@v2
with:
# Path to the artifact serving as the subject of the attestation. Must
# specify exactly one of "subject-path" or "subject-digest". May contain
@@ -109,9 +119,11 @@ See [action.yml](action.yml)
<!-- markdownlint-disable MD013 -->
| Name | Description | Example |
| ------------- | -------------------------------------------------------------- | ----------------------- |
| `bundle-path` | Absolute path to the file containing the generated attestation | `/tmp/attestation.json` |
| Name | Description | Example |
| ----------------- | -------------------------------------------------------------- | ------------------------------------------------ |
| `attestation-id` | GitHub ID for the attestation | `123456` |
| `attestation-url` | Absolute path to the file containing the generated attestation | `https://github.com/foo/bar/attestations/123456` |
| `bundle-path` | Absolute path to the file containing the generated attestation | `/tmp/attestation.json` |
<!-- markdownlint-enable MD013 -->
@@ -157,7 +169,7 @@ jobs:
- name: Build artifact
run: make my-app
- name: Attest
uses: actions/attest@v1
uses: actions/attest@v2
with:
subject-path: '${{ github.workspace }}/my-app'
predicate-type: 'https://example.com/predicate/v1'
@@ -170,7 +182,7 @@ If you are generating multiple artifacts, you can attest all of them at the same
time by using a wildcard in the `subject-path` input.
```yaml
- uses: actions/attest@v1
- uses: actions/attest@v2
with:
subject-path: 'dist/**/my-bin-*'
predicate-type: 'https://example.com/predicate/v1'
@@ -184,13 +196,13 @@ Alternatively, you can explicitly list multiple subjects with either a comma or
newline delimited list:
```yaml
- uses: actions/attest@v1
- uses: actions/attest@v2
with:
subject-path: 'dist/foo, dist/bar'
```
```yaml
- uses: actions/attest@v1
- uses: actions/attest@v2
with:
subject-path: |
dist/foo
@@ -247,7 +259,7 @@ jobs:
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- name: Attest
uses: actions/attest@v1
uses: actions/attest@v2
id: attest
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+20
View File
@@ -199,6 +199,16 @@ describe('action', () => {
'bundle-path',
expect.stringMatching('attestation.json')
)
expect(setOutputMock).toHaveBeenNthCalledWith(
2,
'attestation-id',
expect.stringMatching(attestationID)
)
expect(setOutputMock).toHaveBeenNthCalledWith(
3,
'attestation-url',
expect.stringContaining(`foo/bar/attestations/${attestationID}`)
)
expect(setFailedMock).not.toHaveBeenCalled()
})
})
@@ -285,6 +295,16 @@ describe('action', () => {
'bundle-path',
expect.stringMatching('attestation.json')
)
expect(setOutputMock).toHaveBeenNthCalledWith(
2,
'attestation-id',
expect.stringMatching(attestationID)
)
expect(setOutputMock).toHaveBeenNthCalledWith(
3,
'attestation-url',
expect.stringContaining(`foo/bar/attestations/${attestationID}`)
)
expect(setFailedMock).not.toHaveBeenCalled()
})
})
+25
View File
@@ -362,6 +362,31 @@ describe('subjectFromInputs', () => {
})
})
})
describe('when duplicate subjects are supplied', () => {
let otherDir = ''
// Add duplicate subject in alternate directory
beforeEach(async () => {
// Set-up temp directory
const tmpDir = await fs.realpath(os.tmpdir())
otherDir = await fs.mkdtemp(tmpDir + path.sep)
// Write file to temp directory
await fs.writeFile(path.join(otherDir, filename), content)
})
it('returns de-duplicated subjects', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: `${path.join(dir, 'subject')}, ${path.join(otherDir, 'subject')} `
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toBeDefined()
expect(subjects).toHaveLength(1)
})
})
})
})
+4
View File
@@ -61,6 +61,10 @@ inputs:
outputs:
bundle-path:
description: 'The path to the file containing the attestation bundle.'
attestation-id:
description: 'The ID of the attestation.'
attestation-url:
description: 'The URL for the attestation summary.'
runs:
using: node20
Generated Vendored
+8 -1
View File
@@ -70970,6 +70970,10 @@ async function run(inputs) {
encoding: 'utf-8',
flag: 'a'
});
if (att.attestationID) {
core.setOutput('attestation-id', att.attestationID);
core.setOutput('attestation-url', attestationURL(att.attestationID));
}
if (inputs.showSummary) {
logSummary(att);
}
@@ -71205,7 +71209,10 @@ const getSubjectFromPath = async (subjectPath, subjectName) => {
for (const file of files) {
const name = subjectName || path_1.default.parse(file).base;
const digest = await digestFile(DIGEST_ALGORITHM, file);
digestedSubjects.push({ name, digest: { [DIGEST_ALGORITHM]: digest } });
// Only add the subject if it is not already in the list
if (!digestedSubjects.some(s => s.name === name && s.digest[DIGEST_ALGORITHM] === digest)) {
digestedSubjects.push({ name, digest: { [DIGEST_ALGORITHM]: digest } });
}
}
if (digestedSubjects.length === 0) {
throw new Error(`Could not find subject at path ${subjectPath}`);
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "actions/attest",
"version": "2.0.0",
"version": "2.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "actions/attest",
"version": "2.0.0",
"version": "2.1.0",
"license": "MIT",
"dependencies": {
"@actions/attest": "^1.5.0",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "actions/attest",
"description": "Generate signed attestations for workflow artifacts",
"version": "2.0.0",
"version": "2.1.0",
"author": "",
"private": true,
"homepage": "https://github.com/actions/attest",
+5
View File
@@ -79,6 +79,11 @@ export async function run(inputs: RunInputs): Promise<void> {
flag: 'a'
})
if (att.attestationID) {
core.setOutput('attestation-id', att.attestationID)
core.setOutput('attestation-url', attestationURL(att.attestationID))
}
if (inputs.showSummary) {
logSummary(att)
}
+8 -1
View File
@@ -84,7 +84,14 @@ const getSubjectFromPath = async (
const name = subjectName || path.parse(file).base
const digest = await digestFile(DIGEST_ALGORITHM, file)
digestedSubjects.push({ name, digest: { [DIGEST_ALGORITHM]: digest } })
// Only add the subject if it is not already in the list
if (
!digestedSubjects.some(
s => s.name === name && s.digest[DIGEST_ALGORITHM] === digest
)
) {
digestedSubjects.push({ name, digest: { [DIGEST_ALGORITHM]: digest } })
}
}
if (digestedSubjects.length === 0) {