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')
|
||||
})
|
||||
|
||||
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 () => {
|
||||
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(() => {
|
||||
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(() => {
|
||||
return {
|
||||
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 () => {
|
||||
const options = baseOptions()
|
||||
options.isEnterprise = true
|
||||
@@ -535,13 +546,6 @@ describe('run', () => {
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
await main.run()
|
||||
|
||||
|
||||
+6
-6
@@ -107636,13 +107636,9 @@ async function run() {
|
||||
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 manifestDigest = ociContainer.sha256Digest(manifest);
|
||||
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}`);
|
||||
}
|
||||
// Attestations are not currently supported in GHES.
|
||||
// Attestations are not supported in GHES.
|
||||
if (!options.isEnterprise) {
|
||||
const attestation = await uploadAttestation(publishedDigest, semverTag.raw, options);
|
||||
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);
|
||||
@@ -107652,6 +107648,10 @@ async function run() {
|
||||
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-manifest', JSON.stringify(manifest));
|
||||
core.setOutput('package-manifest-sha', publishedDigest);
|
||||
|
||||
+17
-17
@@ -53,6 +53,23 @@ export async function run(): Promise<void> {
|
||||
|
||||
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(
|
||||
options.token,
|
||||
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-manifest', JSON.stringify(manifest))
|
||||
core.setOutput('package-manifest-sha', publishedDigest)
|
||||
|
||||
Reference in New Issue
Block a user