diff --git a/__tests__/buildkit/buildkit.test.ts b/__tests__/buildkit/buildkit.test.ts index ca261a9..8650c2c 100644 --- a/__tests__/buildkit/buildkit.test.ts +++ b/__tests__/buildkit/buildkit.test.ts @@ -18,7 +18,6 @@ import {beforeEach, describe, expect, it, jest, test} from '@jest/globals'; import {BuildKit} from '../../src/buildkit/buildkit'; import {Builder} from '../../src/buildx/builder'; -import {Context} from '../../src/context'; import {BuilderInfo} from '../../src/types/builder'; @@ -47,13 +46,9 @@ jest.spyOn(Builder.prototype, 'inspect').mockImplementation(async (): Promise { it('valid', async () => { - const builder = new Builder({ - context: new Context() - }); + const builder = new Builder(); const builderInfo = await builder.inspect('builder2'); - const buildkit = new BuildKit({ - context: new Context() - }); + const buildkit = new BuildKit(); const version = await buildkit.getVersion(builderInfo.nodes[0]); expect(version).toBe('v0.11.0'); }); @@ -64,9 +59,7 @@ describe('satisfies', () => { ['builder2', '>=0.10.0', true], ['builder2', '>0.11.0', false] ])('given %p', async (builderName, range, expected) => { - const buildkit = new BuildKit({ - context: new Context() - }); + const buildkit = new BuildKit(); expect(await buildkit.versionSatisfies(builderName, range)).toBe(expected); }); }); diff --git a/__tests__/buildkit/config.test.ts b/__tests__/buildkit/config.test.ts index 6c71081..0ee209f 100644 --- a/__tests__/buildkit/config.test.ts +++ b/__tests__/buildkit/config.test.ts @@ -27,13 +27,14 @@ const fixturesDir = path.join(__dirname, '..', 'fixtures'); const tmpDir = path.join(process.env.TEMP || '/tmp', 'buildkit-config-jest'); const tmpName = path.join(tmpDir, '.tmpname-jest'); -jest.spyOn(Context.prototype, 'tmpDir').mockImplementation((): string => { +jest.spyOn(Context, 'tmpDir').mockImplementation((): string => { if (!fs.existsSync(tmpDir)) { fs.mkdirSync(tmpDir, {recursive: true}); } return tmpDir; }); -jest.spyOn(Context.prototype, 'tmpName').mockImplementation((): string => { + +jest.spyOn(Context, 'tmpName').mockImplementation((): string => { return tmpName; }); @@ -60,9 +61,7 @@ describe('resolve', () => { ] ])('given %p config', async (val, file, exValue, error: Error) => { try { - const buildkit = new BuildKit({ - context: new Context() - }); + const buildkit = new BuildKit(); let config: string; if (file) { config = buildkit.config.resolveFromFile(val); diff --git a/__tests__/buildx/builder.test.ts b/__tests__/buildx/builder.test.ts index 6c04fc2..156d394 100644 --- a/__tests__/buildx/builder.test.ts +++ b/__tests__/buildx/builder.test.ts @@ -19,7 +19,6 @@ import * as fs from 'fs'; import * as path from 'path'; import {Builder} from '../../src/buildx/builder'; -import {Context} from '../../src/context'; import {BuilderInfo} from '../../src/types/builder'; @@ -50,9 +49,7 @@ jest.spyOn(Builder.prototype, 'inspect').mockImplementation(async (): Promise { it('valid', async () => { - const builder = new Builder({ - context: new Context() - }); + const builder = new Builder(); const builderInfo = await builder.inspect(''); expect(builderInfo).not.toBeUndefined(); expect(builderInfo.name).not.toEqual(''); diff --git a/__tests__/buildx/buildx.test.ts b/__tests__/buildx/buildx.test.ts index 7f02dcf..0417427 100644 --- a/__tests__/buildx/buildx.test.ts +++ b/__tests__/buildx/buildx.test.ts @@ -30,13 +30,14 @@ import {Cert} from '../../src/types/buildx'; const tmpDir = path.join(process.env.TEMP || '/tmp', 'buildx-jest'); const tmpName = path.join(tmpDir, '.tmpname-jest'); -jest.spyOn(Context.prototype, 'tmpDir').mockImplementation((): string => { +jest.spyOn(Context, 'tmpDir').mockImplementation((): string => { if (!fs.existsSync(tmpDir)) { fs.mkdirSync(tmpDir, {recursive: true}); } return tmpDir; }); -jest.spyOn(Context.prototype, 'tmpName').mockImplementation((): string => { + +jest.spyOn(Context, 'tmpName').mockImplementation((): string => { return tmpName; }); @@ -92,7 +93,6 @@ describe('isAvailable', () => { it('docker cli', async () => { const execSpy = jest.spyOn(exec, 'getExecOutput'); const buildx = new Buildx({ - context: new Context(), standalone: false }); await buildx.isAvailable(); @@ -105,7 +105,6 @@ describe('isAvailable', () => { it('standalone', async () => { const execSpy = jest.spyOn(exec, 'getExecOutput'); const buildx = new Buildx({ - context: new Context(), standalone: true }); await buildx.isAvailable(); @@ -121,7 +120,6 @@ describe('printInspect', () => { it('prints builder2 instance', async () => { const execSpy = jest.spyOn(exec, 'exec'); const buildx = new Buildx({ - context: new Context(), standalone: true }); await buildx.printInspect('builder2').catch(() => { @@ -137,7 +135,6 @@ describe('printVersion', () => { it('docker cli', async () => { const execSpy = jest.spyOn(exec, 'exec'); const buildx = new Buildx({ - context: new Context(), standalone: false }); await buildx.printVersion(); @@ -148,7 +145,6 @@ describe('printVersion', () => { it('standalone', async () => { const execSpy = jest.spyOn(exec, 'exec'); const buildx = new Buildx({ - context: new Context(), standalone: true }); await buildx.printVersion(); @@ -160,9 +156,7 @@ describe('printVersion', () => { describe('version', () => { it('valid', async () => { - const buildx = new Buildx({ - context: new Context() - }); + const buildx = new Buildx(); expect(semver.valid(await buildx.version)).not.toBeUndefined(); }); }); @@ -184,9 +178,7 @@ describe('versionSatisfies', () => { ['bda4882a65349ca359216b135896bddc1d92461c', '>0.1.0', false], ['f117971', '>0.6.0', true] ])('given %p', async (version, range, expected) => { - const buildx = new Buildx({ - context: new Context() - }); + const buildx = new Buildx(); expect(await buildx.versionSatisfies(range, version)).toBe(expected); }); }); diff --git a/__tests__/buildx/inputs.test.ts b/__tests__/buildx/inputs.test.ts index c600778..fee9b03 100644 --- a/__tests__/buildx/inputs.test.ts +++ b/__tests__/buildx/inputs.test.ts @@ -32,13 +32,14 @@ const metadata = `{ "containerimage.digest": "sha256:b09b9482c72371486bb2c1d2c2a2633ed1d0b8389e12c8d52b9e052725c0c83c" }`; -jest.spyOn(Context.prototype, 'tmpDir').mockImplementation((): string => { +jest.spyOn(Context, 'tmpDir').mockImplementation((): string => { if (!fs.existsSync(tmpDir)) { fs.mkdirSync(tmpDir, {recursive: true}); } return tmpDir; }); -jest.spyOn(Context.prototype, 'tmpName').mockImplementation((): string => { + +jest.spyOn(Context, 'tmpName').mockImplementation((): string => { return tmpName; }); @@ -52,9 +53,7 @@ afterEach(() => { describe('resolveBuildImageID', () => { it('matches', async () => { - const buildx = new Buildx({ - context: new Context() - }); + const buildx = new Buildx(); const imageID = 'sha256:bfb45ab72e46908183546477a08f8867fc40cebadd00af54b071b097aed127a9'; const imageIDFile = buildx.inputs.getBuildImageIDFilePath(); await fs.writeFileSync(imageIDFile, imageID); @@ -65,9 +64,7 @@ describe('resolveBuildImageID', () => { describe('resolveBuildMetadata', () => { it('matches', async () => { - const buildx = new Buildx({ - context: new Context() - }); + const buildx = new Buildx(); const metadataFile = buildx.inputs.getBuildMetadataFilePath(); await fs.writeFileSync(metadataFile, metadata); const expected = buildx.inputs.resolveBuildMetadata(); @@ -77,9 +74,7 @@ describe('resolveBuildMetadata', () => { describe('resolveDigest', () => { it('matches', async () => { - const buildx = new Buildx({ - context: new Context() - }); + const buildx = new Buildx(); const metadataFile = buildx.inputs.getBuildMetadataFilePath(); await fs.writeFileSync(metadataFile, metadata); const expected = buildx.inputs.resolveDigest(); @@ -129,9 +124,7 @@ describe('getProvenanceInput', () => { ], ])('given input %p', async (input: string, expected: string) => { await setInput('provenance', input); - const buildx = new Buildx({ - context: new Context() - }); + const buildx = new Buildx(); expect(buildx.inputs.getProvenanceInput('provenance')).toEqual(expected); }); }); @@ -160,9 +153,7 @@ describe('resolveProvenanceAttrs', () => { 'builder-id=https://github.com/docker/actions-toolkit/actions/runs/123' ], ])('given %p', async (input: string, expected: string) => { - const buildx = new Buildx({ - context: new Context() - }); + const buildx = new Buildx(); expect(buildx.inputs.resolveProvenanceAttrs(input)).toEqual(expected); }); }); @@ -179,9 +170,7 @@ describe('resolveBuildSecret', () => { [`notfound=secret`, true, '', '', new Error('secret file secret not found')] ])('given %p key and %p secret', async (kvp: string, file: boolean, exKey: string, exValue: string, error: Error) => { try { - const buildx = new Buildx({ - context: new Context() - }); + const buildx = new Buildx(); let secret: string; if (file) { secret = buildx.inputs.resolveBuildSecretFile(kvp); diff --git a/__tests__/context.test.ts b/__tests__/context.test.ts index f9ec49e..94257c4 100644 --- a/__tests__/context.test.ts +++ b/__tests__/context.test.ts @@ -25,13 +25,14 @@ import {Context} from '../src/context'; const tmpDir = path.join(process.env.TEMP || '/tmp', 'context-jest'); const tmpName = path.join(tmpDir, '.tmpname-jest'); -jest.spyOn(Context.prototype, 'tmpDir').mockImplementation((): string => { +jest.spyOn(Context, 'tmpDir').mockImplementation((): string => { if (!fs.existsSync(tmpDir)) { fs.mkdirSync(tmpDir, {recursive: true}); } return tmpDir; }); -jest.spyOn(Context.prototype, 'tmpName').mockImplementation((): string => { + +jest.spyOn(Context, 'tmpName').mockImplementation((): string => { return tmpName; }); @@ -43,16 +44,20 @@ afterEach(() => { rimraf.sync(tmpDir); }); +describe('gitRef', () => { + it('returns refs/heads/master', async () => { + expect(Context.gitRef()).toEqual('refs/heads/master'); + }); +}); + describe('gitContext', () => { it('returns refs/heads/master', async () => { - const context = new Context(); - expect(context.buildGitContext).toEqual('https://github.com/docker/actions-toolkit.git#refs/heads/master'); + expect(Context.gitContext()).toEqual('https://github.com/docker/actions-toolkit.git#refs/heads/master'); }); }); describe('provenanceBuilderID', () => { it('returns 123', async () => { - const context = new Context(); - expect(context.provenanceBuilderID).toEqual('https://github.com/docker/actions-toolkit/actions/runs/123'); + expect(Context.provenanceBuilderID()).toEqual('https://github.com/docker/actions-toolkit/actions/runs/123'); }); }); diff --git a/src/buildkit/buildkit.ts b/src/buildkit/buildkit.ts index aef84e5..ddbe96b 100644 --- a/src/buildkit/buildkit.ts +++ b/src/buildkit/buildkit.ts @@ -18,7 +18,6 @@ import * as core from '@actions/core'; import * as exec from '@actions/exec'; import * as semver from 'semver'; -import {Context} from '../context'; import {Buildx} from '../buildx/buildx'; import {Builder} from '../buildx/builder'; import {Config} from './config'; @@ -26,24 +25,17 @@ import {Config} from './config'; import {BuilderInfo, NodeInfo} from '../types/builder'; export interface BuildKitOpts { - context: Context; buildx?: Buildx; } export class BuildKit { - private readonly context: Context; private readonly buildx: Buildx; public readonly config: Config; - constructor(opts: BuildKitOpts) { - this.context = opts.context; - this.config = new Config(this.context); - this.buildx = - opts?.buildx || - new Buildx({ - context: this.context - }); + constructor(opts?: BuildKitOpts) { + this.config = new Config(); + this.buildx = opts?.buildx || new Buildx(); } public async getVersion(node: NodeInfo): Promise { @@ -89,10 +81,7 @@ export class BuildKit { public async versionSatisfies(builderName: string, range: string, builderInfo?: BuilderInfo): Promise { if (!builderInfo) { - builderInfo = await new Builder({ - context: this.context, - buildx: this.buildx - }).inspect(builderName); + builderInfo = await new Builder({buildx: this.buildx}).inspect(builderName); } for (const node of builderInfo.nodes) { core.debug(`BuildKit.versionSatisfies ${node}: ${range}`); diff --git a/src/buildkit/config.ts b/src/buildkit/config.ts index 6da6ef3..9563b4c 100644 --- a/src/buildkit/config.ts +++ b/src/buildkit/config.ts @@ -19,12 +19,6 @@ import fs from 'fs'; import {Context} from '../context'; export class Config { - private readonly context: Context; - - constructor(context: Context) { - this.context = context; - } - public resolveFromString(s: string): string { return this.resolve(s, false); } @@ -40,7 +34,7 @@ export class Config { } s = fs.readFileSync(s, {encoding: 'utf-8'}); } - const configFile = this.context.tmpName({tmpdir: this.context.tmpDir()}); + const configFile = Context.tmpName({tmpdir: Context.tmpDir()}); fs.writeFileSync(configFile, s); return configFile; } diff --git a/src/buildx/builder.ts b/src/buildx/builder.ts index 3e07227..18d5fde 100644 --- a/src/buildx/builder.ts +++ b/src/buildx/builder.ts @@ -17,26 +17,18 @@ import * as exec from '@actions/exec'; import {Buildx} from './buildx'; -import {Context} from '../context'; import {BuilderInfo, NodeInfo} from '../types/builder'; export interface BuilderOpts { - context: Context; buildx?: Buildx; } export class Builder { - private readonly context: Context; private readonly buildx: Buildx; - constructor(opts: BuilderOpts) { - this.context = opts.context; - this.buildx = - opts?.buildx || - new Buildx({ - context: this.context - }); + constructor(opts?: BuilderOpts) { + this.buildx = opts?.buildx || new Buildx(); } public async inspect(name: string): Promise { diff --git a/src/buildx/buildx.ts b/src/buildx/buildx.ts index 9dc57c7..5435491 100644 --- a/src/buildx/buildx.ts +++ b/src/buildx/buildx.ts @@ -21,13 +21,11 @@ import * as exec from '@actions/exec'; import * as semver from 'semver'; import {Docker} from '../docker'; -import {Context} from '../context'; import {Inputs} from './inputs'; import {Cert} from '../types/buildx'; export interface BuildxOpts { - context: Context; standalone?: boolean; } @@ -35,15 +33,13 @@ export class Buildx { private _version: string | undefined; private readonly _standalone: boolean | undefined; - private readonly context: Context; public readonly inputs: Inputs; public static readonly containerNamePrefix = 'buildx_buildkit_'; - constructor(opts: BuildxOpts) { + constructor(opts?: BuildxOpts) { this._standalone = opts?.standalone; - this.context = opts.context; - this.inputs = new Inputs(this.context); + this.inputs = new Inputs(); } static get configDir(): string { diff --git a/src/buildx/inputs.ts b/src/buildx/inputs.ts index 3f9559a..c6b3d34 100644 --- a/src/buildx/inputs.ts +++ b/src/buildx/inputs.ts @@ -22,18 +22,12 @@ import {parse} from 'csv-parse/sync'; import {Context} from '../context'; export class Inputs { - private readonly context: Context; - - constructor(context: Context) { - this.context = context; - } - public getBuildImageIDFilePath(): string { - return path.join(this.context.tmpDir(), 'iidfile'); + return path.join(Context.tmpDir(), 'iidfile'); } public getBuildMetadataFilePath(): string { - return path.join(this.context.tmpDir(), 'metadata-file'); + return path.join(Context.tmpDir(), 'metadata-file'); } public resolveBuildImageID(): string | undefined { @@ -89,7 +83,7 @@ export class Inputs { } value = fs.readFileSync(value, {encoding: 'utf-8'}); } - const secretFile = this.context.tmpName({tmpdir: this.context.tmpDir()}); + const secretFile = Context.tmpName({tmpdir: Context.tmpDir()}); fs.writeFileSync(secretFile, value); return `id=${key},src=${secretFile}`; } @@ -100,9 +94,8 @@ export class Inputs { // if input is not set returns empty string return input; } - const builderID = this.context.provenanceBuilderID; try { - return core.getBooleanInput(name) ? `builder-id=${builderID}` : 'false'; + return core.getBooleanInput(name) ? `builder-id=${Context.provenanceBuilderID()}` : 'false'; } catch (err) { // not a valid boolean, so we assume it's a string return this.resolveProvenanceAttrs(input); @@ -111,7 +104,7 @@ export class Inputs { public resolveProvenanceAttrs(input: string): string { if (!input) { - return `builder-id=${this.context.provenanceBuilderID}`; + return `builder-id=${Context.provenanceBuilderID()}`; } // parse attributes from input const fields = parse(input, { @@ -129,7 +122,7 @@ export class Inputs { } } // if not add builder-id attribute - return `${input},builder-id=${this.context.provenanceBuilderID}`; + return `${input},builder-id=${Context.provenanceBuilderID()}`; } public static hasLocalExporter(exporters: string[]): boolean { diff --git a/src/buildx/install.ts b/src/buildx/install.ts index 4630471..7161b23 100644 --- a/src/buildx/install.ts +++ b/src/buildx/install.ts @@ -32,17 +32,13 @@ import {Git} from '../git'; import {GitHubRelease} from '../types/github'; export interface InstallOpts { - context?: Context; standalone?: boolean; } export class Install { private readonly _standalone: boolean | undefined; - private readonly context: Context; - constructor(opts?: InstallOpts) { - this.context = opts?.context || new Context(); this._standalone = opts?.standalone; } @@ -84,7 +80,7 @@ export class Install { let toolPath: string; toolPath = tc.find('buildx', vspec); if (!toolPath) { - const outputDir = path.join(this.context.tmpDir(), 'build-cache'); + const outputDir = path.join(Context.tmpDir(), 'build-cache'); const buildCmd = await this.buildCommand(gitContext, outputDir); toolPath = await exec .getExecOutput(buildCmd.command, buildCmd.args, { @@ -103,7 +99,7 @@ export class Install { public async installStandalone(toolPath: string, dest?: string): Promise { core.info('Standalone mode'); - dest = dest || this.context.tmpDir(); + dest = dest || Context.tmpDir(); const toolBinPath = path.join(toolPath, os.platform() == 'win32' ? 'docker-buildx.exe' : 'docker-buildx'); const binDir = path.join(dest, 'bin'); if (!fs.existsSync(binDir)) { @@ -143,8 +139,8 @@ export class Install { } private async buildCommand(gitContext: string, outputDir: string): Promise<{args: Array; command: string}> { - const buildxStandaloneFound = await new Buildx({context: this.context, standalone: true}).isAvailable(); - const buildxPluginFound = await new Buildx({context: this.context, standalone: false}).isAvailable(); + const buildxStandaloneFound = await new Buildx({standalone: true}).isAvailable(); + const buildxPluginFound = await new Buildx({standalone: false}).isAvailable(); let buildStandalone = false; if ((await this.isStandalone()) && buildxStandaloneFound) { @@ -164,7 +160,7 @@ export class Install { } //prettier-ignore - return await new Buildx({context: this.context, standalone: buildStandalone}).getCommand([ + return await new Buildx({standalone: buildStandalone}).getCommand([ 'build', '--target', 'binaries', '--build-arg', 'BUILDKIT_CONTEXT_KEEP_GIT_DIR=1', diff --git a/src/context.ts b/src/context.ts index c0fbba9..ad50da6 100644 --- a/src/context.ts +++ b/src/context.ts @@ -23,29 +23,32 @@ import * as github from '@actions/github'; import {GitHub} from './github'; export class Context { - public gitRef: string; - public buildGitContext: string; - public provenanceBuilderID: string; + private static readonly _tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-actions-toolkit-')); - private readonly _tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-actions-toolkit-')); - - constructor() { - this.gitRef = github.context.ref; - if (github.context.sha && this.gitRef && !this.gitRef.startsWith('refs/')) { - this.gitRef = `refs/heads/${github.context.ref}`; - } - if (github.context.sha && !this.gitRef.startsWith(`refs/pull/`)) { - this.gitRef = github.context.sha; - } - this.buildGitContext = `${GitHub.serverURL}/${github.context.repo.owner}/${github.context.repo.repo}.git#${this.gitRef}`; - this.provenanceBuilderID = `${GitHub.serverURL}/${github.context.repo.owner}/${github.context.repo.repo}/actions/runs/${github.context.runId}`; + public static tmpDir(): string { + return Context._tmpDir; } - public tmpDir(): string { - return this._tmpDir; - } - - public tmpName(options?: tmp.TmpNameOptions): string { + public static tmpName(options?: tmp.TmpNameOptions): string { return tmp.tmpNameSync(options); } + + public static gitRef(): string { + let gitRef = github.context.ref; + if (github.context.sha && gitRef && !gitRef.startsWith('refs/')) { + gitRef = `refs/heads/${github.context.ref}`; + } + if (github.context.sha && !gitRef.startsWith(`refs/pull/`)) { + gitRef = github.context.sha; + } + return gitRef; + } + + public static gitContext(): string { + return `${GitHub.serverURL}/${github.context.repo.owner}/${github.context.repo.repo}.git#${Context.gitRef()}`; + } + + public static provenanceBuilderID(): string { + return `${GitHub.serverURL}/${github.context.repo.owner}/${github.context.repo.repo}/actions/runs/${github.context.runId}`; + } } diff --git a/src/toolkit.ts b/src/toolkit.ts index 2d7c6e4..d86bcdd 100644 --- a/src/toolkit.ts +++ b/src/toolkit.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import {Context} from './context'; import {Buildx} from './buildx/buildx'; import {Install} from './buildx/install'; import {Builder} from './buildx/builder'; @@ -30,7 +29,6 @@ export interface ToolkitOpts { } export class Toolkit { - public context: Context; public github: GitHub; public buildx: Buildx; public buildxInstall: Install; @@ -38,11 +36,10 @@ export class Toolkit { public buildkit: BuildKit; constructor(opts: ToolkitOpts = {}) { - this.context = new Context(); this.github = new GitHub({token: opts.githubToken}); - this.buildx = new Buildx({context: this.context}); - this.buildxInstall = new Install({context: this.context}); - this.builder = new Builder({context: this.context, buildx: this.buildx}); - this.buildkit = new BuildKit({context: this.context, buildx: this.buildx}); + this.buildx = new Buildx(); + this.buildxInstall = new Install(); + this.builder = new Builder({buildx: this.buildx}); + this.buildkit = new BuildKit({buildx: this.buildx}); } }