diff --git a/badges/coverage.svg b/badges/coverage.svg
index f6ea690..8918069 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 8f74b27..571e965 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -107430,31 +107430,28 @@ const fsHelper = __importStar(__nccwpck_require__(76642));
// Publish the OCI artifact and return the URL where it can be downloaded
async function publishOCIArtifact(token, registry, repository, semver, zipFile, tarFile, manifest) {
const b64Token = Buffer.from(token).toString('base64');
- const checkBlobEndpoint = new URL(`v2/${repository}/blobs/`, registry).toString();
- const uploadBlobEndpoint = new URL(`v2/${repository}/blobs/uploads/`, registry).toString();
- const manifestEndpoint = new URL(`v2/${repository}/manifests/${semver}`, registry).toString();
core.info(`Creating GHCR package for release with semver:${semver} with path:"${zipFile.path}" and "${tarFile.path}".`);
const layerUploads = manifest.layers.map(async (layer) => {
switch (layer.mediaType) {
case 'application/vnd.github.actions.package.layer.v1.tar+gzip':
- return uploadLayer(layer, tarFile, registry, checkBlobEndpoint, uploadBlobEndpoint, b64Token);
+ return uploadLayer(layer, fsHelper.readFileContents(zipFile.path), registry, repository, b64Token);
case 'application/vnd.github.actions.package.layer.v1.zip':
- return uploadLayer(layer, zipFile, registry, checkBlobEndpoint, uploadBlobEndpoint, b64Token);
+ return uploadLayer(layer, fsHelper.readFileContents(zipFile.path), registry, repository, b64Token);
case 'application/vnd.oci.empty.v1+json':
- return uploadLayer(layer, { path: '', size: 2, sha256: layer.digest }, registry, checkBlobEndpoint, uploadBlobEndpoint, b64Token);
+ return uploadLayer(layer, Buffer.from('{}'), registry, repository, b64Token);
default:
throw new Error(`Unknown media type ${layer.mediaType}`);
}
});
await Promise.all(layerUploads);
- const digest = await uploadManifest(JSON.stringify(manifest), manifestEndpoint, b64Token);
+ const digest = await uploadManifest(JSON.stringify(manifest), manifest.mediaType, registry, repository, semver, b64Token);
return {
packageURL: new URL(`${repository}:${semver}`, registry),
publishedDigest: digest
};
}
-async function uploadLayer(layer, file, registryURL, checkBlobEndpoint, uploadBlobEndpoint, b64Token) {
- const checkExistsResponse = await fetchWithDebug(checkBlobEndpoint + layer.digest, {
+async function uploadLayer(layer, data, registryURL, repository, b64Token) {
+ const checkExistsResponse = await fetchWithDebug(checkBlobEndpoint(registryURL, repository, layer.digest), {
method: 'HEAD',
headers: {
Authorization: `Bearer ${b64Token}`
@@ -107469,7 +107466,8 @@ async function uploadLayer(layer, file, registryURL, checkBlobEndpoint, uploadBl
throw new Error(await errorMessageForFailedRequest(`check blob (${layer.digest}) exists`, checkExistsResponse));
}
core.info(`Uploading layer ${layer.digest}.`);
- const initiateUploadResponse = await fetchWithDebug(uploadBlobEndpoint, {
+ const initiateUploadBlobURL = uploadBlobEndpoint(registryURL, repository);
+ const initiateUploadResponse = await fetchWithDebug(initiateUploadBlobURL, {
method: 'POST',
headers: {
Authorization: `Bearer ${b64Token}`
@@ -107481,18 +107479,10 @@ async function uploadLayer(layer, file, registryURL, checkBlobEndpoint, uploadBl
}
const locationResponseHeader = initiateUploadResponse.headers.get('location');
if (locationResponseHeader === undefined) {
- throw new Error(`No location header in response from upload post ${uploadBlobEndpoint} for layer ${layer.digest}`);
+ throw new Error(`No location header in response from upload post ${initiateUploadBlobURL} for layer ${layer.digest}`);
}
const pathname = `${locationResponseHeader}?digest=${layer.digest}`;
const uploadBlobUrl = new URL(pathname, registryURL).toString();
- // TODO: must we handle the empty config layer? Maybe we can just skip calling this at all
- let data;
- if (layer.mediaType === 'application/vnd.oci.empty.v1+json') {
- data = Buffer.from('{}');
- }
- else {
- data = fsHelper.readFileContents(file.path);
- }
const putResponse = await fetchWithDebug(uploadBlobUrl, {
method: 'PUT',
headers: {
@@ -107508,13 +107498,14 @@ async function uploadLayer(layer, file, registryURL, checkBlobEndpoint, uploadBl
}
}
// Uploads the manifest and returns the digest returned by GHCR
-async function uploadManifest(manifestJSON, manifestEndpoint, b64Token) {
- core.info(`Uploading manifest to ${manifestEndpoint}.`);
- const putResponse = await fetchWithDebug(manifestEndpoint, {
+async function uploadManifest(manifestJSON, manifestMediaType, registry, repository, version, b64Token) {
+ const manifestUrl = manifestEndpoint(registry, repository, version);
+ core.info(`Uploading manifest to ${manifestUrl}.`);
+ const putResponse = await fetchWithDebug(manifestUrl, {
method: 'PUT',
headers: {
Authorization: `Bearer ${b64Token}`,
- 'Content-Type': 'application/vnd.oci.image.manifest.v1+json'
+ 'Content-Type': manifestMediaType
},
body: manifestJSON
});
@@ -107523,7 +107514,7 @@ async function uploadManifest(manifestJSON, manifestEndpoint, b64Token) {
}
const digestResponseHeader = putResponse.headers.get('docker-content-digest');
if (digestResponseHeader === undefined || digestResponseHeader === null) {
- throw new Error(`No digest header in response from PUT manifest ${manifestEndpoint}`);
+ throw new Error(`No digest header in response from PUT manifest ${manifestUrl}`);
}
return digestResponseHeader;
}
@@ -107561,6 +107552,16 @@ function isGHCRError(obj) {
'message' in obj &&
typeof obj.message === 'string');
}
+function checkBlobEndpoint(registry, repository, digest) {
+ return new URL(`v2/${repository}/blobs/${digest}`, registry).toString();
+}
+function uploadBlobEndpoint(registry, repository) {
+ return new URL(`v2/${repository}/blobs/uploads/`, registry).toString();
+}
+function manifestEndpoint(registry, repository, version) {
+ return new URL(`v2/${repository}/manifests/${version}`, registry).toString();
+}
+// TODO: Add retries with backoff
const fetchWithDebug = async (url, config = {}) => {
core.debug(`Request from ${url} with config: ${JSON.stringify(config)}`);
try {
diff --git a/src/ghcr-client.ts b/src/ghcr-client.ts
index cf8c570..e943bbe 100644
--- a/src/ghcr-client.ts
+++ b/src/ghcr-client.ts
@@ -15,19 +15,6 @@ export async function publishOCIArtifact(
): Promise<{ packageURL: URL; publishedDigest: string }> {
const b64Token = Buffer.from(token).toString('base64')
- const checkBlobEndpoint = new URL(
- `v2/${repository}/blobs/`,
- registry
- ).toString()
- const uploadBlobEndpoint = new URL(
- `v2/${repository}/blobs/uploads/`,
- registry
- ).toString()
- const manifestEndpoint = new URL(
- `v2/${repository}/manifests/${semver}`,
- registry
- ).toString()
-
core.info(
`Creating GHCR package for release with semver:${semver} with path:"${zipFile.path}" and "${tarFile.path}".`
)
@@ -37,28 +24,25 @@ export async function publishOCIArtifact(
case 'application/vnd.github.actions.package.layer.v1.tar+gzip':
return uploadLayer(
layer,
- tarFile,
+ fsHelper.readFileContents(zipFile.path),
registry,
- checkBlobEndpoint,
- uploadBlobEndpoint,
+ repository,
b64Token
)
case 'application/vnd.github.actions.package.layer.v1.zip':
return uploadLayer(
layer,
- zipFile,
+ fsHelper.readFileContents(zipFile.path),
registry,
- checkBlobEndpoint,
- uploadBlobEndpoint,
+ repository,
b64Token
)
case 'application/vnd.oci.empty.v1+json':
return uploadLayer(
layer,
- { path: '', size: 2, sha256: layer.digest },
+ Buffer.from('{}'),
registry,
- checkBlobEndpoint,
- uploadBlobEndpoint,
+ repository,
b64Token
)
default:
@@ -70,7 +54,10 @@ export async function publishOCIArtifact(
const digest = await uploadManifest(
JSON.stringify(manifest),
- manifestEndpoint,
+ manifest.mediaType,
+ registry,
+ repository,
+ semver,
b64Token
)
@@ -82,14 +69,13 @@ export async function publishOCIArtifact(
async function uploadLayer(
layer: ociContainer.Descriptor,
- file: FileMetadata,
+ data: Buffer,
registryURL: URL,
- checkBlobEndpoint: string,
- uploadBlobEndpoint: string,
+ repository: string,
b64Token: string
): Promise {
const checkExistsResponse = await fetchWithDebug(
- checkBlobEndpoint + layer.digest,
+ checkBlobEndpoint(registryURL, repository, layer.digest),
{
method: 'HEAD',
headers: {
@@ -117,7 +103,9 @@ async function uploadLayer(
core.info(`Uploading layer ${layer.digest}.`)
- const initiateUploadResponse = await fetchWithDebug(uploadBlobEndpoint, {
+ const initiateUploadBlobURL = uploadBlobEndpoint(registryURL, repository)
+
+ const initiateUploadResponse = await fetchWithDebug(initiateUploadBlobURL, {
method: 'POST',
headers: {
Authorization: `Bearer ${b64Token}`
@@ -137,21 +125,13 @@ async function uploadLayer(
const locationResponseHeader = initiateUploadResponse.headers.get('location')
if (locationResponseHeader === undefined) {
throw new Error(
- `No location header in response from upload post ${uploadBlobEndpoint} for layer ${layer.digest}`
+ `No location header in response from upload post ${initiateUploadBlobURL} for layer ${layer.digest}`
)
}
const pathname = `${locationResponseHeader}?digest=${layer.digest}`
const uploadBlobUrl = new URL(pathname, registryURL).toString()
- // TODO: must we handle the empty config layer? Maybe we can just skip calling this at all
- let data: Buffer
- if (layer.mediaType === 'application/vnd.oci.empty.v1+json') {
- data = Buffer.from('{}')
- } else {
- data = fsHelper.readFileContents(file.path)
- }
-
const putResponse = await fetchWithDebug(uploadBlobUrl, {
method: 'PUT',
headers: {
@@ -176,16 +156,21 @@ async function uploadLayer(
// Uploads the manifest and returns the digest returned by GHCR
async function uploadManifest(
manifestJSON: string,
- manifestEndpoint: string,
+ manifestMediaType: string,
+ registry: URL,
+ repository: string,
+ version: string,
b64Token: string
): Promise {
- core.info(`Uploading manifest to ${manifestEndpoint}.`)
+ const manifestUrl = manifestEndpoint(registry, repository, version)
- const putResponse = await fetchWithDebug(manifestEndpoint, {
+ core.info(`Uploading manifest to ${manifestUrl}.`)
+
+ const putResponse = await fetchWithDebug(manifestUrl, {
method: 'PUT',
headers: {
Authorization: `Bearer ${b64Token}`,
- 'Content-Type': 'application/vnd.oci.image.manifest.v1+json'
+ 'Content-Type': manifestMediaType
},
body: manifestJSON
})
@@ -199,7 +184,7 @@ async function uploadManifest(
const digestResponseHeader = putResponse.headers.get('docker-content-digest')
if (digestResponseHeader === undefined || digestResponseHeader === null) {
throw new Error(
- `No digest header in response from PUT manifest ${manifestEndpoint}`
+ `No digest header in response from PUT manifest ${manifestUrl}`
)
}
@@ -257,6 +242,27 @@ function isGHCRError(obj: unknown): boolean {
)
}
+function checkBlobEndpoint(
+ registry: URL,
+ repository: string,
+ digest: string
+): string {
+ return new URL(`v2/${repository}/blobs/${digest}`, registry).toString()
+}
+
+function uploadBlobEndpoint(registry: URL, repository: string): string {
+ return new URL(`v2/${repository}/blobs/uploads/`, registry).toString()
+}
+
+function manifestEndpoint(
+ registry: URL,
+ repository: string,
+ version: string
+): string {
+ return new URL(`v2/${repository}/manifests/${version}`, registry).toString()
+}
+
+// TODO: Add retries with backoff
const fetchWithDebug = async (
url: string,
config: RequestInit = {}