upload attestation and referrer index before artifact
This avoids race conditions when the artifact is read but its attestation doesn't exist
This commit is contained in:
+131
-120
@@ -207,6 +207,112 @@ describe('run', () => {
|
|||||||
expect(setFailedMock).toHaveBeenCalledWith('Something went wrong')
|
expect(setFailedMock).toHaveBeenCalledWith('Something went wrong')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('fails if creating attestation fails', async () => {
|
||||||
|
resolvePublishActionOptionsMock.mockReturnValue(baseOptions())
|
||||||
|
|
||||||
|
ensureCorrectShaCheckedOutMock.mockImplementation(() => {})
|
||||||
|
|
||||||
|
createTempDirMock.mockImplementation(() => {
|
||||||
|
return 'stagingOrArchivesDir'
|
||||||
|
})
|
||||||
|
|
||||||
|
stageActionFilesMock.mockImplementation(() => {})
|
||||||
|
|
||||||
|
calculateManifestDigestMock.mockImplementation(() => {
|
||||||
|
return 'sha256:my-test-digest'
|
||||||
|
})
|
||||||
|
|
||||||
|
createArchivesMock.mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
zipFile: {
|
||||||
|
path: 'test',
|
||||||
|
size: 5,
|
||||||
|
sha256: '123'
|
||||||
|
},
|
||||||
|
tarFile: {
|
||||||
|
path: 'test2',
|
||||||
|
size: 52,
|
||||||
|
sha256: '1234'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
publishOCIArtifactMock.mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
packageURL: 'https://ghcr.io/v2/test-org/test-repo:1.2.3',
|
||||||
|
publishedDigest: 'sha256:my-test-digest'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
generateAttestationMock.mockImplementation(async () => {
|
||||||
|
throw new Error('Something went wrong')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Run the action
|
||||||
|
await main.run()
|
||||||
|
|
||||||
|
// Check the results
|
||||||
|
expect(setFailedMock).toHaveBeenCalledWith('Something went wrong')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('fails if uploading attestation to GHCR fails', async () => {
|
||||||
|
resolvePublishActionOptionsMock.mockReturnValue(baseOptions())
|
||||||
|
|
||||||
|
ensureCorrectShaCheckedOutMock.mockImplementation(() => {})
|
||||||
|
|
||||||
|
createTempDirMock.mockImplementation(() => {
|
||||||
|
return 'stagingOrArchivesDir'
|
||||||
|
})
|
||||||
|
|
||||||
|
stageActionFilesMock.mockImplementation(() => {})
|
||||||
|
|
||||||
|
createArchivesMock.mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
zipFile: {
|
||||||
|
path: 'test',
|
||||||
|
size: 5,
|
||||||
|
sha256: '123'
|
||||||
|
},
|
||||||
|
tarFile: {
|
||||||
|
path: 'test2',
|
||||||
|
size: 52,
|
||||||
|
sha256: '1234'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
calculateManifestDigestMock.mockImplementation(() => {
|
||||||
|
return 'sha256:my-test-digest'
|
||||||
|
})
|
||||||
|
|
||||||
|
generateAttestationMock.mockImplementation(async options => {
|
||||||
|
expect(options).toHaveProperty('skipWrite', true)
|
||||||
|
|
||||||
|
return {
|
||||||
|
attestationID: 'test-attestation-id',
|
||||||
|
certificate: 'test',
|
||||||
|
bundle: {
|
||||||
|
mediaType: 'application/vnd.cncf.notary.v2+jwt',
|
||||||
|
verificationMaterial: {
|
||||||
|
publicKey: {
|
||||||
|
hint: 'test-hint'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
attachArtifactToImageMock.mockImplementation(() => {
|
||||||
|
throw new Error('Something went wrong')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Run the action
|
||||||
|
await main.run()
|
||||||
|
|
||||||
|
// Check the results
|
||||||
|
expect(setFailedMock).toHaveBeenCalledWith('Something went wrong')
|
||||||
|
})
|
||||||
|
|
||||||
it('fails if publishing OCI artifact fails', async () => {
|
it('fails if publishing OCI artifact fails', async () => {
|
||||||
resolvePublishActionOptionsMock.mockReturnValue(baseOptions())
|
resolvePublishActionOptionsMock.mockReturnValue(baseOptions())
|
||||||
|
|
||||||
@@ -254,6 +360,15 @@ describe('run', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
attachArtifactToImageMock.mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
digest: 'sha256:my-test-attestation-digest',
|
||||||
|
urls: [
|
||||||
|
'ghcr.io/v2/test-org/test-package/manifests/sha256:my-test-attestation-digest'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
publishOCIArtifactMock.mockImplementation(() => {
|
publishOCIArtifactMock.mockImplementation(() => {
|
||||||
throw new Error('Something went wrong')
|
throw new Error('Something went wrong')
|
||||||
})
|
})
|
||||||
@@ -312,6 +427,15 @@ describe('run', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
attachArtifactToImageMock.mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
digest: 'sha256:some-other-digest',
|
||||||
|
urls: [
|
||||||
|
'ghcr.io/v2/test-org/test-package/manifests/sha256:some-other-digest'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
publishOCIArtifactMock.mockImplementation(() => {
|
publishOCIArtifactMock.mockImplementation(() => {
|
||||||
return {
|
return {
|
||||||
packageURL: 'https://ghcr.io/v2/test-org/test-repo:1.2.3',
|
packageURL: 'https://ghcr.io/v2/test-org/test-repo:1.2.3',
|
||||||
@@ -328,119 +452,6 @@ describe('run', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('fails if uploading attestation to GHCR fails', async () => {
|
|
||||||
resolvePublishActionOptionsMock.mockReturnValue(baseOptions())
|
|
||||||
|
|
||||||
ensureCorrectShaCheckedOutMock.mockImplementation(() => {})
|
|
||||||
|
|
||||||
createTempDirMock.mockImplementation(() => {
|
|
||||||
return 'stagingOrArchivesDir'
|
|
||||||
})
|
|
||||||
|
|
||||||
stageActionFilesMock.mockImplementation(() => {})
|
|
||||||
|
|
||||||
createArchivesMock.mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
zipFile: {
|
|
||||||
path: 'test',
|
|
||||||
size: 5,
|
|
||||||
sha256: '123'
|
|
||||||
},
|
|
||||||
tarFile: {
|
|
||||||
path: 'test2',
|
|
||||||
size: 52,
|
|
||||||
sha256: '1234'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
calculateManifestDigestMock.mockImplementation(() => {
|
|
||||||
return 'sha256:my-test-digest'
|
|
||||||
})
|
|
||||||
|
|
||||||
publishOCIArtifactMock.mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
packageURL: 'https://ghcr.io/v2/test-org/test-repo:1.2.3',
|
|
||||||
publishedDigest: 'sha256:my-test-digest'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
generateAttestationMock.mockImplementation(async options => {
|
|
||||||
expect(options).toHaveProperty('skipWrite', true)
|
|
||||||
|
|
||||||
return {
|
|
||||||
attestationID: 'test-attestation-id',
|
|
||||||
certificate: 'test',
|
|
||||||
bundle: {
|
|
||||||
mediaType: 'application/vnd.cncf.notary.v2+jwt',
|
|
||||||
verificationMaterial: {
|
|
||||||
publicKey: {
|
|
||||||
hint: 'test-hint'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
attachArtifactToImageMock.mockImplementation(() => {
|
|
||||||
throw new Error('Something went wrong')
|
|
||||||
})
|
|
||||||
|
|
||||||
// Run the action
|
|
||||||
await main.run()
|
|
||||||
|
|
||||||
// Check the results
|
|
||||||
expect(setFailedMock).toHaveBeenCalledWith('Something went wrong')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('fails if creating attestation fails', async () => {
|
|
||||||
resolvePublishActionOptionsMock.mockReturnValue(baseOptions())
|
|
||||||
|
|
||||||
ensureCorrectShaCheckedOutMock.mockImplementation(() => {})
|
|
||||||
|
|
||||||
createTempDirMock.mockImplementation(() => {
|
|
||||||
return 'stagingOrArchivesDir'
|
|
||||||
})
|
|
||||||
|
|
||||||
stageActionFilesMock.mockImplementation(() => {})
|
|
||||||
|
|
||||||
calculateManifestDigestMock.mockImplementation(() => {
|
|
||||||
return 'sha256:my-test-digest'
|
|
||||||
})
|
|
||||||
|
|
||||||
createArchivesMock.mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
zipFile: {
|
|
||||||
path: 'test',
|
|
||||||
size: 5,
|
|
||||||
sha256: '123'
|
|
||||||
},
|
|
||||||
tarFile: {
|
|
||||||
path: 'test2',
|
|
||||||
size: 52,
|
|
||||||
sha256: '1234'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
publishOCIArtifactMock.mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
packageURL: 'https://ghcr.io/v2/test-org/test-repo:1.2.3',
|
|
||||||
publishedDigest: 'sha256:my-test-digest'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
generateAttestationMock.mockImplementation(async () => {
|
|
||||||
throw new Error('Something went wrong')
|
|
||||||
})
|
|
||||||
|
|
||||||
// Run the action
|
|
||||||
await main.run()
|
|
||||||
|
|
||||||
// Check the results
|
|
||||||
expect(setFailedMock).toHaveBeenCalledWith('Something went wrong')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('uploads the artifact, returns package metadata from GHCR, and skips writing attestation in enterprise', async () => {
|
it('uploads the artifact, returns package metadata from GHCR, and skips writing attestation in enterprise', async () => {
|
||||||
const options = baseOptions()
|
const options = baseOptions()
|
||||||
options.isEnterprise = true
|
options.isEnterprise = true
|
||||||
@@ -535,13 +546,6 @@ describe('run', () => {
|
|||||||
return 'sha256:my-test-digest'
|
return 'sha256:my-test-digest'
|
||||||
})
|
})
|
||||||
|
|
||||||
publishOCIArtifactMock.mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
packageURL: 'https://ghcr.io/v2/test-org/test-repo:1.2.3',
|
|
||||||
publishedDigest: 'sha256:my-test-digest'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
generateAttestationMock.mockImplementation(async options => {
|
generateAttestationMock.mockImplementation(async options => {
|
||||||
expect(options).toHaveProperty('skipWrite', true)
|
expect(options).toHaveProperty('skipWrite', true)
|
||||||
|
|
||||||
@@ -568,6 +572,13 @@ describe('run', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
publishOCIArtifactMock.mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
packageURL: 'https://ghcr.io/v2/test-org/test-repo:1.2.3',
|
||||||
|
publishedDigest: 'sha256:my-test-digest'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Run the action
|
// Run the action
|
||||||
await main.run()
|
await main.run()
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -107636,13 +107636,9 @@ async function run() {
|
|||||||
const archives = await fsHelper.createArchives(stagedActionFilesDir, archiveDir);
|
const archives = await fsHelper.createArchives(stagedActionFilesDir, archiveDir);
|
||||||
const manifest = ociContainer.createActionPackageManifest(archives.tarFile, archives.zipFile, options.nameWithOwner, options.repositoryId, options.repositoryOwnerId, options.sha, semverTag.raw, new Date());
|
const manifest = ociContainer.createActionPackageManifest(archives.tarFile, archives.zipFile, options.nameWithOwner, options.repositoryId, options.repositoryOwnerId, options.sha, semverTag.raw, new Date());
|
||||||
const manifestDigest = ociContainer.sha256Digest(manifest);
|
const manifestDigest = ociContainer.sha256Digest(manifest);
|
||||||
const { packageURL, publishedDigest } = await ghcr.publishOCIArtifact(options.token, options.containerRegistryUrl, options.nameWithOwner, semverTag.raw, archives.zipFile, archives.tarFile, manifest);
|
// Attestations are not supported in GHES.
|
||||||
if (manifestDigest !== publishedDigest) {
|
|
||||||
throw new Error(`Unexpected digest returned for manifest. Expected ${manifestDigest}, got ${publishedDigest}`);
|
|
||||||
}
|
|
||||||
// Attestations are not currently supported in GHES.
|
|
||||||
if (!options.isEnterprise) {
|
if (!options.isEnterprise) {
|
||||||
const attestation = await uploadAttestation(publishedDigest, semverTag.raw, options);
|
const attestation = await uploadAttestation(manifestDigest, semverTag.raw, options);
|
||||||
if (attestation.digest !== undefined) {
|
if (attestation.digest !== undefined) {
|
||||||
core.info(`Uploaded attestation ${attestation.digest}`);
|
core.info(`Uploaded attestation ${attestation.digest}`);
|
||||||
core.setOutput('attestation-manifest-sha', attestation.digest);
|
core.setOutput('attestation-manifest-sha', attestation.digest);
|
||||||
@@ -107652,6 +107648,10 @@ async function run() {
|
|||||||
core.setOutput('attestation-url', attestation.urls[0]);
|
core.setOutput('attestation-url', attestation.urls[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const { packageURL, publishedDigest } = await ghcr.publishOCIArtifact(options.token, options.containerRegistryUrl, options.nameWithOwner, semverTag.raw, archives.zipFile, archives.tarFile, manifest);
|
||||||
|
if (manifestDigest !== publishedDigest) {
|
||||||
|
throw new Error(`Unexpected digest returned for manifest. Expected ${manifestDigest}, got ${publishedDigest}`);
|
||||||
|
}
|
||||||
core.setOutput('package-url', packageURL.toString());
|
core.setOutput('package-url', packageURL.toString());
|
||||||
core.setOutput('package-manifest', JSON.stringify(manifest));
|
core.setOutput('package-manifest', JSON.stringify(manifest));
|
||||||
core.setOutput('package-manifest-sha', publishedDigest);
|
core.setOutput('package-manifest-sha', publishedDigest);
|
||||||
|
|||||||
+17
-17
@@ -53,6 +53,23 @@ export async function run(): Promise<void> {
|
|||||||
|
|
||||||
const manifestDigest = ociContainer.sha256Digest(manifest)
|
const manifestDigest = ociContainer.sha256Digest(manifest)
|
||||||
|
|
||||||
|
// Attestations are not supported in GHES.
|
||||||
|
if (!options.isEnterprise) {
|
||||||
|
const attestation = await uploadAttestation(
|
||||||
|
manifestDigest,
|
||||||
|
semverTag.raw,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
if (attestation.digest !== undefined) {
|
||||||
|
core.info(`Uploaded attestation ${attestation.digest}`)
|
||||||
|
core.setOutput('attestation-manifest-sha', attestation.digest)
|
||||||
|
}
|
||||||
|
if (attestation.urls !== undefined && attestation.urls.length > 0) {
|
||||||
|
core.info(`Attestation URL: ${attestation.digest}`)
|
||||||
|
core.setOutput('attestation-url', attestation.urls[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const { packageURL, publishedDigest } = await ghcr.publishOCIArtifact(
|
const { packageURL, publishedDigest } = await ghcr.publishOCIArtifact(
|
||||||
options.token,
|
options.token,
|
||||||
options.containerRegistryUrl,
|
options.containerRegistryUrl,
|
||||||
@@ -69,23 +86,6 @@ export async function run(): Promise<void> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attestations are not currently supported in GHES.
|
|
||||||
if (!options.isEnterprise) {
|
|
||||||
const attestation = await uploadAttestation(
|
|
||||||
publishedDigest,
|
|
||||||
semverTag.raw,
|
|
||||||
options
|
|
||||||
)
|
|
||||||
if (attestation.digest !== undefined) {
|
|
||||||
core.info(`Uploaded attestation ${attestation.digest}`)
|
|
||||||
core.setOutput('attestation-manifest-sha', attestation.digest)
|
|
||||||
}
|
|
||||||
if (attestation.urls !== undefined && attestation.urls.length > 0) {
|
|
||||||
core.info(`Attestation URL: ${attestation.digest}`)
|
|
||||||
core.setOutput('attestation-url', attestation.urls[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
core.setOutput('package-url', packageURL.toString())
|
core.setOutput('package-url', packageURL.toString())
|
||||||
core.setOutput('package-manifest', JSON.stringify(manifest))
|
core.setOutput('package-manifest', JSON.stringify(manifest))
|
||||||
core.setOutput('package-manifest-sha', publishedDigest)
|
core.setOutput('package-manifest-sha', publishedDigest)
|
||||||
|
|||||||
Reference in New Issue
Block a user