diff --git a/__tests__/util.test.ts b/__tests__/util.test.ts index 2dfbc30c..d183896f 100644 --- a/__tests__/util.test.ts +++ b/__tests__/util.test.ts @@ -267,6 +267,12 @@ describe('trimSuffix', () => { }); }); +describe('hash', () => { + it('returns 2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae', async () => { + expect(Util.hash('foo')).toEqual('2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae'); + }); +}); + // See: https://github.com/actions/toolkit/blob/a1b068ec31a042ff1e10a522d8fdf0b8869d53ca/packages/core/src/core.ts#L89 function getInputName(name: string): string { return `INPUT_${name.replace(/ /g, '_').toUpperCase()}`; diff --git a/src/buildx/install.ts b/src/buildx/install.ts index e359e1a3..6b9b397c 100644 --- a/src/buildx/install.ts +++ b/src/buildx/install.ts @@ -14,18 +14,17 @@ * limitations under the License. */ -import crypto from 'crypto'; import fs from 'fs'; import os from 'os'; import path from 'path'; import * as core from '@actions/core'; import * as httpm from '@actions/http-client'; import * as tc from '@actions/tool-cache'; -import * as cache from '@actions/cache'; import * as semver from 'semver'; import * as util from 'util'; import {Buildx} from './buildx'; +import {Cache} from '../cache'; import {Context} from '../context'; import {Exec} from '../exec'; import {Docker} from '../docker/docker'; @@ -66,7 +65,12 @@ export class Install { throw new Error(`Invalid Buildx version "${vspec}".`); } - const installCache = new InstallCache(version.key != 'official' ? `buildx-dl-bin-${version.key}` : 'buildx-dl-bin', vspec); + const installCache = new Cache({ + htcName: version.key != 'official' ? `buildx-dl-bin-${version.key}` : 'buildx-dl-bin', + htcVersion: vspec, + baseCacheDir: path.join(Buildx.configDir, '.bin'), + cacheFile: os.platform() == 'win32' ? 'docker-buildx.exe' : 'docker-buildx' + }); const cacheFoundPath = await installCache.find(); if (cacheFoundPath) { @@ -94,7 +98,12 @@ export class Install { const vspec = await this.vspec(gitContext); core.debug(`Install.build vspec: ${vspec}`); - const installCache = new InstallCache('buildx-build-bin', vspec); + const installCache = new Cache({ + htcName: 'buildx-build-bin', + htcVersion: vspec, + baseCacheDir: path.join(Buildx.configDir, '.bin'), + cacheFile: os.platform() == 'win32' ? 'docker-buildx.exe' : 'docker-buildx' + }); const cacheFoundPath = await installCache.find(); if (cacheFoundPath) { @@ -252,7 +261,7 @@ export class Install { const [owner, repo] = baseURL.substring('https://github.com/'.length).split('/'); const key = `${owner}/${Util.trimSuffix(repo, '.git')}/${sha}`; - const hash = crypto.createHash('sha256').update(key).digest('hex'); + const hash = Util.hash(key); core.info(`Use ${hash} version spec cache key for ${key}`); return hash; } @@ -301,74 +310,3 @@ export class Install { return releases[version.version]; } } - -class InstallCache { - private readonly htcName: string; - private readonly htcVersion: string; - private readonly ghaCacheKey: string; - private readonly cacheDir: string; - private readonly cacheFile: string; - private readonly cachePath: string; - - constructor(htcName: string, htcVersion: string) { - this.htcName = htcName; - this.htcVersion = htcVersion; - this.ghaCacheKey = util.format('%s-%s-%s', this.htcName, this.htcVersion, this.platform()); - this.cacheDir = path.join(Buildx.configDir, '.bin', htcVersion, this.platform()); - this.cacheFile = os.platform() == 'win32' ? 'docker-buildx.exe' : 'docker-buildx'; - this.cachePath = path.join(this.cacheDir, this.cacheFile); - if (!fs.existsSync(this.cacheDir)) { - fs.mkdirSync(this.cacheDir, {recursive: true}); - } - } - - public async save(file: string): Promise { - core.debug(`InstallCache.save ${file}`); - const cachePath = this.copyToCache(file); - - const htcPath = await tc.cacheDir(this.cacheDir, this.htcName, this.htcVersion, this.platform()); - core.debug(`InstallCache.save cached to hosted tool cache ${htcPath}`); - - if (cache.isFeatureAvailable()) { - core.debug(`InstallCache.save caching ${this.ghaCacheKey} to GitHub Actions cache`); - await cache.saveCache([this.cacheDir], this.ghaCacheKey); - } - - return cachePath; - } - - public async find(): Promise { - let htcPath = tc.find(this.htcName, this.htcVersion, this.platform()); - if (htcPath) { - core.info(`Restored from hosted tool cache ${htcPath}`); - return this.copyToCache(`${htcPath}/${this.cacheFile}`); - } - - if (cache.isFeatureAvailable()) { - core.debug(`GitHub Actions cache feature available`); - if (await cache.restoreCache([this.cacheDir], this.ghaCacheKey)) { - core.info(`Restored ${this.ghaCacheKey} from GitHub Actions cache`); - htcPath = await tc.cacheDir(this.cacheDir, this.htcName, this.htcVersion, this.platform()); - core.info(`Restored to hosted tool cache ${htcPath}`); - return this.copyToCache(`${htcPath}/${this.cacheFile}`); - } - } else { - core.info(`GitHub Actions cache feature not available`); - } - - return ''; - } - - private copyToCache(file: string): string { - core.debug(`Copying ${file} to ${this.cachePath}`); - fs.copyFileSync(file, this.cachePath); - fs.chmodSync(this.cachePath, '0755'); - return this.cachePath; - } - - private platform(): string { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const arm_version = (process.config.variables as any).arm_version; - return `${os.platform()}-${os.arch()}${arm_version ? 'v' + arm_version : ''}`; - } -} diff --git a/src/cache.ts b/src/cache.ts new file mode 100644 index 00000000..63e78364 --- /dev/null +++ b/src/cache.ts @@ -0,0 +1,96 @@ +/** + * Copyright 2023 actions-toolkit authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +import * as core from '@actions/core'; +import * as tc from '@actions/tool-cache'; +import * as cache from '@actions/cache'; +import * as util from 'util'; + +export interface CacheOpts { + htcName: string; + htcVersion: string; + baseCacheDir: string; + cacheFile: string; +} + +export class Cache { + private readonly opts: CacheOpts; + private readonly ghaCacheKey: string; + private readonly cacheDir: string; + private readonly cachePath: string; + + constructor(opts: CacheOpts) { + this.opts = opts; + this.ghaCacheKey = util.format('%s-%s-%s', this.opts.htcName, this.opts.htcVersion, this.platform()); + this.cacheDir = path.join(this.opts.baseCacheDir, this.opts.htcVersion, this.platform()); + this.cachePath = path.join(this.cacheDir, this.opts.cacheFile); + if (!fs.existsSync(this.cacheDir)) { + fs.mkdirSync(this.cacheDir, {recursive: true}); + } + } + + public async save(file: string): Promise { + core.debug(`Cache.save ${file}`); + const cachePath = this.copyToCache(file); + + const htcPath = await tc.cacheDir(this.cacheDir, this.opts.htcName, this.opts.htcVersion, this.platform()); + core.debug(`Cache.save cached to hosted tool cache ${htcPath}`); + + if (cache.isFeatureAvailable()) { + core.debug(`Cache.save caching ${this.ghaCacheKey} to GitHub Actions cache`); + await cache.saveCache([this.cacheDir], this.ghaCacheKey); + } + + return cachePath; + } + + public async find(): Promise { + let htcPath = tc.find(this.opts.htcName, this.opts.htcVersion, this.platform()); + if (htcPath) { + core.info(`Restored from hosted tool cache ${htcPath}`); + return this.copyToCache(`${htcPath}/${this.opts.cacheFile}`); + } + + if (cache.isFeatureAvailable()) { + core.debug(`GitHub Actions cache feature available`); + if (await cache.restoreCache([this.cacheDir], this.ghaCacheKey)) { + core.info(`Restored ${this.ghaCacheKey} from GitHub Actions cache`); + htcPath = await tc.cacheDir(this.cacheDir, this.opts.htcName, this.opts.htcVersion, this.platform()); + core.info(`Restored to hosted tool cache ${htcPath}`); + return this.copyToCache(`${htcPath}/${this.opts.cacheFile}`); + } + } else { + core.info(`GitHub Actions cache feature not available`); + } + + return ''; + } + + private copyToCache(file: string): string { + core.debug(`Copying ${file} to ${this.cachePath}`); + fs.copyFileSync(file, this.cachePath); + return this.cachePath; + } + + private platform(): string { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const arm_version = (process.config.variables as any).arm_version; + return `${os.platform()}-${os.arch()}${arm_version ? 'v' + arm_version : ''}`; + } +} diff --git a/src/util.ts b/src/util.ts index 75fcc3b4..f9c41130 100644 --- a/src/util.ts +++ b/src/util.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import crypto from 'crypto'; import fs from 'fs'; import * as core from '@actions/core'; import * as io from '@actions/io'; @@ -139,4 +140,8 @@ export class Util { public static sleep(seconds: number) { return new Promise(resolve => setTimeout(resolve, seconds * 1000)); } + + public static hash(input: string): string { + return crypto.createHash('sha256').update(input).digest('hex'); + } }