This commit is contained in:
boxofyellow
2024-02-02 12:58:40 -05:00
committed by Edwin Sirko
parent c41316d7a8
commit 5e2391735e
23 changed files with 986 additions and 139 deletions
-3
View File
@@ -8,8 +8,5 @@ jobs:
steps: steps:
- name: Checking out! - name: Checking out!
uses: actions/checkout@v4 uses: actions/checkout@v4
- uses: actions/[email protected]
with:
node-version: 16
- name: Publish action package - name: Publish action package
uses: ./ uses: ./
+9 -3
View File
@@ -74,8 +74,6 @@ describe('createArchives', () => {
expect(zipSHA).toEqual(systemZipHash) expect(zipSHA).toEqual(systemZipHash)
expect(tarSHA).toEqual(systemTarHash) expect(tarSHA).toEqual(systemTarHash)
}) })
// TODO: Test the failure cases
}) })
describe('createTempDir', () => { describe('createTempDir', () => {
@@ -189,11 +187,15 @@ describe('bundleFilesintoDirectory', () => {
// Create some test files and folders in the sourceDir // Create some test files and folders in the sourceDir
const file1 = `${sourceDir}/file1.txt` const file1 = `${sourceDir}/file1.txt`
const folder1 = `${sourceDir}/folder1` const folder1 = `${sourceDir}/folder1`
const file2 = `${folder1}/file3.txt` const file2 = `${folder1}/file2.txt`
const folder2 = `${folder1}/folder2`
const file3 = `${folder2}/file3.txt`
fs.mkdirSync(folder1) fs.mkdirSync(folder1)
fs.mkdirSync(folder2)
fs.writeFileSync(file1, fileContent) fs.writeFileSync(file1, fileContent)
fs.writeFileSync(file2, fileContent) fs.writeFileSync(file2, fileContent)
fs.writeFileSync(file3, fileContent)
// Bundle the files and folders into the targetDir // Bundle the files and folders into the targetDir
fsHelper.bundleFilesintoDirectory([file1, folder1], targetDir) fsHelper.bundleFilesintoDirectory([file1, folder1], targetDir)
@@ -206,6 +208,10 @@ describe('bundleFilesintoDirectory', () => {
expect(fs.existsSync(file2)).toEqual(true) expect(fs.existsSync(file2)).toEqual(true)
expect(fsHelper.readFileContents(file2).toString()).toEqual(fileContent) expect(fsHelper.readFileContents(file2).toString()).toEqual(fileContent)
expect(fs.existsSync(`${targetDir}/folder1/folder2`)).toEqual(true)
expect(fs.existsSync(file3)).toEqual(true)
expect(fsHelper.readFileContents(file3).toString()).toEqual(fileContent)
}) })
it('throws an error if a file or directory does not exist', () => { it('throws an error if a file or directory does not exist', () => {
+107 -32
View File
@@ -9,20 +9,21 @@ let axiosPostMock: jest.SpyInstance
let axiosPutMock: jest.SpyInstance let axiosPutMock: jest.SpyInstance
let axiosHeadMock: jest.SpyInstance let axiosHeadMock: jest.SpyInstance
const token = '1234567890' const token = 'test-token'
const registry = new URL('https://ghcr.io') const registry = new URL('https://ghcr.io')
const repository = 'test/test' const repository = 'test-org/test-repo'
const releaseId = '1234567890' const releaseId = 'test-release-id'
const semver = '1.0.0' const semver = '1.2.3'
const genericSha = '1234567890' // We should look at using different shas here to catch bug, but that make location validation harder
const zipFile: fsHelper.FileMetadata = { const zipFile: fsHelper.FileMetadata = {
path: 'test-repo-1.0.0.zip', path: `test-repo-${semver}.zip`,
size: 100, size: 123,
sha256: '1234567890' sha256: genericSha
} }
const tarFile: fsHelper.FileMetadata = { const tarFile: fsHelper.FileMetadata = {
path: 'test-repo-1.0.0.tar.gz', path: `test-repo-${semver}.tar.gz`,
size: 100, size: 456,
sha256: '1234567890' sha256: genericSha
} }
const testManifest: ociContainer.Manifest = { const testManifest: ociContainer.Manifest = {
@@ -50,25 +51,25 @@ const testManifest: ociContainer.Manifest = {
}, },
{ {
mediaType: 'application/vnd.github.actions.package.layer.v1.tar+gzip', mediaType: 'application/vnd.github.actions.package.layer.v1.tar+gzip',
size: 100, size: tarFile.size,
digest: 'sha256:1234567890', digest: `sha256:${tarFile.sha256}`,
annotations: { annotations: {
'org.opencontainers.image.title': 'test-repo-1.0.0.tar.gz' 'org.opencontainers.image.title': tarFile.path
} }
}, },
{ {
mediaType: 'application/vnd.github.actions.package.layer.v1.zip', mediaType: 'application/vnd.github.actions.package.layer.v1.zip',
size: 100, size: zipFile.size,
digest: 'sha256:1234567890', digest: `sha256:${zipFile.sha256}`,
annotations: { annotations: {
'org.opencontainers.image.title': 'test-repo-1.0.0.zip' 'org.opencontainers.image.title': zipFile.path
} }
} }
], ],
annotations: { annotations: {
'org.opencontainers.image.created': '2021-01-01T00:00:00.000Z', 'org.opencontainers.image.created': '2021-01-01T00:00:00.000Z',
'action.tar.gz.digest': '1234567890', 'action.tar.gz.digest': tarFile.sha256,
'action.zip.digest': '1234567890', 'action.zip.digest': zipFile.sha256,
'com.github.package.type': 'actions_oci_pkg' 'com.github.package.type': 'actions_oci_pkg'
} }
} }
@@ -101,7 +102,7 @@ describe('publishOCIArtifact', () => {
return { return {
status: 202, status: 202,
headers: { headers: {
location: 'https://ghcr.io/v2/test/test/blobs/uploads/1234567890' location: `https://ghcr.io/v2/${repository}/blobs/uploads/${genericSha}`
} }
} }
}) })
@@ -133,11 +134,9 @@ describe('publishOCIArtifact', () => {
expect(axiosHeadMock).toHaveBeenCalledTimes(3) expect(axiosHeadMock).toHaveBeenCalledTimes(3)
expect(axiosPostMock).toHaveBeenCalledTimes(3) expect(axiosPostMock).toHaveBeenCalledTimes(3)
expect(axiosPutMock).toHaveBeenCalledTimes(4) expect(axiosPutMock).toHaveBeenCalledTimes(4)
// TODO: Check that the base64 encoded token is sent in the Authorization header
}) })
it('skips uploading layer blobs that already exist', async () => { it('skips uploading all layer blobs when they all already exist', async () => {
// Simulate all blobs already existing // Simulate all blobs already existing
axiosHeadMock.mockImplementation(async (url, config) => { axiosHeadMock.mockImplementation(async (url, config) => {
validateRequestConfig(200, url, config) validateRequestConfig(200, url, config)
@@ -152,7 +151,7 @@ describe('publishOCIArtifact', () => {
return { return {
status: 202, status: 202,
headers: { headers: {
location: 'https://ghcr.io/v2/test/test/blobs/uploads/1234567890' location: `https://ghcr.io/v2/${repository}/blobs/uploads/${genericSha}`
} }
} }
}) })
@@ -187,6 +186,68 @@ describe('publishOCIArtifact', () => {
expect(axiosPutMock).toHaveBeenCalledTimes(1) expect(axiosPutMock).toHaveBeenCalledTimes(1)
}) })
it('skips uploading layer blobs that already exist', async () => {
// Simulate some blobs already existing
let count = 0
axiosHeadMock.mockImplementation(async (url, config) => {
count++
if (count === 1) {
// report the first blob as being there
validateRequestConfig(200, url, config)
return {
status: 200
}
} else {
// report all others are missing
validateRequestConfig(404, url, config)
return {
status: 404
}
}
})
// Simulate successful initiation of uploads for all blobs & return location
axiosPostMock.mockImplementation(async (url, data, config) => {
validateRequestConfig(202, url, config)
return {
status: 202,
headers: {
location: `https://ghcr.io/v2/${repository}/blobs/uploads/${genericSha}`
}
}
})
// Simulate successful reading of all the files
fsReadFileSyncMock.mockImplementation(() => {
return Buffer.from('test')
})
// Simulate successful upload of all blobs & then the manifest
axiosPutMock.mockImplementation(async (url, data, config) => {
validateRequestConfig(201, url, config)
return {
status: 201
}
})
await publishOCIArtifact(
token,
registry,
repository,
releaseId,
semver,
zipFile,
tarFile,
testManifest
)
// We should only head all the blobs and then upload the missing blobs and manifest
expect(axiosHeadMock).toHaveBeenCalledTimes(3)
expect(axiosPostMock).toHaveBeenCalledTimes(2)
expect(axiosPutMock).toHaveBeenCalledTimes(3)
})
it('throws an error if checking for existing blobs fails', async () => { it('throws an error if checking for existing blobs fails', async () => {
// Simulate failed response code // Simulate failed response code
axiosHeadMock.mockImplementation(async (url, config) => { axiosHeadMock.mockImplementation(async (url, config) => {
@@ -288,7 +349,7 @@ describe('publishOCIArtifact', () => {
return { return {
status: 202, status: 202,
headers: { headers: {
location: 'https://ghcr.io/v2/test/test/blobs/uploads/1234567890' location: `https://ghcr.io/v2/${repository}/blobs/uploads/${genericSha}`
} }
} }
}) })
@@ -335,7 +396,7 @@ describe('publishOCIArtifact', () => {
return { return {
status: 202, status: 202,
headers: { headers: {
location: 'https://ghcr.io/v2/test/test/blobs/uploads/1234567890' location: `https://ghcr.io/v2/${repository}/blobs/uploads/${genericSha}`
} }
} }
}) })
@@ -389,7 +450,7 @@ describe('publishOCIArtifact', () => {
return { return {
status: 202, status: 202,
headers: { headers: {
location: 'https://ghcr.io/v2/test/test/blobs/uploads/1234567890' location: `https://ghcr.io/v2/${repository}/blobs/uploads/${genericSha}`
} }
} }
}) })
@@ -422,9 +483,15 @@ describe('publishOCIArtifact', () => {
}) })
it('throws an error if one of the layers has the wrong media type', async () => { it('throws an error if one of the layers has the wrong media type', async () => {
const modifiedTestManifest = testManifest const modifiedTestManifest = { ...testManifest } // This is _NOT_ a deep clone
modifiedTestManifest.layers = cloneLayers(modifiedTestManifest.layers)
modifiedTestManifest.layers[0].mediaType = 'application/json' modifiedTestManifest.layers[0].mediaType = 'application/json'
// just checking to make sure we are not changing the shared object
expect(modifiedTestManifest.layers[0].mediaType).not.toEqual(
testManifest.layers[0].mediaType
)
await expect( await expect(
publishOCIArtifact( publishOCIArtifact(
token, token,
@@ -434,7 +501,7 @@ describe('publishOCIArtifact', () => {
semver, semver,
zipFile, zipFile,
tarFile, tarFile,
testManifest modifiedTestManifest
) )
).rejects.toThrow('Unknown media type application/json') ).rejects.toThrow('Unknown media type application/json')
}) })
@@ -446,12 +513,12 @@ describe('publishOCIArtifact', () => {
function validateRequestConfig(status: number, url: string, config: any): void { function validateRequestConfig(status: number, url: string, config: any): void {
// Basic URL checks // Basic URL checks
expect(url).toBeDefined() expect(url).toBeDefined()
if (!url.startsWith(registry.toString())) { if (!url.startsWith(registry.toString())) {
console.log(url) console.log(`${url} does not start with ${registry}`)
} }
// if these expect fails, run the test again with `-- --silent=false`
expect(url.startsWith(registry.toString())).toBe(true) // the console.log above should give a clue about which URL is failing
expect(url.startsWith(registry.toString())).toBeTruthy()
// Config checks // Config checks
expect(config).toBeDefined() expect(config).toBeDefined()
@@ -472,3 +539,11 @@ function validateRequestConfig(status: number, url: string, config: any): void {
) )
} }
} }
function cloneLayers(layers: ociContainer.Layer[]): ociContainer.Layer[] {
const result: ociContainer.Layer[] = []
for (const layer of layers) {
result.push({ ...layer }) // this is _NOT_ a deep clone
}
return result
}
+12 -12
View File
@@ -69,7 +69,7 @@ describe('action', () => {
it('fails if event is not a release', async () => { it('fails if event is not a release', async () => {
// Mock the environment // Mock the environment
process.env.GITHUB_REPOSITORY = 'test/test' process.env.GITHUB_REPOSITORY = 'test-org/test-repo'
github.context.eventName = 'push' github.context.eventName = 'push'
// Run the action // Run the action
@@ -83,7 +83,7 @@ describe('action', () => {
it('fails if release tag is not a valid semantic version', async () => { it('fails if release tag is not a valid semantic version', async () => {
// Mock the environment // Mock the environment
process.env.GITHUB_REPOSITORY = 'test/test' process.env.GITHUB_REPOSITORY = 'test-org/test-repo'
github.context.eventName = 'release' github.context.eventName = 'release'
github.context.payload = { github.context.payload = {
release: { release: {
@@ -103,12 +103,12 @@ describe('action', () => {
it('fails if multiple paths are provided and staging files fails', async () => { it('fails if multiple paths are provided and staging files fails', async () => {
// Mock the environment // Mock the environment
process.env.GITHUB_REPOSITORY = 'test/test' process.env.GITHUB_REPOSITORY = 'test-org/test-repo'
github.context.eventName = 'release' github.context.eventName = 'release'
github.context.payload = { github.context.payload = {
release: { release: {
id: '123', id: '123',
tag_name: 'v1.0.0' tag_name: 'v1.2.3'
} }
} }
getInputMock.mockImplementation((name: string) => { getInputMock.mockImplementation((name: string) => {
@@ -135,12 +135,12 @@ describe('action', () => {
it('fails if an error is thrown from dependent code', async () => { it('fails if an error is thrown from dependent code', async () => {
// Mock the environment // Mock the environment
process.env.GITHUB_REPOSITORY = 'test/test' process.env.GITHUB_REPOSITORY = 'test-org/test-repo'
github.context.eventName = 'release' github.context.eventName = 'release'
github.context.payload = { github.context.payload = {
release: { release: {
id: '123', id: '123',
tag_name: 'v1.0.0' tag_name: 'v1.2.3'
} }
} }
getInputMock.mockImplementation((name: string) => { getInputMock.mockImplementation((name: string) => {
@@ -172,22 +172,22 @@ describe('action', () => {
}) })
it('successfully uploads if the release tag is a semver without v prefix', async () => { it('successfully uploads if the release tag is a semver without v prefix', async () => {
await testHappyPath('1.0.0', 'test') await testHappyPath('1.2.3', 'test')
}) })
it('successfully uploads if the release tag is a semver with v prefix', async () => { it('successfully uploads if the release tag is a semver with v prefix', async () => {
await testHappyPath('v1.0.0', 'test') await testHappyPath('v1.2.3', 'test')
}) })
it('successfully uploads if multiple paths are provided', async () => { it('successfully uploads if multiple paths are provided', async () => {
await testHappyPath('v1.0.0', 'test test2') await testHappyPath('v1.2.3', 'test test2')
}) })
}) })
// Test that main successfully uploads and returns the manifest & package URL // Test that main successfully uploads and returns the manifest & package URL
async function testHappyPath(version: string, path: string): Promise<void> { async function testHappyPath(version: string, path: string): Promise<void> {
// Mock the environment // Mock the environment
process.env.GITHUB_REPOSITORY = 'test/test' process.env.GITHUB_REPOSITORY = 'test-org/test-repo'
github.context.eventName = 'release' github.context.eventName = 'release'
github.context.payload = { github.context.payload = {
release: { release: {
@@ -228,7 +228,7 @@ async function testHappyPath(version: string, path: string): Promise<void> {
}) })
publishOCIArtifactMock.mockImplementation(() => { publishOCIArtifactMock.mockImplementation(() => {
return new URL('https://ghcr.io/v2/test/test:1.0.0') return new URL('https://ghcr.io/v2/test-org/test-repo:1.2.3')
}) })
// Run the action // Run the action
@@ -239,7 +239,7 @@ async function testHappyPath(version: string, path: string): Promise<void> {
// Check manifest is in output // Check manifest is in output
expect(setOutputMock).toHaveBeenCalledWith( expect(setOutputMock).toHaveBeenCalledWith(
'package-url', 'package-url',
'https://ghcr.io/v2/test/test:1.0.0' 'https://ghcr.io/v2/test-org/test-repo:1.2.3'
) )
expect(setOutputMock).toHaveBeenCalledWith( expect(setOutputMock).toHaveBeenCalledWith(
'package-manifest', 'package-manifest',
+14 -14
View File
@@ -1,21 +1,21 @@
import { createActionPackageManifest } from '../src/oci-container' import { createActionPackageManifest } from '../src/oci-container'
import { FileMetadata } from '../src/fs-helper' import { FileMetadata } from '../src/fs-helper'
describe('createActionPackageManigest', () => { describe('createActionPackageManifest', () => {
it('creates a manifest containing the provided information', () => { it('creates a manifest containing the provided information', () => {
const date = new Date() const date = new Date()
const repo = 'test-org/test-repo' const repo = 'test-org/test-repo'
const sanitizedRepo = 'test-org-test-repo' const sanitizedRepo = 'test-org-test-repo'
const version = '1.0.0' const version = '1.2.3'
const tarFile: FileMetadata = { const tarFile: FileMetadata = {
path: '/test/test/test', path: '/test/test/test.tar.gz',
sha256: '1234567890', sha256: 'tarSha',
size: 100 size: 123
} }
const zipFile: FileMetadata = { const zipFile: FileMetadata = {
path: '/test/test/test', path: '/test/test/test.zip',
sha256: '1234567890', sha256: 'zipSha',
size: 100 size: 456
} }
const expectedJSON = `{ const expectedJSON = `{
@@ -49,8 +49,8 @@ describe('createActionPackageManigest', () => {
}, },
{ {
"mediaType":"application/vnd.github.actions.package.layer.v1.zip", "mediaType":"application/vnd.github.actions.package.layer.v1.zip",
"size":${tarFile.size}, "size":${zipFile.size},
"digest":"${tarFile.sha256}", "digest":"${zipFile.sha256}",
"annotations":{ "annotations":{
"org.opencontainers.image.title":"${sanitizedRepo}_${version}.zip" "org.opencontainers.image.title":"${sanitizedRepo}_${version}.zip"
} }
@@ -67,13 +67,13 @@ describe('createActionPackageManigest', () => {
const manifest = createActionPackageManifest( const manifest = createActionPackageManifest(
{ {
path: 'test.tar.gz', path: 'test.tar.gz',
size: 100, size: tarFile.size,
sha256: '1234567890' sha256: tarFile.sha256
}, },
{ {
path: 'test.zip', path: 'test.zip',
size: 100, size: zipFile.size,
sha256: '1234567890' sha256: zipFile.sha256
}, },
repo, repo,
version, version,
+13 -12
View File
@@ -17,26 +17,27 @@ outputs:
description: 'The name of package published to GHCR along with semver. For example, https://ghcr.io/actions/package-action:1.0.1' description: 'The name of package published to GHCR along with semver. For example, https://ghcr.io/actions/package-action:1.0.1'
package-manifest: package-manifest:
description: 'The package manifest of the published package in JSON format' description: 'The package manifest of the published package in JSON format'
package-manifest-sha:
description: 'A sha256 hash of the package manifest'
runs: runs:
using: 'composite' using: 'composite'
steps: steps:
- run: ls - run: 'node ${{github.action_path}}/dist/index.js --path ${{ inputs.path }}'
shell: bash shell: bash
# - run: node ./dist/index.js
# shell: bash
- uses: ddivad195/publish-action-package/[email protected]
id: publish id: publish
env: env:
TOKEN: ${{ github.token }} TOKEN: ${{ github.token }}
GITHUB_REPOSITORY: ${{ github.repository }}
- name: Output variables - name: Output variables
shell: bash shell: bash
run: | run: |
echo "package manifest": ${{steps.publish.outputs.package-manifest}} echo "package manifest": ${{steps.publish.outputs.package-manifest}}
echo "package manifest": ${{steps.publish.outputs.package-url}} echo "package manifest sha": ${{steps.publish.outputs.package-manifest-sha}}
echo "package-manifest=${{steps.publish.outputs.package-manifest}}" >> $GITHUB_OUTPUT echo "package url": ${{steps.publish.outputs.package-url}}
echo "package-url=${{steps.publish.outputs.package-url}}" >> $GITHUB_OUTPUT echo "subject name": ${{github.repository}}_${{github.ref}}
cat $GITHUB_OUTPUT - uses: github-early-access/generate-build-provenance@main
id: build-provenance
# runs: with:
# using: node20 subject-name: $${{github.repository}}_${{github.ref}}
# main: dist/index.js subject-digest: ${{steps.publish.outputs.package-manifest-sha}}
push-to-registry: false
+1 -1
View File
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="106" height="20" role="img" aria-label="Coverage: 100%"><title>Coverage: 100%</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="106" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="63" height="20" fill="#555"/><rect x="63" width="43" height="20" fill="#4c1"/><rect width="106" 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="835" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="330">100%</text><text x="835" y="140" transform="scale(.1)" fill="#fff" textLength="330">100%</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: 95.05%"><title>Coverage: 95.05%</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">95.05%</text><text x="885" y="140" transform="scale(.1)" fill="#fff" textLength="430">95.05%</text></g></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Generated Vendored
+144
View File
@@ -0,0 +1,144 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.bundleFilesintoDirectory = exports.readFileContents = exports.isDirectory = exports.createArchives = exports.removeDir = exports.createTempDir = void 0;
const fs = __importStar(require("fs"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const path = __importStar(require("path"));
const tar = __importStar(require("tar"));
const archiver = __importStar(require("archiver"));
const crypto = __importStar(require("crypto"));
const os = __importStar(require("os"));
function createTempDir() {
const randomDirName = crypto.randomBytes(4).toString('hex');
const tempDir = path.join(os.tmpdir(), randomDirName);
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir);
}
return tempDir;
}
exports.createTempDir = createTempDir;
function removeDir(dir) {
if (fs.existsSync(dir)) {
fs.rmSync(dir, { recursive: true });
}
}
exports.removeDir = removeDir;
// Creates both a tar.gz and zip archive of the given directory and returns the paths to both archives (stored in the provided target directory)
// as well as the size/sha256 hash of each file.
async function createArchives(distPath, archiveTargetPath = createTempDir()) {
const zipPath = path.join(archiveTargetPath, `archive.zip`);
const tarPath = path.join(archiveTargetPath, `archive.tar.gz`);
const createZipPromise = new Promise((resolve, reject) => {
const output = fs.createWriteStream(zipPath);
const archive = archiver.create('zip');
output.on('error', (err) => {
reject(err);
});
archive.on('error', (err) => {
reject(err);
});
output.on('close', () => {
resolve(fileMetadata(zipPath));
});
archive.pipe(output);
archive.directory(distPath, false);
archive.finalize();
});
const createTarPromise = new Promise((resolve, reject) => {
tar
.c({
file: tarPath,
C: distPath, // Change to the source directory for relative paths (TODO)
gzip: true
}, ['.'])
// eslint-disable-next-line github/no-then
.catch(err => {
reject(err);
})
// eslint-disable-next-line github/no-then
.then(() => {
resolve(fileMetadata(tarPath));
});
});
const [zipFile, tarFile] = await Promise.all([
createZipPromise,
createTarPromise
]);
return { zipFile, tarFile };
}
exports.createArchives = createArchives;
function isDirectory(dirPath) {
return fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory();
}
exports.isDirectory = isDirectory;
function readFileContents(filePath) {
return fs.readFileSync(filePath);
}
exports.readFileContents = readFileContents;
function bundleFilesintoDirectory(files, targetDir = createTempDir()) {
for (const file of files) {
if (!fs.existsSync(file)) {
throw new Error(`File ${file} does not exist`);
}
if (isDirectory(file)) {
const targetFolder = path.join(targetDir, path.basename(file));
fs_extra_1.default.copySync(file, targetFolder);
}
else {
const targetFile = path.join(targetDir, path.basename(file));
fs.copyFileSync(file, targetFile);
}
}
return targetDir;
}
exports.bundleFilesintoDirectory = bundleFilesintoDirectory;
// Converts a file path to a filemetadata object by querying the fs for relevant metadata.
async function fileMetadata(filePath) {
const stats = fs.statSync(filePath);
const size = stats.size;
const hash = crypto.createHash('sha256');
const fileStream = fs.createReadStream(filePath);
return new Promise((resolve, reject) => {
fileStream.on('data', data => {
hash.update(data);
});
fileStream.on('end', () => {
const sha256 = hash.digest('hex');
resolve({
path: filePath,
size,
sha256: `sha256:${sha256}`
});
});
fileStream.on('error', err => {
reject(err);
});
});
}
//# sourceMappingURL=fs-helper.js.map
Generated Vendored
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"fs-helper.js","sourceRoot":"","sources":["../src/fs-helper.ts"],"names":[],"mappings":"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"}
Generated Vendored
+148
View File
@@ -0,0 +1,148 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.publishOCIArtifact = void 0;
const core = __importStar(require("@actions/core"));
const axios_1 = __importDefault(require("axios"));
const fsHelper = __importStar(require("./fs-helper"));
const axios_debug_log_1 = __importDefault(require("axios-debug-log"));
// Publish the OCI artifact and return the URL where it can be downloaded
async function publishOCIArtifact(token, registry, repository, releaseId, semver, zipFile, tarFile, manifest, debugRequests = false) {
if (debugRequests) {
configureRequestDebugLogging();
}
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);
case 'application/vnd.github.actions.package.layer.v1.zip':
return uploadLayer(layer, zipFile, registry, checkBlobEndpoint, uploadBlobEndpoint, b64Token);
case 'application/vnd.github.actions.package.config.v1+json':
return uploadLayer(layer, { path: '', size: 0, sha256: layer.digest }, registry, checkBlobEndpoint, uploadBlobEndpoint, b64Token);
default:
throw new Error(`Unknown media type ${layer.mediaType}`);
}
});
await Promise.all(layerUploads);
await uploadManifest(JSON.stringify(manifest), manifestEndpoint, b64Token);
return new URL(`${repository}:${semver}`, registry);
}
exports.publishOCIArtifact = publishOCIArtifact;
async function uploadLayer(layer, file, registryURL, checkBlobEndpoint, uploadBlobEndpoint, b64Token) {
const checkExistsResponse = await axios_1.default.head(checkBlobEndpoint + layer.digest, {
headers: {
Authorization: `Bearer ${b64Token}`
},
validateStatus: () => {
return true; // Allow non 2xx responses
}
});
if (checkExistsResponse.status === 200 ||
checkExistsResponse.status === 202) {
core.info(`Layer ${layer.digest} already exists. Skipping upload.`);
return;
}
if (checkExistsResponse.status !== 404) {
throw new Error(`Unexpected response from blob check for layer ${layer.digest}: ${checkExistsResponse.status} ${checkExistsResponse.statusText}`);
}
core.info(`Uploading layer ${layer.digest}.`);
const initiateUploadResponse = await axios_1.default.post(uploadBlobEndpoint, layer, {
headers: {
Authorization: `Bearer ${b64Token}`
},
validateStatus: () => {
return true; // Allow non 2xx responses
}
});
if (initiateUploadResponse.status !== 202) {
core.error(`Unexpected response from upload post ${uploadBlobEndpoint}: ${initiateUploadResponse.status}`);
throw new Error(`Unexpected response from POST upload ${initiateUploadResponse.status}`);
}
const locationResponseHeader = initiateUploadResponse.headers['location'];
if (locationResponseHeader === undefined) {
throw new Error(`No location header in response from upload post ${uploadBlobEndpoint} 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 (file.size === 0) {
data = Buffer.alloc(0);
}
else {
data = fsHelper.readFileContents(file.path);
}
const putResponse = await axios_1.default.put(uploadBlobUrl, data, {
headers: {
Authorization: `Bearer ${b64Token}`,
'Content-Type': 'application/octet-stream',
'Accept-Encoding': 'gzip', // TODO: What about for the config layer?
'Content-Length': layer.size.toString()
},
validateStatus: () => {
return true; // Allow non 2xx responses
}
});
if (putResponse.status !== 201) {
throw new Error(`Unexpected response from PUT upload ${putResponse.status} for layer ${layer.digest}`);
}
}
async function uploadManifest(manifestJSON, manifestEndpoint, b64Token) {
core.info(`Uploading manifest to ${manifestEndpoint}.`);
const putResponse = await axios_1.default.put(manifestEndpoint, manifestJSON, {
headers: {
Authorization: `Bearer ${b64Token}`,
'Content-Type': 'application/vnd.oci.image.manifest.v1+json'
},
validateStatus: () => {
return true; // Allow non 2xx responses
}
});
if (putResponse.status !== 201) {
throw new Error(`Unexpected response from PUT manifest ${putResponse.status}`);
}
}
function configureRequestDebugLogging() {
(0, axios_debug_log_1.default)({
request: (debug, config) => {
core.debug(`Request with ${config}`);
},
response: (debug, response) => {
core.debug(`Response with ${response}`);
},
error: (debug, error) => {
core.debug(`Error with ${error}`);
}
});
}
//# sourceMappingURL=ghcr-client.js.map
Generated Vendored
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"ghcr-client.js","sourceRoot":"","sources":["../src/ghcr-client.ts"],"names":[],"mappings":"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"}
Generated Vendored
+311 -24
View File
@@ -33737,6 +33737,277 @@ function populateMaps (extensions, types) {
} }
/***/ }),
/***/ 35871:
/***/ ((module) => {
"use strict";
function hasKey(obj, keys) {
var o = obj;
keys.slice(0, -1).forEach(function (key) {
o = o[key] || {};
});
var key = keys[keys.length - 1];
return key in o;
}
function isNumber(x) {
if (typeof x === 'number') { return true; }
if ((/^0x[0-9a-f]+$/i).test(x)) { return true; }
return (/^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/).test(x);
}
function isConstructorOrProto(obj, key) {
return (key === 'constructor' && typeof obj[key] === 'function') || key === '__proto__';
}
module.exports = function (args, opts) {
if (!opts) { opts = {}; }
var flags = {
bools: {},
strings: {},
unknownFn: null,
};
if (typeof opts.unknown === 'function') {
flags.unknownFn = opts.unknown;
}
if (typeof opts.boolean === 'boolean' && opts.boolean) {
flags.allBools = true;
} else {
[].concat(opts.boolean).filter(Boolean).forEach(function (key) {
flags.bools[key] = true;
});
}
var aliases = {};
function aliasIsBoolean(key) {
return aliases[key].some(function (x) {
return flags.bools[x];
});
}
Object.keys(opts.alias || {}).forEach(function (key) {
aliases[key] = [].concat(opts.alias[key]);
aliases[key].forEach(function (x) {
aliases[x] = [key].concat(aliases[key].filter(function (y) {
return x !== y;
}));
});
});
[].concat(opts.string).filter(Boolean).forEach(function (key) {
flags.strings[key] = true;
if (aliases[key]) {
[].concat(aliases[key]).forEach(function (k) {
flags.strings[k] = true;
});
}
});
var defaults = opts.default || {};
var argv = { _: [] };
function argDefined(key, arg) {
return (flags.allBools && (/^--[^=]+$/).test(arg))
|| flags.strings[key]
|| flags.bools[key]
|| aliases[key];
}
function setKey(obj, keys, value) {
var o = obj;
for (var i = 0; i < keys.length - 1; i++) {
var key = keys[i];
if (isConstructorOrProto(o, key)) { return; }
if (o[key] === undefined) { o[key] = {}; }
if (
o[key] === Object.prototype
|| o[key] === Number.prototype
|| o[key] === String.prototype
) {
o[key] = {};
}
if (o[key] === Array.prototype) { o[key] = []; }
o = o[key];
}
var lastKey = keys[keys.length - 1];
if (isConstructorOrProto(o, lastKey)) { return; }
if (
o === Object.prototype
|| o === Number.prototype
|| o === String.prototype
) {
o = {};
}
if (o === Array.prototype) { o = []; }
if (o[lastKey] === undefined || flags.bools[lastKey] || typeof o[lastKey] === 'boolean') {
o[lastKey] = value;
} else if (Array.isArray(o[lastKey])) {
o[lastKey].push(value);
} else {
o[lastKey] = [o[lastKey], value];
}
}
function setArg(key, val, arg) {
if (arg && flags.unknownFn && !argDefined(key, arg)) {
if (flags.unknownFn(arg) === false) { return; }
}
var value = !flags.strings[key] && isNumber(val)
? Number(val)
: val;
setKey(argv, key.split('.'), value);
(aliases[key] || []).forEach(function (x) {
setKey(argv, x.split('.'), value);
});
}
Object.keys(flags.bools).forEach(function (key) {
setArg(key, defaults[key] === undefined ? false : defaults[key]);
});
var notFlags = [];
if (args.indexOf('--') !== -1) {
notFlags = args.slice(args.indexOf('--') + 1);
args = args.slice(0, args.indexOf('--'));
}
for (var i = 0; i < args.length; i++) {
var arg = args[i];
var key;
var next;
if ((/^--.+=/).test(arg)) {
// Using [\s\S] instead of . because js doesn't support the
// 'dotall' regex modifier. See:
// http://stackoverflow.com/a/1068308/13216
var m = arg.match(/^--([^=]+)=([\s\S]*)$/);
key = m[1];
var value = m[2];
if (flags.bools[key]) {
value = value !== 'false';
}
setArg(key, value, arg);
} else if ((/^--no-.+/).test(arg)) {
key = arg.match(/^--no-(.+)/)[1];
setArg(key, false, arg);
} else if ((/^--.+/).test(arg)) {
key = arg.match(/^--(.+)/)[1];
next = args[i + 1];
if (
next !== undefined
&& !(/^(-|--)[^-]/).test(next)
&& !flags.bools[key]
&& !flags.allBools
&& (aliases[key] ? !aliasIsBoolean(key) : true)
) {
setArg(key, next, arg);
i += 1;
} else if ((/^(true|false)$/).test(next)) {
setArg(key, next === 'true', arg);
i += 1;
} else {
setArg(key, flags.strings[key] ? '' : true, arg);
}
} else if ((/^-[^-]+/).test(arg)) {
var letters = arg.slice(1, -1).split('');
var broken = false;
for (var j = 0; j < letters.length; j++) {
next = arg.slice(j + 2);
if (next === '-') {
setArg(letters[j], next, arg);
continue;
}
if ((/[A-Za-z]/).test(letters[j]) && next[0] === '=') {
setArg(letters[j], next.slice(1), arg);
broken = true;
break;
}
if (
(/[A-Za-z]/).test(letters[j])
&& (/-?\d+(\.\d*)?(e-?\d+)?$/).test(next)
) {
setArg(letters[j], next, arg);
broken = true;
break;
}
if (letters[j + 1] && letters[j + 1].match(/\W/)) {
setArg(letters[j], arg.slice(j + 2), arg);
broken = true;
break;
} else {
setArg(letters[j], flags.strings[letters[j]] ? '' : true, arg);
}
}
key = arg.slice(-1)[0];
if (!broken && key !== '-') {
if (
args[i + 1]
&& !(/^(-|--)[^-]/).test(args[i + 1])
&& !flags.bools[key]
&& (aliases[key] ? !aliasIsBoolean(key) : true)
) {
setArg(key, args[i + 1], arg);
i += 1;
} else if (args[i + 1] && (/^(true|false)$/).test(args[i + 1])) {
setArg(key, args[i + 1] === 'true', arg);
i += 1;
} else {
setArg(key, flags.strings[key] ? '' : true, arg);
}
}
} else {
if (!flags.unknownFn || flags.unknownFn(arg) !== false) {
argv._.push(flags.strings._ || !isNumber(arg) ? arg : Number(arg));
}
if (opts.stopEarly) {
argv._.push.apply(argv._, args.slice(i + 1));
break;
}
}
}
Object.keys(defaults).forEach(function (k) {
if (!hasKey(argv, k.split('.'))) {
setKey(argv, k.split('.'), defaults[k]);
(aliases[k] || []).forEach(function (x) {
setKey(argv, x.split('.'), defaults[k]);
});
}
});
if (opts['--']) {
argv['--'] = notFlags.slice();
} else {
notFlags.forEach(function (k) {
argv._.push(k);
});
}
return argv;
};
/***/ }), /***/ }),
/***/ 41077: /***/ 41077:
@@ -74694,6 +74965,28 @@ function configureRequestDebugLogging() {
} }
/***/ }),
/***/ 6144:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
/**
* The entrypoint for the action.
*/
const main_1 = __nccwpck_require__(70399);
const minimist_1 = __importDefault(__nccwpck_require__(35871));
const path = (0, minimist_1.default)(process.argv.slice(2)).path || '.';
console.log(path);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
(0, main_1.run)(path);
/***/ }), /***/ }),
/***/ 70399: /***/ 70399:
@@ -74735,11 +75028,12 @@ const fsHelper = __importStar(__nccwpck_require__(76642));
const ociContainer = __importStar(__nccwpck_require__(33207)); const ociContainer = __importStar(__nccwpck_require__(33207));
const ghcr = __importStar(__nccwpck_require__(62894)); const ghcr = __importStar(__nccwpck_require__(62894));
const semver_1 = __importDefault(__nccwpck_require__(11383)); const semver_1 = __importDefault(__nccwpck_require__(11383));
const crypto_1 = __importDefault(__nccwpck_require__(6113));
/** /**
* The main function for the action. * The main function for the action.
* @returns {Promise<void>} Resolves when the action is complete. * @returns {Promise<void>} Resolves when the action is complete.
*/ */
async function run() { async function run(pathInput) {
const tmpDirs = []; const tmpDirs = [];
try { try {
// Parse and validate Actions execution context, including the repository name, release name and event type // Parse and validate Actions execution context, including the repository name, release name and event type
@@ -74777,7 +75071,8 @@ async function run() {
console.log(`Container registry URL: ${registryURL}`); console.log(`Container registry URL: ${registryURL}`);
// Gather & validate user input // Gather & validate user input
// Paths to be included in the OCI image // Paths to be included in the OCI image
const paths = core.getInput('path').split(' '); // const paths: string[] = core.getInput('path').split(' ')
const paths = pathInput.split(' ');
let path = ''; let path = '';
if (paths.length === 1 && fsHelper.isDirectory(paths[0])) { if (paths.length === 1 && fsHelper.isDirectory(paths[0])) {
// If the path is a single directory, we can skip the bundling step // If the path is a single directory, we can skip the bundling step
@@ -74794,17 +75089,19 @@ async function run() {
tmpDirs.push(archiveDir); tmpDirs.push(archiveDir);
const archives = await fsHelper.createArchives(path, archiveDir); const archives = await fsHelper.createArchives(path, archiveDir);
const manifest = ociContainer.createActionPackageManifest(archives.tarFile, archives.zipFile, repository, targetVersion.raw, new Date()); const manifest = ociContainer.createActionPackageManifest(archives.tarFile, archives.zipFile, repository, targetVersion.raw, new Date());
// Generate SHA-256 hash of the manifest
const manifestSHA = crypto_1.default.createHash('sha256');
const manifestHash = manifestSHA
.update(JSON.stringify(manifest))
.digest('hex');
const packageURL = await ghcr.publishOCIArtifact(token, registryURL, repository, releaseId.toString(), targetVersion.raw, archives.zipFile, archives.tarFile, manifest, true); const packageURL = await ghcr.publishOCIArtifact(token, registryURL, repository, releaseId.toString(), targetVersion.raw, archives.zipFile, archives.tarFile, manifest, true);
core.setOutput('package-url', packageURL.toString()); core.setOutput('package-url', packageURL.toString());
// TODO: We might need to do some attestation stuff here, but unsure how to integrate it yet.
// We might need to return the manifest JSON from the Action and link it to another action,
// or we might be able to make an API call here. It's unclear at this point.
core.setOutput('package-manifest', JSON.stringify(manifest)); core.setOutput('package-manifest', JSON.stringify(manifest));
core.setOutput('package-manifest-sha', `sha256:${manifestHash}`);
} }
catch (error) { catch (error) {
// Fail the workflow run if an error occurs // Fail the workflow run if an error occurs
if (error instanceof Error) // if (error instanceof Error) core.setFailed(error.message)
core.setFailed(error.message);
} }
finally { finally {
// Clean up any temporary directories that exist // Clean up any temporary directories that exist
@@ -79533,22 +79830,12 @@ module.exports = JSON.parse('{"application/1d-interleaved-parityfec":{"source":"
/******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/"; /******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/";
/******/ /******/
/************************************************************************/ /************************************************************************/
var __webpack_exports__ = {}; /******/
// This entry need to be wrapped in an IIFE because it need to be in strict mode. /******/ // startup
(() => { /******/ // Load entry module and return exports
"use strict"; /******/ // This entry module is referenced by other modules so it can't be inlined
var exports = __webpack_exports__; /******/ var __webpack_exports__ = __nccwpck_require__(6144);
/******/ module.exports = __webpack_exports__;
Object.defineProperty(exports, "__esModule", ({ value: true })); /******/
/**
* The entrypoint for the action.
*/
const main_1 = __nccwpck_require__(70399);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
(0, main_1.run)();
})();
module.exports = __webpack_exports__;
/******/ })() /******/ })()
; ;
Generated Vendored
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA;;GAEG;AACH,iCAA4B;AAE5B,mEAAmE;AACnE,IAAA,UAAG,GAAE,CAAA"}
Generated Vendored
+22
View File
@@ -1528,6 +1528,28 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
minimist
MIT
This software is released under the MIT license:
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
minipass minipass
ISC ISC
The ISC License The ISC License
Generated Vendored
+108
View File
@@ -0,0 +1,108 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.run = void 0;
const core = __importStar(require("@actions/core"));
const github = __importStar(require("@actions/github"));
const fsHelper = __importStar(require("./fs-helper"));
const ociContainer = __importStar(require("./oci-container"));
const ghcr = __importStar(require("./ghcr-client"));
const semver_1 = __importDefault(require("semver"));
/**
* The main function for the action.
* @returns {Promise<void>} Resolves when the action is complete.
*/
async function run() {
const tmpDirs = [];
try {
// Parse and validate Actions execution context, including the repository name, release name and event type
const repository = process.env.GITHUB_REPOSITORY || '';
if (repository === '') {
core.setFailed(`Could not find Repository.`);
return;
}
if (github.context.eventName !== 'release') {
core.setFailed('Please ensure you have the workflow trigger as release.');
return;
}
const releaseId = github.context.payload.release.id;
const releaseTag = github.context.payload.release.tag_name;
// Strip any leading 'v' from the tag in case the release format is e.g. 'v1.0.0' as recommended by GitHub docs
// https://docs.github.com/en/actions/creating-actions/releasing-and-maintaining-actions
const targetVersion = semver_1.default.parse(releaseTag.replace(/^v/, ''));
if (!targetVersion) {
// TODO: We may want to limit semvers to only x.x.x, without the pre-release tags, but for now we'll allow them.
core.setFailed(`${releaseTag} is not a valid semantic version, and so cannot be uploaded as an Immutable Action.`);
return;
}
// Gather & validate user inputs
const token = core.getInput('token');
const registryURL = new URL('https://ghcr.io/'); // TODO: Should this be dynamic? Maybe an API endpoint to grab the registry for GHES/proxima purposes.
console.log(core.getInput('registry'));
console.log(`registryURL: ${registryURL}`);
// Paths to be included in the OCI image
const paths = core.getInput('path').split(' ');
let path = '';
if (paths.length === 1 && fsHelper.isDirectory(paths[0])) {
// If the path is a single directory, we can skip the bundling step
path = paths[0];
}
else {
// Otherwise, we need to bundle the files & folders into a temporary directory
const bundleDir = fsHelper.createTempDir();
tmpDirs.push(bundleDir);
path = fsHelper.bundleFilesintoDirectory(paths, bundleDir);
}
// Create a temporary directory to store the archives
const archiveDir = fsHelper.createTempDir();
tmpDirs.push(archiveDir);
const archives = await fsHelper.createArchives(path, archiveDir);
const manifest = ociContainer.createActionPackageManifest(archives.tarFile, archives.zipFile, repository, targetVersion.raw, new Date());
const packageURL = await ghcr.publishOCIArtifact(token, registryURL, repository, releaseId.toString(), targetVersion.raw, archives.zipFile, archives.tarFile, manifest, true);
core.setOutput('package-url', packageURL.toString());
// TODO: We might need to do some attestation stuff here, but unsure how to integrate it yet.
// We might need to return the manifest JSON from the Action and link it to another action,
// or we might be able to make an API call here. It's unclear at this point.
core.setOutput('package-manifest', JSON.stringify(manifest));
}
catch (error) {
// Fail the workflow run if an error occurs
if (error instanceof Error)
core.setFailed(error.message);
}
finally {
// Clean up any temporary directories that exist
for (const tmpDir of tmpDirs) {
if (tmpDir !== '') {
fsHelper.removeDir(tmpDir);
}
}
}
}
exports.run = run;
//# sourceMappingURL=main.js.map
Generated Vendored
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
Generated Vendored
+65
View File
@@ -0,0 +1,65 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createActionPackageManifest = void 0;
// Given a name and archive metadata, creates a manifest in the format expected by GHCR for an Actions Package.
function createActionPackageManifest(tarFile, zipFile, repository, version, created) {
const configLayer = createConfigLayer();
const sanitizedRepo = sanitizeRepository(repository);
const tarLayer = createTarLayer(tarFile, sanitizedRepo, version);
const zipLayer = createZipLayer(zipFile, sanitizedRepo, version);
const manifest = {
schemaVersion: 2,
mediaType: 'application/vnd.oci.image.manifest.v1+json',
artifactType: 'application/vnd.oci.image.manifest.v1+json',
config: configLayer,
layers: [configLayer, tarLayer, zipLayer],
annotations: {
'org.opencontainers.image.created': created.toISOString(),
'action.tar.gz.digest': tarFile.sha256,
'action.zip.digest': zipFile.sha256,
'com.github.package.type': 'actions_oci_pkg'
}
};
return manifest;
}
exports.createActionPackageManifest = createActionPackageManifest;
// TODO: is this ok hardcoded?
function createConfigLayer() {
const configLayer = {
mediaType: 'application/vnd.github.actions.package.config.v1+json',
size: 0,
digest: 'sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
annotations: {
'org.opencontainers.image.title': 'config.json'
}
};
return configLayer;
}
function createZipLayer(zipFile, repository, version) {
const zipLayer = {
mediaType: 'application/vnd.github.actions.package.layer.v1.zip',
size: zipFile.size,
digest: zipFile.sha256,
annotations: {
'org.opencontainers.image.title': `${repository}_${version}.zip`
}
};
return zipLayer;
}
function createTarLayer(tarFile, repository, version) {
const tarLayer = {
mediaType: 'application/vnd.github.actions.package.layer.v1.tar+gzip',
size: tarFile.size,
digest: tarFile.sha256,
annotations: {
'org.opencontainers.image.title': `${repository}_${version}.tar.gz`
}
};
return tarLayer;
}
// Remove slashes so we can use the repository in a filename
// repository usually includes the namespace too, e.g. my-org/my-repo
function sanitizeRepository(repository) {
return repository.replace('/', '-');
}
//# sourceMappingURL=oci-container.js.map
Generated Vendored
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"oci-container.js","sourceRoot":"","sources":["../src/oci-container.ts"],"names":[],"mappings":";;;AAkBA,+GAA+G;AAC/G,SAAgB,2BAA2B,CACzC,OAAqB,EACrB,OAAqB,EACrB,UAAkB,EAClB,OAAe,EACf,OAAa;IAEb,MAAM,WAAW,GAAG,iBAAiB,EAAE,CAAA;IACvC,MAAM,aAAa,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAA;IACpD,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,CAAC,CAAA;IAChE,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,CAAC,CAAA;IAEhE,MAAM,QAAQ,GAAa;QACzB,aAAa,EAAE,CAAC;QAChB,SAAS,EAAE,4CAA4C;QACvD,YAAY,EAAE,4CAA4C;QAC1D,MAAM,EAAE,WAAW;QACnB,MAAM,EAAE,CAAC,WAAW,EAAE,QAAQ,EAAE,QAAQ,CAAC;QACzC,WAAW,EAAE;YACX,kCAAkC,EAAE,OAAO,CAAC,WAAW,EAAE;YACzD,sBAAsB,EAAE,OAAO,CAAC,MAAM;YACtC,mBAAmB,EAAE,OAAO,CAAC,MAAM;YACnC,yBAAyB,EAAE,iBAAiB;SAC7C;KACF,CAAA;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC;AA3BD,kEA2BC;AAED,8BAA8B;AAC9B,SAAS,iBAAiB;IACxB,MAAM,WAAW,GAAU;QACzB,SAAS,EAAE,uDAAuD;QAClE,IAAI,EAAE,CAAC;QACP,MAAM,EACJ,yEAAyE;QAC3E,WAAW,EAAE;YACX,gCAAgC,EAAE,aAAa;SAChD;KACF,CAAA;IAED,OAAO,WAAW,CAAA;AACpB,CAAC;AAED,SAAS,cAAc,CACrB,OAAqB,EACrB,UAAkB,EAClB,OAAe;IAEf,MAAM,QAAQ,GAAU;QACtB,SAAS,EAAE,qDAAqD;QAChE,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,WAAW,EAAE;YACX,gCAAgC,EAAE,GAAG,UAAU,IAAI,OAAO,MAAM;SACjE;KACF,CAAA;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,SAAS,cAAc,CACrB,OAAqB,EACrB,UAAkB,EAClB,OAAe;IAEf,MAAM,QAAQ,GAAU;QACtB,SAAS,EAAE,0DAA0D;QACrE,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,WAAW,EAAE;YACX,gCAAgC,EAAE,GAAG,UAAU,IAAI,OAAO,SAAS;SACpE;KACF,CAAA;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,4DAA4D;AAC5D,qEAAqE;AACrE,SAAS,kBAAkB,CAAC,UAAkB;IAC5C,OAAO,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;AACrC,CAAC"}
-27
View File
@@ -1,27 +0,0 @@
name: 'Package and Publish'
description: 'Publish actions as OCI artifacts to GHCR'
# TODO: Add your action's branding here. This will appear on the GitHub Marketplace.
branding:
icon: 'heart'
color: 'red'
inputs:
path:
required: false
description: The work directory or path to be tar archived and uploaded as OCI Artifact layer.
default: '.'
registry:
required: false
description: The registry to publish the action package to.
default: https://ghcr.io/ # TODO: this should perhaps be fetched from GitHub API.
outputs:
package-url:
description: 'The name of package published to GHCR along with semver. For example, https://ghcr.io/actions/package-action:1.0.1'
package-manifest:
description: 'The package manifest of the published package in JSON format'
runs:
using: node20
main: ../dist/index.js
+8 -1
View File
@@ -17,12 +17,14 @@
"axios": "^1.6.2", "axios": "^1.6.2",
"axios-debug-log": "^1.0.0", "axios-debug-log": "^1.0.0",
"fs-extra": "^11.1.1", "fs-extra": "^11.1.1",
"minimist": "^1.2.8",
"tar": "^6.2.0" "tar": "^6.2.0"
}, },
"devDependencies": { "devDependencies": {
"@types/archiver": "^6.0.2", "@types/archiver": "^6.0.2",
"@types/axios": "^0.14.0", "@types/axios": "^0.14.0",
"@types/jest": "^29.5.11", "@types/jest": "^29.5.11",
"@types/minimist": "^1.2.5",
"@types/node": "^20.11.4", "@types/node": "^20.11.4",
"@types/tar": "^6.1.10", "@types/tar": "^6.1.10",
"@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/eslint-plugin": "^6.12.0",
@@ -1672,6 +1674,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==",
"dev": true
},
"node_modules/@types/ms": { "node_modules/@types/ms": {
"version": "0.7.34", "version": "0.7.34",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
@@ -6157,7 +6165,6 @@
"version": "1.2.8", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
+2
View File
@@ -74,12 +74,14 @@
"axios": "^1.6.2", "axios": "^1.6.2",
"axios-debug-log": "^1.0.0", "axios-debug-log": "^1.0.0",
"fs-extra": "^11.1.1", "fs-extra": "^11.1.1",
"minimist": "^1.2.8",
"tar": "^6.2.0" "tar": "^6.2.0"
}, },
"devDependencies": { "devDependencies": {
"@types/archiver": "^6.0.2", "@types/archiver": "^6.0.2",
"@types/axios": "^0.14.0", "@types/axios": "^0.14.0",
"@types/jest": "^29.5.11", "@types/jest": "^29.5.11",
"@types/minimist": "^1.2.5",
"@types/node": "^20.11.4", "@types/node": "^20.11.4",
"@types/tar": "^6.1.10", "@types/tar": "^6.1.10",
"@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/eslint-plugin": "^6.12.0",
+4 -1
View File
@@ -2,6 +2,9 @@
* The entrypoint for the action. * The entrypoint for the action.
*/ */
import { run } from './main' import { run } from './main'
import minimist from 'minimist'
const path = minimist(process.argv.slice(2)).path || '.'
console.log(path)
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
run() run(path)
+13 -9
View File
@@ -4,17 +4,19 @@ import * as fsHelper from './fs-helper'
import * as ociContainer from './oci-container' import * as ociContainer from './oci-container'
import * as ghcr from './ghcr-client' import * as ghcr from './ghcr-client'
import semver from 'semver' import semver from 'semver'
import crypto from 'crypto'
/** /**
* The main function for the action. * The main function for the action.
* @returns {Promise<void>} Resolves when the action is complete. * @returns {Promise<void>} Resolves when the action is complete.
*/ */
export async function run(): Promise<void> { export async function run(pathInput: string): Promise<void> {
const tmpDirs: string[] = [] const tmpDirs: string[] = []
try { try {
// Parse and validate Actions execution context, including the repository name, release name and event type // Parse and validate Actions execution context, including the repository name, release name and event type
const repository: string = process.env.GITHUB_REPOSITORY || '' const repository: string = process.env.GITHUB_REPOSITORY || ''
if (repository === '') { if (repository === '') {
core.setFailed(`Could not find Repository.`) core.setFailed(`Could not find Repository.`)
return return
@@ -25,7 +27,6 @@ export async function run(): Promise<void> {
} }
const releaseId: string = github.context.payload.release.id const releaseId: string = github.context.payload.release.id
const releaseTag: string = github.context.payload.release.tag_name const releaseTag: string = github.context.payload.release.tag_name
// Strip any leading 'v' from the tag in case the release format is e.g. 'v1.0.0' as recommended by GitHub docs // Strip any leading 'v' from the tag in case the release format is e.g. 'v1.0.0' as recommended by GitHub docs
// https://docs.github.com/en/actions/creating-actions/releasing-and-maintaining-actions // https://docs.github.com/en/actions/creating-actions/releasing-and-maintaining-actions
const targetVersion = semver.parse(releaseTag.replace(/^v/, '')) const targetVersion = semver.parse(releaseTag.replace(/^v/, ''))
@@ -38,7 +39,6 @@ export async function run(): Promise<void> {
} }
const token: string = process.env.TOKEN! const token: string = process.env.TOKEN!
// TODO: once https://github.com/github/github/pull/309384 goes in, we can switch to the actual endpoint // TODO: once https://github.com/github/github/pull/309384 goes in, we can switch to the actual endpoint
//const response = await fetch( //const response = await fetch(
// process.env.GITHUB_API_URL + '/packages/container-registry-url' // process.env.GITHUB_API_URL + '/packages/container-registry-url'
@@ -55,7 +55,8 @@ export async function run(): Promise<void> {
// Gather & validate user input // Gather & validate user input
// Paths to be included in the OCI image // Paths to be included in the OCI image
const paths: string[] = core.getInput('path').split(' ') // const paths: string[] = core.getInput('path').split(' ')
const paths: string[] = pathInput.split(' ')
let path = '' let path = ''
if (paths.length === 1 && fsHelper.isDirectory(paths[0])) { if (paths.length === 1 && fsHelper.isDirectory(paths[0])) {
@@ -82,6 +83,12 @@ export async function run(): Promise<void> {
new Date() new Date()
) )
// Generate SHA-256 hash of the manifest
const manifestSHA = crypto.createHash('sha256')
const manifestHash = manifestSHA
.update(JSON.stringify(manifest))
.digest('hex')
const packageURL = await ghcr.publishOCIArtifact( const packageURL = await ghcr.publishOCIArtifact(
token, token,
registryURL, registryURL,
@@ -95,14 +102,11 @@ export async function run(): Promise<void> {
) )
core.setOutput('package-url', packageURL.toString()) core.setOutput('package-url', packageURL.toString())
// TODO: We might need to do some attestation stuff here, but unsure how to integrate it yet.
// We might need to return the manifest JSON from the Action and link it to another action,
// or we might be able to make an API call here. It's unclear at this point.
core.setOutput('package-manifest', JSON.stringify(manifest)) core.setOutput('package-manifest', JSON.stringify(manifest))
core.setOutput('package-manifest-sha', `sha256:${manifestHash}`)
} catch (error) { } catch (error) {
// Fail the workflow run if an error occurs // Fail the workflow run if an error occurs
if (error instanceof Error) core.setFailed(error.message) // if (error instanceof Error) core.setFailed(error.message)
} finally { } finally {
// Clean up any temporary directories that exist // Clean up any temporary directories that exist
for (const tmpDir of tmpDirs) { for (const tmpDir of tmpDirs) {