Create Artifact Metadata Storage Record on registry push (#313)
* first pass at creating storage record Signed-off-by: Meredith Lancaster <[email protected]> * include storage record param in action config Signed-off-by: Meredith Lancaster <[email protected]> * use latest actions/attest version Signed-off-by: Meredith Lancaster <[email protected]> * update storage record params Signed-off-by: Meredith Lancaster <[email protected]> * include storage record id in result Signed-off-by: Meredith Lancaster <[email protected]> * regenerate dist Signed-off-by: Meredith Lancaster <[email protected]> * add documentation on storage records Signed-off-by: Meredith Lancaster <[email protected]> * log storage record creation Signed-off-by: Meredith Lancaster <[email protected]> * add storage record output Signed-off-by: Meredith Lancaster <[email protected]> * add new param Signed-off-by: Meredith Lancaster <[email protected]> * add storage record id output Signed-off-by: Meredith Lancaster <[email protected]> * fix linter errors Signed-off-by: Meredith Lancaster <[email protected]> * return all storage record ids Signed-off-by: Meredith Lancaster <[email protected]> * bump minor version Signed-off-by: Meredith Lancaster <[email protected]> * use expect string match function Signed-off-by: Meredith Lancaster <[email protected]> * add try catch block for storage record creation Signed-off-by: Meredith Lancaster <[email protected]> * fix table column spacing Signed-off-by: Meredith Lancaster <[email protected]> * check for protocol Signed-off-by: Meredith Lancaster <[email protected]> * check for artifact url protocol Signed-off-by: Meredith Lancaster <[email protected]> * only fill registry_url for now Signed-off-by: Meredith Lancaster <[email protected]> * cleanup protocol handling Signed-off-by: Meredith Lancaster <[email protected]> * regenerate dist Signed-off-by: Meredith Lancaster <[email protected]> * handle subject name correctly Signed-off-by: Meredith Lancaster <[email protected]> * move test Signed-off-by: Meredith Lancaster <[email protected]> * add back assert statements Signed-off-by: Meredith Lancaster <[email protected]> * add back output assert statements Signed-off-by: Meredith Lancaster <[email protected]> * Apply suggestion from @Copilot Co-authored-by: Copilot <[email protected]> * Apply suggestion from @Copilot Co-authored-by: Copilot <[email protected]> * Apply suggestion from @Copilot Co-authored-by: Copilot <[email protected]> * use url for subject name parsing Signed-off-by: Meredith Lancaster <[email protected]> * add missing test setpu Signed-off-by: Meredith Lancaster <[email protected]> * fix storage record fail test Signed-off-by: Meredith Lancaster <[email protected]> * regenerate dist Signed-off-by: Meredith Lancaster <[email protected]> --------- Signed-off-by: Meredith Lancaster <[email protected]> Co-authored-by: Copilot <[email protected]>
This commit is contained in:
co-authored by
Copilot
parent
0512723b04
commit
fb72c3ec85
@@ -52,11 +52,13 @@ attest:
|
|||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
attestations: write
|
attestations: write
|
||||||
|
artifact-metadata: write
|
||||||
```
|
```
|
||||||
|
|
||||||
The `id-token` permission gives the action the ability to mint the OIDC token
|
The `id-token` permission gives the action the ability to mint the OIDC token
|
||||||
necessary to request a Sigstore signing certificate. The `attestations`
|
necessary to request a Sigstore signing certificate. The `attestations`
|
||||||
permission is necessary to persist the attestation.
|
permission is necessary to persist the attestation. The `artifact-metadata`
|
||||||
|
permission is necessary to create the artifact storage record.
|
||||||
|
|
||||||
1. Add the following to your workflow after your artifact has been built:
|
1. Add the following to your workflow after your artifact has been built:
|
||||||
|
|
||||||
@@ -118,6 +120,12 @@ See [action.yml](action.yml)
|
|||||||
# the "subject-digest" parameter be specified. Defaults to false.
|
# the "subject-digest" parameter be specified. Defaults to false.
|
||||||
push-to-registry:
|
push-to-registry:
|
||||||
|
|
||||||
|
# Whether to create a storage record for the artifact.
|
||||||
|
# Requires that push-to-registry is set to true.
|
||||||
|
# Requires that the "subject-name" parameter specify the fully-qualified
|
||||||
|
# image name. Defaults to true.
|
||||||
|
create-storage-record:
|
||||||
|
|
||||||
# Whether to attach a list of generated attestations to the workflow run
|
# Whether to attach a list of generated attestations to the workflow run
|
||||||
# summary page. Defaults to true.
|
# summary page. Defaults to true.
|
||||||
show-summary:
|
show-summary:
|
||||||
@@ -131,11 +139,12 @@ See [action.yml](action.yml)
|
|||||||
|
|
||||||
<!-- markdownlint-disable MD013 -->
|
<!-- markdownlint-disable MD013 -->
|
||||||
|
|
||||||
| Name | Description | Example |
|
| Name | Description | Example |
|
||||||
| ----------------- | -------------------------------------------------------------- | ------------------------------------------------ |
|
| ------------------- | -------------------------------------------------------------- | ------------------------------------------------ |
|
||||||
| `attestation-id` | GitHub ID for the attestation | `123456` |
|
| `attestation-id` | GitHub ID for the attestation | `123456` |
|
||||||
| `attestation-url` | URL for the attestation summary | `https://github.com/foo/bar/attestations/123456` |
|
| `attestation-url` | URL for the attestation summary | `https://github.com/foo/bar/attestations/123456` |
|
||||||
| `bundle-path` | Absolute path to the file containing the generated attestation | `/tmp/attestation.json` |
|
| `bundle-path` | Absolute path to the file containing the generated attestation | `/tmp/attestation.json` |
|
||||||
|
| `storage-record-ids` | GitHub IDs for the storage records | `987654` |
|
||||||
|
|
||||||
<!-- markdownlint-enable MD013 -->
|
<!-- markdownlint-enable MD013 -->
|
||||||
|
|
||||||
@@ -269,6 +278,10 @@ fully-qualified image name (e.g. "ghcr.io/user/app" or
|
|||||||
"acme.azurecr.io/user/app"). Do NOT include a tag as part of the image name --
|
"acme.azurecr.io/user/app"). Do NOT include a tag as part of the image name --
|
||||||
the specific image being attested is identified by the supplied digest.
|
the specific image being attested is identified by the supplied digest.
|
||||||
|
|
||||||
|
If the `push-to-registry` option is set to true, the Action will also
|
||||||
|
emit an Artifact Metadata Storage Record. If you do not want to emit a
|
||||||
|
storage record, set `create-storage-record` to `false`.
|
||||||
|
|
||||||
> **NOTE**: When pushing to Docker Hub, please use "docker.io" as the registry
|
> **NOTE**: When pushing to Docker Hub, please use "docker.io" as the registry
|
||||||
> portion of the image name.
|
> portion of the image name.
|
||||||
|
|
||||||
@@ -287,6 +300,7 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
contents: read
|
contents: read
|
||||||
attestations: write
|
attestations: write
|
||||||
|
artifact-metadata: write
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|||||||
+45
-3
@@ -9,6 +9,7 @@ import * as core from '@actions/core'
|
|||||||
import * as github from '@actions/github'
|
import * as github from '@actions/github'
|
||||||
import { mockFulcio, mockRekor, mockTSA } from '@sigstore/mock'
|
import { mockFulcio, mockRekor, mockTSA } from '@sigstore/mock'
|
||||||
import * as oci from '@sigstore/oci'
|
import * as oci from '@sigstore/oci'
|
||||||
|
import * as attest from '@actions/attest'
|
||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
import nock from 'nock'
|
import nock from 'nock'
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
@@ -19,6 +20,7 @@ import * as main from '../src/main'
|
|||||||
|
|
||||||
// Mock the GitHub Actions core library
|
// Mock the GitHub Actions core library
|
||||||
const infoMock = jest.spyOn(core, 'info')
|
const infoMock = jest.spyOn(core, 'info')
|
||||||
|
const warningMock = jest.spyOn(core, 'warning')
|
||||||
const startGroupMock = jest.spyOn(core, 'startGroup')
|
const startGroupMock = jest.spyOn(core, 'startGroup')
|
||||||
const setOutputMock = jest.spyOn(core, 'setOutput')
|
const setOutputMock = jest.spyOn(core, 'setOutput')
|
||||||
const setFailedMock = jest.spyOn(core, 'setFailed')
|
const setFailedMock = jest.spyOn(core, 'setFailed')
|
||||||
@@ -45,6 +47,7 @@ const defaultInputs: main.RunInputs = {
|
|||||||
subjectPath: '',
|
subjectPath: '',
|
||||||
subjectChecksums: '',
|
subjectChecksums: '',
|
||||||
pushToRegistry: false,
|
pushToRegistry: false,
|
||||||
|
createStorageRecord: true,
|
||||||
showSummary: true,
|
showSummary: true,
|
||||||
githubToken: '',
|
githubToken: '',
|
||||||
privateSigning: false
|
privateSigning: false
|
||||||
@@ -66,13 +69,14 @@ describe('action', () => {
|
|||||||
'base64'
|
'base64'
|
||||||
)}.}`
|
)}.}`
|
||||||
|
|
||||||
const subjectName = 'registry/foo/bar'
|
const subjectName = 'ghcr.io/registry/foo/bar'
|
||||||
const subjectDigest =
|
const subjectDigest =
|
||||||
'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32'
|
'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32'
|
||||||
const predicate = '{}'
|
const predicate = '{}'
|
||||||
const predicateType = 'https://in-toto.io/attestation/release/v0.1'
|
const predicateType = 'https://in-toto.io/attestation/release/v0.1'
|
||||||
|
|
||||||
const attestationID = '1234567890'
|
const attestationID = '1234567890'
|
||||||
|
const storageRecordID = 987654321
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
@@ -82,14 +86,21 @@ describe('action', () => {
|
|||||||
.query({ audience: 'sigstore' })
|
.query({ audience: 'sigstore' })
|
||||||
.reply(200, { value: oidcToken })
|
.reply(200, { value: oidcToken })
|
||||||
|
|
||||||
mockAgent
|
const pool = mockAgent.get('https://api.github.com')
|
||||||
.get('https://api.github.com')
|
pool
|
||||||
.intercept({
|
.intercept({
|
||||||
path: /^\/repos\/.*\/.*\/attestations$/,
|
path: /^\/repos\/.*\/.*\/attestations$/,
|
||||||
method: 'post'
|
method: 'post'
|
||||||
})
|
})
|
||||||
.reply(201, { id: attestationID })
|
.reply(201, { id: attestationID })
|
||||||
|
|
||||||
|
pool
|
||||||
|
.intercept({
|
||||||
|
path: /^\/orgs\/.*\/artifacts\/metadata\/storage-record$/,
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
|
.reply(200, { storage_records: [{ id: storageRecordID }] })
|
||||||
|
|
||||||
process.env = {
|
process.env = {
|
||||||
...originalEnv,
|
...originalEnv,
|
||||||
ACTIONS_ID_TOKEN_REQUEST_URL: tokenURL,
|
ACTIONS_ID_TOKEN_REQUEST_URL: tokenURL,
|
||||||
@@ -263,6 +274,7 @@ describe('action', () => {
|
|||||||
expect(setFailedMock).not.toHaveBeenCalled()
|
expect(setFailedMock).not.toHaveBeenCalled()
|
||||||
expect(getRegCredsSpy).toHaveBeenCalledWith(subjectName)
|
expect(getRegCredsSpy).toHaveBeenCalledWith(subjectName)
|
||||||
expect(attachArtifactSpy).toHaveBeenCalled()
|
expect(attachArtifactSpy).toHaveBeenCalled()
|
||||||
|
expect(warningMock).not.toHaveBeenCalled()
|
||||||
expect(infoMock).toHaveBeenNthCalledWith(
|
expect(infoMock).toHaveBeenNthCalledWith(
|
||||||
1,
|
1,
|
||||||
expect.stringMatching(
|
expect.stringMatching(
|
||||||
@@ -293,6 +305,14 @@ describe('action', () => {
|
|||||||
6,
|
6,
|
||||||
expect.stringMatching(attestationID)
|
expect.stringMatching(attestationID)
|
||||||
)
|
)
|
||||||
|
expect(infoMock).toHaveBeenNthCalledWith(
|
||||||
|
9,
|
||||||
|
expect.stringMatching('Storage record created')
|
||||||
|
)
|
||||||
|
expect(infoMock).toHaveBeenNthCalledWith(
|
||||||
|
10,
|
||||||
|
expect.stringMatching('Storage record IDs: 987654321')
|
||||||
|
)
|
||||||
expect(setOutputMock).toHaveBeenNthCalledWith(
|
expect(setOutputMock).toHaveBeenNthCalledWith(
|
||||||
1,
|
1,
|
||||||
'bundle-path',
|
'bundle-path',
|
||||||
@@ -308,8 +328,30 @@ describe('action', () => {
|
|||||||
'attestation-url',
|
'attestation-url',
|
||||||
expect.stringContaining(`foo/bar/attestations/${attestationID}`)
|
expect.stringContaining(`foo/bar/attestations/${attestationID}`)
|
||||||
)
|
)
|
||||||
|
expect(setOutputMock).toHaveBeenNthCalledWith(
|
||||||
|
4,
|
||||||
|
'storage-record-ids',
|
||||||
|
expect.stringMatching(storageRecordID.toString())
|
||||||
|
)
|
||||||
expect(setFailedMock).not.toHaveBeenCalled()
|
expect(setFailedMock).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('catches error when storage record creation fails and continues', async () => {
|
||||||
|
// Mock the createStorageRecord function and throw an error
|
||||||
|
const createStorageRecordSpy = jest.spyOn(attest, 'createStorageRecord')
|
||||||
|
createStorageRecordSpy.mockRejectedValueOnce(
|
||||||
|
new Error('Failed to persist storage record: Not Found')
|
||||||
|
)
|
||||||
|
|
||||||
|
await main.run(inputs)
|
||||||
|
|
||||||
|
expect(runMock).toHaveReturned()
|
||||||
|
expect(setFailedMock).not.toHaveBeenCalled()
|
||||||
|
expect(warningMock).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
expect.stringMatching('Failed to create storage record')
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('when the subject count is greater than 1', () => {
|
describe('when the subject count is greater than 1', () => {
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ inputs:
|
|||||||
the "subject-digest" parameter be specified. Defaults to false.
|
the "subject-digest" parameter be specified. Defaults to false.
|
||||||
default: false
|
default: false
|
||||||
required: false
|
required: false
|
||||||
|
create-storage-record:
|
||||||
|
description: >
|
||||||
|
Whether to create a storage record for the artifact.
|
||||||
|
Requires that push-to-registry is set to true. Defaults to true.
|
||||||
|
default: true
|
||||||
|
required: false
|
||||||
show-summary:
|
show-summary:
|
||||||
description: >
|
description: >
|
||||||
Whether to attach a list of generated attestations to the workflow run
|
Whether to attach a list of generated attestations to the workflow run
|
||||||
@@ -71,6 +77,8 @@ outputs:
|
|||||||
description: 'The ID of the attestation.'
|
description: 'The ID of the attestation.'
|
||||||
attestation-url:
|
attestation-url:
|
||||||
description: 'The URL for the attestation summary.'
|
description: 'The URL for the attestation summary.'
|
||||||
|
storage-record-ids:
|
||||||
|
description: 'The IDs of the storage records created for the artifact.'
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: node24
|
using: node24
|
||||||
|
|||||||
+186
-3
@@ -1,6 +1,106 @@
|
|||||||
/******/ (() => { // webpackBootstrap
|
/******/ (() => { // webpackBootstrap
|
||||||
/******/ var __webpack_modules__ = ({
|
/******/ var __webpack_modules__ = ({
|
||||||
|
|
||||||
|
/***/ 33354:
|
||||||
|
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||||
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||||
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||||
|
}
|
||||||
|
Object.defineProperty(o, k2, desc);
|
||||||
|
}) : (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
o[k2] = m[k];
|
||||||
|
}));
|
||||||
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||||
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||||
|
}) : function(o, v) {
|
||||||
|
o["default"] = v;
|
||||||
|
});
|
||||||
|
var __importStar = (this && this.__importStar) || (function () {
|
||||||
|
var ownKeys = function(o) {
|
||||||
|
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||||
|
var ar = [];
|
||||||
|
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||||
|
return ar;
|
||||||
|
};
|
||||||
|
return ownKeys(o);
|
||||||
|
};
|
||||||
|
return function (mod) {
|
||||||
|
if (mod && mod.__esModule) return mod;
|
||||||
|
var result = {};
|
||||||
|
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||||
|
__setModuleDefault(result, mod);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var __rest = (this && this.__rest) || function (s, e) {
|
||||||
|
var t = {};
|
||||||
|
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
||||||
|
t[p] = s[p];
|
||||||
|
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
||||||
|
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
||||||
|
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
||||||
|
t[p[i]] = s[p[i]];
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
|
exports.createStorageRecord = createStorageRecord;
|
||||||
|
const github = __importStar(__nccwpck_require__(93228));
|
||||||
|
const plugin_retry_1 = __nccwpck_require__(33450);
|
||||||
|
const CREATE_STORAGE_RECORD_REQUEST = 'POST /orgs/{owner}/artifacts/metadata/storage-record';
|
||||||
|
const DEFAULT_RETRY_COUNT = 5;
|
||||||
|
/**
|
||||||
|
* Writes a storage record on behalf of an artifact that has been attested
|
||||||
|
* @param artifactOptions - parameters for the storage record API request.
|
||||||
|
* @param packageRegistryOptions - parameters for the package registry API request.
|
||||||
|
* @param token - GitHub token used to authenticate the request.
|
||||||
|
* @param retryAttempts - The number of retries to attempt if the request fails.
|
||||||
|
* @param headers - Additional headers to include in the request.
|
||||||
|
*
|
||||||
|
* @returns The ID of the storage record.
|
||||||
|
* @throws Error if the storage record fails to persist.
|
||||||
|
*/
|
||||||
|
function createStorageRecord(artifactOptions, packageRegistryOptions, token, retryAttempts, headers) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const retries = retryAttempts !== null && retryAttempts !== void 0 ? retryAttempts : DEFAULT_RETRY_COUNT;
|
||||||
|
const octokit = github.getOctokit(token, { retry: { retries } }, plugin_retry_1.retry);
|
||||||
|
try {
|
||||||
|
const response = yield octokit.request(CREATE_STORAGE_RECORD_REQUEST, Object.assign({ owner: github.context.repo.owner, headers }, buildRequestParams(artifactOptions, packageRegistryOptions)));
|
||||||
|
const data = typeof response.data == 'string'
|
||||||
|
? JSON.parse(response.data)
|
||||||
|
: response.data;
|
||||||
|
return data === null || data === void 0 ? void 0 : data.storage_records.map((r) => r.id);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
const message = err instanceof Error ? err.message : err;
|
||||||
|
throw new Error(`Failed to persist storage record: ${message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function buildRequestParams(artifactOptions, packageRegistryOptions) {
|
||||||
|
const { registryUrl, artifactUrl } = packageRegistryOptions, rest = __rest(packageRegistryOptions, ["registryUrl", "artifactUrl"]);
|
||||||
|
return Object.assign(Object.assign(Object.assign({}, artifactOptions), { registry_url: registryUrl, artifact_url: artifactUrl }), rest);
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=artifactMetadata.js.map
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
/***/ 17492:
|
/***/ 17492:
|
||||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||||
|
|
||||||
@@ -184,7 +284,9 @@ function buildGitHubEndpoints() {
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
exports.buildSLSAProvenancePredicate = exports.attestProvenance = exports.attest = void 0;
|
exports.buildSLSAProvenancePredicate = exports.attestProvenance = exports.attest = exports.createStorageRecord = void 0;
|
||||||
|
var artifactMetadata_1 = __nccwpck_require__(33354);
|
||||||
|
Object.defineProperty(exports, "createStorageRecord", ({ enumerable: true, get: function () { return artifactMetadata_1.createStorageRecord; } }));
|
||||||
var attest_1 = __nccwpck_require__(17492);
|
var attest_1 = __nccwpck_require__(17492);
|
||||||
Object.defineProperty(exports, "attest", ({ enumerable: true, get: function () { return attest_1.attest; } }));
|
Object.defineProperty(exports, "attest", ({ enumerable: true, get: function () { return attest_1.attest; } }));
|
||||||
var provenance_1 = __nccwpck_require__(13042);
|
var provenance_1 = __nccwpck_require__(13042);
|
||||||
@@ -79579,15 +79681,49 @@ function wrappy (fn, cb) {
|
|||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
/***/ 93738:
|
/***/ 93738:
|
||||||
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||||
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||||
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||||
|
}
|
||||||
|
Object.defineProperty(o, k2, desc);
|
||||||
|
}) : (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
o[k2] = m[k];
|
||||||
|
}));
|
||||||
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||||
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||||
|
}) : function(o, v) {
|
||||||
|
o["default"] = v;
|
||||||
|
});
|
||||||
|
var __importStar = (this && this.__importStar) || (function () {
|
||||||
|
var ownKeys = function(o) {
|
||||||
|
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||||
|
var ar = [];
|
||||||
|
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||||
|
return ar;
|
||||||
|
};
|
||||||
|
return ownKeys(o);
|
||||||
|
};
|
||||||
|
return function (mod) {
|
||||||
|
if (mod && mod.__esModule) return mod;
|
||||||
|
var result = {};
|
||||||
|
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||||
|
__setModuleDefault(result, mod);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
})();
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
exports.createAttestation = void 0;
|
exports.createAttestation = void 0;
|
||||||
const attest_1 = __nccwpck_require__(11485);
|
const attest_1 = __nccwpck_require__(11485);
|
||||||
const oci_1 = __nccwpck_require__(81057);
|
const oci_1 = __nccwpck_require__(81057);
|
||||||
const subject_1 = __nccwpck_require__(36303);
|
const subject_1 = __nccwpck_require__(36303);
|
||||||
|
const core = __importStar(__nccwpck_require__(37484));
|
||||||
const OCI_TIMEOUT = 30000;
|
const OCI_TIMEOUT = 30000;
|
||||||
const OCI_RETRY = 3;
|
const OCI_RETRY = 3;
|
||||||
const createAttestation = async (subjects, predicate, opts) => {
|
const createAttestation = async (subjects, predicate, opts) => {
|
||||||
@@ -79603,10 +79739,11 @@ const createAttestation = async (subjects, predicate, opts) => {
|
|||||||
if (subjects.length === 1 && opts.pushToRegistry) {
|
if (subjects.length === 1 && opts.pushToRegistry) {
|
||||||
const subject = subjects[0];
|
const subject = subjects[0];
|
||||||
const credentials = (0, oci_1.getRegistryCredentials)(subject.name);
|
const credentials = (0, oci_1.getRegistryCredentials)(subject.name);
|
||||||
|
const subjectDigest = (0, subject_1.formatSubjectDigest)(subject);
|
||||||
const artifact = await (0, oci_1.attachArtifactToImage)({
|
const artifact = await (0, oci_1.attachArtifactToImage)({
|
||||||
credentials,
|
credentials,
|
||||||
imageName: subject.name,
|
imageName: subject.name,
|
||||||
imageDigest: (0, subject_1.formatSubjectDigest)(subject),
|
imageDigest: subjectDigest,
|
||||||
artifact: Buffer.from(JSON.stringify(attestation.bundle)),
|
artifact: Buffer.from(JSON.stringify(attestation.bundle)),
|
||||||
mediaType: attestation.bundle.mediaType,
|
mediaType: attestation.bundle.mediaType,
|
||||||
annotations: {
|
annotations: {
|
||||||
@@ -79617,10 +79754,47 @@ const createAttestation = async (subjects, predicate, opts) => {
|
|||||||
});
|
});
|
||||||
// Add the attestation's digest to the result
|
// Add the attestation's digest to the result
|
||||||
result.attestationDigest = artifact.digest;
|
result.attestationDigest = artifact.digest;
|
||||||
|
// Because creating a storage record requires the 'artifact-metadata:write'
|
||||||
|
// permission, we wrap this in a try/catch to avoid failing the entire
|
||||||
|
// attestation process if the token does not have the correct permissions.
|
||||||
|
if (opts.createStorageRecord) {
|
||||||
|
try {
|
||||||
|
const registryUrl = getRegistryURL(subject.name);
|
||||||
|
const artifactOpts = {
|
||||||
|
name: subject.name,
|
||||||
|
digest: subjectDigest
|
||||||
|
};
|
||||||
|
const packageRegistryOpts = {
|
||||||
|
registryUrl
|
||||||
|
};
|
||||||
|
const records = await (0, attest_1.createStorageRecord)(artifactOpts, packageRegistryOpts, opts.githubToken);
|
||||||
|
if (!records || records.length === 0) {
|
||||||
|
core.warning('No storage records were created.');
|
||||||
|
}
|
||||||
|
result.storageRecordIds = records;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
core.warning(`Failed to create storage record: ${error}`);
|
||||||
|
core.warning('Please check that the "artifact-metadata:write" permission has been included');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
exports.createAttestation = createAttestation;
|
exports.createAttestation = createAttestation;
|
||||||
|
function getRegistryURL(subjectName) {
|
||||||
|
let url;
|
||||||
|
try {
|
||||||
|
url = new URL(subjectName);
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
url = new URL(`https://${subjectName}`);
|
||||||
|
}
|
||||||
|
if (url.protocol !== 'https:') {
|
||||||
|
throw new Error(`Unsupported protocol ${url.protocol} in subject name ${subjectName}`);
|
||||||
|
}
|
||||||
|
return url.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
@@ -79690,6 +79864,7 @@ const inputs = {
|
|||||||
predicate: core.getInput('predicate'),
|
predicate: core.getInput('predicate'),
|
||||||
predicatePath: core.getInput('predicate-path'),
|
predicatePath: core.getInput('predicate-path'),
|
||||||
pushToRegistry: core.getBooleanInput('push-to-registry'),
|
pushToRegistry: core.getBooleanInput('push-to-registry'),
|
||||||
|
createStorageRecord: core.getBooleanInput('create-storage-record'),
|
||||||
showSummary: core.getBooleanInput('show-summary'),
|
showSummary: core.getBooleanInput('show-summary'),
|
||||||
githubToken: core.getInput('github-token'),
|
githubToken: core.getInput('github-token'),
|
||||||
// undocumented -- not part of public interface
|
// undocumented -- not part of public interface
|
||||||
@@ -79790,6 +79965,7 @@ async function run(inputs) {
|
|||||||
const att = await (0, attest_1.createAttestation)(subjects, predicate, {
|
const att = await (0, attest_1.createAttestation)(subjects, predicate, {
|
||||||
sigstoreInstance,
|
sigstoreInstance,
|
||||||
pushToRegistry: inputs.pushToRegistry,
|
pushToRegistry: inputs.pushToRegistry,
|
||||||
|
createStorageRecord: inputs.createStorageRecord,
|
||||||
githubToken: inputs.githubToken
|
githubToken: inputs.githubToken
|
||||||
});
|
});
|
||||||
logAttestation(subjects, att, sigstoreInstance);
|
logAttestation(subjects, att, sigstoreInstance);
|
||||||
@@ -79816,6 +79992,9 @@ async function run(inputs) {
|
|||||||
core.setOutput('attestation-id', att.attestationID);
|
core.setOutput('attestation-id', att.attestationID);
|
||||||
core.setOutput('attestation-url', attestationURL(att.attestationID));
|
core.setOutput('attestation-url', attestationURL(att.attestationID));
|
||||||
}
|
}
|
||||||
|
if (att.storageRecordIds) {
|
||||||
|
core.setOutput('storage-record-ids', att.storageRecordIds.join(','));
|
||||||
|
}
|
||||||
/* istanbul ignore else */
|
/* istanbul ignore else */
|
||||||
if (inputs.showSummary) {
|
if (inputs.showSummary) {
|
||||||
await logSummary(att);
|
await logSummary(att);
|
||||||
@@ -79860,6 +80039,10 @@ const logAttestation = (subjects, attestation, sigstoreInstance) => {
|
|||||||
core.info(style.highlight('Attestation uploaded to registry'));
|
core.info(style.highlight('Attestation uploaded to registry'));
|
||||||
core.info(`${subjects[0].name}@${attestation.attestationDigest}`);
|
core.info(`${subjects[0].name}@${attestation.attestationDigest}`);
|
||||||
}
|
}
|
||||||
|
if (attestation.storageRecordIds && attestation.storageRecordIds.length > 0) {
|
||||||
|
core.info(style.highlight('Storage record created'));
|
||||||
|
core.info(`Storage record IDs: ${attestation.storageRecordIds.join(',')}`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// Attach summary information to the GitHub Actions run
|
// Attach summary information to the GitHub Actions run
|
||||||
const logSummary = async (attestation) => {
|
const logSummary = async (attestation) => {
|
||||||
|
|||||||
Generated
+10
-9
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "actions/attest",
|
"name": "actions/attest",
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "actions/attest",
|
"name": "actions/attest",
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/attest": "^2.0.0",
|
"@actions/attest": "^2.1.0",
|
||||||
"@actions/core": "^2.0.1",
|
"@actions/core": "^2.0.1",
|
||||||
"@actions/github": "^6.0.1",
|
"@actions/github": "^6.0.1",
|
||||||
"@actions/glob": "^0.5.0",
|
"@actions/glob": "^0.5.0",
|
||||||
@@ -49,9 +49,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/attest": {
|
"node_modules/@actions/attest": {
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/attest/-/attest-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/attest/-/attest-2.1.0.tgz",
|
||||||
"integrity": "sha512-9dy7ad6B98JnXPEcXlg372fWWRKPWct1d3FSzu1/h7QtrgqE9uRlgrrx9DIHuwojJ63aaDSciXkX53Tf1Swmpw==",
|
"integrity": "sha512-MQOaqBL1uYMZXNYUKf2aBr7Exgs2OkGIhWY3BB4hz5eIvV1x/JulQ58sz4haHYjJxcvNd5/GzuCaycfiHLLL+g==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.11.1",
|
"@actions/core": "^1.11.1",
|
||||||
"@actions/github": "^6.0.0",
|
"@actions/github": "^6.0.0",
|
||||||
@@ -9590,9 +9591,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@actions/attest": {
|
"@actions/attest": {
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/attest/-/attest-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/attest/-/attest-2.1.0.tgz",
|
||||||
"integrity": "sha512-9dy7ad6B98JnXPEcXlg372fWWRKPWct1d3FSzu1/h7QtrgqE9uRlgrrx9DIHuwojJ63aaDSciXkX53Tf1Swmpw==",
|
"integrity": "sha512-MQOaqBL1uYMZXNYUKf2aBr7Exgs2OkGIhWY3BB4hz5eIvV1x/JulQ58sz4haHYjJxcvNd5/GzuCaycfiHLLL+g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@actions/core": "^1.11.1",
|
"@actions/core": "^1.11.1",
|
||||||
"@actions/github": "^6.0.0",
|
"@actions/github": "^6.0.0",
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "actions/attest",
|
"name": "actions/attest",
|
||||||
"description": "Generate signed attestations for workflow artifacts",
|
"description": "Generate signed attestations for workflow artifacts",
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "https://github.com/actions/attest",
|
"homepage": "https://github.com/actions/attest",
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/attest": "^2.0.0",
|
"@actions/attest": "^2.1.0",
|
||||||
"@actions/core": "^2.0.1",
|
"@actions/core": "^2.0.1",
|
||||||
"@actions/github": "^6.0.1",
|
"@actions/github": "^6.0.1",
|
||||||
"@actions/glob": "^0.5.0",
|
"@actions/glob": "^0.5.0",
|
||||||
|
|||||||
+62
-2
@@ -1,6 +1,13 @@
|
|||||||
import { Attestation, Predicate, Subject, attest } from '@actions/attest'
|
import {
|
||||||
|
Attestation,
|
||||||
|
Predicate,
|
||||||
|
Subject,
|
||||||
|
attest,
|
||||||
|
createStorageRecord
|
||||||
|
} from '@actions/attest'
|
||||||
import { attachArtifactToImage, getRegistryCredentials } from '@sigstore/oci'
|
import { attachArtifactToImage, getRegistryCredentials } from '@sigstore/oci'
|
||||||
import { formatSubjectDigest } from './subject'
|
import { formatSubjectDigest } from './subject'
|
||||||
|
import * as core from '@actions/core'
|
||||||
|
|
||||||
const OCI_TIMEOUT = 30000
|
const OCI_TIMEOUT = 30000
|
||||||
const OCI_RETRY = 3
|
const OCI_RETRY = 3
|
||||||
@@ -8,6 +15,7 @@ const OCI_RETRY = 3
|
|||||||
export type SigstoreInstance = 'public-good' | 'github'
|
export type SigstoreInstance = 'public-good' | 'github'
|
||||||
export type AttestResult = Attestation & {
|
export type AttestResult = Attestation & {
|
||||||
attestationDigest?: string
|
attestationDigest?: string
|
||||||
|
storageRecordIds?: number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createAttestation = async (
|
export const createAttestation = async (
|
||||||
@@ -16,6 +24,7 @@ export const createAttestation = async (
|
|||||||
opts: {
|
opts: {
|
||||||
sigstoreInstance: SigstoreInstance
|
sigstoreInstance: SigstoreInstance
|
||||||
pushToRegistry: boolean
|
pushToRegistry: boolean
|
||||||
|
createStorageRecord: boolean
|
||||||
githubToken: string
|
githubToken: string
|
||||||
}
|
}
|
||||||
): Promise<AttestResult> => {
|
): Promise<AttestResult> => {
|
||||||
@@ -33,10 +42,11 @@ export const createAttestation = async (
|
|||||||
if (subjects.length === 1 && opts.pushToRegistry) {
|
if (subjects.length === 1 && opts.pushToRegistry) {
|
||||||
const subject = subjects[0]
|
const subject = subjects[0]
|
||||||
const credentials = getRegistryCredentials(subject.name)
|
const credentials = getRegistryCredentials(subject.name)
|
||||||
|
const subjectDigest = formatSubjectDigest(subject)
|
||||||
const artifact = await attachArtifactToImage({
|
const artifact = await attachArtifactToImage({
|
||||||
credentials,
|
credentials,
|
||||||
imageName: subject.name,
|
imageName: subject.name,
|
||||||
imageDigest: formatSubjectDigest(subject),
|
imageDigest: subjectDigest,
|
||||||
artifact: Buffer.from(JSON.stringify(attestation.bundle)),
|
artifact: Buffer.from(JSON.stringify(attestation.bundle)),
|
||||||
mediaType: attestation.bundle.mediaType,
|
mediaType: attestation.bundle.mediaType,
|
||||||
annotations: {
|
annotations: {
|
||||||
@@ -48,7 +58,57 @@ export const createAttestation = async (
|
|||||||
|
|
||||||
// Add the attestation's digest to the result
|
// Add the attestation's digest to the result
|
||||||
result.attestationDigest = artifact.digest
|
result.attestationDigest = artifact.digest
|
||||||
|
|
||||||
|
// Because creating a storage record requires the 'artifact-metadata:write'
|
||||||
|
// permission, we wrap this in a try/catch to avoid failing the entire
|
||||||
|
// attestation process if the token does not have the correct permissions.
|
||||||
|
if (opts.createStorageRecord) {
|
||||||
|
try {
|
||||||
|
const registryUrl = getRegistryURL(subject.name)
|
||||||
|
const artifactOpts = {
|
||||||
|
name: subject.name,
|
||||||
|
digest: subjectDigest
|
||||||
|
}
|
||||||
|
const packageRegistryOpts = {
|
||||||
|
registryUrl
|
||||||
|
}
|
||||||
|
const records = await createStorageRecord(
|
||||||
|
artifactOpts,
|
||||||
|
packageRegistryOpts,
|
||||||
|
opts.githubToken
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!records || records.length === 0) {
|
||||||
|
core.warning('No storage records were created.')
|
||||||
|
}
|
||||||
|
|
||||||
|
result.storageRecordIds = records
|
||||||
|
} catch (error) {
|
||||||
|
core.warning(`Failed to create storage record: ${error}`)
|
||||||
|
core.warning(
|
||||||
|
'Please check that the "artifact-metadata:write" permission has been included'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRegistryURL(subjectName: string): string {
|
||||||
|
let url: URL
|
||||||
|
|
||||||
|
try {
|
||||||
|
url = new URL(subjectName)
|
||||||
|
} catch {
|
||||||
|
url = new URL(`https://${subjectName}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.protocol !== 'https:') {
|
||||||
|
throw new Error(
|
||||||
|
`Unsupported protocol ${url.protocol} in subject name ${subjectName}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.origin
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const inputs: RunInputs = {
|
|||||||
predicate: core.getInput('predicate'),
|
predicate: core.getInput('predicate'),
|
||||||
predicatePath: core.getInput('predicate-path'),
|
predicatePath: core.getInput('predicate-path'),
|
||||||
pushToRegistry: core.getBooleanInput('push-to-registry'),
|
pushToRegistry: core.getBooleanInput('push-to-registry'),
|
||||||
|
createStorageRecord: core.getBooleanInput('create-storage-record'),
|
||||||
showSummary: core.getBooleanInput('show-summary'),
|
showSummary: core.getBooleanInput('show-summary'),
|
||||||
githubToken: core.getInput('github-token'),
|
githubToken: core.getInput('github-token'),
|
||||||
// undocumented -- not part of public interface
|
// undocumented -- not part of public interface
|
||||||
|
|||||||
+10
@@ -21,6 +21,7 @@ const ATTESTATION_PATHS_FILE_NAME = 'created_attestation_paths.txt'
|
|||||||
export type RunInputs = SubjectInputs &
|
export type RunInputs = SubjectInputs &
|
||||||
PredicateInputs & {
|
PredicateInputs & {
|
||||||
pushToRegistry: boolean
|
pushToRegistry: boolean
|
||||||
|
createStorageRecord: boolean
|
||||||
githubToken: string
|
githubToken: string
|
||||||
showSummary: boolean
|
showSummary: boolean
|
||||||
privateSigning: boolean
|
privateSigning: boolean
|
||||||
@@ -69,6 +70,7 @@ export async function run(inputs: RunInputs): Promise<void> {
|
|||||||
const att = await createAttestation(subjects, predicate, {
|
const att = await createAttestation(subjects, predicate, {
|
||||||
sigstoreInstance,
|
sigstoreInstance,
|
||||||
pushToRegistry: inputs.pushToRegistry,
|
pushToRegistry: inputs.pushToRegistry,
|
||||||
|
createStorageRecord: inputs.createStorageRecord,
|
||||||
githubToken: inputs.githubToken
|
githubToken: inputs.githubToken
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -100,6 +102,9 @@ export async function run(inputs: RunInputs): Promise<void> {
|
|||||||
core.setOutput('attestation-id', att.attestationID)
|
core.setOutput('attestation-id', att.attestationID)
|
||||||
core.setOutput('attestation-url', attestationURL(att.attestationID))
|
core.setOutput('attestation-url', attestationURL(att.attestationID))
|
||||||
}
|
}
|
||||||
|
if (att.storageRecordIds) {
|
||||||
|
core.setOutput('storage-record-ids', att.storageRecordIds.join(','))
|
||||||
|
}
|
||||||
|
|
||||||
/* istanbul ignore else */
|
/* istanbul ignore else */
|
||||||
if (inputs.showSummary) {
|
if (inputs.showSummary) {
|
||||||
@@ -169,6 +174,11 @@ const logAttestation = (
|
|||||||
core.info(style.highlight('Attestation uploaded to registry'))
|
core.info(style.highlight('Attestation uploaded to registry'))
|
||||||
core.info(`${subjects[0].name}@${attestation.attestationDigest}`)
|
core.info(`${subjects[0].name}@${attestation.attestationDigest}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (attestation.storageRecordIds && attestation.storageRecordIds.length > 0) {
|
||||||
|
core.info(style.highlight('Storage record created'))
|
||||||
|
core.info(`Storage record IDs: ${attestation.storageRecordIds.join(',')}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach summary information to the GitHub Actions run
|
// Attach summary information to the GitHub Actions run
|
||||||
|
|||||||
Reference in New Issue
Block a user