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
7667f588f2
+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 { formatSubjectDigest } from './subject'
|
||||
import * as core from '@actions/core'
|
||||
|
||||
const OCI_TIMEOUT = 30000
|
||||
const OCI_RETRY = 3
|
||||
@@ -8,6 +15,7 @@ const OCI_RETRY = 3
|
||||
export type SigstoreInstance = 'public-good' | 'github'
|
||||
export type AttestResult = Attestation & {
|
||||
attestationDigest?: string
|
||||
storageRecordIds?: number[]
|
||||
}
|
||||
|
||||
export const createAttestation = async (
|
||||
@@ -16,6 +24,7 @@ export const createAttestation = async (
|
||||
opts: {
|
||||
sigstoreInstance: SigstoreInstance
|
||||
pushToRegistry: boolean
|
||||
createStorageRecord: boolean
|
||||
githubToken: string
|
||||
}
|
||||
): Promise<AttestResult> => {
|
||||
@@ -33,10 +42,11 @@ export const createAttestation = async (
|
||||
if (subjects.length === 1 && opts.pushToRegistry) {
|
||||
const subject = subjects[0]
|
||||
const credentials = getRegistryCredentials(subject.name)
|
||||
const subjectDigest = formatSubjectDigest(subject)
|
||||
const artifact = await attachArtifactToImage({
|
||||
credentials,
|
||||
imageName: subject.name,
|
||||
imageDigest: formatSubjectDigest(subject),
|
||||
imageDigest: subjectDigest,
|
||||
artifact: Buffer.from(JSON.stringify(attestation.bundle)),
|
||||
mediaType: attestation.bundle.mediaType,
|
||||
annotations: {
|
||||
@@ -48,7 +58,57 @@ export const createAttestation = async (
|
||||
|
||||
// Add the attestation's digest to the result
|
||||
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
|
||||
}
|
||||
|
||||
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'),
|
||||
predicatePath: core.getInput('predicate-path'),
|
||||
pushToRegistry: core.getBooleanInput('push-to-registry'),
|
||||
createStorageRecord: core.getBooleanInput('create-storage-record'),
|
||||
showSummary: core.getBooleanInput('show-summary'),
|
||||
githubToken: core.getInput('github-token'),
|
||||
// undocumented -- not part of public interface
|
||||
|
||||
+10
@@ -21,6 +21,7 @@ const ATTESTATION_PATHS_FILE_NAME = 'created_attestation_paths.txt'
|
||||
export type RunInputs = SubjectInputs &
|
||||
PredicateInputs & {
|
||||
pushToRegistry: boolean
|
||||
createStorageRecord: boolean
|
||||
githubToken: string
|
||||
showSummary: boolean
|
||||
privateSigning: boolean
|
||||
@@ -69,6 +70,7 @@ export async function run(inputs: RunInputs): Promise<void> {
|
||||
const att = await createAttestation(subjects, predicate, {
|
||||
sigstoreInstance,
|
||||
pushToRegistry: inputs.pushToRegistry,
|
||||
createStorageRecord: inputs.createStorageRecord,
|
||||
githubToken: inputs.githubToken
|
||||
})
|
||||
|
||||
@@ -100,6 +102,9 @@ export async function run(inputs: RunInputs): Promise<void> {
|
||||
core.setOutput('attestation-id', att.attestationID)
|
||||
core.setOutput('attestation-url', attestationURL(att.attestationID))
|
||||
}
|
||||
if (att.storageRecordIds) {
|
||||
core.setOutput('storage-record-ids', att.storageRecordIds.join(','))
|
||||
}
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (inputs.showSummary) {
|
||||
@@ -169,6 +174,11 @@ const logAttestation = (
|
||||
core.info(style.highlight('Attestation uploaded to registry'))
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user