Skip to content

Commit

Permalink
undock install
Browse files Browse the repository at this point in the history
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
  • Loading branch information
crazy-max committed Oct 28, 2024
1 parent 517a5c5 commit faef3be
Show file tree
Hide file tree
Showing 5 changed files with 355 additions and 1 deletion.
2 changes: 1 addition & 1 deletion __tests__/buildx/install.test.itg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const maybe = !process.env.GITHUB_ACTIONS || (process.env.GITHUB_ACTIONS === 'tr
maybe('download', () => {
// prettier-ignore
test.each(['latest'])(
'install docker %s', async (version) => {
'install buildx %s', async (version) => {
await expect((async () => {
const install = new Install({
standalone: true
Expand Down
38 changes: 38 additions & 0 deletions __tests__/undock/install.test.itg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Copyright 2024 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, test} from '@jest/globals';
import * as fs from 'fs';

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

describe('download', () => {
// prettier-ignore
test.each(['latest'])(
'install undock %s', async (version) => {
await expect((async () => {
const install = new Install();
const toolPath = await install.download(version);
if (!fs.existsSync(toolPath)) {
throw new Error('toolPath does not exist');
}
const binPath = await install.install(toolPath);
if (!fs.existsSync(binPath)) {
throw new Error('binPath does not exist');
}
})()).resolves.not.toThrow();
}, 60000);
});
129 changes: 129 additions & 0 deletions __tests__/undock/install.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/**
* Copyright 2024 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, afterEach} from '@jest/globals';
import fs from 'fs';
import os from 'os';
import path from 'path';
import * as rimraf from 'rimraf';
import osm = require('os');

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

const tmpDir = fs.mkdtempSync(path.join(process.env.TEMP || os.tmpdir(), 'undock-install-'));

afterEach(function () {
rimraf.sync(tmpDir);
});

describe('download', () => {
// prettier-ignore
test.each([
['v0.4.0'],
['v0.7.0'],
['latest']
])(
'acquires %p of undock (standalone: %p)', async (version) => {
const install = new Install();
const toolPath = await install.download(version);
expect(fs.existsSync(toolPath)).toBe(true);
const undockBin = await install.install(toolPath, tmpDir);
expect(fs.existsSync(undockBin)).toBe(true);
},
100000
);

// prettier-ignore
test.each([
// following versions are already cached to htc from previous test cases
['v0.4.0'],
['v0.7.0'],
])(
'acquires %p of undock with cache', async (version) => {
const install = new Install();
const toolPath = await install.download(version);
expect(fs.existsSync(toolPath)).toBe(true);
});

// prettier-ignore
test.each([
['v0.5.0'],
['v0.6.0'],
])(
'acquires %p of undock without cache', async (version) => {
const install = new Install();
const toolPath = await install.download(version, true);
expect(fs.existsSync(toolPath)).toBe(true);
});

// 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 undock for %s/%s', async (os, arch) => {
jest.spyOn(osm, 'platform').mockImplementation(() => os as NodeJS.Platform);
jest.spyOn(osm, 'arch').mockImplementation(() => arch);
const install = new Install();
const undockBin = await install.download('latest');
expect(fs.existsSync(undockBin)).toBe(true);
},
100000
);
});

describe('getDownloadVersion', () => {
it('returns latest download version', async () => {
const version = await Install.getDownloadVersion('latest');
expect(version.version).toEqual('latest');
expect(version.downloadURL).toEqual('https://github.com/crazy-max/undock/releases/download/v%s/%s');
expect(version.releasesURL).toEqual('https://raw.githubusercontent.com/docker/actions-toolkit/main/.github/undock-releases.json');
});
it('returns v0.6.0 download version', async () => {
const version = await Install.getDownloadVersion('v0.6.0');
expect(version.version).toEqual('v0.6.0');
expect(version.downloadURL).toEqual('https://github.com/crazy-max/undock/releases/download/v%s/%s');
expect(version.releasesURL).toEqual('https://raw.githubusercontent.com/docker/actions-toolkit/main/.github/undock-releases.json');
});
});

describe('getRelease', () => {
it('returns latest GitHub release', async () => {
const version = await Install.getDownloadVersion('latest');
const release = await Install.getRelease(version);
expect(release).not.toBeNull();
expect(release?.tag_name).not.toEqual('');
});
it('returns v0.6.0 GitHub release', async () => {
const version = await Install.getDownloadVersion('v0.6.0');
const release = await Install.getRelease(version);
expect(release).not.toBeNull();
expect(release?.id).toEqual(121362767);
expect(release?.tag_name).toEqual('v0.6.0');
expect(release?.html_url).toEqual('https://github.com/crazy-max/undock/releases/tag/v0.6.0');
});
it('unknown release', async () => {
const version = await Install.getDownloadVersion('foo');
await expect(Install.getRelease(version)).rejects.toThrow(new Error('Cannot find Undock release foo in https://raw.githubusercontent.com/docker/actions-toolkit/main/.github/undock-releases.json'));
});
});
21 changes: 21 additions & 0 deletions src/types/undock/undock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Copyright 2024 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.
*/

export interface DownloadVersion {
version: string;
downloadURL: string;
releasesURL: string;
}
166 changes: 166 additions & 0 deletions src/undock/install.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/**
* Copyright 2024 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 {Cache} from '../cache';
import {Context} from '../context';

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

export class Install {
/*
* Download undock binary from GitHub release
* @param v: version semver version or latest
* @param ghaNoCache: disable binary caching in GitHub Actions cache backend
* @returns path to the undock binary
*/
public async download(v: string, ghaNoCache?: boolean): Promise<string> {
const version: DownloadVersion = await Install.getDownloadVersion(v);
core.debug(`Install.download version: ${version.version}`);

const release: GitHubRelease = await Install.getRelease(version);
core.debug(`Install.download release tag name: ${release.tag_name}`);

const vspec = await this.vspec(release.tag_name);
core.debug(`Install.download vspec: ${vspec}`);

const c = semver.clean(vspec) || '';
if (!semver.valid(c)) {
throw new Error(`Invalid Undock version "${vspec}".`);
}

const installCache = new Cache({
htcName: 'undock-dl-bin',
htcVersion: vspec,
baseCacheDir: path.join(os.homedir(), '.bin'),
cacheFile: os.platform() == 'win32' ? 'undock.exe' : 'undock',
ghaNoCache: ghaNoCache
});

const cacheFoundPath = await installCache.find();
if (cacheFoundPath) {
core.info(`Unodck binary found in ${cacheFoundPath}`);
return cacheFoundPath;
}

const downloadURL = util.format(version.downloadURL, vspec, this.filename(vspec));
core.info(`Downloading ${downloadURL}`);

const htcDownloadPath = await tc.downloadTool(downloadURL);
core.debug(`Install.download htcDownloadPath: ${htcDownloadPath}`);

let htcExtPath: string;
if (os.platform() == 'win32') {
htcExtPath = await tc.extractZip(htcDownloadPath);
} else {
htcExtPath = await tc.extractTar(htcDownloadPath);
}
core.info(`Extracted to ${htcExtPath}`);

const exePath: string = path.join(htcExtPath, os.platform() == 'win32' ? 'undock.exe' : 'undock');
core.debug(`Install.download exePath: ${exePath}`);

const cacheSavePath = await installCache.save(exePath);
core.info(`Cached to ${cacheSavePath}`);
return cacheSavePath;
}

public async install(binPath: string, dest?: string): Promise<string> {
dest = dest || Context.tmpDir();

const binDir = path.join(dest, 'undock-bin');
if (!fs.existsSync(binDir)) {
fs.mkdirSync(binDir, {recursive: true});
}
const binName: string = os.platform() == 'win32' ? 'undock.exe' : 'undock';
const undockPath: string = path.join(binDir, binName);
fs.copyFileSync(binPath, undockPath);

core.info('Fixing perms');
fs.chmodSync(undockPath, '0755');

core.addPath(binDir);
core.info('Added Unodck to PATH');

core.info(`Binary path: ${undockPath}`);
return undockPath;
}

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 ? 'armv' + arm_version : 'arm';
break;
}
default: {
arch = os.arch();
break;
}
}
const platform: string = os.platform() == 'win32' ? 'windows' : os.platform();
const ext: string = os.platform() == 'win32' ? '.zip' : '.tar.gz';
return util.format('undock_%s_%s_%s%s', version, platform, arch, ext);
}

private async vspec(version: string): Promise<string> {
const v = version.replace(/^v+|v+$/g, '');
core.info(`Use ${v} version spec cache key for ${version}`);
return v;
}

public static async getDownloadVersion(v: string): Promise<DownloadVersion> {
return {
version: v,
downloadURL: 'https://github.com/crazy-max/undock/releases/download/v%s/%s',
releasesURL: 'https://raw.githubusercontent.com/docker/actions-toolkit/main/.github/undock-releases.json'
};
}

public static async getRelease(version: DownloadVersion): Promise<GitHubRelease> {
const http: httpm.HttpClient = new httpm.HttpClient('docker-actions-toolkit');
const resp: httpm.HttpClientResponse = await http.get(version.releasesURL);
const body = await resp.readBody();
const statusCode = resp.message.statusCode || 500;
if (statusCode >= 400) {
throw new Error(`Failed to get Undock releases from ${version.releasesURL} with status code ${statusCode}: ${body}`);
}
const releases = <Record<string, GitHubRelease>>JSON.parse(body);
if (!releases[version.version]) {
throw new Error(`Cannot find Undock release ${version.version} in ${version.releasesURL}`);
}
return releases[version.version];
}
}

0 comments on commit faef3be

Please sign in to comment.