From 252c717cc3456b54d8692dacee6a7b334591550d Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Sat, 18 Feb 2023 10:09:41 +0100 Subject: [PATCH] fix buildx standalone and check for docker availability Signed-off-by: CrazyMax --- __tests__/buildx/buildx.test.ts | 20 +++++-------- __tests__/docker.test.ts | 2 +- src/buildkit/buildkit.ts | 5 ++++ src/buildx/builder.ts | 2 +- src/buildx/buildx.ts | 28 ++++++++++------- src/buildx/install.ts | 29 +++++++++++++----- src/docker.ts | 53 ++++++++++++++++----------------- 7 files changed, 80 insertions(+), 59 deletions(-) diff --git a/__tests__/buildx/buildx.test.ts b/__tests__/buildx/buildx.test.ts index b56bd70..0e182ea 100644 --- a/__tests__/buildx/buildx.test.ts +++ b/__tests__/buildx/buildx.test.ts @@ -95,9 +95,7 @@ describe('isAvailable', () => { context: new Context(), standalone: false }); - buildx.isAvailable().catch(() => { - // noop - }); + await buildx.isAvailable(); // eslint-disable-next-line jest/no-standalone-expect expect(execSpy).toHaveBeenCalledWith(`docker`, ['buildx'], { silent: true, @@ -110,9 +108,7 @@ describe('isAvailable', () => { context: new Context(), standalone: true }); - buildx.isAvailable().catch(() => { - // noop - }); + await buildx.isAvailable(); // eslint-disable-next-line jest/no-standalone-expect expect(execSpy).toHaveBeenCalledWith(`buildx`, [], { silent: true, @@ -122,13 +118,13 @@ describe('isAvailable', () => { }); describe('printInspect', () => { - it('prints builder2 instance', () => { + it('prints builder2 instance', async () => { const execSpy = jest.spyOn(exec, 'exec'); const buildx = new Buildx({ context: new Context(), standalone: true }); - buildx.printInspect('builder2').catch(() => { + await buildx.printInspect('builder2').catch(() => { // noop }); expect(execSpy).toHaveBeenCalledWith(`buildx`, ['inspect', 'builder2'], { @@ -138,24 +134,24 @@ describe('printInspect', () => { }); describe('printVersion', () => { - it('docker cli', () => { + it('docker cli', async () => { const execSpy = jest.spyOn(exec, 'exec'); const buildx = new Buildx({ context: new Context(), standalone: false }); - buildx.printVersion(); + await buildx.printVersion(); expect(execSpy).toHaveBeenCalledWith(`docker`, ['buildx', 'version'], { failOnStdErr: false }); }); - it('standalone', () => { + it('standalone', async () => { const execSpy = jest.spyOn(exec, 'exec'); const buildx = new Buildx({ context: new Context(), standalone: true }); - buildx.printVersion(); + await buildx.printVersion(); expect(execSpy).toHaveBeenCalledWith(`buildx`, ['version'], { failOnStdErr: false }); diff --git a/__tests__/docker.test.ts b/__tests__/docker.test.ts index 6550d53..75d125d 100644 --- a/__tests__/docker.test.ts +++ b/__tests__/docker.test.ts @@ -50,7 +50,7 @@ describe('configDir', () => { describe('isAvailable', () => { it('cli', async () => { const execSpy = jest.spyOn(exec, 'getExecOutput'); - Docker.getInstance().available; + await Docker.getInstance().isAvailable(); // eslint-disable-next-line jest/no-standalone-expect expect(execSpy).toHaveBeenCalledWith(`docker`, undefined, { silent: true, diff --git a/src/buildkit/buildkit.ts b/src/buildkit/buildkit.ts index 42e5994..aef84e5 100644 --- a/src/buildkit/buildkit.ts +++ b/src/buildkit/buildkit.ts @@ -58,6 +58,7 @@ export class BuildKit { } private async getVersionWithinImage(nodeName: string): Promise { + core.debug(`BuildKit.getVersionWithinImage nodeName: ${nodeName}`); return exec .getExecOutput(`docker`, ['inspect', '--format', '{{.Config.Image}}', `${Buildx.containerNamePrefix}${nodeName}`], { ignoreReturnCode: true, @@ -65,6 +66,7 @@ export class BuildKit { }) .then(bkitimage => { if (bkitimage.exitCode == 0 && bkitimage.stdout.length > 0) { + core.debug(`BuildKit.getVersionWithinImage image: ${bkitimage.stdout.trim()}`); return exec .getExecOutput(`docker`, ['run', '--rm', bkitimage.stdout.trim(), '--version'], { ignoreReturnCode: true, @@ -93,14 +95,17 @@ export class BuildKit { }).inspect(builderName); } for (const node of builderInfo.nodes) { + core.debug(`BuildKit.versionSatisfies ${node}: ${range}`); let bkversion = node.buildkitVersion; if (!bkversion) { try { bkversion = await this.getVersionWithinImage(node.name || ''); } catch (e) { + core.debug(`BuildKit.versionSatisfies ${node}: can't get version`); return false; } } + core.debug(`BuildKit.versionSatisfies ${node}: version ${bkversion}`); // BuildKit version reported by moby is in the format of `v0.11.0-moby` if (builderInfo.driver == 'docker' && !bkversion.endsWith('-moby')) { return false; diff --git a/src/buildx/builder.ts b/src/buildx/builder.ts index 9059b09..3e07227 100644 --- a/src/buildx/builder.ts +++ b/src/buildx/builder.ts @@ -40,7 +40,7 @@ export class Builder { } public async inspect(name: string): Promise { - const cmd = this.buildx.getCommand(['inspect', name]); + const cmd = await this.buildx.getCommand(['inspect', name]); return await exec .getExecOutput(cmd.command, cmd.args, { ignoreReturnCode: true, diff --git a/src/buildx/buildx.ts b/src/buildx/buildx.ts index cc71e0b..7ca3208 100644 --- a/src/buildx/buildx.ts +++ b/src/buildx/buildx.ts @@ -32,16 +32,17 @@ export interface BuildxOpts { } export class Buildx { - private readonly context: Context; private _version: string | undefined; + private readonly _standalone: boolean | undefined; + private readonly context: Context; public readonly inputs: Inputs; - public readonly standalone: boolean; + public static readonly containerNamePrefix = 'buildx_buildkit_'; constructor(opts: BuildxOpts) { + this._standalone = opts?.standalone; this.context = opts.context; - this.standalone = opts?.standalone ?? !Docker.getInstance().available; this.inputs = new Inputs(this.context); } @@ -53,15 +54,22 @@ export class Buildx { return path.join(Buildx.configDir, 'certs'); } - public getCommand(args: Array) { + public async isStandalone(): Promise { + const standalone = this._standalone ?? !(await Docker.getInstance().isAvailable()); + core.debug(`Buildx.isStandalone: ${standalone}`); + return standalone; + } + + public async getCommand(args: Array) { + const standalone = await this.isStandalone(); return { - command: this.standalone ? 'buildx' : 'docker', - args: this.standalone ? args : ['buildx', ...args] + command: standalone ? 'buildx' : 'docker', + args: standalone ? args : ['buildx', ...args] }; } public async isAvailable(): Promise { - const cmd = this.getCommand([]); + const cmd = await this.getCommand([]); return await exec .getExecOutput(cmd.command, cmd.args, { ignoreReturnCode: true, @@ -80,7 +88,7 @@ export class Buildx { } public async printInspect(name: string): Promise { - const cmd = this.getCommand(['inspect', name]); + const cmd = await this.getCommand(['inspect', name]); await exec.exec(cmd.command, cmd.args, { failOnStdErr: false }); @@ -89,7 +97,7 @@ export class Buildx { get version() { return (async () => { if (!this._version) { - const cmd = this.getCommand(['version']); + const cmd = await this.getCommand(['version']); this._version = await exec .getExecOutput(cmd.command, cmd.args, { ignoreReturnCode: true, @@ -107,7 +115,7 @@ export class Buildx { } public async printVersion() { - const cmd = this.getCommand(['version']); + const cmd = await this.getCommand(['version']); await exec.exec(cmd.command, cmd.args, { failOnStdErr: false }); diff --git a/src/buildx/install.ts b/src/buildx/install.ts index f206dc9..3b8a04b 100644 --- a/src/buildx/install.ts +++ b/src/buildx/install.ts @@ -37,17 +37,19 @@ export interface InstallOpts { } export class Install { + private readonly _standalone: boolean | undefined; + private readonly context: Context; - private readonly standalone: boolean; constructor(opts?: InstallOpts) { this.context = opts?.context || new Context(); - this.standalone = opts?.standalone ?? !Docker.getInstance().available; + this._standalone = opts?.standalone; } public async download(version: string, dest?: string): Promise { const release: GitHubRelease = await Install.getRelease(version); const fversion = release.tag_name.replace(/^v+|v+$/g, ''); + core.debug(`Install.download version: ${fversion}`); let toolPath: string; toolPath = tc.find('buildx', fversion, this.platform()); @@ -58,9 +60,11 @@ export class Install { } toolPath = await this.fetchBinary(fversion); } + core.debug(`Install.download toolPath: ${toolPath}`); - dest = dest || (this.standalone ? this.context.tmpDir() : Docker.configDir); - if (this.standalone) { + dest = dest || ((await this.isStandalone()) ? this.context.tmpDir() : Docker.configDir); + core.debug(`Install.download dest: ${dest}`); + if (await this.isStandalone()) { return this.setStandalone(toolPath, dest); } return this.setPlugin(toolPath, dest); @@ -100,7 +104,8 @@ export class Install { } dest = dest || Docker.configDir; - if (this.standalone) { + core.debug(`Install.build dest: ${dest}`); + if (await this.isStandalone()) { return this.setStandalone(toolPath, dest); } return this.setPlugin(toolPath, dest); @@ -111,10 +116,10 @@ export class Install { const buildxPluginFound = await new Buildx({context: this.context, standalone: false}).isAvailable(); let buildStandalone = false; - if (this.standalone && buildxStandaloneFound) { + if ((await this.isStandalone()) && buildxStandaloneFound) { core.debug(`Install.buildCommand: Buildx standalone found, build with it`); buildStandalone = true; - } else if (!this.standalone && buildxPluginFound) { + } else if (!(await this.isStandalone()) && buildxPluginFound) { core.debug(`Install.buildCommand: Buildx plugin found, build with it`); buildStandalone = false; } else if (buildxStandaloneFound) { @@ -128,7 +133,7 @@ export class Install { } //prettier-ignore - return new Buildx({context: this.context, standalone: buildStandalone}).getCommand([ + return await new Buildx({context: this.context, standalone: buildStandalone}).getCommand([ 'build', '--target', 'binaries', '--build-arg', 'BUILDKIT_CONTEXT_KEEP_GIT_DIR=1', @@ -137,6 +142,12 @@ export class Install { ]); } + private async isStandalone(): Promise { + const standalone = this._standalone ?? !(await Docker.getInstance().isAvailable()); + core.debug(`Install.isStandalone: ${standalone}`); + return standalone; + } + private async setStandalone(toolPath: string, dest: string): Promise { const toolBinPath = path.join(toolPath, os.platform() == 'win32' ? 'docker-buildx.exe' : 'docker-buildx'); const binDir = path.join(dest, 'bin'); @@ -148,6 +159,7 @@ export class Install { fs.copyFileSync(toolBinPath, buildxPath); fs.chmodSync(buildxPath, '0755'); core.addPath(binDir); + core.debug(`Install.setStandalone buildxPath: ${buildxPath}`); return buildxPath; } @@ -161,6 +173,7 @@ export class Install { const pluginPath: string = path.join(pluginsDir, filename); fs.copyFileSync(toolBinPath, pluginPath); fs.chmodSync(pluginPath, '0755'); + core.debug(`Install.setPlugin pluginPath: ${pluginPath}`); return pluginPath; } diff --git a/src/docker.ts b/src/docker.ts index bf75a8a..25e205d 100644 --- a/src/docker.ts +++ b/src/docker.ts @@ -32,35 +32,34 @@ export class Docker { return process.env.DOCKER_CONFIG || path.join(os.homedir(), '.docker'); } - get available() { - return (async () => { - if (!this._available) { - this._available = await exec - .getExecOutput('docker', undefined, { - ignoreReturnCode: true, - silent: true - }) - .then(res => { - if (res.stderr.length > 0 && res.exitCode != 0) { - core.debug(`Docker.isAvailable error: ${res.stderr}`); - return false; - } else { - core.debug(`Docker.isAvailable ok`); - return res.exitCode == 0; - } - }) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .catch(error => { - core.debug(`Docker.isAvailable failed: ${error}`); - return false; - }); - } - return this._available; - })(); + public async isAvailable(): Promise { + if (this._available === undefined) { + await exec + .getExecOutput('docker', undefined, { + ignoreReturnCode: true, + silent: true + }) + .then(res => { + if (res.stderr.length > 0 && res.exitCode != 0) { + core.debug(`Docker.available error: ${res.stderr}`); + this._available = false; + } else { + core.debug(`Docker.available ok`); + this._available = res.exitCode == 0; + } + }) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .catch(error => { + core.debug(`Docker.available failed: ${error}`); + this._available = false; + }); + } + core.debug(`Docker.available: ${this._available}`); + return this._available ?? false; } public static async printVersion(standalone?: boolean): Promise { - const noDocker = standalone ?? !Docker.getInstance().available; + const noDocker = standalone ?? !(await Docker.getInstance().isAvailable()); if (noDocker) { core.debug('Docker.printVersion: Docker is not available, skipping.'); return; @@ -71,7 +70,7 @@ export class Docker { } public static async printInfo(standalone?: boolean): Promise { - const noDocker = standalone ?? !Docker.getInstance().available; + const noDocker = standalone ?? !(await Docker.getInstance().isAvailable()); if (noDocker) { core.debug('Docker.printInfo: Docker is not available, skipping.'); return;