only write attestation for non-private repos
This commit is contained in:
@@ -162,6 +162,34 @@ describe('config.resolvePublishActionOptions', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('throws an error when returned repository id does not match env var', async () => {
|
||||
getInputMock.mockReturnValueOnce('token')
|
||||
getContainerRegistryURLMock.mockResolvedValue(ghcrUrl)
|
||||
getRepositoryMetadataMock.mockResolvedValue({
|
||||
visibility: 'public',
|
||||
ownerId: '12345',
|
||||
repoId: '54321'
|
||||
})
|
||||
|
||||
await expect(cfg.resolvePublishActionOptions()).rejects.toThrow(
|
||||
'Repository ID mismatch.'
|
||||
)
|
||||
})
|
||||
|
||||
it('throws an error when returned repository owner id does not match env var', async () => {
|
||||
getInputMock.mockReturnValueOnce('token')
|
||||
getContainerRegistryURLMock.mockResolvedValue(ghcrUrl)
|
||||
getRepositoryMetadataMock.mockResolvedValue({
|
||||
visibility: 'public',
|
||||
ownerId: '123124',
|
||||
repoId: 'repositoryId'
|
||||
})
|
||||
|
||||
await expect(cfg.resolvePublishActionOptions()).rejects.toThrow(
|
||||
'Repository Owner ID mismatch.'
|
||||
)
|
||||
})
|
||||
|
||||
it('returns options when all values are present', async () => {
|
||||
getInputMock.mockImplementation((name: string) => {
|
||||
expect(name).toBe('github-token')
|
||||
@@ -170,7 +198,9 @@ describe('config.resolvePublishActionOptions', () => {
|
||||
getContainerRegistryURLMock.mockResolvedValue(ghcrUrl)
|
||||
|
||||
getRepositoryMetadataMock.mockResolvedValue({
|
||||
visibility: 'public'
|
||||
visibility: 'public',
|
||||
repoId: 'repositoryId',
|
||||
ownerId: 'repositoryOwnerId'
|
||||
})
|
||||
|
||||
const options = await cfg.resolvePublishActionOptions()
|
||||
@@ -198,8 +228,11 @@ describe('config.resolvePublishActionOptions', () => {
|
||||
return 'token'
|
||||
})
|
||||
getContainerRegistryURLMock.mockResolvedValue(ghcrUrl)
|
||||
|
||||
getRepositoryMetadataMock.mockResolvedValue({
|
||||
visibility: 'public'
|
||||
visibility: 'public',
|
||||
repoId: 'repositoryId',
|
||||
ownerId: 'repositoryOwnerId'
|
||||
})
|
||||
|
||||
process.env.GITHUB_SERVER_URL = 'https://github-enterprise.com'
|
||||
|
||||
+86
-3
@@ -241,7 +241,7 @@ describe('run', () => {
|
||||
expect(setFailedMock).toHaveBeenCalledWith('Something went wrong')
|
||||
})
|
||||
|
||||
it('uploads the artifact, returns package metadata from GHCR, and creates an attestation in enterprise', async () => {
|
||||
it('uploads the artifact, returns package metadata from GHCR, and skips writing attestation in enterprise', async () => {
|
||||
const options = baseOptions()
|
||||
options.isEnterprise = true
|
||||
resolvePublishActionOptionsMock.mockReturnValue(options)
|
||||
@@ -299,7 +299,7 @@ describe('run', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('uploads the artifact, returns package metadata from GHCR, and creates an attestation in non-enterprise', async () => {
|
||||
it('uploads the artifact, returns package metadata from GHCR, and creates an attestation in non-enterprise for public repo', async () => {
|
||||
resolvePublishActionOptionsMock.mockReturnValue(baseOptions())
|
||||
|
||||
createTempDirMock.mockImplementation(() => {
|
||||
@@ -330,7 +330,90 @@ describe('run', () => {
|
||||
}
|
||||
})
|
||||
|
||||
generateAttestationMock.mockImplementation(async () => {
|
||||
generateAttestationMock.mockImplementation(async options => {
|
||||
expect(options).toHaveProperty('skipWrite', false)
|
||||
|
||||
return {
|
||||
attestationID: 'test-attestation-id',
|
||||
certificate: 'test',
|
||||
bundle: {
|
||||
mediaType: 'application/vnd.cncf.notary.v2+jwt',
|
||||
verificationMaterial: {
|
||||
publicKey: {
|
||||
hint: 'test-hint'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Run the action
|
||||
await main.run()
|
||||
|
||||
// Check the results
|
||||
expect(publishOCIArtifactMock).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Check outputs
|
||||
expect(setOutputMock).toHaveBeenCalledTimes(4)
|
||||
|
||||
expect(setOutputMock).toHaveBeenCalledWith(
|
||||
'package-url',
|
||||
'https://ghcr.io/v2/test-org/test-repo:1.2.3'
|
||||
)
|
||||
|
||||
expect(setOutputMock).toHaveBeenCalledWith(
|
||||
'package-manifest',
|
||||
expect.any(String)
|
||||
)
|
||||
|
||||
expect(setOutputMock).toHaveBeenCalledWith(
|
||||
'package-manifest-sha',
|
||||
'sha256:my-test-digest'
|
||||
)
|
||||
|
||||
expect(setOutputMock).toHaveBeenCalledWith(
|
||||
'attestation-id',
|
||||
'test-attestation-id'
|
||||
)
|
||||
})
|
||||
|
||||
it('uploads the artifact, returns package metadata from GHCR, and creates an attestation but skips storing it in non-enterprise for private repo', async () => {
|
||||
const opts = baseOptions()
|
||||
opts.repositoryVisibility = 'private'
|
||||
|
||||
resolvePublishActionOptionsMock.mockReturnValue(opts)
|
||||
|
||||
createTempDirMock.mockImplementation(() => {
|
||||
return 'stagingOrArchivesDir'
|
||||
})
|
||||
|
||||
stageActionFilesMock.mockImplementation(() => {})
|
||||
|
||||
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',
|
||||
manifestDigest: 'sha256:my-test-digest'
|
||||
}
|
||||
})
|
||||
|
||||
generateAttestationMock.mockImplementation(async options => {
|
||||
expect(options).toHaveProperty('skipWrite', true)
|
||||
|
||||
return {
|
||||
attestationID: 'test-attestation-id',
|
||||
certificate: 'test',
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="110" height="20" role="img" aria-label="Coverage: 93.8%"><title>Coverage: 93.8%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="110" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="63" height="20" fill="#555"/><rect x="63" width="47" height="20" fill="#4c1"/><rect width="110" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="325" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="530">Coverage</text><text x="325" y="140" transform="scale(.1)" fill="#fff" textLength="530">Coverage</text><text aria-hidden="true" x="855" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="370">93.8%</text><text x="855" y="140" transform="scale(.1)" fill="#fff" textLength="370">93.8%</text></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="116" height="20" role="img" aria-label="Coverage: 96.63%"><title>Coverage: 96.63%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="116" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="63" height="20" fill="#555"/><rect x="63" width="53" height="20" fill="#4c1"/><rect width="116" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="325" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="530">Coverage</text><text x="325" y="140" transform="scale(.1)" fill="#fff" textLength="530">Coverage</text><text aria-hidden="true" x="885" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">96.63%</text><text x="885" y="140" transform="scale(.1)" fill="#fff" textLength="430">96.63%</text></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
+13
-14
@@ -99284,7 +99284,7 @@ ZipStream.prototype.finalize = function() {
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.getRepositoryVisibility = exports.getContainerRegistryURL = exports.getRepositoryMetadata = void 0;
|
||||
exports.getContainerRegistryURL = exports.getRepositoryMetadata = void 0;
|
||||
async function getRepositoryMetadata(githubAPIURL, repository, token) {
|
||||
const response = await fetch(`${githubAPIURL}/repos/${repository}`, {
|
||||
method: 'GET',
|
||||
@@ -99321,18 +99321,6 @@ async function getContainerRegistryURL(githubAPIURL) {
|
||||
return registryURL;
|
||||
}
|
||||
exports.getContainerRegistryURL = getContainerRegistryURL;
|
||||
async function getRepositoryVisibility(githubAPIURL) {
|
||||
const response = await fetch(`${githubAPIURL}/`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch repository metadata due to bad status code: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
if (!data.full_name) {
|
||||
throw new Error(`Failed to fetch repository metadata: unexpected response format`);
|
||||
}
|
||||
return data.full_name;
|
||||
}
|
||||
exports.getRepositoryVisibility = getRepositoryVisibility;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -99426,6 +99414,12 @@ async function resolvePublishActionOptions() {
|
||||
if (repoMetadata.visibility === '') {
|
||||
throw new Error(`Could not find repository visibility.`);
|
||||
}
|
||||
if (repoMetadata.repoId !== repositoryId) {
|
||||
throw new Error(`Repository ID mismatch.`);
|
||||
}
|
||||
if (repoMetadata.ownerId !== repositoryOwnerId) {
|
||||
throw new Error(`Repository Owner ID mismatch.`);
|
||||
}
|
||||
const repositoryVisibility = repoMetadata.visibility;
|
||||
return {
|
||||
event,
|
||||
@@ -99816,6 +99810,7 @@ async function run() {
|
||||
core.setOutput('package-url', packageURL.toString());
|
||||
core.setOutput('package-manifest', JSON.stringify(manifest));
|
||||
core.setOutput('package-manifest-sha', manifestDigest);
|
||||
// Attestations are not currently supported in GHES.
|
||||
if (!options.isEnterprise) {
|
||||
const attestation = await generateAttestation(manifestDigest, semverTag.raw, options);
|
||||
if (attestation.attestationID !== undefined) {
|
||||
@@ -99854,7 +99849,11 @@ async function generateAttestation(manifestDigest, semverTag, options) {
|
||||
subjectDigest: { sha256: subjectDigest },
|
||||
token: options.token,
|
||||
sigstore: 'github',
|
||||
skipWrite: false // TODO: Attestation storage is only supported for public repositories or repositories which belong to a GitHub Enterprise Cloud account
|
||||
// Attestation storage is only supported for public repositories or repositories which belong to a GitHub Enterprise Cloud account.
|
||||
// See: https://github.com/actions/toolkit/tree/main/packages/attest#storage
|
||||
// Since internal repos can only be owned by Enterprises, we'll use this visibility as a proxy for "owned by a GitHub Enterprise Cloud account."
|
||||
// See: https://docs.github.com/en/enterprise-cloud@latest/repositories/creating-and-managing-repositories/about-repositories#about-internal-repositories
|
||||
skipWrite: options.repositoryVisibility === 'private'
|
||||
});
|
||||
}
|
||||
function removePrefix(str, prefix) {
|
||||
|
||||
@@ -55,23 +55,3 @@ export async function getContainerRegistryURL(
|
||||
const registryURL: URL = new URL(data.url)
|
||||
return registryURL
|
||||
}
|
||||
|
||||
export async function getRepositoryVisibility(
|
||||
githubAPIURL: string
|
||||
): Promise<string> {
|
||||
const response = await fetch(`${githubAPIURL}/`)
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch repository metadata due to bad status code: ${response.status}`
|
||||
)
|
||||
}
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.full_name) {
|
||||
throw new Error(
|
||||
`Failed to fetch repository metadata: unexpected response format`
|
||||
)
|
||||
}
|
||||
|
||||
return data.full_name
|
||||
}
|
||||
|
||||
@@ -109,6 +109,14 @@ export async function resolvePublishActionOptions(): Promise<PublishActionOption
|
||||
throw new Error(`Could not find repository visibility.`)
|
||||
}
|
||||
|
||||
if (repoMetadata.repoId !== repositoryId) {
|
||||
throw new Error(`Repository ID mismatch.`)
|
||||
}
|
||||
|
||||
if (repoMetadata.ownerId !== repositoryOwnerId) {
|
||||
throw new Error(`Repository Owner ID mismatch.`)
|
||||
}
|
||||
|
||||
const repositoryVisibility = repoMetadata.visibility
|
||||
|
||||
return {
|
||||
|
||||
+6
-1
@@ -57,6 +57,7 @@ export async function run(): Promise<void> {
|
||||
core.setOutput('package-manifest', JSON.stringify(manifest))
|
||||
core.setOutput('package-manifest-sha', manifestDigest)
|
||||
|
||||
// Attestations are not currently supported in GHES.
|
||||
if (!options.isEnterprise) {
|
||||
const attestation = await generateAttestation(
|
||||
manifestDigest,
|
||||
@@ -106,7 +107,11 @@ async function generateAttestation(
|
||||
subjectDigest: { sha256: subjectDigest },
|
||||
token: options.token,
|
||||
sigstore: 'github',
|
||||
skipWrite: false // TODO: Attestation storage is only supported for public repositories or repositories which belong to a GitHub Enterprise Cloud account
|
||||
// Attestation storage is only supported for public repositories or repositories which belong to a GitHub Enterprise Cloud account.
|
||||
// See: https://github.com/actions/toolkit/tree/main/packages/attest#storage
|
||||
// Since internal repos can only be owned by Enterprises, we'll use this visibility as a proxy for "owned by a GitHub Enterprise Cloud account."
|
||||
// See: https://docs.github.com/en/enterprise-cloud@latest/repositories/creating-and-managing-repositories/about-repositories#about-internal-repositories
|
||||
skipWrite: options.repositoryVisibility === 'private'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user