diff --git a/__tests__/config.test.ts b/__tests__/config.test.ts
index a55944b..e60a74c 100644
--- a/__tests__/config.test.ts
+++ b/__tests__/config.test.ts
@@ -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'
diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts
index 6e68f36..703a39b 100644
--- a/__tests__/main.test.ts
+++ b/__tests__/main.test.ts
@@ -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',
diff --git a/badges/coverage.svg b/badges/coverage.svg
index 0005240..6746ba8 100644
--- a/badges/coverage.svg
+++ b/badges/coverage.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/dist/index.js b/dist/index.js
index d7dd128..b0cba62 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -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) {
diff --git a/src/api-client.ts b/src/api-client.ts
index 952f811..ac92e53 100644
--- a/src/api-client.ts
+++ b/src/api-client.ts
@@ -55,23 +55,3 @@ export async function getContainerRegistryURL(
const registryURL: URL = new URL(data.url)
return registryURL
}
-
-export async function getRepositoryVisibility(
- githubAPIURL: string
-): Promise {
- 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
-}
diff --git a/src/config.ts b/src/config.ts
index ef307ab..7d356cb 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -109,6 +109,14 @@ export async function resolvePublishActionOptions(): Promise {
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'
})
}