fix low hanging fruit linter errors

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