Move from composite to regular node action.
This involves generating the attestation in the code using the new attest library in the actions toolkit.
This commit is contained in:
+75
-75
@@ -1,10 +1,8 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as github from '@actions/github'
|
||||
import * as fsHelper from './fs-helper'
|
||||
import * as ociContainer from './oci-container'
|
||||
import * as ghcr from './ghcr-client'
|
||||
import * as api from './api-client'
|
||||
import semver from 'semver'
|
||||
import * as iaToolkit from '@immutable-actions/toolkit'
|
||||
import * as attest from '@actions/attest'
|
||||
import * as cfg from './config'
|
||||
|
||||
/**
|
||||
* The main function for the action.
|
||||
@@ -12,66 +10,45 @@ import semver from 'semver'
|
||||
*/
|
||||
export async function run(): Promise<void> {
|
||||
try {
|
||||
const workspace: string = process.env.GITHUB_WORKSPACE || ''
|
||||
if (workspace === '') {
|
||||
core.setFailed(`Could not find GITHUB_WORKSPACE.`)
|
||||
return
|
||||
}
|
||||
const options: cfg.PublishActionOptions =
|
||||
await cfg.resolvePublishActionOptions()
|
||||
|
||||
const repository: string = process.env.GITHUB_REPOSITORY || ''
|
||||
if (repository === '') {
|
||||
core.setFailed(`Could not find Repository.`)
|
||||
return
|
||||
}
|
||||
core.info(`Publishing action package version with options:`)
|
||||
core.info(cfg.serializeOptions(options))
|
||||
|
||||
const token: string = process.env.TOKEN || ''
|
||||
const sourceCommit: string = process.env.GITHUB_SHA || ''
|
||||
if (token === '') {
|
||||
core.setFailed(`Could not find GITHUB_TOKEN.`)
|
||||
return
|
||||
}
|
||||
if (sourceCommit === '') {
|
||||
core.setFailed(`Could not find source commit.`)
|
||||
return
|
||||
}
|
||||
const semverTag: semver.SemVer = parseSemverTagFromRef(options.ref)
|
||||
|
||||
const semanticVersion = parseSourceSemanticVersion()
|
||||
const stagedActionFilesDir = iaToolkit.createTempDir(
|
||||
options.runnerTempDir,
|
||||
'staging'
|
||||
)
|
||||
iaToolkit.stageActionFiles(options.workspaceDir, stagedActionFilesDir)
|
||||
|
||||
// Create a temporary directory to stage files for packaging in archives
|
||||
const stagedActionFilesDir = fsHelper.createTempDir('staging')
|
||||
fsHelper.stageActionFiles(workspace, stagedActionFilesDir)
|
||||
|
||||
// Create a temporary directory to store the archives
|
||||
const archiveDir = fsHelper.createTempDir('archive')
|
||||
const archives = await fsHelper.createArchives(
|
||||
const archiveDir = iaToolkit.createTempDir(
|
||||
options.runnerTempDir,
|
||||
'archives'
|
||||
)
|
||||
const archives = await iaToolkit.createArchives(
|
||||
stagedActionFilesDir,
|
||||
archiveDir
|
||||
)
|
||||
|
||||
const { repoId, ownerId } = await api.getRepositoryMetadata(
|
||||
repository,
|
||||
token
|
||||
)
|
||||
|
||||
const manifest = ociContainer.createActionPackageManifest(
|
||||
const manifest = iaToolkit.createActionPackageManifest(
|
||||
archives.tarFile,
|
||||
archives.zipFile,
|
||||
repository,
|
||||
repoId,
|
||||
ownerId,
|
||||
sourceCommit,
|
||||
semanticVersion.raw,
|
||||
options.nameWithOwner,
|
||||
options.repositoryId,
|
||||
options.repositoryOwnerId,
|
||||
options.sha,
|
||||
semverTag.raw,
|
||||
new Date()
|
||||
)
|
||||
|
||||
const containerRegistryURL = await api.getContainerRegistryURL()
|
||||
console.log(`Container registry URL: ${containerRegistryURL}`)
|
||||
|
||||
const { packageURL, manifestDigest } = await ghcr.publishOCIArtifact(
|
||||
token,
|
||||
containerRegistryURL,
|
||||
repository,
|
||||
semanticVersion.raw,
|
||||
const { packageURL, manifestDigest } = await iaToolkit.publishOCIArtifact(
|
||||
options.token,
|
||||
options.containerRegistryUrl,
|
||||
options.nameWithOwner,
|
||||
semverTag.raw,
|
||||
archives.zipFile,
|
||||
archives.tarFile,
|
||||
manifest
|
||||
@@ -80,40 +57,63 @@ export async function run(): Promise<void> {
|
||||
core.setOutput('package-url', packageURL.toString())
|
||||
core.setOutput('package-manifest', JSON.stringify(manifest))
|
||||
core.setOutput('package-manifest-sha', manifestDigest)
|
||||
|
||||
if (!options.isEnterprise) {
|
||||
const attestation = await generateAttestation(
|
||||
manifestDigest,
|
||||
semverTag.raw,
|
||||
options
|
||||
)
|
||||
if (attestation.attestationID !== undefined) {
|
||||
core.setOutput('attestation-id', attestation.attestationID)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Fail the workflow run if an error occurs
|
||||
if (error instanceof Error) core.setFailed(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
// This action can be triggered by release events or tag push events.
|
||||
// In each case, the source event should produce a Semantic Version compliant tag representing the code to be packaged.
|
||||
function parseSourceSemanticVersion(): semver.SemVer {
|
||||
const event = github.context.eventName
|
||||
let semverTag = ''
|
||||
// This action can be triggered by any workflow that specifies a tag as its GITHUB_REF.
|
||||
// This includes releases, creating or pushing tags, or workflow_dispatch.
|
||||
// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#about-events-that-trigger-workflows.
|
||||
function parseSemverTagFromRef(ref: string): semver.SemVer {
|
||||
if (!ref.startsWith('refs/tags/')) {
|
||||
throw new Error(`The ref ${ref} is not a valid tag reference.`)
|
||||
}
|
||||
|
||||
// Grab the raw tag
|
||||
if (event === 'release') semverTag = github.context.payload.release.tag_name
|
||||
else if (event === 'push' && github.context.ref.startsWith('refs/tags/')) {
|
||||
semverTag = github.context.ref.replace(/^refs\/tags\//, '')
|
||||
} else {
|
||||
const rawTag = ref.replace(/^refs\/tags\//, '')
|
||||
const semverTag = semver.parse(rawTag)
|
||||
if (!semverTag) {
|
||||
throw new Error(
|
||||
`This action can only be triggered by release events or tag push events.`
|
||||
`${rawTag} is not a valid semantic version tag, and so cannot be uploaded to the action package.`
|
||||
)
|
||||
}
|
||||
|
||||
if (semverTag === '') {
|
||||
throw new Error(
|
||||
`Could not find a Semantic Version tag in the event payload.`
|
||||
)
|
||||
}
|
||||
return semverTag
|
||||
}
|
||||
|
||||
// Generate an attestation using the actions toolkit
|
||||
// Subject name will contain the repo/package name and the tag name
|
||||
async function generateAttestation(
|
||||
manifestDigest: string,
|
||||
semverTag: string,
|
||||
options: cfg.PublishActionOptions
|
||||
): Promise<attest.Attestation> {
|
||||
const subjectName = `${options.nameWithOwner}_${semverTag}`
|
||||
const subjectDigest = removePrefix(manifestDigest, 'sha256:')
|
||||
|
||||
return await attest.attestProvenance({
|
||||
subjectName,
|
||||
subjectDigest: { sha256: subjectDigest },
|
||||
token: options.token,
|
||||
skipWrite: false // TODO: Attestation storage is only supported for public repositories or repositories which belong to a GitHub Enterprise Cloud account
|
||||
})
|
||||
}
|
||||
|
||||
const semanticVersion = semver.parse(semverTag.replace(/^v/, ''))
|
||||
if (!semanticVersion) {
|
||||
throw new Error(
|
||||
`${semverTag} is not a valid semantic version, and so cannot be uploaded as an Immutable Action.`
|
||||
)
|
||||
function removePrefix(str: string, prefix: string): string {
|
||||
if (str.startsWith(prefix)) {
|
||||
return str.slice(prefix.length)
|
||||
}
|
||||
|
||||
return semanticVersion
|
||||
return str
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user