fix low hanging fruit linter errors
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { publishOCIArtifact } from '../src/ghcr-client'
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
import axios from 'axios'
|
||||
import * as fsHelper from '../src/fs-helper'
|
||||
import * as ociContainer from '../src/oci-container'
|
||||
|
||||
@@ -88,14 +88,12 @@ describe('publishOCIArtifact', () => {
|
||||
|
||||
it('publishes layer blobs & then a manifest to the provided registry', async () => {
|
||||
// Simulate none of the blobs existing currently
|
||||
axiosHeadMock.mockImplementation(
|
||||
async (url: string, config: AxiosRequestConfig) => {
|
||||
validateRequestConfig(404, url, config)
|
||||
return {
|
||||
status: 404
|
||||
}
|
||||
axiosHeadMock.mockImplementation(async (url, config) => {
|
||||
validateRequestConfig(404, url, config)
|
||||
return {
|
||||
status: 404
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
// Simulate successful initiation of uploads for all blobs & return location
|
||||
axiosPostMock.mockImplementation(async (url, data, config) => {
|
||||
@@ -109,7 +107,7 @@ describe('publishOCIArtifact', () => {
|
||||
})
|
||||
|
||||
// Simulate successful reading of all the files
|
||||
fsReadFileSyncMock.mockImplementation(async _ => {
|
||||
fsReadFileSyncMock.mockImplementation(() => {
|
||||
return Buffer.from('test')
|
||||
})
|
||||
|
||||
@@ -160,7 +158,7 @@ describe('publishOCIArtifact', () => {
|
||||
})
|
||||
|
||||
// Simulate successful reading of all the files
|
||||
fsReadFileSyncMock.mockImplementation(async _ => {
|
||||
fsReadFileSyncMock.mockImplementation(() => {
|
||||
return Buffer.from('test')
|
||||
})
|
||||
|
||||
@@ -198,7 +196,7 @@ describe('publishOCIArtifact', () => {
|
||||
}
|
||||
})
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
publishOCIArtifact(
|
||||
token,
|
||||
registry,
|
||||
@@ -229,7 +227,7 @@ describe('publishOCIArtifact', () => {
|
||||
}
|
||||
})
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
publishOCIArtifact(
|
||||
token,
|
||||
registry,
|
||||
@@ -261,7 +259,7 @@ describe('publishOCIArtifact', () => {
|
||||
}
|
||||
})
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
publishOCIArtifact(
|
||||
token,
|
||||
registry,
|
||||
@@ -296,7 +294,7 @@ describe('publishOCIArtifact', () => {
|
||||
})
|
||||
|
||||
// Simulate successful reading of all the files
|
||||
fsReadFileSyncMock.mockImplementation(async path => {
|
||||
fsReadFileSyncMock.mockImplementation(() => {
|
||||
return Buffer.from('test')
|
||||
})
|
||||
|
||||
@@ -308,7 +306,7 @@ describe('publishOCIArtifact', () => {
|
||||
}
|
||||
})
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
publishOCIArtifact(
|
||||
token,
|
||||
registry,
|
||||
@@ -343,7 +341,7 @@ describe('publishOCIArtifact', () => {
|
||||
})
|
||||
|
||||
// Simulate successful reading of all the files
|
||||
fsReadFileSyncMock.mockImplementation(async path => {
|
||||
fsReadFileSyncMock.mockImplementation(() => {
|
||||
return Buffer.from('test')
|
||||
})
|
||||
|
||||
@@ -362,7 +360,7 @@ describe('publishOCIArtifact', () => {
|
||||
}
|
||||
})
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
publishOCIArtifact(
|
||||
token,
|
||||
registry,
|
||||
@@ -397,7 +395,7 @@ describe('publishOCIArtifact', () => {
|
||||
})
|
||||
|
||||
// Simulate successful reading of all the files
|
||||
fsReadFileSyncMock.mockImplementation(path => {
|
||||
fsReadFileSyncMock.mockImplementation(() => {
|
||||
throw new Error('failed to read a file: test')
|
||||
})
|
||||
|
||||
@@ -409,7 +407,7 @@ describe('publishOCIArtifact', () => {
|
||||
}
|
||||
})
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
publishOCIArtifact(
|
||||
token,
|
||||
registry,
|
||||
@@ -424,10 +422,10 @@ describe('publishOCIArtifact', () => {
|
||||
})
|
||||
|
||||
it('throws an error if one of the layers has the wrong media type', async () => {
|
||||
let modifiedTestManifest = testManifest
|
||||
const modifiedTestManifest = testManifest
|
||||
modifiedTestManifest.layers[0].mediaType = 'application/json'
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
publishOCIArtifact(
|
||||
token,
|
||||
registry,
|
||||
@@ -444,11 +442,8 @@ describe('publishOCIArtifact', () => {
|
||||
|
||||
// We expect all axios calls to have auth headers set and to not intercept any status codes so we can handle them.
|
||||
// This function verifies that given an axios request config.
|
||||
function validateRequestConfig(
|
||||
status: number,
|
||||
url: string,
|
||||
config: AxiosRequestConfig
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function validateRequestConfig(status: number, url: string, config: any): void {
|
||||
// Basic URL checks
|
||||
expect(url).toBeDefined()
|
||||
|
||||
|
||||
@@ -11,15 +11,9 @@ import * as main from '../src/main'
|
||||
import * as github from '@actions/github'
|
||||
|
||||
import * as fsHelper from '../src/fs-helper'
|
||||
import * as ociContainer from '../src/oci-container'
|
||||
import * as ghcr from '../src/ghcr-client'
|
||||
|
||||
// Mock the action's main function
|
||||
const runMock = jest.spyOn(main, 'run')
|
||||
|
||||
// Mock the GitHub Actions core library
|
||||
let debugMock: jest.SpyInstance
|
||||
let errorMock: jest.SpyInstance
|
||||
let getInputMock: jest.SpyInstance
|
||||
let setFailedMock: jest.SpyInstance
|
||||
let setOutputMock: jest.SpyInstance
|
||||
@@ -38,8 +32,6 @@ describe('action', () => {
|
||||
jest.clearAllMocks()
|
||||
|
||||
// Core mocks
|
||||
debugMock = jest.spyOn(core, 'debug').mockImplementation()
|
||||
errorMock = jest.spyOn(core, 'error').mockImplementation()
|
||||
getInputMock = jest.spyOn(core, 'getInput').mockImplementation()
|
||||
setFailedMock = jest.spyOn(core, 'setFailed').mockImplementation()
|
||||
setOutputMock = jest.spyOn(core, 'setOutput').mockImplementation()
|
||||
|
||||
@@ -3,21 +3,21 @@ import { FileMetadata } from '../src/fs-helper'
|
||||
|
||||
describe('createActionPackageManigest', () => {
|
||||
it('creates a manifest containing the provided information', () => {
|
||||
let date = new Date()
|
||||
let repo = 'test-repo'
|
||||
let version = '1.0.0'
|
||||
let tarFile: FileMetadata = {
|
||||
const date = new Date()
|
||||
const repo = 'test-repo'
|
||||
const version = '1.0.0'
|
||||
const tarFile: FileMetadata = {
|
||||
path: '/test/test/test',
|
||||
sha256: '1234567890',
|
||||
size: 100
|
||||
}
|
||||
let zipFile: FileMetadata = {
|
||||
const zipFile: FileMetadata = {
|
||||
path: '/test/test/test',
|
||||
sha256: '1234567890',
|
||||
size: 100
|
||||
}
|
||||
|
||||
let expectedJSON: String = `{
|
||||
const expectedJSON = `{
|
||||
"schemaVersion": 2,
|
||||
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||
"artifactType": "application/vnd.oci.image.manifest.v1+json",
|
||||
@@ -63,7 +63,7 @@ describe('createActionPackageManigest', () => {
|
||||
}
|
||||
}`
|
||||
|
||||
let manifest = createActionPackageManifest(
|
||||
const manifest = createActionPackageManifest(
|
||||
{
|
||||
path: 'test.tar.gz',
|
||||
size: 100,
|
||||
@@ -79,7 +79,7 @@ describe('createActionPackageManigest', () => {
|
||||
date
|
||||
)
|
||||
|
||||
let manifestJSON = JSON.stringify(manifest)
|
||||
const manifestJSON = JSON.stringify(manifest)
|
||||
expect(manifestJSON).toEqual(expectedJSON.replace(/\s/g, ''))
|
||||
})
|
||||
})
|
||||
|
||||
+1
-1
@@ -69,7 +69,6 @@
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/github": "^6.0.0",
|
||||
"@types/axios": "^0.14.0",
|
||||
"archiver": "^6.0.1",
|
||||
"axios": "^1.6.2",
|
||||
"axios-debug-log": "^1.0.0",
|
||||
@@ -77,6 +76,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/archiver": "^6.0.1",
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/jest": "^29.5.8",
|
||||
"@types/node": "^20.9.1",
|
||||
"@types/tar": "^6.1.9",
|
||||
|
||||
+13
-16
@@ -1,14 +1,11 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as exec from '@actions/exec'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import * as tar from 'tar'
|
||||
import * as archiver from 'archiver'
|
||||
import * as crypto from 'crypto'
|
||||
import * as os from 'os'
|
||||
import * as zlib from 'zlib'
|
||||
|
||||
export function createTempDir() {
|
||||
export function createTempDir(): string {
|
||||
const randomDirName = crypto.randomBytes(4).toString('hex')
|
||||
const tempDir = path.join(os.tmpdir(), randomDirName)
|
||||
|
||||
@@ -19,7 +16,7 @@ export function createTempDir() {
|
||||
return tempDir
|
||||
}
|
||||
|
||||
export function removeDir(dir: string) {
|
||||
export function removeDir(dir: string): void {
|
||||
fs.rmSync(dir, { recursive: true })
|
||||
}
|
||||
|
||||
@@ -60,7 +57,7 @@ export async function createArchives(
|
||||
archive.finalize()
|
||||
}),
|
||||
new Promise<FileMetadata>((resolve, reject) => {
|
||||
const tarStream = tar
|
||||
tar
|
||||
.c(
|
||||
{
|
||||
file: tarPath,
|
||||
@@ -77,20 +74,20 @@ export async function createArchives(
|
||||
]).then(([zipFile, tarFile]) => ({ zipFile, tarFile }))
|
||||
}
|
||||
|
||||
export function isDirectory(path: string): boolean {
|
||||
return fs.existsSync(path) && fs.lstatSync(path).isDirectory()
|
||||
export function isDirectory(dirPath: string): boolean {
|
||||
return fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory()
|
||||
}
|
||||
|
||||
export function readFileContents(path: string): Buffer {
|
||||
return fs.readFileSync(path)
|
||||
export function readFileContents(filePath: string): Buffer {
|
||||
return fs.readFileSync(filePath)
|
||||
}
|
||||
|
||||
// Converts a file path to a filemetadata object by querying the fs for relevant metadata.
|
||||
async function fileMetadata(path: string): Promise<FileMetadata> {
|
||||
const stats = fs.statSync(path)
|
||||
async function fileMetadata(filePath: string): Promise<FileMetadata> {
|
||||
const stats = fs.statSync(filePath)
|
||||
const size = stats.size
|
||||
const hash = crypto.createHash('sha256')
|
||||
const fileStream = fs.createReadStream(path)
|
||||
const fileStream = fs.createReadStream(filePath)
|
||||
return new Promise((resolve, reject) => {
|
||||
fileStream.on('data', data => {
|
||||
hash.update(data)
|
||||
@@ -98,9 +95,9 @@ async function fileMetadata(path: string): Promise<FileMetadata> {
|
||||
fileStream.on('end', () => {
|
||||
const sha256 = hash.digest('hex')
|
||||
resolve({
|
||||
path: path,
|
||||
size: size,
|
||||
sha256: 'sha256:' + sha256
|
||||
path: filePath,
|
||||
size,
|
||||
sha256: `sha256:${sha256}`
|
||||
})
|
||||
})
|
||||
fileStream.on('error', err => {
|
||||
|
||||
+16
-19
@@ -2,9 +2,6 @@ import * as core from '@actions/core'
|
||||
import { FileMetadata } from './fs-helper'
|
||||
import * as ociContainer from './oci-container'
|
||||
import axios from 'axios'
|
||||
import { fieldEnds } from 'tar'
|
||||
import * as fs from 'fs'
|
||||
import { promiseHooks } from 'v8'
|
||||
import * as fsHelper from './fs-helper'
|
||||
import axiosDebugLog from 'axios-debug-log'
|
||||
|
||||
@@ -18,7 +15,7 @@ export async function publishOCIArtifact(
|
||||
zipFile: FileMetadata,
|
||||
tarFile: FileMetadata,
|
||||
manifest: ociContainer.Manifest,
|
||||
debugRequests: boolean = false
|
||||
debugRequests = false
|
||||
): Promise<URL> {
|
||||
if (debugRequests) {
|
||||
configureRequestDebugLogging()
|
||||
@@ -43,7 +40,7 @@ export async function publishOCIArtifact(
|
||||
`Creating GHCR package for release with semver:${semver} with path:"${zipFile.path}" and "${tarFile.path}".`
|
||||
)
|
||||
|
||||
let layerUploads: Promise<void>[] = manifest.layers.map(layer => {
|
||||
const layerUploads: Promise<void>[] = manifest.layers.map(async layer => {
|
||||
switch (layer.mediaType) {
|
||||
case 'application/vnd.github.actions.package.layer.v1.tar+gzip':
|
||||
return uploadLayer(
|
||||
@@ -98,7 +95,7 @@ async function uploadLayer(
|
||||
headers: {
|
||||
Authorization: `Bearer ${b64Token}`
|
||||
},
|
||||
validateStatus: function (status: number) {
|
||||
validateStatus: () => {
|
||||
return true // Allow non 2xx responses
|
||||
}
|
||||
}
|
||||
@@ -124,12 +121,12 @@ async function uploadLayer(
|
||||
headers: {
|
||||
Authorization: `Bearer ${b64Token}`
|
||||
},
|
||||
validateStatus: function (status: number) {
|
||||
validateStatus: () => {
|
||||
return true // Allow non 2xx responses
|
||||
}
|
||||
})
|
||||
|
||||
if (initiateUploadResponse.status != 202) {
|
||||
if (initiateUploadResponse.status !== 202) {
|
||||
core.error(
|
||||
`Unexpected response from upload post ${uploadBlobEndpoint}: ${initiateUploadResponse.status}`
|
||||
)
|
||||
@@ -139,17 +136,17 @@ async function uploadLayer(
|
||||
}
|
||||
|
||||
const locationResponseHeader = initiateUploadResponse.headers['location']
|
||||
if (locationResponseHeader == undefined) {
|
||||
if (locationResponseHeader === undefined) {
|
||||
throw new Error(
|
||||
`No location header in response from upload post ${uploadBlobEndpoint} for layer ${layer.digest}`
|
||||
)
|
||||
}
|
||||
|
||||
let pathname = (locationResponseHeader as string) + '?digest=' + 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
|
||||
var data: Buffer
|
||||
let data: Buffer
|
||||
if (file.size === 0) {
|
||||
data = Buffer.alloc(0)
|
||||
} else {
|
||||
@@ -163,12 +160,12 @@ async function uploadLayer(
|
||||
'Accept-Encoding': 'gzip', // TODO: What about for the config layer?
|
||||
'Content-Length': layer.size.toString()
|
||||
},
|
||||
validateStatus: function (status: number) {
|
||||
validateStatus: () => {
|
||||
return true // Allow non 2xx responses
|
||||
}
|
||||
})
|
||||
|
||||
if (putResponse.status != 201) {
|
||||
if (putResponse.status !== 201) {
|
||||
throw new Error(
|
||||
`Unexpected response from PUT upload ${putResponse.status} for layer ${layer.digest}`
|
||||
)
|
||||
@@ -187,27 +184,27 @@ async function uploadManifest(
|
||||
Authorization: `Bearer ${b64Token}`,
|
||||
'Content-Type': 'application/vnd.oci.image.manifest.v1+json'
|
||||
},
|
||||
validateStatus: function (status: number) {
|
||||
validateStatus: () => {
|
||||
return true // Allow non 2xx responses
|
||||
}
|
||||
})
|
||||
|
||||
if (putResponse.status != 201) {
|
||||
if (putResponse.status !== 201) {
|
||||
throw new Error(
|
||||
`Unexpected response from PUT manifest ${putResponse.status}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function configureRequestDebugLogging() {
|
||||
function configureRequestDebugLogging(): void {
|
||||
axiosDebugLog({
|
||||
request: function (debug, config) {
|
||||
request: (debug, config) => {
|
||||
core.debug(`Request with ${config}`)
|
||||
},
|
||||
response: function (debug, response) {
|
||||
response: (debug, response) => {
|
||||
core.debug(`Response with ${response}`)
|
||||
},
|
||||
error: function (debug, error) {
|
||||
error: (debug, error) => {
|
||||
core.debug(`Error with ${error}`)
|
||||
}
|
||||
})
|
||||
|
||||
+3
-4
@@ -4,14 +4,13 @@ import * as fsHelper from './fs-helper'
|
||||
import * as ociContainer from './oci-container'
|
||||
import * as ghcr from './ghcr-client'
|
||||
import semver from 'semver'
|
||||
import { url } from 'inspector'
|
||||
|
||||
/**
|
||||
* The main function for the action.
|
||||
* @returns {Promise<void>} Resolves when the action is complete.
|
||||
*/
|
||||
export async function run(): Promise<void> {
|
||||
let tmpDir: string = ''
|
||||
let tmpDir = ''
|
||||
|
||||
try {
|
||||
// Parse and validate Actions execution context, including the repository name, release name and event type
|
||||
@@ -29,7 +28,7 @@ export async function run(): Promise<void> {
|
||||
|
||||
// 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
|
||||
let targetVersion = semver.parse(releaseTag.replace(/^v/, ''))
|
||||
const targetVersion = semver.parse(releaseTag.replace(/^v/, ''))
|
||||
if (!targetVersion) {
|
||||
core.setFailed(
|
||||
`${releaseTag} is not a valid semantic version, and so cannot be uploaded as an Immutable Action.`
|
||||
@@ -62,7 +61,7 @@ export async function run(): Promise<void> {
|
||||
new Date()
|
||||
)
|
||||
|
||||
let packageURL = await ghcr.publishOCIArtifact(
|
||||
const packageURL = await ghcr.publishOCIArtifact(
|
||||
token,
|
||||
registryURL,
|
||||
repository,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Tracing } from 'trace_events'
|
||||
import { FileMetadata } from './fs-helper'
|
||||
|
||||
export interface Manifest {
|
||||
@@ -7,14 +6,14 @@ export interface Manifest {
|
||||
artifactType: string
|
||||
config: Layer
|
||||
layers: Layer[]
|
||||
annotations: {}
|
||||
annotations: { [key: string]: string }
|
||||
}
|
||||
|
||||
export interface Layer {
|
||||
mediaType: string
|
||||
size: number
|
||||
digest: string
|
||||
annotations: {}
|
||||
annotations: { [key: string]: string }
|
||||
}
|
||||
|
||||
// Given a name and archive metadata, creates a manifest in the format expected by GHCR for an Actions Package.
|
||||
|
||||
Reference in New Issue
Block a user