Compare commits

..

12 Commits

Author SHA1 Message Date
CrazyMax
580aee99c0 Merge pull request #46 from crazy-max/buildx-split-install
Some checks failed
publish / publish (push) Has been cancelled
buildx: split install from download and build methods
2023-02-19 03:39:55 +01:00
CrazyMax
89ecd37681 buildx: info logs on install
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-02-19 03:34:30 +01:00
CrazyMax
139fb39ab0 buildx: split install from download and build methods
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-02-19 03:25:07 +01:00
CrazyMax
67957d8c7a Merge pull request #45 from crazy-max/docker-fix
docker: various fixes
2023-02-19 02:57:48 +01:00
CrazyMax
768df5fbf4 buildx: debug logs in isAvailable method
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-02-19 02:54:07 +01:00
CrazyMax
e9db81b6a1 docker: fix printVersion and printInfo
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-02-19 02:54:06 +01:00
CrazyMax
cd825ae548 docker: remove singleton
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-02-19 02:54:06 +01:00
CrazyMax
28c11a1819 Merge pull request #44 from crazy-max/cleanup
chore: remove path sep conversion
2023-02-19 01:52:35 +01:00
CrazyMax
ed087e5b0d chore: remove path sep conversion
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-02-19 00:59:04 +01:00
CrazyMax
2038d87306 Merge pull request #43 from crazy-max/fix-standalone
Some checks failed
publish / publish (push) Has been cancelled
fix buildx standalone and check for docker availability
2023-02-18 10:15:26 +01:00
CrazyMax
252c717cc3 fix buildx standalone and check for docker availability
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-02-18 10:09:41 +01:00
CrazyMax
9b338b58a7 toolkit: docker property not needed
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-02-18 10:08:21 +01:00
15 changed files with 163 additions and 175 deletions

View File

