This commit is contained in:
boxofyellow
2024-01-25 08:34:02 -08:00
committed by Edwin Sirko
parent c41316d7a8
commit 5e2391735e
23 changed files with 986 additions and 139 deletions
+9 -3
View File
@@ -74,8 +74,6 @@ describe('createArchives', () => {
expect(zipSHA).toEqual(systemZipHash)
expect(tarSHA).toEqual(systemTarHash)
})
// TODO: Test the failure cases
})
describe('createTempDir', () => {
@@ -189,11 +187,15 @@ describe('bundleFilesintoDirectory', () => {
// Create some test files and folders in the sourceDir
const file1 = `${sourceDir}/file1.txt`
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(folder2)
fs.writeFileSync(file1, fileContent)
fs.writeFileSync(file2, fileContent)
fs.writeFileSync(file3, fileContent)
// Bundle the files and folders into the targetDir
fsHelper.bundleFilesintoDirectory([file1, folder1], targetDir)
@@ -206,6 +208,10 @@ describe('bundleFilesintoDirectory', () => {
expect(fs.existsSync(file2)).toEqual(true)
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', () => {
+107 -32
View File
@@ -9,20 +9,21 @@ let axiosPostMock: jest.SpyInstance
let axiosPutMock: jest.SpyInstance
let axiosHeadMock: jest.SpyInstance
const token = '1234567890'
const token = 'test-token'
const registry = new URL('https://ghcr.io')
const repository = 'test/test'
const releaseId = '1234567890'
const semver = '1.0.0'
const repository = 'test-org/test-repo'
const releaseId = 'test-release-id'
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 = {
path: 'test-repo-1.0.0.zip',
size: 100,
sha256: '1234567890'
path: `test-repo-${semver}.zip`,
size: 123,
sha256: genericSha
}
const tarFile: fsHelper.FileMetadata = {
path: 'test-repo-1.0.0.tar.gz',
size: 100,
sha256: '1234567890'
path: `test-repo-${semver}.tar.gz`,
size: 456,
sha256: genericSha
}
const testManifest: ociContainer.Manifest = {
@@ -50,25 +51,25 @@ const testManifest: ociContainer.Manifest = {
},
{
mediaType: 'application/vnd.github.actions.package.layer.v1.tar+gzip',
size: 100,
digest: 'sha256:1234567890',
size: tarFile.size,
digest: `sha256:${tarFile.sha256}`,
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',
size: 100,
digest: 'sha256:1234567890',
size: zipFile.size,
digest: `sha256:${zipFile.sha256}`,
annotations: {
'org.opencontainers.image.title': 'test-repo-1.0.0.zip'
'org.opencontainers.image.title': zipFile.path
}
}
],
annotations: {
'org.opencontainers.image.created': '2021-01-01T00:00:00.000Z',
'action.tar.gz.digest': '1234567890',
'action.zip.digest': '1234567890',
'action.tar.gz.digest': tarFile.sha256,
'action.zip.digest': zipFile.sha256,
'com.github.package.type': 'actions_oci_pkg'
}
}
@@ -101,7 +102,7 @@ describe('publishOCIArtifact', () => {
return {
status: 202,
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(axiosPostMock).toHaveBeenCalledTimes(3)
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
axiosHeadMock.mockImplementation(async (url, config) => {
validateRequestConfig(200, url, config)
@@ -152,7 +151,7 @@ describe('publishOCIArtifact', () => {
return {
status: 202,
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)
})
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 () => {
// Simulate failed response code
axiosHeadMock.mockImplementation(async (url, config) => {
@@ -288,7 +349,7 @@ describe('publishOCIArtifact', () => {
return {
status: 202,
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 {
status: 202,
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 {
status: 202,
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 () => {
const modifiedTestManifest = testManifest
const modifiedTestManifest = { ...testManifest } // This is _NOT_ a deep clone
modifiedTestManifest.layers = cloneLayers(modifiedTestManifest.layers)
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(
publishOCIArtifact(
token,
@@ -434,7 +501,7 @@ describe('publishOCIArtifact', () => {
semver,
zipFile,
tarFile,
testManifest
modifiedTestManifest
)
).rejects.toThrow('Unknown media type application/json')
})
@@ -446,12 +513,12 @@ describe('publishOCIArtifact', () => {
function validateRequestConfig(status: number, url: string, config: any): void {
// Basic URL checks
expect(url).toBeDefined()
if (!url.startsWith(registry.toString())) {
console.log(url)
console.log(`${url} does not start with ${registry}`)
}
expect(url.startsWith(registry.toString())).toBe(true)
// if these expect fails, run the test again with `-- --silent=false`
// the console.log above should give a clue about which URL is failing
expect(url.startsWith(registry.toString())).toBeTruthy()
// Config checks
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 () => {
// Mock the environment
process.env.GITHUB_REPOSITORY = 'test/test'
process.env.GITHUB_REPOSITORY = 'test-org/test-repo'
github.context.eventName = 'push'
// Run the action
@@ -83,7 +83,7 @@ describe('action', () => {
it('fails if release tag is not a valid semantic version', async () => {
// Mock the environment
process.env.GITHUB_REPOSITORY = 'test/test'
process.env.GITHUB_REPOSITORY = 'test-org/test-repo'
github.context.eventName = 'release'
github.context.payload = {
release: {
@@ -103,12 +103,12 @@ describe('action', () => {
it('fails if multiple paths are provided and staging files fails', async () => {
// Mock the environment
process.env.GITHUB_REPOSITORY = 'test/test'
process.env.GITHUB_REPOSITORY = 'test-org/test-repo'
github.context.eventName = 'release'
github.context.payload = {
release: {
id: '123',
tag_name: 'v1.0.0'
tag_name: 'v1.2.3'
}
}
getInputMock.mockImplementation((name: string) => {
@@ -135,12 +135,12 @@ describe('action', () => {
it('fails if an error is thrown from dependent code', async () => {
// Mock the environment
process.env.GITHUB_REPOSITORY = 'test/test'
process.env.GITHUB_REPOSITORY = 'test-org/test-repo'
github.context.eventName = 'release'
github.context.payload = {
release: {
id: '123',
tag_name: 'v1.0.0'
tag_name: 'v1.2.3'
}
}
getInputMock.mockImplementation((name: string) => {
@@ -172,22 +172,22 @@ describe('action', () => {
})
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 () => {
await testHappyPath('v1.0.0', 'test')
await testHappyPath('v1.2.3', 'test')
})
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
async function testHappyPath(version: string, path: string): Promise<void> {
// Mock the environment
process.env.GITHUB_REPOSITORY = 'test/test'
process.env.GITHUB_REPOSITORY = 'test-org/test-repo'
github.context.eventName = 'release'
github.context.payload = {
release: {
@@ -228,7 +228,7 @@ async function testHappyPath(version: string, path: string): Promise<void> {
})
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
@@ -239,7 +239,7 @@ async function testHappyPath(version: string, path: string): Promise<void> {
// Check manifest is in output
expect(setOutputMock).toHaveBeenCalledWith(
'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(
'package-manifest',
+14 -14
View File
@@ -1,21 +1,21 @@
import { createActionPackageManifest } from '../src/oci-container'
import { FileMetadata } from '../src/fs-helper'
describe('createActionPackageManigest', () => {
describe('createActionPackageManifest', () => {
it('creates a manifest containing the provided information', () => {
const date = new Date()
const repo = 'test-org/test-repo'
const sanitizedRepo = 'test-org-test-repo'
const version = '1.0.0'
const version = '1.2.3'
const tarFile: FileMetadata = {
path: '/test/test/test',
sha256: '1234567890',
size: 100
path: '/test/test/test.tar.gz',
sha256: 'tarSha',
size: 123
}
const zipFile: FileMetadata = {
path: '/test/test/test',
sha256: '1234567890',
size: 100
path: '/test/test/test.zip',
sha256: 'zipSha',
size: 456
}
const expectedJSON = `{
@@ -49,8 +49,8 @@ describe('createActionPackageManigest', () => {
},
{
"mediaType":"application/vnd.github.actions.package.layer.v1.zip",
"size":${tarFile.size},
"digest":"${tarFile.sha256}",
"size":${zipFile.size},
"digest":"${zipFile.sha256}",
"annotations":{
"org.opencontainers.image.title":"${sanitizedRepo}_${version}.zip"
}
@@ -67,13 +67,13 @@ describe('createActionPackageManigest', () => {
const manifest = createActionPackageManifest(
{
path: 'test.tar.gz',
size: 100,
sha256: '1234567890'
size: tarFile.size,
sha256: tarFile.sha256
},
{
path: 'test.zip',
size: 100,
sha256: '1234567890'
size: zipFile.size,
sha256: zipFile.sha256
},
repo,
version,