diff --git a/__tests__/api-client.test.ts b/__tests__/api-client.test.ts
index d10f022..5ed5a00 100644
--- a/__tests__/api-client.test.ts
+++ b/__tests__/api-client.test.ts
@@ -18,10 +18,20 @@ afterEach(() => {
describe('getRepositoryMetadata', () => {
it('returns repository metadata when the fetch response is ok', async () => {
fetchMock.mockResolvedValueOnce(
- new Response(JSON.stringify({ id: '123', owner: { id: '456' } }))
+ new Response(
+ JSON.stringify({
+ id: '123',
+ owner: { id: '456' },
+ visibility: 'public'
+ })
+ )
)
const result = await getRepositoryMetadata(url, 'repository', 'token')
- expect(result).toEqual({ repoId: '123', ownerId: '456' })
+ expect(result).toEqual({
+ repoId: '123',
+ ownerId: '456',
+ visibility: 'public'
+ })
})
it('throws an error when the fetch errors', async () => {
diff --git a/__tests__/config.test.ts b/__tests__/config.test.ts
index 14a293d..a55944b 100644
--- a/__tests__/config.test.ts
+++ b/__tests__/config.test.ts
@@ -4,6 +4,7 @@ import * as cfg from '../src/config'
import * as apiClient from '../src/api-client'
let getContainerRegistryURLMock: jest.SpyInstance
+let getRepositoryMetadataMock: jest.SpyInstance
let getInputMock: jest.SpyInstance
const ghcrUrl = new URL('https://ghcr.io')
@@ -14,6 +15,10 @@ describe('config.resolvePublishActionOptions', () => {
.spyOn(apiClient, 'getContainerRegistryURL')
.mockImplementation()
+ getRepositoryMetadataMock = jest
+ .spyOn(apiClient, 'getRepositoryMetadata')
+ .mockImplementation()
+
getInputMock = jest.spyOn(core, 'getInput').mockImplementation()
configureEventContext()
@@ -133,6 +138,30 @@ describe('config.resolvePublishActionOptions', () => {
)
})
+ it('throws an error when getting the repository metadata fails', async () => {
+ getInputMock.mockReturnValueOnce('token')
+ getContainerRegistryURLMock.mockResolvedValue(ghcrUrl)
+ getRepositoryMetadataMock.mockRejectedValue(
+ new Error('Failed to get repository metadata')
+ )
+
+ await expect(cfg.resolvePublishActionOptions()).rejects.toThrow(
+ 'Failed to get repository metadata'
+ )
+ })
+
+ it('throws an error when returned repository visibility is empty', async () => {
+ getInputMock.mockReturnValueOnce('token')
+ getContainerRegistryURLMock.mockResolvedValue(ghcrUrl)
+ getRepositoryMetadataMock.mockResolvedValue({
+ visibility: ''
+ })
+
+ await expect(cfg.resolvePublishActionOptions()).rejects.toThrow(
+ 'Could not find repository visibility.'
+ )
+ })
+
it('returns options when all values are present', async () => {
getInputMock.mockImplementation((name: string) => {
expect(name).toBe('github-token')
@@ -140,6 +169,10 @@ describe('config.resolvePublishActionOptions', () => {
})
getContainerRegistryURLMock.mockResolvedValue(ghcrUrl)
+ getRepositoryMetadataMock.mockResolvedValue({
+ visibility: 'public'
+ })
+
const options = await cfg.resolvePublishActionOptions()
expect(options).toEqual({
@@ -150,6 +183,7 @@ describe('config.resolvePublishActionOptions', () => {
apiBaseUrl: 'apiBaseUrl',
runnerTempDir: 'runnerTempDir',
sha: 'sha',
+ repositoryVisibility: 'public',
repositoryId: 'repositoryId',
repositoryOwnerId: 'repositoryOwnerId',
isEnterprise: false,
@@ -164,6 +198,9 @@ describe('config.resolvePublishActionOptions', () => {
return 'token'
})
getContainerRegistryURLMock.mockResolvedValue(ghcrUrl)
+ getRepositoryMetadataMock.mockResolvedValue({
+ visibility: 'public'
+ })
process.env.GITHUB_SERVER_URL = 'https://github-enterprise.com'
@@ -181,7 +218,8 @@ describe('config.resolvePublishActionOptions', () => {
repositoryOwnerId: 'repositoryOwnerId',
isEnterprise: true,
containerRegistryUrl: ghcrUrl,
- token: 'token'
+ token: 'token',
+ repositoryVisibility: 'public'
})
})
})
@@ -200,7 +238,8 @@ describe('config.serializeOptions', () => {
repositoryOwnerId: 'repositoryOwnerId',
isEnterprise: false,
containerRegistryUrl: ghcrUrl,
- token: 'token'
+ token: 'token',
+ repositoryVisibility: 'public'
}
const serialized = cfg.serializeOptions(options)
diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts
index a56b34a..6e68f36 100644
--- a/__tests__/main.test.ts
+++ b/__tests__/main.test.ts
@@ -389,6 +389,7 @@ function baseOptions(): cfg.PublishActionOptions {
isEnterprise: false,
containerRegistryUrl: ghcrUrl,
token: 'token',
- ref: 'refs/tags/v1.2.3'
+ ref: 'refs/tags/v1.2.3',
+ repositoryVisibility: 'public'
}
}
diff --git a/badges/coverage.svg b/badges/coverage.svg
index cfc625f..0005240 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 b48e3f4..d7dd128 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.getContainerRegistryURL = exports.getRepositoryMetadata = void 0;
+exports.getRepositoryVisibility = exports.getContainerRegistryURL = exports.getRepositoryMetadata = void 0;
async function getRepositoryMetadata(githubAPIURL, repository, token) {
const response = await fetch(`${githubAPIURL}/repos/${repository}`, {
method: 'GET',
@@ -99301,7 +99301,11 @@ async function getRepositoryMetadata(githubAPIURL, repository, token) {
if (!data.id || !data.owner.id) {
throw new Error(`Failed to fetch repository metadata: unexpected response format`);
}
- return { repoId: String(data.id), ownerId: String(data.owner.id) };
+ return {
+ repoId: String(data.id),
+ ownerId: String(data.owner.id),
+ visibility: String(data.visibility)
+ };
}
exports.getRepositoryMetadata = getRepositoryMetadata;
async function getContainerRegistryURL(githubAPIURL) {
@@ -99317,6 +99321,18 @@ 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;
/***/ }),
@@ -99406,6 +99422,11 @@ async function resolvePublishActionOptions() {
const containerRegistryUrl = await apiClient.getContainerRegistryURL(apiBaseUrl);
const isEnterprise = !githubServerUrl.includes('https://github.com') &&
!githubServerUrl.endsWith('.ghe.com');
+ const repoMetadata = await apiClient.getRepositoryMetadata(apiBaseUrl, nameWithOwner, token);
+ if (repoMetadata.visibility === '') {
+ throw new Error(`Could not find repository visibility.`);
+ }
+ const repositoryVisibility = repoMetadata.visibility;
return {
event,
ref,
@@ -99417,6 +99438,7 @@ async function resolvePublishActionOptions() {
sha,
containerRegistryUrl,
isEnterprise,
+ repositoryVisibility,
repositoryId,
repositoryOwnerId
};
@@ -99825,12 +99847,13 @@ function parseSemverTagFromRef(ref) {
// Generate an attestation using the actions toolkit
// Subject name will contain the repo/package name and the tag name
async function generateAttestation(manifestDigest, semverTag, options) {
- const subjectName = `${options.nameWithOwner}_${semverTag}`;
+ const subjectName = `${options.nameWithOwner}@${semverTag}`;
const subjectDigest = removePrefix(manifestDigest, 'sha256:');
return await attest.attestProvenance({
subjectName,
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
});
}
diff --git a/src/api-client.ts b/src/api-client.ts
index e415992..952f811 100644
--- a/src/api-client.ts
+++ b/src/api-client.ts
@@ -2,7 +2,7 @@ export async function getRepositoryMetadata(
githubAPIURL: string,
repository: string,
token: string
-): Promise<{ repoId: string; ownerId: string }> {
+): Promise<{ repoId: string; ownerId: string; visibility: string }> {
const response = await fetch(`${githubAPIURL}/repos/${repository}`, {
method: 'GET',
headers: {
@@ -26,7 +26,11 @@ export async function getRepositoryMetadata(
)
}
- return { repoId: String(data.id), ownerId: String(data.owner.id) }
+ return {
+ repoId: String(data.id),
+ ownerId: String(data.owner.id),
+ visibility: String(data.visibility)
+ }
}
export async function getContainerRegistryURL(
@@ -51,3 +55,23 @@ 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 90c74e6..ef307ab 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -20,6 +20,8 @@ export interface PublishActionOptions {
runnerTempDir: string
// Whether this action is running in enterprise, determined from the github URL
isEnterprise: boolean
+ // The visibility of the action repository ("public", "internal" or "private")
+ repositoryVisibility: string
// The repository ID of the action repository
repositoryId: string
// The owner ID of the action repository
@@ -97,6 +99,18 @@ export async function resolvePublishActionOptions(): Promise {
- const subjectName = `${options.nameWithOwner}_${semverTag}`
+ const subjectName = `${options.nameWithOwner}@${semverTag}`
const subjectDigest = removePrefix(manifestDigest, 'sha256:')
return await attest.attestProvenance({
subjectName,
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
})
}