@@ -24,8 +24,8 @@ import {Context} from '../../src/context';
const fixturesDir = path.join(__dirname, '..', 'fixtures'); const fixturesDir = path.join(__dirname, '..', 'fixtures');
// prettier-ignore // prettier-ignore
const tmpDir = path.join(process.env.TEMP || '/tmp', 'buildkit-config-jest').split(path.sep).join(path.posix.sep); const tmpDir = path.join(process.env.TEMP || '/tmp', 'buildkit-config-jest');
const tmpName = path.join(tmpDir, '.tmpname-jest').split(path.sep).join(path.posix.sep); const tmpName = path.join(tmpDir, '.tmpname-jest');
jest.spyOn(Context.prototype, 'tmpDir').mockImplementation((): string => { jest.spyOn(Context.prototype, 'tmpDir').mockImplementation((): string => {
if (!fs.existsSync(tmpDir)) { if (!fs.existsSync(tmpDir)) {
@@ -50,7 +50,7 @@ describe('resolve', () => {
['debug = true', false, 'debug = true', null], ['debug = true', false, 'debug = true', null],
[`notfound.toml`, true, '', new Error('config file notfound.toml not found')], [`notfound.toml`, true, '', new Error('config file notfound.toml not found')],
[ [
`${path.join(fixturesDir, 'buildkitd.toml').split(path.sep).join(path.posix.sep)}`, `${path.join(fixturesDir, 'buildkitd.toml')}`,
true, true,
`debug = true `debug = true
[registry."docker.io"] [registry."docker.io"]

View File

@@ -27,8 +27,8 @@ import {Context} from '../../src/context';
import {Cert} from '../../src/types/buildx'; import {Cert} from '../../src/types/buildx';
// prettier-ignore // prettier-ignore
const tmpDir = path.join(process.env.TEMP || '/tmp', 'buildx-jest').split(path.sep).join(path.posix.sep); const tmpDir = path.join(process.env.TEMP || '/tmp', 'buildx-jest');
const tmpName = path.join(tmpDir, '.tmpname-jest').split(path.sep).join(path.posix.sep); const tmpName = path.join(tmpDir, '.tmpname-jest');
jest.spyOn(Context.prototype, 'tmpDir').mockImplementation((): string => { jest.spyOn(Context.prototype, 'tmpDir').mockImplementation((): string => {
if (!fs.existsSync(tmpDir)) { if (!fs.existsSync(tmpDir)) {
@@ -95,9 +95,7 @@ describe('isAvailable', () => {
context: new Context(), context: new Context(),
standalone: false standalone: false
}); });
buildx.isAvailable().catch(() => { await buildx.isAvailable();
// noop
});
// eslint-disable-next-line jest/no-standalone-expect // eslint-disable-next-line jest/no-standalone-expect
expect(execSpy).toHaveBeenCalledWith(`docker`, ['buildx'], { expect(execSpy).toHaveBeenCalledWith(`docker`, ['buildx'], {
silent: true, silent: true,
@@ -110,9 +108,7 @@ describe('isAvailable', () => {
context: new Context(), context: new Context(),
standalone: true standalone: true
}); });
buildx.isAvailable().catch(() => { await buildx.isAvailable();
// noop
});
// eslint-disable-next-line jest/no-standalone-expect // eslint-disable-next-line jest/no-standalone-expect
expect(execSpy).toHaveBeenCalledWith(`buildx`, [], { expect(execSpy).toHaveBeenCalledWith(`buildx`, [], {
silent: true, silent: true,
@@ -122,13 +118,13 @@ describe('isAvailable', () => {
}); });
describe('printInspect', () => { describe('printInspect', () => {
it('prints builder2 instance', () => { it('prints builder2 instance', async () => {
const execSpy = jest.spyOn(exec, 'exec'); const execSpy = jest.spyOn(exec, 'exec');
const buildx = new Buildx({ const buildx = new Buildx({
context: new Context(), context: new Context(),
standalone: true standalone: true
}); });
buildx.printInspect('builder2').catch(() => { await buildx.printInspect('builder2').catch(() => {
// noop // noop
}); });
expect(execSpy).toHaveBeenCalledWith(`buildx`, ['inspect', 'builder2'], { expect(execSpy).toHaveBeenCalledWith(`buildx`, ['inspect', 'builder2'], {
@@ -138,24 +134,24 @@ describe('printInspect', () => {
}); });
describe('printVersion', () => { describe('printVersion', () => {
it('docker cli', () => { it('docker cli', async () => {
const execSpy = jest.spyOn(exec, 'exec'); const execSpy = jest.spyOn(exec, 'exec');
const buildx = new Buildx({ const buildx = new Buildx({
context: new Context(), context: new Context(),
standalone: false standalone: false
}); });
buildx.printVersion(); await buildx.printVersion();
expect(execSpy).toHaveBeenCalledWith(`docker`, ['buildx', 'version'], { expect(execSpy).toHaveBeenCalledWith(`docker`, ['buildx', 'version'], {
failOnStdErr: false failOnStdErr: false
}); });
}); });
it('standalone', () => { it('standalone', async () => {
const execSpy = jest.spyOn(exec, 'exec'); const execSpy = jest.spyOn(exec, 'exec');
const buildx = new Buildx({ const buildx = new Buildx({
context: new Context(), context: new Context(),
standalone: true standalone: true
}); });
buildx.printVersion(); await buildx.printVersion();
expect(execSpy).toHaveBeenCalledWith(`buildx`, ['version'], { expect(execSpy).toHaveBeenCalledWith(`buildx`, ['version'], {
failOnStdErr: false failOnStdErr: false
}); });

View File

@@ -25,8 +25,8 @@ import {Inputs} from '../../src/buildx/inputs';
const fixturesDir = path.join(__dirname, '..', 'fixtures'); const fixturesDir = path.join(__dirname, '..', 'fixtures');
// prettier-ignore // prettier-ignore
const tmpDir = path.join(process.env.TEMP || '/tmp', 'buildx-inputs-jest').split(path.sep).join(path.posix.sep); const tmpDir = path.join(process.env.TEMP || '/tmp', 'buildx-inputs-jest');
const tmpName = path.join(tmpDir, '.tmpname-jest').split(path.sep).join(path.posix.sep); const tmpName = path.join(tmpDir, '.tmpname-jest');
const metadata = `{ const metadata = `{
"containerimage.config.digest": "sha256:059b68a595b22564a1cbc167af369349fdc2ecc1f7bc092c2235cbf601a795fd", "containerimage.config.digest": "sha256:059b68a595b22564a1cbc167af369349fdc2ecc1f7bc092c2235cbf601a795fd",
"containerimage.digest": "sha256:b09b9482c72371486bb2c1d2c2a2633ed1d0b8389e12c8d52b9e052725c0c83c" "containerimage.digest": "sha256:b09b9482c72371486bb2c1d2c2a2633ed1d0b8389e12c8d52b9e052725c0c83c"
@@ -175,7 +175,7 @@ describe('resolveBuildSecret', () => {
['aaaaaaaa', false, '', '', new Error('aaaaaaaa is not a valid secret')], ['aaaaaaaa', false, '', '', new Error('aaaaaaaa is not a valid secret')],
['aaaaaaaa=', false, '', '', new Error('aaaaaaaa= is not a valid secret')], ['aaaaaaaa=', false, '', '', new Error('aaaaaaaa= is not a valid secret')],
['=bbbbbbb', false, '', '', new Error('=bbbbbbb is not a valid secret')], ['=bbbbbbb', false, '', '', new Error('=bbbbbbb is not a valid secret')],
[`foo=${path.join(fixturesDir, 'secret.txt').split(path.sep).join(path.posix.sep)}`, true, 'foo', 'bar', null], [`foo=${path.join(fixturesDir, 'secret.txt')}`, true, 'foo', 'bar', null],
[`notfound=secret`, true, '', '', new Error('secret file secret not found')] [`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) => { ])('given %p key and %p secret', async (kvp: string, file: boolean, exKey: string, exValue: string, error: Error) => {
try { try {

View File

@@ -23,7 +23,7 @@ import osm = require('os');
import {Install} from '../../src/buildx/install'; import {Install} from '../../src/buildx/install';
// prettier-ignore // prettier-ignore
const tmpDir = path.join(process.env.TEMP || '/tmp', 'buildx-jest').split(path.sep).join(path.posix.sep); const tmpDir = path.join(process.env.TEMP || '/tmp', 'buildx-jest');
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
@@ -43,7 +43,14 @@ describe('download', () => {
])( ])(
'acquires %p of buildx (standalone: %p)', async (version, standalone) => { 'acquires %p of buildx (standalone: %p)', async (version, standalone) => {
const install = new Install({standalone: standalone}); const install = new Install({standalone: standalone});
const buildxBin = await install.download(version, tmpDir); const toolPath = await install.download(version);
expect(fs.existsSync(toolPath)).toBe(true);
let buildxBin: string;
if (standalone) {
buildxBin = await install.installStandalone(toolPath, tmpDir);
} else {
buildxBin = await install.installPlugin(toolPath, tmpDir);
}
expect(fs.existsSync(buildxBin)).toBe(true); expect(fs.existsSync(buildxBin)).toBe(true);
}, },
100000 100000
@@ -65,7 +72,7 @@ describe('download', () => {
jest.spyOn(osm, 'platform').mockImplementation(() => os); jest.spyOn(osm, 'platform').mockImplementation(() => os);
jest.spyOn(osm, 'arch').mockImplementation(() => arch); jest.spyOn(osm, 'arch').mockImplementation(() => arch);
const install = new Install(); const install = new Install();
const buildxBin = await install.download('latest', tmpDir); const buildxBin = await install.download('latest');
expect(fs.existsSync(buildxBin)).toBe(true); expect(fs.existsSync(buildxBin)).toBe(true);
}, },
100000 100000
@@ -82,14 +89,18 @@ describe('build', () => {
// eslint-disable-next-line jest/no-disabled-tests // eslint-disable-next-line jest/no-disabled-tests
it.skip('builds refs/pull/648/head', async () => { it.skip('builds refs/pull/648/head', async () => {
const install = new Install(); const install = new Install();
const buildxBin = await install.build('https://github.com/docker/buildx.git#refs/pull/648/head', tmpDir); const toolPath = await install.build('https://github.com/docker/buildx.git#refs/pull/648/head');
expect(fs.existsSync(toolPath)).toBe(true);
const buildxBin = await install.installStandalone(toolPath, tmpDir);
expect(fs.existsSync(buildxBin)).toBe(true); expect(fs.existsSync(buildxBin)).toBe(true);
}, 100000); }, 100000);
// eslint-disable-next-line jest/no-disabled-tests // eslint-disable-next-line jest/no-disabled-tests
it.skip('builds 67bd6f4dc82a9cd96f34133dab3f6f7af803bb14', async () => { it.skip('builds 67bd6f4dc82a9cd96f34133dab3f6f7af803bb14', async () => {
const install = new Install(); const install = new Install();
const buildxBin = await install.build('https://github.com/docker/buildx.git#67bd6f4dc82a9cd96f34133dab3f6f7af803bb14', tmpDir); const toolPath = await install.build('https://github.com/docker/buildx.git#67bd6f4dc82a9cd96f34133dab3f6f7af803bb14');
expect(fs.existsSync(toolPath)).toBe(true);
const buildxBin = await install.installPlugin(toolPath, tmpDir);
expect(fs.existsSync(buildxBin)).toBe(true); expect(fs.existsSync(buildxBin)).toBe(true);
}, 100000); }, 100000);
}); });

View File

@@ -22,8 +22,8 @@ import {describe, expect, jest, it, beforeEach, afterEach} from '@jest/globals';
import {Context} from '../src/context'; import {Context} from '../src/context';
// prettier-ignore // prettier-ignore
const tmpDir = path.join(process.env.TEMP || '/tmp', 'context-jest').split(path.sep).join(path.posix.sep); const tmpDir = path.join(process.env.TEMP || '/tmp', 'context-jest');
const tmpName = path.join(tmpDir, '.tmpname-jest').split(path.sep).join(path.posix.sep); const tmpName = path.join(tmpDir, '.tmpname-jest');
jest.spyOn(Context.prototype, 'tmpDir').mockImplementation((): string => { jest.spyOn(Context.prototype, 'tmpDir').mockImplementation((): string => {
if (!fs.existsSync(tmpDir)) { if (!fs.existsSync(tmpDir)) {

View File

@@ -50,7 +50,7 @@ describe('configDir', () => {
describe('isAvailable', () => { describe('isAvailable', () => {
it('cli', async () => { it('cli', async () => {
const execSpy = jest.spyOn(exec, 'getExecOutput'); const execSpy = jest.spyOn(exec, 'getExecOutput');
Docker.getInstance().available; await Docker.isAvailable();
// eslint-disable-next-line jest/no-standalone-expect // eslint-disable-next-line jest/no-standalone-expect
expect(execSpy).toHaveBeenCalledWith(`docker`, undefined, { expect(execSpy).toHaveBeenCalledWith(`docker`, undefined, {
silent: true, silent: true,
@@ -60,43 +60,21 @@ describe('isAvailable', () => {
}); });
describe('printVersion', () => { describe('printVersion', () => {
it('docker cli', () => { it('call docker version', async () => {
const execSpy = jest.spyOn(exec, 'exec'); const execSpy = jest.spyOn(exec, 'exec');
Docker.printVersion(false).catch(() => { await Docker.printVersion().catch(() => {
// noop // noop
}); });
expect(execSpy).toHaveBeenCalledWith(`docker`, ['version'], { expect(execSpy).toHaveBeenCalledWith(`docker`, ['version']);
failOnStdErr: false
});
});
it('standalone', () => {
const execSpy = jest.spyOn(exec, 'exec');
Docker.printVersion(true).catch(() => {
// noop
});
expect(execSpy).not.toHaveBeenCalledWith(`docker`, ['version'], {
failOnStdErr: false
});
}); });
}); });
describe('printInfo', () => { describe('printInfo', () => {
it('docker cli', () => { it('call docker info', async () => {
const execSpy = jest.spyOn(exec, 'exec'); const execSpy = jest.spyOn(exec, 'exec');
Docker.printInfo(false).catch(() => { await Docker.printInfo().catch(() => {
// noop // noop
}); });
expect(execSpy).toHaveBeenCalledWith(`docker`, ['info'], { expect(execSpy).toHaveBeenCalledWith(`docker`, ['info']);
failOnStdErr: false
});
});
it('standalone', () => {
const execSpy = jest.spyOn(exec, 'exec');
Docker.printInfo(true).catch(() => {
// noop
});
expect(execSpy).not.toHaveBeenCalledWith(`docker`, ['info'], {
failOnStdErr: false
});
}); });
}); });

View File

@@ -18,13 +18,13 @@ import fs from 'fs';
import os from 'os'; import os from 'os';
import path from 'path'; import path from 'path';
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-actions-toolkit-')).split(path.sep).join(path.posix.sep); const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-actions-toolkit-'));
process.env = Object.assign({}, process.env, { process.env = Object.assign({}, process.env, {
TEMP: tmpDir, TEMP: tmpDir,
GITHUB_REPOSITORY: 'docker/actions-toolkit', GITHUB_REPOSITORY: 'docker/actions-toolkit',
RUNNER_TEMP: path.join(tmpDir, 'runner-temp').split(path.sep).join(path.posix.sep), RUNNER_TEMP: path.join(tmpDir, 'runner-temp'),
RUNNER_TOOL_CACHE: path.join(tmpDir, 'runner-tool-cache').split(path.sep).join(path.posix.sep) RUNNER_TOOL_CACHE: path.join(tmpDir, 'runner-tool-cache')
}) as { }) as {
[key: string]: string; [key: string]: string;
}; };

View File

@@ -58,6 +58,7 @@ export class BuildKit {
} }
private async getVersionWithinImage(nodeName: string): Promise<string> { private async getVersionWithinImage(nodeName: string): Promise<string> {
core.debug(`BuildKit.getVersionWithinImage nodeName: ${nodeName}`);
return exec return exec
.getExecOutput(`docker`, ['inspect', '--format', '{{.Config.Image}}', `${Buildx.containerNamePrefix}${nodeName}`], { .getExecOutput(`docker`, ['inspect', '--format', '{{.Config.Image}}', `${Buildx.containerNamePrefix}${nodeName}`], {
ignoreReturnCode: true, ignoreReturnCode: true,
@@ -65,6 +66,7 @@ export class BuildKit {
}) })
.then(bkitimage => { .then(bkitimage => {
if (bkitimage.exitCode == 0 && bkitimage.stdout.length > 0) { if (bkitimage.exitCode == 0 && bkitimage.stdout.length > 0) {
core.debug(`BuildKit.getVersionWithinImage image: ${bkitimage.stdout.trim()}`);
return exec return exec
.getExecOutput(`docker`, ['run', '--rm', bkitimage.stdout.trim(), '--version'], { .getExecOutput(`docker`, ['run', '--rm', bkitimage.stdout.trim(), '--version'], {
ignoreReturnCode: true, ignoreReturnCode: true,
@@ -93,14 +95,17 @@ export class BuildKit {
}).inspect(builderName); }).inspect(builderName);
} }
for (const node of builderInfo.nodes) { for (const node of builderInfo.nodes) {
core.debug(`BuildKit.versionSatisfies ${node}: ${range}`);
let bkversion = node.buildkitVersion; let bkversion = node.buildkitVersion;
if (!bkversion) { if (!bkversion) {
try { try {
bkversion = await this.getVersionWithinImage(node.name || ''); bkversion = await this.getVersionWithinImage(node.name || '');
} catch (e) { } catch (e) {
core.debug(`BuildKit.versionSatisfies ${node}: can't get version`);
return false; return false;
} }
} }
core.debug(`BuildKit.versionSatisfies ${node}: version ${bkversion}`);
// BuildKit version reported by moby is in the format of `v0.11.0-moby` // BuildKit version reported by moby is in the format of `v0.11.0-moby`
if (builderInfo.driver == 'docker' && !bkversion.endsWith('-moby')) { if (builderInfo.driver == 'docker' && !bkversion.endsWith('-moby')) {
return false; return false;

View File

@@ -40,7 +40,7 @@ export class Builder {
} }
public async inspect(name: string): Promise<BuilderInfo> { public async inspect(name: string): Promise<BuilderInfo> {
const cmd = this.buildx.getCommand(['inspect', name]); const cmd = await this.buildx.getCommand(['inspect', name]);
return await exec return await exec
.getExecOutput(cmd.command, cmd.args, { .getExecOutput(cmd.command, cmd.args, {
ignoreReturnCode: true, ignoreReturnCode: true,

View File

@@ -32,16 +32,17 @@ export interface BuildxOpts {
} }
export class Buildx { export class Buildx {
private readonly context: Context;
private _version: string | undefined; private _version: string | undefined;
private readonly _standalone: boolean | undefined;
private readonly context: Context;
public readonly inputs: Inputs; public readonly inputs: Inputs;
public readonly standalone: boolean;
public static readonly containerNamePrefix = 'buildx_buildkit_'; public static readonly containerNamePrefix = 'buildx_buildkit_';
constructor(opts: BuildxOpts) { constructor(opts: BuildxOpts) {
this._standalone = opts?.standalone;
this.context = opts.context; this.context = opts.context;
this.standalone = opts?.standalone ?? !Docker.getInstance().available;
this.inputs = new Inputs(this.context); this.inputs = new Inputs(this.context);
} }
@@ -53,34 +54,46 @@ export class Buildx {
return path.join(Buildx.configDir, 'certs'); return path.join(Buildx.configDir, 'certs');
} }
public getCommand(args: Array<string>) { public async isStandalone(): Promise<boolean> {
const standalone = this._standalone ?? !(await Docker.isAvailable());
core.debug(`Buildx.isStandalone: ${standalone}`);
return standalone;
}
public async getCommand(args: Array<string>) {
const standalone = await this.isStandalone();
return { return {
command: this.standalone ? 'buildx' : 'docker', command: standalone ? 'buildx' : 'docker',
args: this.standalone ? args : ['buildx', ...args] args: standalone ? args : ['buildx', ...args]
}; };
} }
public async isAvailable(): Promise<boolean> { public async isAvailable(): Promise<boolean> {
const cmd = this.getCommand([]); const cmd = await this.getCommand([]);
return await exec
const ok: boolean = await exec
.getExecOutput(cmd.command, cmd.args, { .getExecOutput(cmd.command, cmd.args, {
ignoreReturnCode: true, ignoreReturnCode: true,
silent: true silent: true
}) })
.then(res => { .then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) { if (res.stderr.length > 0 && res.exitCode != 0) {
core.debug(`Buildx.isAvailable cmd err: ${res.stderr}`);
return false; return false;
} }
return res.exitCode == 0; return res.exitCode == 0;
}) })
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.catch(error => { .catch(error => {
core.debug(`Buildx.isAvailable error: ${error}`);
return false; return false;
}); });
core.debug(`Buildx.isAvailable: ${ok}`);
return ok;
} }
public async printInspect(name: string): Promise<void> { public async printInspect(name: string): Promise<void> {
const cmd = this.getCommand(['inspect', name]); const cmd = await this.getCommand(['inspect', name]);
await exec.exec(cmd.command, cmd.args, { await exec.exec(cmd.command, cmd.args, {
failOnStdErr: false failOnStdErr: false
}); });
@@ -89,7 +102,7 @@ export class Buildx {
get version() { get version() {
return (async () => { return (async () => {
if (!this._version) { if (!this._version) {
const cmd = this.getCommand(['version']); const cmd = await this.getCommand(['version']);
this._version = await exec this._version = await exec
.getExecOutput(cmd.command, cmd.args, { .getExecOutput(cmd.command, cmd.args, {
ignoreReturnCode: true, ignoreReturnCode: true,
@@ -107,7 +120,7 @@ export class Buildx {
} }
public async printVersion() { public async printVersion() {
const cmd = this.getCommand(['version']); const cmd = await this.getCommand(['version']);
await exec.exec(cmd.command, cmd.args, { await exec.exec(cmd.command, cmd.args, {
failOnStdErr: false failOnStdErr: false
}); });

View File

@@ -29,11 +29,11 @@ export class Inputs {
} }
public getBuildImageIDFilePath(): string { public getBuildImageIDFilePath(): string {
return path.join(this.context.tmpDir(), 'iidfile').split(path.sep).join(path.posix.sep); return path.join(this.context.tmpDir(), 'iidfile');
} }
public getBuildMetadataFilePath(): string { public getBuildMetadataFilePath(): string {
return path.join(this.context.tmpDir(), 'metadata-file').split(path.sep).join(path.posix.sep); return path.join(this.context.tmpDir(), 'metadata-file');
} }
public resolveBuildImageID(): string | undefined { public resolveBuildImageID(): string | undefined {

View File

@@ -37,17 +37,19 @@ export interface InstallOpts {
} }
export class Install { export class Install {
private readonly _standalone: boolean | undefined;
private readonly context: Context; private readonly context: Context;
private readonly standalone: boolean;
constructor(opts?: InstallOpts) { constructor(opts?: InstallOpts) {
this.context = opts?.context || new Context(); 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<string> { public async download(version: string): Promise<string> {
const release: GitHubRelease = await Install.getRelease(version); const release: GitHubRelease = await Install.getRelease(version);
const fversion = release.tag_name.replace(/^v+|v+$/g, ''); const fversion = release.tag_name.replace(/^v+|v+$/g, '');
core.debug(`Install.download version: ${fversion}`);
let toolPath: string; let toolPath: string;
toolPath = tc.find('buildx', fversion, this.platform()); toolPath = tc.find('buildx', fversion, this.platform());
@@ -58,15 +60,12 @@ export class Install {
} }
toolPath = await this.fetchBinary(fversion); toolPath = await this.fetchBinary(fversion);
} }
core.debug(`Install.download toolPath: ${toolPath}`);
dest = dest || (this.standalone ? this.context.tmpDir() : Docker.configDir); return toolPath;
if (this.standalone) {
return this.setStandalone(toolPath, dest);
}
return this.setPlugin(toolPath, dest);
} }
public async build(gitContext: string, dest?: string): Promise<string> { public async build(gitContext: string): Promise<string> {
// eslint-disable-next-line prefer-const // eslint-disable-next-line prefer-const
let [repo, ref] = gitContext.split('#'); let [repo, ref] = gitContext.split('#');
if (ref.length == 0) { if (ref.length == 0) {
@@ -85,7 +84,7 @@ export class Install {
let toolPath: string; let toolPath: string;
toolPath = tc.find('buildx', vspec); toolPath = tc.find('buildx', vspec);
if (!toolPath) { if (!toolPath) {
const outputDir = path.join(this.context.tmpDir(), 'build-cache').split(path.sep).join(path.posix.sep); const outputDir = path.join(this.context.tmpDir(), 'build-cache');
const buildCmd = await this.buildCommand(gitContext, outputDir); const buildCmd = await this.buildCommand(gitContext, outputDir);
toolPath = await exec toolPath = await exec
.getExecOutput(buildCmd.command, buildCmd.args, { .getExecOutput(buildCmd.command, buildCmd.args, {
@@ -99,11 +98,48 @@ export class Install {
}); });
} }
dest = dest || Docker.configDir; return toolPath;
if (this.standalone) { }
return this.setStandalone(toolPath, dest);
public async installStandalone(toolPath: string, dest?: string): Promise<string> {
core.info('Standalone mode');
dest = dest || this.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)) {
fs.mkdirSync(binDir, {recursive: true});
} }
return this.setPlugin(toolPath, dest); const filename: string = os.platform() == 'win32' ? 'buildx.exe' : 'buildx';
const buildxPath: string = path.join(binDir, filename);
fs.copyFileSync(toolBinPath, buildxPath);
core.info('Fixing perms');
fs.chmodSync(buildxPath, '0755');
core.addPath(binDir);
core.info('Added Buildx to PATH');
core.info(`Binary path: ${buildxPath}`);
return buildxPath;
}
public async installPlugin(toolPath: string, dest?: string): Promise<string> {
core.info('Docker plugin mode');
dest = dest || Docker.configDir;
const toolBinPath = path.join(toolPath, os.platform() == 'win32' ? 'docker-buildx.exe' : 'docker-buildx');
const pluginsDir: string = path.join(dest, 'cli-plugins');
if (!fs.existsSync(pluginsDir)) {
fs.mkdirSync(pluginsDir, {recursive: true});
}
const filename: string = os.platform() == 'win32' ? 'docker-buildx.exe' : 'docker-buildx';
const pluginPath: string = path.join(pluginsDir, filename);
fs.copyFileSync(toolBinPath, pluginPath);
core.info('Fixing perms');
fs.chmodSync(pluginPath, '0755');
core.info(`Plugin path: ${pluginPath}`);
return pluginPath;
} }
private async buildCommand(gitContext: string, outputDir: string): Promise<{args: Array<string>; command: string}> { private async buildCommand(gitContext: string, outputDir: string): Promise<{args: Array<string>; command: string}> {
@@ -111,10 +147,10 @@ export class Install {
const buildxPluginFound = await new Buildx({context: this.context, standalone: false}).isAvailable(); const buildxPluginFound = await new Buildx({context: this.context, standalone: false}).isAvailable();
let buildStandalone = false; let buildStandalone = false;
if (this.standalone && buildxStandaloneFound) { if ((await this.isStandalone()) && buildxStandaloneFound) {
core.debug(`Install.buildCommand: Buildx standalone found, build with it`); core.debug(`Install.buildCommand: Buildx standalone found, build with it`);
buildStandalone = true; buildStandalone = true;
} else if (!this.standalone && buildxPluginFound) { } else if (!(await this.isStandalone()) && buildxPluginFound) {
core.debug(`Install.buildCommand: Buildx plugin found, build with it`); core.debug(`Install.buildCommand: Buildx plugin found, build with it`);
buildStandalone = false; buildStandalone = false;
} else if (buildxStandaloneFound) { } else if (buildxStandaloneFound) {
@@ -128,7 +164,7 @@ export class Install {
} }
//prettier-ignore //prettier-ignore
return new Buildx({context: this.context, standalone: buildStandalone}).getCommand([ return await new Buildx({context: this.context, standalone: buildStandalone}).getCommand([
'build', 'build',
'--target', 'binaries', '--target', 'binaries',
'--build-arg', 'BUILDKIT_CONTEXT_KEEP_GIT_DIR=1', '--build-arg', 'BUILDKIT_CONTEXT_KEEP_GIT_DIR=1',
@@ -137,39 +173,18 @@ export class Install {
]); ]);
} }
private async setStandalone(toolPath: string, dest: string): Promise<string> { private async isStandalone(): Promise<boolean> {
const toolBinPath = path.join(toolPath, os.platform() == 'win32' ? 'docker-buildx.exe' : 'docker-buildx'); const standalone = this._standalone ?? !(await Docker.isAvailable());
const binDir = path.join(dest, 'bin'); core.debug(`Install.isStandalone: ${standalone}`);
if (!fs.existsSync(binDir)) { return standalone;
fs.mkdirSync(binDir, {recursive: true});
}
const filename: string = os.platform() == 'win32' ? 'buildx.exe' : 'buildx';
const buildxPath: string = path.join(binDir, filename);
fs.copyFileSync(toolBinPath, buildxPath);
fs.chmodSync(buildxPath, '0755');
core.addPath(binDir);
return buildxPath;
}
private async setPlugin(toolPath: string, dest: string): Promise<string> {
const toolBinPath = path.join(toolPath, os.platform() == 'win32' ? 'docker-buildx.exe' : 'docker-buildx');
const pluginsDir: string = path.join(dest, 'cli-plugins');
if (!fs.existsSync(pluginsDir)) {
fs.mkdirSync(pluginsDir, {recursive: true});
}
const filename: string = os.platform() == 'win32' ? 'docker-buildx.exe' : 'docker-buildx';
const pluginPath: string = path.join(pluginsDir, filename);
fs.copyFileSync(toolBinPath, pluginPath);
fs.chmodSync(pluginPath, '0755');
return pluginPath;
} }
private async fetchBinary(version: string): Promise<string> { private async fetchBinary(version: string): Promise<string> {
const targetFile: string = os.platform() == 'win32' ? 'docker-buildx.exe' : 'docker-buildx'; const targetFile: string = os.platform() == 'win32' ? 'docker-buildx.exe' : 'docker-buildx';
const downloadURL = util.format('https://github.com/docker/buildx/releases/download/v%s/%s', version, this.filename(version)); const downloadURL = util.format('https://github.com/docker/buildx/releases/download/v%s/%s', version, this.filename(version));
core.info(`Downloading ${downloadURL}`);
const downloadPath = await tc.downloadTool(downloadURL); const downloadPath = await tc.downloadTool(downloadURL);
core.debug(`downloadURL: ${downloadURL}`); core.debug(`Install.fetchBinary downloadPath: ${downloadPath}`);
core.debug(`downloadPath: ${downloadPath}`);
return await tc.cacheFile(downloadPath, targetFile, 'buildx', version); return await tc.cacheFile(downloadPath, targetFile, 'buildx', version);
} }

View File

@@ -27,7 +27,7 @@ export class Context {
public buildGitContext: string; public buildGitContext: string;
public provenanceBuilderID: string; public provenanceBuilderID: string;
private readonly _tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-actions-toolkit-')).split(path.sep).join(path.posix.sep); private readonly _tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-actions-toolkit-'));
constructor() { constructor() {
this.gitRef = github.context.ref; this.gitRef = github.context.ref;

View File

@@ -20,64 +20,37 @@ import * as core from '@actions/core';
import * as exec from '@actions/exec'; import * as exec from '@actions/exec';
export class Docker { export class Docker {
private static instance?: Docker;
static getInstance = (): Docker => (Docker.instance = Docker.instance ?? new Docker());
private _available: boolean | undefined;
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
static get configDir(): string { static get configDir(): string {
return process.env.DOCKER_CONFIG || path.join(os.homedir(), '.docker'); return process.env.DOCKER_CONFIG || path.join(os.homedir(), '.docker');
} }
get available() { public static async isAvailable(): Promise<boolean> {
return (async () => { const ok: boolean = await exec
if (!this._available) { .getExecOutput('docker', undefined, {
this._available = await exec ignoreReturnCode: true,
.getExecOutput('docker', undefined, { silent: true
ignoreReturnCode: true, })
silent: true .then(res => {
}) if (res.stderr.length > 0 && res.exitCode != 0) {
.then(res => { core.debug(`Docker.isAvailable cmd err: ${res.stderr}`);
if (res.stderr.length > 0 && res.exitCode != 0) { return false;
core.debug(`Docker.isAvailable error: ${res.stderr}`); }
return false; return res.exitCode == 0;
} else { })
core.debug(`Docker.isAvailable ok`); .catch(error => {
return res.exitCode == 0; core.debug(`Docker.isAvailable error: ${error}`);
} return false;
}) });
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.catch(error => { core.debug(`Docker.isAvailable: ${ok}`);
core.debug(`Docker.isAvailable failed: ${error}`); return ok;
return false;
});
}
return this._available;
})();
} }
public static async printVersion(standalone?: boolean): Promise<void> { public static async printVersion(): Promise<void> {
const noDocker = standalone ?? !Docker.getInstance().available; await exec.exec('docker', ['version']);
if (noDocker) {
core.debug('Docker.printVersion: Docker is not available, skipping.');
return;
}
await exec.exec('docker', ['version'], {
failOnStdErr: false
});
} }
public static async printInfo(standalone?: boolean): Promise<void> { public static async printInfo(): Promise<void> {
const noDocker = standalone ?? !Docker.getInstance().available; await exec.exec('docker', ['info']);
if (noDocker) {
core.debug('Docker.printInfo: Docker is not available, skipping.');
return;
}
await exec.exec('docker', ['info'], {
failOnStdErr: false
});
} }
} }

View File

@@ -20,7 +20,6 @@ import {Install} from './buildx/install';
import {Builder} from './buildx/builder'; import {Builder} from './buildx/builder';
import {BuildKit} from './buildkit/buildkit'; import {BuildKit} from './buildkit/buildkit';
import {GitHub} from './github'; import {GitHub} from './github';
import {Docker} from './docker';
export interface ToolkitOpts { export interface ToolkitOpts {
/** /**
@@ -33,7 +32,6 @@ export interface ToolkitOpts {
export class Toolkit { export class Toolkit {
public context: Context; public context: Context;
public github: GitHub; public github: GitHub;
public docker: Docker;
public buildx: Buildx; public buildx: Buildx;
public buildxInstall: Install; public buildxInstall: Install;
public builder: Builder; public builder: Builder;
@@ -42,9 +40,8 @@ export class Toolkit {
constructor(opts: ToolkitOpts = {}) { constructor(opts: ToolkitOpts = {}) {
this.context = new Context(); this.context = new Context();
this.github = new GitHub({token: opts.githubToken}); this.github = new GitHub({token: opts.githubToken});
this.docker = Docker.getInstance(); this.buildx = new Buildx({context: this.context});
this.buildx = new Buildx({context: this.context, standalone: !this.docker.available}); this.buildxInstall = new Install({context: this.context});
this.buildxInstall = new Install({context: this.context, standalone: this.buildx.standalone});
this.builder = new Builder({context: this.context, buildx: this.buildx}); this.builder = new Builder({context: this.context, buildx: this.buildx});
this.buildkit = new BuildKit({context: this.context, buildx: this.buildx}); this.buildkit = new BuildKit({context: this.context, buildx: this.buildx});
} }