mirror of
https://github.com/docker/actions-toolkit.git
synced 2024-11-26 22:26:08 +08:00
Some improvements
- Use classes - Split buildx/builder modules - Additional tests Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
parent
c7dfb4c220
commit
aae4a2d7bc
18
.github/workflows/publish.yml
vendored
Normal file
18
.github/workflows/publish.yml
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
name: publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
-
|
||||||
|
name: Publish
|
||||||
|
run: |
|
||||||
|
PUBLISH_VERSION=${} make publish
|
158
__tests__/builder.test.ts
Normal file
158
__tests__/builder.test.ts
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
import {describe, expect, it, test} from '@jest/globals';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import {Builder} from '../src/builder';
|
||||||
|
|
||||||
|
describe('inspect', () => {
|
||||||
|
it('valid', async () => {
|
||||||
|
const builder = new Builder();
|
||||||
|
const builderInfo = await builder.inspect('');
|
||||||
|
expect(builderInfo).not.toBeUndefined();
|
||||||
|
expect(builderInfo.name).not.toEqual('');
|
||||||
|
expect(builderInfo.driver).not.toEqual('');
|
||||||
|
expect(builderInfo.nodes).not.toEqual({});
|
||||||
|
}, 100000);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parseInspect', () => {
|
||||||
|
// prettier-ignore
|
||||||
|
test.each([
|
||||||
|
[
|
||||||
|
'inspect1.txt',
|
||||||
|
{
|
||||||
|
"name": "builder-5cb467f7-0940-47e1-b94b-d51f54054d62",
|
||||||
|
"driver": "docker-container",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"name": "builder-5cb467f7-0940-47e1-b94b-d51f54054d620",
|
||||||
|
"endpoint": "unix:///var/run/docker.sock",
|
||||||
|
"status": "running",
|
||||||
|
"buildkitdFlags": "--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host",
|
||||||
|
"buildkitVersion": "v0.10.4",
|
||||||
|
"platforms": "linux/amd64,linux/amd64/v2,linux/amd64/v3,linux/amd64/v4,linux/arm64,linux/riscv64,linux/386,linux/arm/v7,linux/arm/v6"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'inspect2.txt',
|
||||||
|
{
|
||||||
|
"name": "builder-5f449644-ff29-48af-8344-abb0292d0673",
|
||||||
|
"driver": "docker-container",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"name": "builder-5f449644-ff29-48af-8344-abb0292d06730",
|
||||||
|
"endpoint": "unix:///var/run/docker.sock",
|
||||||
|
"driverOpts": [
|
||||||
|
"image=moby/buildkit:latest"
|
||||||
|
],
|
||||||
|
"status": "running",
|
||||||
|
"buildkitdFlags": "--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host",
|
||||||
|
"buildkitVersion": "v0.10.4",
|
||||||
|
"platforms": "linux/amd64,linux/amd64/v2,linux/amd64/v3,linux/amd64/v4,linux/386"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'inspect3.txt',
|
||||||
|
{
|
||||||
|
"name": "builder-9929e463-7954-4dc3-89cd-514cca29ff80",
|
||||||
|
"driver": "docker-container",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"name": "builder-9929e463-7954-4dc3-89cd-514cca29ff800",
|
||||||
|
"endpoint": "unix:///var/run/docker.sock",
|
||||||
|
"driverOpts": [
|
||||||
|
"image=moby/buildkit:master",
|
||||||
|
"network=host"
|
||||||
|
],
|
||||||
|
"status": "running",
|
||||||
|
"buildkitdFlags": "--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host",
|
||||||
|
"buildkitVersion": "3fab389",
|
||||||
|
"platforms": "linux/amd64,linux/amd64/v2,linux/amd64/v3,linux/amd64/v4,linux/386"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'inspect4.txt',
|
||||||
|
{
|
||||||
|
"name": "default",
|
||||||
|
"driver": "docker",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"name": "default",
|
||||||
|
"endpoint": "default",
|
||||||
|
"status": "running",
|
||||||
|
"buildkitVersion": "20.10.17",
|
||||||
|
"platforms": "linux/amd64,linux/arm64,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/arm/v7,linux/arm/v6"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'inspect5.txt',
|
||||||
|
{
|
||||||
|
"name": "remote-builder",
|
||||||
|
"driver": "remote",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"name": "aws_graviton2",
|
||||||
|
"endpoint": "tcp://1.23.45.67:1234",
|
||||||
|
"driverOpts": [
|
||||||
|
"cert=/home/user/.certs/aws_graviton2/cert.pem",
|
||||||
|
"key=/home/user/.certs/aws_graviton2/key.pem",
|
||||||
|
"cacert=/home/user/.certs/aws_graviton2/ca.pem"
|
||||||
|
],
|
||||||
|
"status": "running",
|
||||||
|
"platforms": "darwin/arm64,linux/arm64,linux/arm/v5,linux/arm/v6,linux/arm/v7,windows/arm64"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'inspect6.txt',
|
||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"name": "builder-17cfff01-48d9-4c3d-9332-9992e308a5100",
|
||||||
|
"endpoint": "unix:///var/run/docker.sock",
|
||||||
|
"status": "running",
|
||||||
|
"buildkitdFlags": "--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host",
|
||||||
|
"platforms": "linux/amd64,linux/amd64/v2,linux/amd64/v3,linux/386"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "builder-17cfff01-48d9-4c3d-9332-9992e308a510",
|
||||||
|
"driver": "docker-container"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'inspect7.txt',
|
||||||
|
{
|
||||||
|
"name": "builder2",
|
||||||
|
"driver": "docker-container",
|
||||||
|
"lastActivity": new Date("2023-01-16T09:45:23.000Z"),
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"buildkitVersion": "v0.11.0",
|
||||||
|
"buildkitdFlags": "--debug --allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host",
|
||||||
|
"driverOpts": [
|
||||||
|
"BUILDKIT_STEP_LOG_MAX_SIZE=10485760",
|
||||||
|
"BUILDKIT_STEP_LOG_MAX_SPEED=10485760",
|
||||||
|
"JAEGER_TRACE=localhost:6831",
|
||||||
|
"image=moby/buildkit:latest",
|
||||||
|
"network=host"
|
||||||
|
],
|
||||||
|
"endpoint": "unix:///var/run/docker.sock",
|
||||||
|
"name": "builder20",
|
||||||
|
"platforms": "linux/amd64,linux/amd64/v2,linux/amd64/v3,linux/arm64,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6",
|
||||||
|
"status": "running"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])('given %p', async (inspectFile, expected) => {
|
||||||
|
expect(await Builder.parseInspect(fs.readFileSync(path.join(__dirname, 'fixtures', inspectFile)).toString())).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
@ -1,32 +1,40 @@
|
|||||||
import {describe, expect, it, jest, test} from '@jest/globals';
|
import {afterEach, describe, expect, it, jest, test} from '@jest/globals';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as os from 'os';
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
import * as buildkit from '../src/buildkit';
|
import rimraf from 'rimraf';
|
||||||
import * as buildx from '../src/buildx';
|
|
||||||
import * as util from '../src/util';
|
|
||||||
|
|
||||||
const tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-actions-toolkit-')).split(path.sep).join(path.posix.sep);
|
import {BuildKit} from '../src/buildkit';
|
||||||
jest.spyOn(util, 'tmpDir').mockImplementation((): string => {
|
import {Builder, BuilderInfo} from '../src/builder';
|
||||||
return tmpdir;
|
|
||||||
|
const tmpDir = path.join('/tmp/.docker-actions-toolkit-jest').split(path.sep).join(path.posix.sep);
|
||||||
|
const tmpName = path.join(tmpDir, '.tmpname-jest').split(path.sep).join(path.posix.sep);
|
||||||
|
|
||||||
|
jest.spyOn(BuildKit.prototype as any, 'tmpDir').mockImplementation((): string => {
|
||||||
|
if (!fs.existsSync(tmpDir)) {
|
||||||
|
fs.mkdirSync(tmpDir, {recursive: true});
|
||||||
|
}
|
||||||
|
return tmpDir;
|
||||||
});
|
});
|
||||||
|
|
||||||
const tmpname = path.join(tmpdir, '.tmpname').split(path.sep).join(path.posix.sep);
|
jest.spyOn(BuildKit.prototype as any, 'tmpName').mockImplementation((): string => {
|
||||||
jest.spyOn(util, 'tmpNameSync').mockImplementation((): string => {
|
return tmpName;
|
||||||
return tmpname;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.spyOn(buildx, 'inspect').mockImplementation(async (): Promise<buildx.Builder> => {
|
afterEach(() => {
|
||||||
|
rimraf.sync(tmpDir);
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.spyOn(Builder.prototype, 'inspect').mockImplementation(async (): Promise<BuilderInfo> => {
|
||||||
return {
|
return {
|
||||||
name: 'builder2',
|
name: 'builder2',
|
||||||
driver: 'docker-container',
|
driver: 'docker-container',
|
||||||
'last-activity': new Date('2023-01-16 09:45:23 +0000 UTC'),
|
lastActivity: new Date('2023-01-16 09:45:23 +0000 UTC'),
|
||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
buildkit: 'v0.11.0',
|
buildkitVersion: 'v0.11.0',
|
||||||
'buildkitd-flags': '--debug --allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host',
|
buildkitdFlags: '--debug --allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host',
|
||||||
'driver-opts': ['BUILDKIT_STEP_LOG_MAX_SIZE=10485760', 'BUILDKIT_STEP_LOG_MAX_SPEED=10485760', 'JAEGER_TRACE=localhost:6831', 'image=moby/buildkit:latest', 'network=host'],
|
driverOpts: ['BUILDKIT_STEP_LOG_MAX_SIZE=10485760', 'BUILDKIT_STEP_LOG_MAX_SPEED=10485760', 'JAEGER_TRACE=localhost:6831', 'image=moby/buildkit:latest', 'network=host'],
|
||||||
endpoint: 'unix:///var/run/docker.sock',
|
endpoint: 'unix:///var/run/docker.sock',
|
||||||
name: 'builder20',
|
name: 'builder20',
|
||||||
platforms: 'linux/amd64,linux/amd64/v2,linux/amd64/v3,linux/arm64,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6',
|
platforms: 'linux/amd64,linux/amd64/v2,linux/amd64/v3,linux/arm64,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6',
|
||||||
@ -38,6 +46,7 @@ jest.spyOn(buildx, 'inspect').mockImplementation(async (): Promise<buildx.Builde
|
|||||||
|
|
||||||
describe('getVersion', () => {
|
describe('getVersion', () => {
|
||||||
it('valid', async () => {
|
it('valid', async () => {
|
||||||
|
const buildkit = new BuildKit();
|
||||||
const version = await buildkit.getVersion('builder2');
|
const version = await buildkit.getVersion('builder2');
|
||||||
expect(semver.valid(version)).not.toBeNull();
|
expect(semver.valid(version)).not.toBeNull();
|
||||||
});
|
});
|
||||||
@ -48,14 +57,15 @@ describe('satisfies', () => {
|
|||||||
['builder2', '>=0.10.0', true],
|
['builder2', '>=0.10.0', true],
|
||||||
['builder2', '>0.11.0', false]
|
['builder2', '>0.11.0', false]
|
||||||
])('given %p', async (builderName, range, expected) => {
|
])('given %p', async (builderName, range, expected) => {
|
||||||
expect(await buildkit.satisfies(builderName, range)).toBe(expected);
|
const buildkit = new BuildKit();
|
||||||
|
expect(await buildkit.versionSatisfies(builderName, range)).toBe(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getConfig', () => {
|
describe('generateConfig', () => {
|
||||||
test.each([
|
test.each([
|
||||||
['debug = true', false, 'debug = true', false],
|
['debug = true', false, 'debug = true', null],
|
||||||
[`notfound.toml`, true, '', true],
|
[`notfound.toml`, true, '', new Error('config file notfound.toml not found')],
|
||||||
[
|
[
|
||||||
`${path.join(__dirname, 'fixtures', 'buildkitd.toml').split(path.sep).join(path.posix.sep)}`,
|
`${path.join(__dirname, 'fixtures', 'buildkitd.toml').split(path.sep).join(path.posix.sep)}`,
|
||||||
true,
|
true,
|
||||||
@ -63,23 +73,23 @@ describe('getConfig', () => {
|
|||||||
[registry."docker.io"]
|
[registry."docker.io"]
|
||||||
mirrors = ["mirror.gcr.io"]
|
mirrors = ["mirror.gcr.io"]
|
||||||
`,
|
`,
|
||||||
false
|
null
|
||||||
]
|
]
|
||||||
])('given %p config', async (val, file, exValue, invalid) => {
|
])('given %p config', async (val, file, exValue, error: Error) => {
|
||||||
try {
|
try {
|
||||||
|
const buildkit = new BuildKit();
|
||||||
let config: string;
|
let config: string;
|
||||||
if (file) {
|
if (file) {
|
||||||
config = await buildkit.getConfigFile(val);
|
config = buildkit.generateConfigFile(val);
|
||||||
} else {
|
} else {
|
||||||
config = await buildkit.getConfigInline(val);
|
config = buildkit.generateConfigInline(val);
|
||||||
}
|
}
|
||||||
expect(true).toBe(!invalid);
|
expect(config).toEqual(tmpName);
|
||||||
expect(config).toEqual(tmpname);
|
const configValue = fs.readFileSync(tmpName, 'utf-8');
|
||||||
const configValue = fs.readFileSync(tmpname, 'utf-8');
|
|
||||||
expect(configValue).toEqual(exValue);
|
expect(configValue).toEqual(exValue);
|
||||||
} catch (err) {
|
} catch (e) {
|
||||||
// eslint-disable-next-line jest/no-conditional-expect
|
// eslint-disable-next-line jest/no-conditional-expect
|
||||||
expect(true).toBe(invalid);
|
expect(e.message).toEqual(error?.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,53 +1,61 @@
|
|||||||
import {describe, expect, it, jest, test} from '@jest/globals';
|
import {afterEach, describe, expect, it, jest, test} from '@jest/globals';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
import * as exec from '@actions/exec';
|
import * as exec from '@actions/exec';
|
||||||
import * as buildx from '../src/buildx';
|
import rimraf from 'rimraf';
|
||||||
import * as util from '../src/util';
|
|
||||||
|
|
||||||
const tmpNameSync = path.join('/tmp/.docker-actions-toolkit-jest', '.tmpname-jest').split(path.sep).join(path.posix.sep);
|
import {Buildx} from '../src/buildx';
|
||||||
const imageID = 'sha256:bfb45ab72e46908183546477a08f8867fc40cebadd00af54b071b097aed127a9';
|
|
||||||
|
const tmpDir = path.join('/tmp/.docker-actions-toolkit-jest').split(path.sep).join(path.posix.sep);
|
||||||
|
const tmpName = path.join(tmpDir, '.tmpname-jest').split(path.sep).join(path.posix.sep);
|
||||||
const metadata = `{
|
const metadata = `{
|
||||||
"containerimage.config.digest": "sha256:059b68a595b22564a1cbc167af369349fdc2ecc1f7bc092c2235cbf601a795fd",
|
"containerimage.config.digest": "sha256:059b68a595b22564a1cbc167af369349fdc2ecc1f7bc092c2235cbf601a795fd",
|
||||||
"containerimage.digest": "sha256:b09b9482c72371486bb2c1d2c2a2633ed1d0b8389e12c8d52b9e052725c0c83c"
|
"containerimage.digest": "sha256:b09b9482c72371486bb2c1d2c2a2633ed1d0b8389e12c8d52b9e052725c0c83c"
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
jest.spyOn(util, 'tmpDir').mockImplementation((): string => {
|
jest.spyOn(Buildx.prototype as any, 'tmpDir').mockImplementation((): string => {
|
||||||
const tmpDir = path.join('/tmp/.docker-actions-toolkit-jest').split(path.sep).join(path.posix.sep);
|
|
||||||
if (!fs.existsSync(tmpDir)) {
|
if (!fs.existsSync(tmpDir)) {
|
||||||
fs.mkdirSync(tmpDir, {recursive: true});
|
fs.mkdirSync(tmpDir, {recursive: true});
|
||||||
}
|
}
|
||||||
return tmpDir;
|
return tmpDir;
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.spyOn(util, 'tmpNameSync').mockImplementation((): string => {
|
afterEach(() => {
|
||||||
return tmpNameSync;
|
rimraf.sync(tmpDir);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getImageID', () => {
|
jest.spyOn(Buildx.prototype as any, 'tmpName').mockImplementation((): string => {
|
||||||
|
return tmpName;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getBuildImageID', () => {
|
||||||
it('matches', async () => {
|
it('matches', async () => {
|
||||||
const imageIDFile = await buildx.getImageIDFile();
|
const buildx = new Buildx();
|
||||||
|
const imageID = 'sha256:bfb45ab72e46908183546477a08f8867fc40cebadd00af54b071b097aed127a9';
|
||||||
|
const imageIDFile = buildx.getBuildImageIDFilePath();
|
||||||
await fs.writeFileSync(imageIDFile, imageID);
|
await fs.writeFileSync(imageIDFile, imageID);
|
||||||
const expected = await buildx.getImageID();
|
const expected = buildx.getBuildImageID();
|
||||||
expect(expected).toEqual(imageID);
|
expect(expected).toEqual(imageID);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getMetadata', () => {
|
describe('getBuildMetadata', () => {
|
||||||
it('matches', async () => {
|
it('matches', async () => {
|
||||||
const metadataFile = await buildx.getMetadataFile();
|
const buildx = new Buildx();
|
||||||
|
const metadataFile = buildx.getBuildMetadataFilePath();
|
||||||
await fs.writeFileSync(metadataFile, metadata);
|
await fs.writeFileSync(metadataFile, metadata);
|
||||||
const expected = await buildx.getMetadata();
|
const expected = buildx.getBuildMetadata();
|
||||||
expect(expected).toEqual(metadata);
|
expect(expected).toEqual(metadata);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getDigest', () => {
|
describe('getDigest', () => {
|
||||||
it('matches', async () => {
|
it('matches', async () => {
|
||||||
const metadataFile = await buildx.getMetadataFile();
|
const buildx = new Buildx();
|
||||||
|
const metadataFile = buildx.getBuildMetadataFilePath();
|
||||||
await fs.writeFileSync(metadataFile, metadata);
|
await fs.writeFileSync(metadataFile, metadata);
|
||||||
const expected = await buildx.getDigest(metadata);
|
const expected = buildx.getDigest();
|
||||||
expect(expected).toEqual('sha256:b09b9482c72371486bb2c1d2c2a2633ed1d0b8389e12c8d52b9e052725c0c83c');
|
expect(expected).toEqual('sha256:b09b9482c72371486bb2c1d2c2a2633ed1d0b8389e12c8d52b9e052725c0c83c');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -62,14 +70,15 @@ describe('hasLocalOrTarExporter', () => {
|
|||||||
[['"type=tar","dest=/tmp/image.tar"'], true],
|
[['"type=tar","dest=/tmp/image.tar"'], true],
|
||||||
[['" type= local" , dest=./release-out'], true],
|
[['" type= local" , dest=./release-out'], true],
|
||||||
[['.'], true]
|
[['.'], true]
|
||||||
])('given %p returns %p', async (outputs: Array<string>, expected: boolean) => {
|
])('given %p returns %p', async (exporters: Array<string>, expected: boolean) => {
|
||||||
expect(buildx.hasLocalOrTarExporter(outputs)).toEqual(expected);
|
expect(Buildx.hasLocalExporter(exporters) || Buildx.hasTarExporter(exporters)).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('isAvailable', () => {
|
describe('isAvailable', () => {
|
||||||
it('docker cli', async () => {
|
it('docker cli', async () => {
|
||||||
const execSpy = jest.spyOn(exec, 'getExecOutput');
|
const execSpy = jest.spyOn(exec, 'getExecOutput');
|
||||||
|
const buildx = new Buildx();
|
||||||
await buildx.isAvailable();
|
await buildx.isAvailable();
|
||||||
// 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'], {
|
||||||
@ -79,7 +88,10 @@ describe('isAvailable', () => {
|
|||||||
});
|
});
|
||||||
it('standalone', async () => {
|
it('standalone', async () => {
|
||||||
const execSpy = jest.spyOn(exec, 'getExecOutput');
|
const execSpy = jest.spyOn(exec, 'getExecOutput');
|
||||||
await buildx.isAvailable(true);
|
const buildx = new Buildx({
|
||||||
|
standalone: true
|
||||||
|
});
|
||||||
|
await buildx.isAvailable();
|
||||||
// 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,
|
||||||
@ -88,161 +100,9 @@ describe('isAvailable', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('inspect', () => {
|
|
||||||
it('valid', async () => {
|
|
||||||
const builder = await buildx.inspect('');
|
|
||||||
expect(builder).not.toBeUndefined();
|
|
||||||
expect(builder.name).not.toEqual('');
|
|
||||||
expect(builder.driver).not.toEqual('');
|
|
||||||
expect(builder.nodes).not.toEqual({});
|
|
||||||
}, 100000);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('parseInspect', () => {
|
|
||||||
// prettier-ignore
|
|
||||||
test.each([
|
|
||||||
[
|
|
||||||
'inspect1.txt',
|
|
||||||
{
|
|
||||||
"name": "builder-5cb467f7-0940-47e1-b94b-d51f54054d62",
|
|
||||||
"driver": "docker-container",
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"name": "builder-5cb467f7-0940-47e1-b94b-d51f54054d620",
|
|
||||||
"endpoint": "unix:///var/run/docker.sock",
|
|
||||||
"status": "running",
|
|
||||||
"buildkitd-flags": "--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host",
|
|
||||||
"buildkit": "v0.10.4",
|
|
||||||
"platforms": "linux/amd64,linux/amd64/v2,linux/amd64/v3,linux/amd64/v4,linux/arm64,linux/riscv64,linux/386,linux/arm/v7,linux/arm/v6"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'inspect2.txt',
|
|
||||||
{
|
|
||||||
"name": "builder-5f449644-ff29-48af-8344-abb0292d0673",
|
|
||||||
"driver": "docker-container",
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"name": "builder-5f449644-ff29-48af-8344-abb0292d06730",
|
|
||||||
"endpoint": "unix:///var/run/docker.sock",
|
|
||||||
"driver-opts": [
|
|
||||||
"image=moby/buildkit:latest"
|
|
||||||
],
|
|
||||||
"status": "running",
|
|
||||||
"buildkitd-flags": "--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host",
|
|
||||||
"buildkit": "v0.10.4",
|
|
||||||
"platforms": "linux/amd64,linux/amd64/v2,linux/amd64/v3,linux/amd64/v4,linux/386"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'inspect3.txt',
|
|
||||||
{
|
|
||||||
"name": "builder-9929e463-7954-4dc3-89cd-514cca29ff80",
|
|
||||||
"driver": "docker-container",
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"name": "builder-9929e463-7954-4dc3-89cd-514cca29ff800",
|
|
||||||
"endpoint": "unix:///var/run/docker.sock",
|
|
||||||
"driver-opts": [
|
|
||||||
"image=moby/buildkit:master",
|
|
||||||
"network=host"
|
|
||||||
],
|
|
||||||
"status": "running",
|
|
||||||
"buildkitd-flags": "--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host",
|
|
||||||
"buildkit": "3fab389",
|
|
||||||
"platforms": "linux/amd64,linux/amd64/v2,linux/amd64/v3,linux/amd64/v4,linux/386"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'inspect4.txt',
|
|
||||||
{
|
|
||||||
"name": "default",
|
|
||||||
"driver": "docker",
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"name": "default",
|
|
||||||
"endpoint": "default",
|
|
||||||
"status": "running",
|
|
||||||
"buildkit": "20.10.17",
|
|
||||||
"platforms": "linux/amd64,linux/arm64,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/arm/v7,linux/arm/v6"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'inspect5.txt',
|
|
||||||
{
|
|
||||||
"name": "remote-builder",
|
|
||||||
"driver": "remote",
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"name": "aws_graviton2",
|
|
||||||
"endpoint": "tcp://1.23.45.67:1234",
|
|
||||||
"driver-opts": [
|
|
||||||
"cert=/home/user/.certs/aws_graviton2/cert.pem",
|
|
||||||
"key=/home/user/.certs/aws_graviton2/key.pem",
|
|
||||||
"cacert=/home/user/.certs/aws_graviton2/ca.pem"
|
|
||||||
],
|
|
||||||
"status": "running",
|
|
||||||
"platforms": "darwin/arm64,linux/arm64,linux/arm/v5,linux/arm/v6,linux/arm/v7,windows/arm64"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'inspect6.txt',
|
|
||||||
{
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"name": "builder-17cfff01-48d9-4c3d-9332-9992e308a5100",
|
|
||||||
"endpoint": "unix:///var/run/docker.sock",
|
|
||||||
"status": "running",
|
|
||||||
"buildkitd-flags": "--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host",
|
|
||||||
"platforms": "linux/amd64,linux/amd64/v2,linux/amd64/v3,linux/386"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "builder-17cfff01-48d9-4c3d-9332-9992e308a510",
|
|
||||||
"driver": "docker-container"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'inspect7.txt',
|
|
||||||
{
|
|
||||||
"name": "builder2",
|
|
||||||
"driver": "docker-container",
|
|
||||||
"last-activity": new Date("2023-01-16T09:45:23.000Z"),
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"buildkit": "v0.11.0",
|
|
||||||
"buildkitd-flags": "--debug --allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host",
|
|
||||||
"driver-opts": [
|
|
||||||
"BUILDKIT_STEP_LOG_MAX_SIZE=10485760",
|
|
||||||
"BUILDKIT_STEP_LOG_MAX_SPEED=10485760",
|
|
||||||
"JAEGER_TRACE=localhost:6831",
|
|
||||||
"image=moby/buildkit:latest",
|
|
||||||
"network=host"
|
|
||||||
],
|
|
||||||
"endpoint": "unix:///var/run/docker.sock",
|
|
||||||
"name": "builder20",
|
|
||||||
"platforms": "linux/amd64,linux/amd64/v2,linux/amd64/v3,linux/arm64,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6",
|
|
||||||
"status": "running"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
])('given %p', async (inspectFile, expected) => {
|
|
||||||
expect(await buildx.parseInspect(fs.readFileSync(path.join(__dirname, 'fixtures', inspectFile)).toString())).toEqual(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getVersion', () => {
|
describe('getVersion', () => {
|
||||||
it('valid', async () => {
|
it('valid', async () => {
|
||||||
|
const buildx = new Buildx();
|
||||||
const version = await buildx.getVersion();
|
const version = await buildx.getVersion();
|
||||||
expect(semver.valid(version)).not.toBeNull();
|
expect(semver.valid(version)).not.toBeNull();
|
||||||
});
|
});
|
||||||
@ -255,44 +115,54 @@ describe('parseVersion', () => {
|
|||||||
['github.com/docker/buildx v0.4.2 fb7b670b764764dc4716df3eba07ffdae4cc47b2', '0.4.2'],
|
['github.com/docker/buildx v0.4.2 fb7b670b764764dc4716df3eba07ffdae4cc47b2', '0.4.2'],
|
||||||
['github.com/docker/buildx f117971 f11797113e5a9b86bd976329c5dbb8a8bfdfadfa', 'f117971']
|
['github.com/docker/buildx f117971 f11797113e5a9b86bd976329c5dbb8a8bfdfadfa', 'f117971']
|
||||||
])('given %p', async (stdout, expected) => {
|
])('given %p', async (stdout, expected) => {
|
||||||
expect(buildx.parseVersion(stdout)).toEqual(expected);
|
expect(Buildx.parseVersion(stdout)).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('satisfies', () => {
|
describe('versionSatisfies', () => {
|
||||||
test.each([
|
test.each([
|
||||||
['0.4.1', '>=0.3.2', true],
|
['0.4.1', '>=0.3.2', true],
|
||||||
['bda4882a65349ca359216b135896bddc1d92461c', '>0.1.0', false],
|
['bda4882a65349ca359216b135896bddc1d92461c', '>0.1.0', false],
|
||||||
['f117971', '>0.6.0', true]
|
['f117971', '>0.6.0', true]
|
||||||
])('given %p', async (version, range, expected) => {
|
])('given %p', async (version, range, expected) => {
|
||||||
expect(buildx.satisfies(version, range)).toBe(expected);
|
expect(Buildx.versionSatisfies(version, range)).toBe(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getSecret', () => {
|
describe('generateBuildSecret', () => {
|
||||||
test.each([
|
test.each([
|
||||||
['A_SECRET=abcdef0123456789', false, 'A_SECRET', 'abcdef0123456789', false],
|
['A_SECRET=abcdef0123456789', false, 'A_SECRET', 'abcdef0123456789', null],
|
||||||
['GIT_AUTH_TOKEN=abcdefghijklmno=0123456789', false, 'GIT_AUTH_TOKEN', 'abcdefghijklmno=0123456789', false],
|
['GIT_AUTH_TOKEN=abcdefghijklmno=0123456789', false, 'GIT_AUTH_TOKEN', 'abcdefghijklmno=0123456789', null],
|
||||||
['MY_KEY=c3RyaW5nLXdpdGgtZXF1YWxzCg==', false, 'MY_KEY', 'c3RyaW5nLXdpdGgtZXF1YWxzCg==', false],
|
['MY_KEY=c3RyaW5nLXdpdGgtZXF1YWxzCg==', false, 'MY_KEY', 'c3RyaW5nLXdpdGgtZXF1YWxzCg==', null],
|
||||||
['aaaaaaaa', false, '', '', true],
|
['aaaaaaaa', false, '', '', new Error('aaaaaaaa is not a valid secret')],
|
||||||
['aaaaaaaa=', false, '', '', true],
|
['aaaaaaaa=', false, '', '', new Error('aaaaaaaa= is not a valid secret')],
|
||||||
['=bbbbbbb', false, '', '', true],
|
['=bbbbbbb', false, '', '', new Error('=bbbbbbb is not a valid secret')],
|
||||||
[`foo=${path.join(__dirname, 'fixtures', 'secret.txt').split(path.sep).join(path.posix.sep)}`, true, 'foo', 'bar', false],
|
[`foo=${path.join(__dirname, 'fixtures', 'secret.txt').split(path.sep).join(path.posix.sep)}`, true, 'foo', 'bar', null],
|
||||||
[`notfound=secret`, true, '', '', true]
|
[`notfound=secret`, true, '', '', new Error('secret file secret not found')]
|
||||||
])('given %p key and %p secret', async (kvp, file, exKey, exValue, invalid) => {
|
])('given %p key and %p secret', async (kvp: string, file: boolean, exKey: string, exValue: string, error: Error) => {
|
||||||
try {
|
try {
|
||||||
|
const buildx = new Buildx();
|
||||||
let secret: string;
|
let secret: string;
|
||||||
if (file) {
|
if (file) {
|
||||||
secret = await buildx.getSecretFile(kvp);
|
secret = buildx.generateBuildSecretFile(kvp);
|
||||||
} else {
|
} else {
|
||||||
secret = await buildx.getSecretString(kvp);
|
secret = buildx.generateBuildSecretString(kvp);
|
||||||
}
|
}
|
||||||
expect(true).toBe(!invalid);
|
expect(secret).toEqual(`id=${exKey},src=${tmpName}`);
|
||||||
expect(secret).toEqual(`id=${exKey},src=${tmpNameSync}`);
|
expect(fs.readFileSync(tmpName, 'utf-8')).toEqual(exValue);
|
||||||
expect(fs.readFileSync(tmpNameSync, 'utf-8')).toEqual(exValue);
|
} catch (e) {
|
||||||
} catch (err) {
|
|
||||||
// eslint-disable-next-line jest/no-conditional-expect
|
// eslint-disable-next-line jest/no-conditional-expect
|
||||||
expect(true).toBe(invalid);
|
expect(e.message).toEqual(error?.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('hasGitAuthTokenSecret', () => {
|
||||||
|
// prettier-ignore
|
||||||
|
test.each([
|
||||||
|
[['A_SECRET=abcdef0123456789'], false],
|
||||||
|
[['GIT_AUTH_TOKEN=abcdefghijklmno=0123456789'], true],
|
||||||
|
])('given %p secret', async (kvp: Array<string>, expected: boolean) => {
|
||||||
|
expect(Buildx.hasGitAuthTokenSecret(kvp)).toBe(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import {describe, expect, it, jest} from '@jest/globals';
|
import {describe, expect, it, jest} from '@jest/globals';
|
||||||
import * as docker from '../src/docker';
|
|
||||||
import * as exec from '@actions/exec';
|
import * as exec from '@actions/exec';
|
||||||
|
|
||||||
|
import {Docker} from '../src/docker';
|
||||||
|
|
||||||
describe('isAvailable', () => {
|
describe('isAvailable', () => {
|
||||||
it('cli', () => {
|
it('cli', () => {
|
||||||
const execSpy = jest.spyOn(exec, 'getExecOutput');
|
const execSpy = jest.spyOn(exec, 'getExecOutput');
|
||||||
docker.isAvailable();
|
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, {
|
||||||
|
195
__tests__/fixtures/github-payload.json
Normal file
195
__tests__/fixtures/github-payload.json
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
{
|
||||||
|
"after": "860c1904a1ce19322e91ac35af1ab07466440c37",
|
||||||
|
"base_ref": null,
|
||||||
|
"before": "5f3331d7f7044c18ca9f12c77d961c4d7cf3276a",
|
||||||
|
"commits": [
|
||||||
|
{
|
||||||
|
"author": {
|
||||||
|
"email": "crazy-max@users.noreply.github.com",
|
||||||
|
"name": "CrazyMax",
|
||||||
|
"username": "crazy-max"
|
||||||
|
},
|
||||||
|
"committer": {
|
||||||
|
"email": "crazy-max@users.noreply.github.com",
|
||||||
|
"name": "CrazyMax",
|
||||||
|
"username": "crazy-max"
|
||||||
|
},
|
||||||
|
"distinct": true,
|
||||||
|
"id": "860c1904a1ce19322e91ac35af1ab07466440c37",
|
||||||
|
"message": "hello dev",
|
||||||
|
"timestamp": "2022-04-19T11:27:24+02:00",
|
||||||
|
"tree_id": "d2c60af597e863787d2d27f569e30495b0b92820",
|
||||||
|
"url": "https://github.com/docker/test-docker-action/commit/860c1904a1ce19322e91ac35af1ab07466440c37"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compare": "https://github.com/docker/test-docker-action/compare/5f3331d7f704...860c1904a1ce",
|
||||||
|
"created": false,
|
||||||
|
"deleted": false,
|
||||||
|
"forced": false,
|
||||||
|
"head_commit": {
|
||||||
|
"author": {
|
||||||
|
"email": "crazy-max@users.noreply.github.com",
|
||||||
|
"name": "CrazyMax",
|
||||||
|
"username": "crazy-max"
|
||||||
|
},
|
||||||
|
"committer": {
|
||||||
|
"email": "crazy-max@users.noreply.github.com",
|
||||||
|
"name": "CrazyMax",
|
||||||
|
"username": "crazy-max"
|
||||||
|
},
|
||||||
|
"distinct": true,
|
||||||
|
"id": "860c1904a1ce19322e91ac35af1ab07466440c37",
|
||||||
|
"message": "hello dev",
|
||||||
|
"timestamp": "2022-04-19T11:27:24+02:00",
|
||||||
|
"tree_id": "d2c60af597e863787d2d27f569e30495b0b92820",
|
||||||
|
"url": "https://github.com/docker/test-docker-action/commit/860c1904a1ce19322e91ac35af1ab07466440c37"
|
||||||
|
},
|
||||||
|
"organization": {
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/5429470?v=4",
|
||||||
|
"description": "Docker helps developers bring their ideas to life by conquering the complexity of app development.",
|
||||||
|
"events_url": "https://api.github.com/orgs/docker/events",
|
||||||
|
"hooks_url": "https://api.github.com/orgs/docker/hooks",
|
||||||
|
"id": 5429470,
|
||||||
|
"issues_url": "https://api.github.com/orgs/docker/issues",
|
||||||
|
"login": "docker",
|
||||||
|
"members_url": "https://api.github.com/orgs/docker/members{/member}",
|
||||||
|
"node_id": "MDEyOk9yZ2FuaXphdGlvbjU0Mjk0NzA=",
|
||||||
|
"public_members_url": "https://api.github.com/orgs/docker/public_members{/member}",
|
||||||
|
"repos_url": "https://api.github.com/orgs/docker/repos",
|
||||||
|
"url": "https://api.github.com/orgs/docker"
|
||||||
|
},
|
||||||
|
"pusher": {
|
||||||
|
"email": "github@crazymax.dev",
|
||||||
|
"name": "crazy-max"
|
||||||
|
},
|
||||||
|
"ref": "refs/heads/dev",
|
||||||
|
"repository": {
|
||||||
|
"allow_forking": true,
|
||||||
|
"archive_url": "https://api.github.com/repos/docker/test-docker-action/{archive_format}{/ref}",
|
||||||
|
"archived": false,
|
||||||
|
"assignees_url": "https://api.github.com/repos/docker/test-docker-action/assignees{/user}",
|
||||||
|
"blobs_url": "https://api.github.com/repos/docker/test-docker-action/git/blobs{/sha}",
|
||||||
|
"branches_url": "https://api.github.com/repos/docker/test-docker-action/branches{/branch}",
|
||||||
|
"clone_url": "https://github.com/docker/test-docker-action.git",
|
||||||
|
"collaborators_url": "https://api.github.com/repos/docker/test-docker-action/collaborators{/collaborator}",
|
||||||
|
"comments_url": "https://api.github.com/repos/docker/test-docker-action/comments{/number}",
|
||||||
|
"commits_url": "https://api.github.com/repos/docker/test-docker-action/commits{/sha}",
|
||||||
|
"compare_url": "https://api.github.com/repos/docker/test-docker-action/compare/{base}...{head}",
|
||||||
|
"contents_url": "https://api.github.com/repos/docker/test-docker-action/contents/{+path}",
|
||||||
|
"contributors_url": "https://api.github.com/repos/docker/test-docker-action/contributors",
|
||||||
|
"created_at": 1596792180,
|
||||||
|
"default_branch": "master",
|
||||||
|
"deployments_url": "https://api.github.com/repos/docker/test-docker-action/deployments",
|
||||||
|
"description": "Test \"Docker\" Actions",
|
||||||
|
"disabled": false,
|
||||||
|
"downloads_url": "https://api.github.com/repos/docker/test-docker-action/downloads",
|
||||||
|
"events_url": "https://api.github.com/repos/docker/test-docker-action/events",
|
||||||
|
"fork": false,
|
||||||
|
"forks": 1,
|
||||||
|
"forks_count": 1,
|
||||||
|
"forks_url": "https://api.github.com/repos/docker/test-docker-action/forks",
|
||||||
|
"full_name": "docker/test-docker-action",
|
||||||
|
"git_commits_url": "https://api.github.com/repos/docker/test-docker-action/git/commits{/sha}",
|
||||||
|
"git_refs_url": "https://api.github.com/repos/docker/test-docker-action/git/refs{/sha}",
|
||||||
|
"git_tags_url": "https://api.github.com/repos/docker/test-docker-action/git/tags{/sha}",
|
||||||
|
"git_url": "git://github.com/docker/test-docker-action.git",
|
||||||
|
"has_downloads": true,
|
||||||
|
"has_issues": true,
|
||||||
|
"has_pages": false,
|
||||||
|
"has_projects": true,
|
||||||
|
"has_wiki": true,
|
||||||
|
"homepage": "",
|
||||||
|
"hooks_url": "https://api.github.com/repos/docker/test-docker-action/hooks",
|
||||||
|
"html_url": "https://github.com/docker/test-docker-action",
|
||||||
|
"id": 285789493,
|
||||||
|
"is_template": false,
|
||||||
|
"issue_comment_url": "https://api.github.com/repos/docker/test-docker-action/issues/comments{/number}",
|
||||||
|
"issue_events_url": "https://api.github.com/repos/docker/test-docker-action/issues/events{/number}",
|
||||||
|
"issues_url": "https://api.github.com/repos/docker/test-docker-action/issues{/number}",
|
||||||
|
"keys_url": "https://api.github.com/repos/docker/test-docker-action/keys{/key_id}",
|
||||||
|
"labels_url": "https://api.github.com/repos/docker/test-docker-action/labels{/name}",
|
||||||
|
"language": "JavaScript",
|
||||||
|
"languages_url": "https://api.github.com/repos/docker/test-docker-action/languages",
|
||||||
|
"license": {
|
||||||
|
"key": "mit",
|
||||||
|
"name": "MIT License",
|
||||||
|
"node_id": "MDc6TGljZW5zZTEz",
|
||||||
|
"spdx_id": "MIT",
|
||||||
|
"url": "https://api.github.com/licenses/mit"
|
||||||
|
},
|
||||||
|
"master_branch": "master",
|
||||||
|
"merges_url": "https://api.github.com/repos/docker/test-docker-action/merges",
|
||||||
|
"milestones_url": "https://api.github.com/repos/docker/test-docker-action/milestones{/number}",
|
||||||
|
"mirror_url": null,
|
||||||
|
"name": "test-docker-action",
|
||||||
|
"node_id": "MDEwOlJlcG9zaXRvcnkyODU3ODk0OTM=",
|
||||||
|
"notifications_url": "https://api.github.com/repos/docker/test-docker-action/notifications{?since,all,participating}",
|
||||||
|
"open_issues": 6,
|
||||||
|
"open_issues_count": 6,
|
||||||
|
"organization": "docker",
|
||||||
|
"owner": {
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/5429470?v=4",
|
||||||
|
"email": "info@docker.com",
|
||||||
|
"events_url": "https://api.github.com/users/docker/events{/privacy}",
|
||||||
|
"followers_url": "https://api.github.com/users/docker/followers",
|
||||||
|
"following_url": "https://api.github.com/users/docker/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/docker/gists{/gist_id}",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"html_url": "https://github.com/docker",
|
||||||
|
"id": 5429470,
|
||||||
|
"login": "docker",
|
||||||
|
"name": "docker",
|
||||||
|
"node_id": "MDEyOk9yZ2FuaXphdGlvbjU0Mjk0NzA=",
|
||||||
|
"organizations_url": "https://api.github.com/users/docker/orgs",
|
||||||
|
"received_events_url": "https://api.github.com/users/docker/received_events",
|
||||||
|
"repos_url": "https://api.github.com/users/docker/repos",
|
||||||
|
"site_admin": false,
|
||||||
|
"starred_url": "https://api.github.com/users/docker/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/docker/subscriptions",
|
||||||
|
"type": "Organization",
|
||||||
|
"url": "https://api.github.com/users/docker"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"pulls_url": "https://api.github.com/repos/docker/test-docker-action/pulls{/number}",
|
||||||
|
"pushed_at": 1650360446,
|
||||||
|
"releases_url": "https://api.github.com/repos/docker/test-docker-action/releases{/id}",
|
||||||
|
"size": 796,
|
||||||
|
"ssh_url": "git@github.com:docker/test-docker-action.git",
|
||||||
|
"stargazers": 0,
|
||||||
|
"stargazers_count": 0,
|
||||||
|
"stargazers_url": "https://api.github.com/repos/docker/test-docker-action/stargazers",
|
||||||
|
"statuses_url": "https://api.github.com/repos/docker/test-docker-action/statuses/{sha}",
|
||||||
|
"subscribers_url": "https://api.github.com/repos/docker/test-docker-action/subscribers",
|
||||||
|
"subscription_url": "https://api.github.com/repos/docker/test-docker-action/subscription",
|
||||||
|
"svn_url": "https://github.com/docker/test-docker-action",
|
||||||
|
"tags_url": "https://api.github.com/repos/docker/test-docker-action/tags",
|
||||||
|
"teams_url": "https://api.github.com/repos/docker/test-docker-action/teams",
|
||||||
|
"topics": [],
|
||||||
|
"trees_url": "https://api.github.com/repos/docker/test-docker-action/git/trees{/sha}",
|
||||||
|
"updated_at": "2022-04-19T09:05:09Z",
|
||||||
|
"url": "https://github.com/docker/test-docker-action",
|
||||||
|
"visibility": "private",
|
||||||
|
"watchers": 0,
|
||||||
|
"watchers_count": 0
|
||||||
|
},
|
||||||
|
"sender": {
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/1951866?v=4",
|
||||||
|
"events_url": "https://api.github.com/users/crazy-max/events{/privacy}",
|
||||||
|
"followers_url": "https://api.github.com/users/crazy-max/followers",
|
||||||
|
"following_url": "https://api.github.com/users/crazy-max/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/crazy-max/gists{/gist_id}",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"html_url": "https://github.com/crazy-max",
|
||||||
|
"id": 1951866,
|
||||||
|
"login": "crazy-max",
|
||||||
|
"node_id": "MDQ6VXNlcjE5NTE4NjY=",
|
||||||
|
"organizations_url": "https://api.github.com/users/crazy-max/orgs",
|
||||||
|
"received_events_url": "https://api.github.com/users/crazy-max/received_events",
|
||||||
|
"repos_url": "https://api.github.com/users/crazy-max/repos",
|
||||||
|
"site_admin": false,
|
||||||
|
"starred_url": "https://api.github.com/users/crazy-max/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/crazy-max/subscriptions",
|
||||||
|
"type": "User",
|
||||||
|
"url": "https://api.github.com/users/crazy-max"
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,41 @@
|
|||||||
import {describe, expect, jest, it} from '@jest/globals';
|
import {describe, expect, jest, it} from '@jest/globals';
|
||||||
import * as github from '../src/github';
|
import {Context} from '@actions/github/lib/context';
|
||||||
|
|
||||||
|
import {GitHub, Payload, ReposGetResponseData} from '../src/github';
|
||||||
|
|
||||||
|
jest.spyOn(GitHub.prototype, 'context').mockImplementation((): Context => {
|
||||||
|
return new Context();
|
||||||
|
});
|
||||||
|
|
||||||
import * as repoFixture from './fixtures/repo.json';
|
import * as repoFixture from './fixtures/repo.json';
|
||||||
jest.spyOn(github, 'repo').mockImplementation((): Promise<github.ReposGetResponseData> => {
|
jest.spyOn(GitHub.prototype, 'repo').mockImplementation((): Promise<ReposGetResponseData> => {
|
||||||
return <Promise<github.ReposGetResponseData>>(repoFixture as unknown);
|
return <Promise<ReposGetResponseData>>(repoFixture as unknown);
|
||||||
|
});
|
||||||
|
|
||||||
|
import * as payloadFixture from './fixtures/github-payload.json';
|
||||||
|
jest.spyOn(GitHub.prototype as any, 'payload').mockImplementation((): Promise<Payload> => {
|
||||||
|
return <Promise<Payload>>(payloadFixture as unknown);
|
||||||
|
});
|
||||||
|
jest.spyOn(GitHub.prototype as any, 'ref').mockImplementation((): string => {
|
||||||
|
return 'refs/heads/master';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('gitContext', () => {
|
||||||
|
it('returns refs/heads/master', async () => {
|
||||||
|
expect(GitHub.getInstance().gitContext()).toEqual('https://github.com/docker/test-docker-action.git#refs/heads/master');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('repo', () => {
|
describe('repo', () => {
|
||||||
it('returns GitHub repository', async () => {
|
it('returns GitHub repository', async () => {
|
||||||
const repo = await github.repo(process.env.GITHUB_TOKEN || '');
|
const repo = await GitHub.getInstance().repo(process.env.GITHUB_TOKEN || '');
|
||||||
expect(repo.name).not.toBeNull();
|
expect(repo.name).toEqual('Hello-World');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fromPayload', () => {
|
||||||
|
it('returns repository name from payload', async () => {
|
||||||
|
const repoName = await GitHub.getInstance().fromPayload('repository.name');
|
||||||
|
expect(repoName).toEqual('test-docker-action');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,76 +1,61 @@
|
|||||||
import {describe, expect, it, jest, test} from '@jest/globals';
|
import {describe, expect, it, test} from '@jest/globals';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as util from '../src/util';
|
|
||||||
|
|
||||||
jest.spyOn(util, 'defaultContext').mockImplementation((): string => {
|
import {Util} from '../src/util';
|
||||||
return 'https://github.com/docker/actions-toolkit.git#refs/heads/test-jest';
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.spyOn(util, 'tmpDir').mockImplementation((): string => {
|
|
||||||
const tmpDir = path.join('/tmp/.docker-build-push-jest').split(path.sep).join(path.posix.sep);
|
|
||||||
if (!fs.existsSync(tmpDir)) {
|
|
||||||
fs.mkdirSync(tmpDir, {recursive: true});
|
|
||||||
}
|
|
||||||
return tmpDir;
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.spyOn(util, 'tmpNameSync').mockImplementation((): string => {
|
|
||||||
return path.join('/tmp/.docker-build-push-jest', '.tmpname-jest').split(path.sep).join(path.posix.sep);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getInputList', () => {
|
describe('getInputList', () => {
|
||||||
it('single line correctly', async () => {
|
it('single line correctly', async () => {
|
||||||
await setInput('foo', 'bar');
|
await setInput('foo', 'bar');
|
||||||
const res = util.getInputList('foo');
|
const res = Util.getInputList('foo');
|
||||||
expect(res).toEqual(['bar']);
|
expect(res).toEqual(['bar']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('multiline correctly', async () => {
|
it('multiline correctly', async () => {
|
||||||
setInput('foo', 'bar\nbaz');
|
setInput('foo', 'bar\nbaz');
|
||||||
const res = util.getInputList('foo');
|
const res = Util.getInputList('foo');
|
||||||
expect(res).toEqual(['bar', 'baz']);
|
expect(res).toEqual(['bar', 'baz']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('empty lines correctly', async () => {
|
it('empty lines correctly', async () => {
|
||||||
setInput('foo', 'bar\n\nbaz');
|
setInput('foo', 'bar\n\nbaz');
|
||||||
const res = util.getInputList('foo');
|
const res = Util.getInputList('foo');
|
||||||
expect(res).toEqual(['bar', 'baz']);
|
expect(res).toEqual(['bar', 'baz']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('comma correctly', async () => {
|
it('comma correctly', async () => {
|
||||||
setInput('foo', 'bar,baz');
|
setInput('foo', 'bar,baz');
|
||||||
const res = util.getInputList('foo');
|
const res = Util.getInputList('foo');
|
||||||
expect(res).toEqual(['bar', 'baz']);
|
expect(res).toEqual(['bar', 'baz']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('empty result correctly', async () => {
|
it('empty result correctly', async () => {
|
||||||
setInput('foo', 'bar,baz,');
|
setInput('foo', 'bar,baz,');
|
||||||
const res = util.getInputList('foo');
|
const res = Util.getInputList('foo');
|
||||||
expect(res).toEqual(['bar', 'baz']);
|
expect(res).toEqual(['bar', 'baz']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('different new lines correctly', async () => {
|
it('different new lines correctly', async () => {
|
||||||
setInput('foo', 'bar\r\nbaz');
|
setInput('foo', 'bar\r\nbaz');
|
||||||
const res = util.getInputList('foo');
|
const res = Util.getInputList('foo');
|
||||||
expect(res).toEqual(['bar', 'baz']);
|
expect(res).toEqual(['bar', 'baz']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('different new lines and comma correctly', async () => {
|
it('different new lines and comma correctly', async () => {
|
||||||
setInput('foo', 'bar\r\nbaz,bat');
|
setInput('foo', 'bar\r\nbaz,bat');
|
||||||
const res = util.getInputList('foo');
|
const res = Util.getInputList('foo');
|
||||||
expect(res).toEqual(['bar', 'baz', 'bat']);
|
expect(res).toEqual(['bar', 'baz', 'bat']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('multiline and ignoring comma correctly', async () => {
|
it('multiline and ignoring comma correctly', async () => {
|
||||||
setInput('cache-from', 'user/app:cache\ntype=local,src=path/to/dir');
|
setInput('cache-from', 'user/app:cache\ntype=local,src=path/to/dir');
|
||||||
const res = util.getInputList('cache-from', true);
|
const res = Util.getInputList('cache-from', true);
|
||||||
expect(res).toEqual(['user/app:cache', 'type=local,src=path/to/dir']);
|
expect(res).toEqual(['user/app:cache', 'type=local,src=path/to/dir']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('different new lines and ignoring comma correctly', async () => {
|
it('different new lines and ignoring comma correctly', async () => {
|
||||||
setInput('cache-from', 'user/app:cache\r\ntype=local,src=path/to/dir');
|
setInput('cache-from', 'user/app:cache\r\ntype=local,src=path/to/dir');
|
||||||
const res = util.getInputList('cache-from', true);
|
const res = Util.getInputList('cache-from', true);
|
||||||
expect(res).toEqual(['user/app:cache', 'type=local,src=path/to/dir']);
|
expect(res).toEqual(['user/app:cache', 'type=local,src=path/to/dir']);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -83,7 +68,7 @@ bbbbbbb
|
|||||||
ccccccccc"
|
ccccccccc"
|
||||||
FOO=bar`
|
FOO=bar`
|
||||||
);
|
);
|
||||||
const res = util.getInputList('secrets', true);
|
const res = Util.getInputList('secrets', true);
|
||||||
expect(res).toEqual([
|
expect(res).toEqual([
|
||||||
'GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789',
|
'GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789',
|
||||||
`MYSECRET=aaaaaaaa
|
`MYSECRET=aaaaaaaa
|
||||||
@ -106,7 +91,7 @@ FOO=bar
|
|||||||
bbbb
|
bbbb
|
||||||
ccc"`
|
ccc"`
|
||||||
);
|
);
|
||||||
const res = util.getInputList('secrets', true);
|
const res = Util.getInputList('secrets', true);
|
||||||
expect(res).toEqual([
|
expect(res).toEqual([
|
||||||
'GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789',
|
'GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789',
|
||||||
`MYSECRET=aaaaaaaa
|
`MYSECRET=aaaaaaaa
|
||||||
@ -129,7 +114,7 @@ bbbbbbb
|
|||||||
ccccccccc
|
ccccccccc
|
||||||
FOO=bar`
|
FOO=bar`
|
||||||
);
|
);
|
||||||
const res = util.getInputList('secrets', true);
|
const res = Util.getInputList('secrets', true);
|
||||||
expect(res).toEqual(['GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789', 'MYSECRET=aaaaaaaa', 'bbbbbbb', 'ccccccccc', 'FOO=bar']);
|
expect(res).toEqual(['GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789', 'MYSECRET=aaaaaaaa', 'bbbbbbb', 'ccccccccc', 'FOO=bar']);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -140,7 +125,7 @@ FOO=bar`
|
|||||||
`"GPG_KEY=${pgp}"
|
`"GPG_KEY=${pgp}"
|
||||||
FOO=bar`
|
FOO=bar`
|
||||||
);
|
);
|
||||||
const res = util.getInputList('secrets', true);
|
const res = Util.getInputList('secrets', true);
|
||||||
expect(res).toEqual([`GPG_KEY=${pgp}`, 'FOO=bar']);
|
expect(res).toEqual([`GPG_KEY=${pgp}`, 'FOO=bar']);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -153,7 +138,7 @@ bbbb""bbb
|
|||||||
ccccccccc"
|
ccccccccc"
|
||||||
FOO=bar`
|
FOO=bar`
|
||||||
);
|
);
|
||||||
const res = util.getInputList('secrets', true);
|
const res = Util.getInputList('secrets', true);
|
||||||
expect(res).toEqual([
|
expect(res).toEqual([
|
||||||
'GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789',
|
'GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789',
|
||||||
`MYSECRET=aaaaaaaa
|
`MYSECRET=aaaaaaaa
|
||||||
@ -169,7 +154,7 @@ describe('asyncForEach', () => {
|
|||||||
const testValues = [1, 2, 3, 4, 5];
|
const testValues = [1, 2, 3, 4, 5];
|
||||||
const results: number[] = [];
|
const results: number[] = [];
|
||||||
|
|
||||||
await util.asyncForEach(testValues, async value => {
|
await Util.asyncForEach(testValues, async value => {
|
||||||
results.push(value);
|
results.push(value);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -183,7 +168,7 @@ describe('isValidUrl', () => {
|
|||||||
['https://github.com/docker/buildx.git#refs/pull/648/head', true],
|
['https://github.com/docker/buildx.git#refs/pull/648/head', true],
|
||||||
['v0.4.1', false]
|
['v0.4.1', false]
|
||||||
])('given %p', async (url, expected) => {
|
])('given %p', async (url, expected) => {
|
||||||
expect(util.isValidUrl(url)).toEqual(expected);
|
expect(Util.isValidUrl(url)).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
process.env = Object.assign({}, process.env, {
|
process.env = Object.assign({}, process.env, {
|
||||||
|
GITHUB_REPOSITORY: 'docker/test-docker-action',
|
||||||
RUNNER_TEMP: '/tmp/github_runner',
|
RUNNER_TEMP: '/tmp/github_runner',
|
||||||
RUNNER_TOOL_CACHE: '/tmp/github_tool_cache'
|
RUNNER_TOOL_CACHE: '/tmp/github_tool_cache'
|
||||||
}) as {
|
}) as {
|
||||||
|
@ -58,6 +58,7 @@
|
|||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"jest": "^27.2.5",
|
"jest": "^27.2.5",
|
||||||
"prettier": "^2.3.1",
|
"prettier": "^2.3.1",
|
||||||
|
"rimraf": "^4.1.1",
|
||||||
"ts-jest": "^27.1.2",
|
"ts-jest": "^27.1.2",
|
||||||
"ts-node": "^10.7.0",
|
"ts-node": "^10.7.0",
|
||||||
"typescript": "^4.4.4"
|
"typescript": "^4.4.4"
|
||||||
|
124
src/builder.ts
Normal file
124
src/builder.ts
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import * as exec from '@actions/exec';
|
||||||
|
|
||||||
|
import {Buildx} from './buildx';
|
||||||
|
|
||||||
|
export interface BuilderInfo {
|
||||||
|
name?: string;
|
||||||
|
driver?: string;
|
||||||
|
lastActivity?: Date;
|
||||||
|
nodes: NodeInfo[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NodeInfo {
|
||||||
|
name?: string;
|
||||||
|
endpoint?: string;
|
||||||
|
driverOpts?: Array<string>;
|
||||||
|
status?: string;
|
||||||
|
buildkitdFlags?: string;
|
||||||
|
buildkitVersion?: string;
|
||||||
|
platforms?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BuilderOpts {
|
||||||
|
buildx?: Buildx;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Builder {
|
||||||
|
private buildx: Buildx;
|
||||||
|
|
||||||
|
constructor(opts?: BuilderOpts) {
|
||||||
|
this.buildx = opts?.buildx || new Buildx();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async inspect(name: string): Promise<BuilderInfo> {
|
||||||
|
const cmd = this.buildx.getCommand(['inspect', name]);
|
||||||
|
return await exec
|
||||||
|
.getExecOutput(cmd.command, cmd.args, {
|
||||||
|
ignoreReturnCode: true,
|
||||||
|
silent: true
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (res.stderr.length > 0 && res.exitCode != 0) {
|
||||||
|
throw new Error(res.stderr.trim());
|
||||||
|
}
|
||||||
|
return Builder.parseInspect(res.stdout);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static parseInspect(data: string): BuilderInfo {
|
||||||
|
const builder: BuilderInfo = {
|
||||||
|
nodes: []
|
||||||
|
};
|
||||||
|
let node: NodeInfo = {};
|
||||||
|
for (const line of data.trim().split(`\n`)) {
|
||||||
|
const [key, ...rest] = line.split(':');
|
||||||
|
const value = rest.map(v => v.trim()).join(':');
|
||||||
|
if (key.length == 0 || value.length == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch (key.toLowerCase()) {
|
||||||
|
case 'name': {
|
||||||
|
if (builder.name == undefined) {
|
||||||
|
builder.name = value;
|
||||||
|
} else {
|
||||||
|
if (Object.keys(node).length > 0) {
|
||||||
|
builder.nodes.push(node);
|
||||||
|
node = {};
|
||||||
|
}
|
||||||
|
node.name = value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'driver': {
|
||||||
|
builder.driver = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'last activity': {
|
||||||
|
builder.lastActivity = new Date(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'endpoint': {
|
||||||
|
node.endpoint = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'driver options': {
|
||||||
|
node.driverOpts = (value.match(/(\w+)="([^"]*)"/g) || []).map(v => v.replace(/^(.*)="(.*)"$/g, '$1=$2'));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'status': {
|
||||||
|
node.status = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'flags': {
|
||||||
|
node.buildkitdFlags = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'buildkit': {
|
||||||
|
node.buildkitVersion = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'platforms': {
|
||||||
|
let platforms: Array<string> = [];
|
||||||
|
// if a preferred platform is being set then use only these
|
||||||
|
// https://docs.docker.com/engine/reference/commandline/buildx_inspect/#get-information-about-a-builder-instance
|
||||||
|
if (value.includes('*')) {
|
||||||
|
for (const platform of value.split(', ')) {
|
||||||
|
if (platform.includes('*')) {
|
||||||
|
platforms.push(platform.replace('*', ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// otherwise set all platforms available
|
||||||
|
platforms = value.split(', ');
|
||||||
|
}
|
||||||
|
node.platforms = platforms.join(',');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Object.keys(node).length > 0) {
|
||||||
|
builder.nodes.push(node);
|
||||||
|
}
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
193
src/buildkit.ts
193
src/buildkit.ts
@ -1,96 +1,125 @@
|
|||||||
import * as fs from 'fs';
|
import fs from 'fs';
|
||||||
import * as semver from 'semver';
|
import os from 'os';
|
||||||
|
import path from 'path';
|
||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import * as exec from '@actions/exec';
|
import * as exec from '@actions/exec';
|
||||||
import * as buildx from './buildx';
|
import * as semver from 'semver';
|
||||||
import * as util from './util';
|
import * as tmp from 'tmp';
|
||||||
|
|
||||||
export async function getConfigInline(s: string): Promise<string> {
|
import {Buildx} from './buildx';
|
||||||
return getConfig(s, false);
|
import {Builder, BuilderInfo} from './builder';
|
||||||
|
|
||||||
|
export interface BuildKitOpts {
|
||||||
|
buildx?: Buildx;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getConfigFile(s: string): Promise<string> {
|
export class BuildKit {
|
||||||
return getConfig(s, true);
|
private buildx: Buildx;
|
||||||
}
|
private containerNamePrefix = 'buildx_buildkit_';
|
||||||
|
private tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-actions-toolkit-')).split(path.sep).join(path.posix.sep);
|
||||||
|
|
||||||
export async function getConfig(s: string, file: boolean): Promise<string> {
|
constructor(opts?: BuildKitOpts) {
|
||||||
if (file) {
|
this.buildx = opts?.buildx || new Buildx();
|
||||||
if (!fs.existsSync(s)) {
|
}
|
||||||
throw new Error(`config file ${s} not found`);
|
|
||||||
|
private async getBuilderInfo(name: string): Promise<BuilderInfo> {
|
||||||
|
const builder = new Builder({buildx: this.buildx});
|
||||||
|
return builder.inspect(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getVersion(builderName: string): Promise<string | undefined> {
|
||||||
|
const builderInfo = await this.getBuilderInfo(builderName);
|
||||||
|
if (builderInfo.nodes.length == 0) {
|
||||||
|
// a builder always have on node, should not happen.
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
s = fs.readFileSync(s, {encoding: 'utf-8'});
|
// TODO: get version for all nodes
|
||||||
}
|
const node = builderInfo.nodes[0];
|
||||||
const configFile = util.tmpNameSync({
|
if (!node.buildkitVersion && node.name) {
|
||||||
tmpdir: util.tmpDir()
|
|
||||||
});
|
|
||||||
fs.writeFileSync(configFile, s);
|
|
||||||
return configFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getVersion(builderName: string, standalone?: boolean): Promise<string | undefined> {
|
|
||||||
const builder = await buildx.inspect(builderName, standalone);
|
|
||||||
if (builder.nodes.length == 0) {
|
|
||||||
// a builder always have on node, should not happen.
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
// TODO: get version for all nodes
|
|
||||||
const node = builder.nodes[0];
|
|
||||||
if (!node.buildkit && node.name) {
|
|
||||||
try {
|
|
||||||
return await getVersionWithinImage(node.name);
|
|
||||||
} catch (e) {
|
|
||||||
core.warning(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return node.buildkit;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getVersionWithinImage(nodeName: string): Promise<string> {
|
|
||||||
return exec
|
|
||||||
.getExecOutput(`docker`, ['inspect', '--format', '{{.Config.Image}}', `buildx_buildkit_${nodeName}`], {
|
|
||||||
ignoreReturnCode: true,
|
|
||||||
silent: true
|
|
||||||
})
|
|
||||||
.then(bkitimage => {
|
|
||||||
if (bkitimage.exitCode == 0 && bkitimage.stdout.length > 0) {
|
|
||||||
return exec
|
|
||||||
.getExecOutput(`docker`, ['run', '--rm', bkitimage.stdout.trim(), '--version'], {
|
|
||||||
ignoreReturnCode: true,
|
|
||||||
silent: true
|
|
||||||
})
|
|
||||||
.then(bkitversion => {
|
|
||||||
if (bkitversion.exitCode == 0 && bkitversion.stdout.length > 0) {
|
|
||||||
return `${bkitimage.stdout.trim()} => ${bkitversion.stdout.trim()}`;
|
|
||||||
} else if (bkitversion.stderr.length > 0) {
|
|
||||||
throw new Error(bkitimage.stderr.trim());
|
|
||||||
}
|
|
||||||
return bkitversion.stdout.trim();
|
|
||||||
});
|
|
||||||
} else if (bkitimage.stderr.length > 0) {
|
|
||||||
throw new Error(bkitimage.stderr.trim());
|
|
||||||
}
|
|
||||||
return bkitimage.stdout.trim();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function satisfies(builderName: string, range: string, standalone?: boolean): Promise<boolean> {
|
|
||||||
const builder = await buildx.inspect(builderName, standalone);
|
|
||||||
for (const node of builder.nodes) {
|
|
||||||
let bkversion = node.buildkit;
|
|
||||||
if (!bkversion) {
|
|
||||||
try {
|
try {
|
||||||
bkversion = await getVersionWithinImage(node.name || '');
|
return await this.getVersionWithinImage(node.name);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
core.warning(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node.buildkitVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getVersionWithinImage(nodeName: string): Promise<string> {
|
||||||
|
return exec
|
||||||
|
.getExecOutput(`docker`, ['inspect', '--format', '{{.Config.Image}}', `${this.containerNamePrefix}${nodeName}`], {
|
||||||
|
ignoreReturnCode: true,
|
||||||
|
silent: true
|
||||||
|
})
|
||||||
|
.then(bkitimage => {
|
||||||
|
if (bkitimage.exitCode == 0 && bkitimage.stdout.length > 0) {
|
||||||
|
return exec
|
||||||
|
.getExecOutput(`docker`, ['run', '--rm', bkitimage.stdout.trim(), '--version'], {
|
||||||
|
ignoreReturnCode: true,
|
||||||
|
silent: true
|
||||||
|
})
|
||||||
|
.then(bkitversion => {
|
||||||
|
if (bkitversion.exitCode == 0 && bkitversion.stdout.length > 0) {
|
||||||
|
return `${bkitimage.stdout.trim()} => ${bkitversion.stdout.trim()}`;
|
||||||
|
} else if (bkitversion.stderr.length > 0) {
|
||||||
|
throw new Error(bkitimage.stderr.trim());
|
||||||
|
}
|
||||||
|
return bkitversion.stdout.trim();
|
||||||
|
});
|
||||||
|
} else if (bkitimage.stderr.length > 0) {
|
||||||
|
throw new Error(bkitimage.stderr.trim());
|
||||||
|
}
|
||||||
|
return bkitimage.stdout.trim();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async versionSatisfies(builderName: string, range: string): Promise<boolean> {
|
||||||
|
const builderInfo = await this.getBuilderInfo(builderName);
|
||||||
|
for (const node of builderInfo.nodes) {
|
||||||
|
let bkversion = node.buildkitVersion;
|
||||||
|
if (!bkversion) {
|
||||||
|
try {
|
||||||
|
bkversion = await this.getVersionWithinImage(node.name || '');
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// BuildKit version reported by moby is in the format of `v0.11.0-moby`
|
||||||
|
if (builderInfo.driver == 'docker' && !bkversion.endsWith('-moby')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!semver.satisfies(bkversion.replace(/-moby$/, ''), range)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// BuildKit version reported by moby is in the format of `v0.11.0-moby`
|
return true;
|
||||||
if (builder.driver == 'docker' && !bkversion.endsWith('-moby')) {
|
}
|
||||||
return false;
|
|
||||||
}
|
public generateConfigInline(s: string): string {
|
||||||
if (!semver.satisfies(bkversion.replace(/-moby$/, ''), range)) {
|
return this.generateConfig(s, false);
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
public generateConfigFile(s: string): string {
|
||||||
|
return this.generateConfig(s, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateConfig(s: string, file: boolean): string {
|
||||||
|
if (file) {
|
||||||
|
if (!fs.existsSync(s)) {
|
||||||
|
throw new Error(`config file ${s} not found`);
|
||||||
|
}
|
||||||
|
s = fs.readFileSync(s, {encoding: 'utf-8'});
|
||||||
|
}
|
||||||
|
const configFile = this.tmpName({tmpdir: this.tmpDir()});
|
||||||
|
fs.writeFileSync(configFile, s);
|
||||||
|
return configFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private tmpDir() {
|
||||||
|
return this.tmpdir;
|
||||||
|
}
|
||||||
|
|
||||||
|
private tmpName(options?: tmp.TmpNameOptions): string {
|
||||||
|
return tmp.tmpNameSync(options);
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
419
src/buildx.ts
419
src/buildx.ts
@ -1,281 +1,188 @@
|
|||||||
import {parse} from 'csv-parse/sync';
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import * as semver from 'semver';
|
|
||||||
import * as exec from '@actions/exec';
|
import * as exec from '@actions/exec';
|
||||||
import * as util from './util';
|
import {parse} from 'csv-parse/sync';
|
||||||
|
import * as semver from 'semver';
|
||||||
|
import * as tmp from 'tmp';
|
||||||
|
|
||||||
export type Builder = {
|
export interface BuildxOpts {
|
||||||
name?: string;
|
standalone?: boolean;
|
||||||
driver?: string;
|
|
||||||
'last-activity'?: Date;
|
|
||||||
nodes: Node[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Node = {
|
|
||||||
name?: string;
|
|
||||||
endpoint?: string;
|
|
||||||
'driver-opts'?: Array<string>;
|
|
||||||
status?: string;
|
|
||||||
'buildkitd-flags'?: string;
|
|
||||||
buildkit?: string;
|
|
||||||
platforms?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function getImageIDFile(): Promise<string> {
|
|
||||||
return path.join(util.tmpDir(), 'iidfile').split(path.sep).join(path.posix.sep);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getImageID(): Promise<string | undefined> {
|
export class Buildx {
|
||||||
const iidFile = await getImageIDFile();
|
private standalone: boolean;
|
||||||
if (!fs.existsSync(iidFile)) {
|
private version: Promise<string>;
|
||||||
return undefined;
|
private tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-actions-toolkit-')).split(path.sep).join(path.posix.sep);
|
||||||
}
|
|
||||||
return fs.readFileSync(iidFile, {encoding: 'utf-8'}).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getMetadataFile(): Promise<string> {
|
constructor(opts?: BuildxOpts) {
|
||||||
return path.join(util.tmpDir(), 'metadata-file').split(path.sep).join(path.posix.sep);
|
this.standalone = opts?.standalone ?? false;
|
||||||
}
|
this.version = this.getVersion();
|
||||||
|
|
||||||
export async function getMetadata(): Promise<string | undefined> {
|
|
||||||
const metadataFile = await getMetadataFile();
|
|
||||||
if (!fs.existsSync(metadataFile)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const content = fs.readFileSync(metadataFile, {encoding: 'utf-8'}).trim();
|
|
||||||
if (content === 'null') {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getDigest(metadata: string | undefined): Promise<string | undefined> {
|
|
||||||
if (metadata === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const metadataJSON = JSON.parse(metadata);
|
|
||||||
if (metadataJSON['containerimage.digest']) {
|
|
||||||
return metadataJSON['containerimage.digest'];
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getSecretString(kvp: string): Promise<string> {
|
|
||||||
return getSecret(kvp, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getSecretFile(kvp: string): Promise<string> {
|
|
||||||
return getSecret(kvp, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getSecret(kvp: string, file: boolean): Promise<string> {
|
|
||||||
const delimiterIndex = kvp.indexOf('=');
|
|
||||||
const key = kvp.substring(0, delimiterIndex);
|
|
||||||
let value = kvp.substring(delimiterIndex + 1);
|
|
||||||
if (key.length == 0 || value.length == 0) {
|
|
||||||
throw new Error(`${kvp} is not a valid secret`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file) {
|
public getCommand(args: Array<string>) {
|
||||||
if (!fs.existsSync(value)) {
|
return {
|
||||||
throw new Error(`secret file ${value} not found`);
|
command: this.standalone ? 'buildx' : 'docker',
|
||||||
|
args: this.standalone ? args : ['buildx', ...args]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async isAvailable(): Promise<boolean> {
|
||||||
|
const cmd = this.getCommand([]);
|
||||||
|
return await exec
|
||||||
|
.getExecOutput(cmd.command, cmd.args, {
|
||||||
|
ignoreReturnCode: true,
|
||||||
|
silent: true
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (res.stderr.length > 0 && res.exitCode != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return res.exitCode == 0;
|
||||||
|
})
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
.catch(error => {
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getVersion(): Promise<string> {
|
||||||
|
const cmd = this.getCommand(['version']);
|
||||||
|
return await exec
|
||||||
|
.getExecOutput(cmd.command, cmd.args, {
|
||||||
|
ignoreReturnCode: true,
|
||||||
|
silent: true
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (res.stderr.length > 0 && res.exitCode != 0) {
|
||||||
|
throw new Error(res.stderr.trim());
|
||||||
|
}
|
||||||
|
return Buildx.parseVersion(res.stdout.trim());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static parseVersion(stdout: string): string {
|
||||||
|
const matches = /\sv?([0-9a-f]{7}|[0-9.]+)/.exec(stdout);
|
||||||
|
if (!matches) {
|
||||||
|
throw new Error(`Cannot parse buildx version`);
|
||||||
}
|
}
|
||||||
value = fs.readFileSync(value, {encoding: 'utf-8'});
|
return matches[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
const secretFile = util.tmpNameSync({
|
public static versionSatisfies(version: string, range: string): boolean {
|
||||||
tmpdir: util.tmpDir()
|
return semver.satisfies(version, range) || /^[0-9a-f]{7}$/.exec(version) !== null;
|
||||||
});
|
}
|
||||||
fs.writeFileSync(secretFile, value);
|
|
||||||
|
|
||||||
return `id=${key},src=${secretFile}`;
|
public getBuildImageIDFilePath(): string {
|
||||||
}
|
return path.join(this.tmpDir(), 'iidfile').split(path.sep).join(path.posix.sep);
|
||||||
|
}
|
||||||
|
|
||||||
export function hasLocalExporter(outputs: string[]): boolean {
|
public getBuildMetadataFilePath(): string {
|
||||||
return hasExporterType('local', outputs);
|
return path.join(this.tmpDir(), 'metadata-file').split(path.sep).join(path.posix.sep);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasTarExporter(outputs: string[]): boolean {
|
public getBuildImageID(): string | undefined {
|
||||||
return hasExporterType('tar', outputs);
|
const iidFile = this.getBuildImageIDFilePath();
|
||||||
}
|
if (!fs.existsSync(iidFile)) {
|
||||||
|
return undefined;
|
||||||
export function hasLocalOrTarExporter(outputs: string[]): boolean {
|
|
||||||
return hasLocalExporter(outputs) || hasTarExporter(outputs);
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasExporterType(name: string, outputs: string[]): boolean {
|
|
||||||
const records = parse(outputs.join(`\n`), {
|
|
||||||
delimiter: ',',
|
|
||||||
trim: true,
|
|
||||||
columns: false,
|
|
||||||
relaxColumnCount: true
|
|
||||||
});
|
|
||||||
for (const record of records) {
|
|
||||||
if (record.length == 1 && !record[0].startsWith('type=')) {
|
|
||||||
// Local if no type is defined
|
|
||||||
// https://github.com/docker/buildx/blob/d2bf42f8b4784d83fde17acb3ed84703ddc2156b/build/output.go#L29-L43
|
|
||||||
return name == 'local';
|
|
||||||
}
|
}
|
||||||
for (const [key, value] of record.map(chunk => chunk.split('=').map(item => item.trim()))) {
|
return fs.readFileSync(iidFile, {encoding: 'utf-8'}).trim();
|
||||||
if (key == 'type' && value == name) {
|
}
|
||||||
|
|
||||||
|
public getBuildMetadata(): string | undefined {
|
||||||
|
const metadataFile = this.getBuildMetadataFilePath();
|
||||||
|
if (!fs.existsSync(metadataFile)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const content = fs.readFileSync(metadataFile, {encoding: 'utf-8'}).trim();
|
||||||
|
if (content === 'null') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDigest(): string | undefined {
|
||||||
|
const metadata = this.getBuildMetadata();
|
||||||
|
if (metadata === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const metadataJSON = JSON.parse(metadata);
|
||||||
|
if (metadataJSON['containerimage.digest']) {
|
||||||
|
return metadataJSON['containerimage.digest'];
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public generateBuildSecretString(kvp: string): string {
|
||||||
|
return this.generateBuildSecret(kvp, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public generateBuildSecretFile(kvp: string): string {
|
||||||
|
return this.generateBuildSecret(kvp, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateBuildSecret(kvp: string, file: boolean): string {
|
||||||
|
const delimiterIndex = kvp.indexOf('=');
|
||||||
|
const key = kvp.substring(0, delimiterIndex);
|
||||||
|
let value = kvp.substring(delimiterIndex + 1);
|
||||||
|
if (key.length == 0 || value.length == 0) {
|
||||||
|
throw new Error(`${kvp} is not a valid secret`);
|
||||||
|
}
|
||||||
|
if (file) {
|
||||||
|
if (!fs.existsSync(value)) {
|
||||||
|
throw new Error(`secret file ${value} not found`);
|
||||||
|
}
|
||||||
|
value = fs.readFileSync(value, {encoding: 'utf-8'});
|
||||||
|
}
|
||||||
|
const secretFile = this.tmpName({tmpdir: this.tmpDir()});
|
||||||
|
fs.writeFileSync(secretFile, value);
|
||||||
|
return `id=${key},src=${secretFile}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static hasLocalExporter(exporters: string[]): boolean {
|
||||||
|
return Buildx.hasExporterType('local', exporters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static hasTarExporter(exporters: string[]): boolean {
|
||||||
|
return Buildx.hasExporterType('tar', exporters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static hasExporterType(name: string, exporters: string[]): boolean {
|
||||||
|
const records = parse(exporters.join(`\n`), {
|
||||||
|
delimiter: ',',
|
||||||
|
trim: true,
|
||||||
|
columns: false,
|
||||||
|
relaxColumnCount: true
|
||||||
|
});
|
||||||
|
for (const record of records) {
|
||||||
|
if (record.length == 1 && !record[0].startsWith('type=')) {
|
||||||
|
// Local if no type is defined
|
||||||
|
// https://github.com/docker/buildx/blob/d2bf42f8b4784d83fde17acb3ed84703ddc2156b/build/output.go#L29-L43
|
||||||
|
return name == 'local';
|
||||||
|
}
|
||||||
|
for (const [key, value] of record.map(chunk => chunk.split('=').map(item => item.trim()))) {
|
||||||
|
if (key == 'type' && value == name) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static hasGitAuthTokenSecret(secrets: string[]): boolean {
|
||||||
|
for (const secret of secrets) {
|
||||||
|
if (secret.startsWith('GIT_AUTH_TOKEN=')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasGitAuthToken(secrets: string[]): boolean {
|
private tmpDir() {
|
||||||
for (const secret of secrets) {
|
return this.tmpdir;
|
||||||
if (secret.startsWith('GIT_AUTH_TOKEN=')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function isAvailable(standalone?: boolean): Promise<boolean> {
|
private tmpName(options?: tmp.TmpNameOptions): string {
|
||||||
const cmd = getCommand([], standalone);
|
return tmp.tmpNameSync(options);
|
||||||
return await exec
|
|
||||||
.getExecOutput(cmd.command, cmd.args, {
|
|
||||||
ignoreReturnCode: true,
|
|
||||||
silent: true
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
if (res.stderr.length > 0 && res.exitCode != 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return res.exitCode == 0;
|
|
||||||
})
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
.catch(error => {
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function inspect(name: string, standalone?: boolean): Promise<Builder> {
|
|
||||||
const cmd = getCommand(['inspect', name], standalone);
|
|
||||||
return await exec
|
|
||||||
.getExecOutput(cmd.command, cmd.args, {
|
|
||||||
ignoreReturnCode: true,
|
|
||||||
silent: true
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
if (res.stderr.length > 0 && res.exitCode != 0) {
|
|
||||||
throw new Error(res.stderr.trim());
|
|
||||||
}
|
|
||||||
return parseInspect(res.stdout);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function parseInspect(data: string): Promise<Builder> {
|
|
||||||
const builder: Builder = {
|
|
||||||
nodes: []
|
|
||||||
};
|
|
||||||
let node: Node = {};
|
|
||||||
for (const line of data.trim().split(`\n`)) {
|
|
||||||
const [key, ...rest] = line.split(':');
|
|
||||||
const value = rest.map(v => v.trim()).join(':');
|
|
||||||
if (key.length == 0 || value.length == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
switch (key.toLowerCase()) {
|
|
||||||
case 'name': {
|
|
||||||
if (builder.name == undefined) {
|
|
||||||
builder.name = value;
|
|
||||||
} else {
|
|
||||||
if (Object.keys(node).length > 0) {
|
|
||||||
builder.nodes.push(node);
|
|
||||||
node = {};
|
|
||||||
}
|
|
||||||
node.name = value;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'driver': {
|
|
||||||
builder.driver = value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'last activity': {
|
|
||||||
builder['last-activity'] = new Date(value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'endpoint': {
|
|
||||||
node.endpoint = value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'driver options': {
|
|
||||||
node['driver-opts'] = (value.match(/(\w+)="([^"]*)"/g) || []).map(v => v.replace(/^(.*)="(.*)"$/g, '$1=$2'));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'status': {
|
|
||||||
node.status = value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'flags': {
|
|
||||||
node['buildkitd-flags'] = value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'buildkit': {
|
|
||||||
node.buildkit = value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'platforms': {
|
|
||||||
let platforms: Array<string> = [];
|
|
||||||
// if a preferred platform is being set then use only these
|
|
||||||
// https://docs.docker.com/engine/reference/commandline/buildx_inspect/#get-information-about-a-builder-instance
|
|
||||||
if (value.includes('*')) {
|
|
||||||
for (const platform of value.split(', ')) {
|
|
||||||
if (platform.includes('*')) {
|
|
||||||
platforms.push(platform.replace('*', ''));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// otherwise set all platforms available
|
|
||||||
platforms = value.split(', ');
|
|
||||||
}
|
|
||||||
node.platforms = platforms.join(',');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (Object.keys(node).length > 0) {
|
|
||||||
builder.nodes.push(node);
|
|
||||||
}
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getVersion(standalone?: boolean): Promise<string> {
|
|
||||||
const cmd = getCommand(['version'], standalone);
|
|
||||||
return await exec
|
|
||||||
.getExecOutput(cmd.command, cmd.args, {
|
|
||||||
ignoreReturnCode: true,
|
|
||||||
silent: true
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
if (res.stderr.length > 0 && res.exitCode != 0) {
|
|
||||||
throw new Error(res.stderr.trim());
|
|
||||||
}
|
|
||||||
return parseVersion(res.stdout.trim());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseVersion(stdout: string): string {
|
|
||||||
const matches = /\sv?([0-9a-f]{7}|[0-9.]+)/.exec(stdout);
|
|
||||||
if (!matches) {
|
|
||||||
throw new Error(`Cannot parse buildx version`);
|
|
||||||
}
|
|
||||||
return matches[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function satisfies(version: string, range: string): boolean {
|
|
||||||
return semver.satisfies(version, range) || /^[0-9a-f]{7}$/.exec(version) !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCommand(args: Array<string>, standalone?: boolean) {
|
|
||||||
return {
|
|
||||||
command: standalone ? 'buildx' : 'docker',
|
|
||||||
args: standalone ? args : ['buildx', ...args]
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
import * as exec from '@actions/exec';
|
import * as exec from '@actions/exec';
|
||||||
|
|
||||||
export async function isAvailable(): Promise<boolean> {
|
export class Docker {
|
||||||
return await exec
|
public static async isAvailable(): Promise<boolean> {
|
||||||
.getExecOutput('docker', undefined, {
|
return 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 => {
|
||||||
|
if (res.stderr.length > 0 && res.exitCode != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return res.exitCode == 0;
|
||||||
|
})
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
.catch(error => {
|
||||||
return false;
|
return false;
|
||||||
}
|
});
|
||||||
return res.exitCode == 0;
|
}
|
||||||
})
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
.catch(error => {
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -2,30 +2,73 @@ import jwt_decode, {JwtPayload} from 'jwt-decode';
|
|||||||
import * as github from '@actions/github';
|
import * as github from '@actions/github';
|
||||||
import {Context} from '@actions/github/lib/context';
|
import {Context} from '@actions/github/lib/context';
|
||||||
import {components as OctoOpenApiTypes} from '@octokit/openapi-types';
|
import {components as OctoOpenApiTypes} from '@octokit/openapi-types';
|
||||||
import * as util from './util';
|
import {WebhookPayload} from '@actions/github/lib/interfaces';
|
||||||
|
|
||||||
|
export type Payload = WebhookPayload;
|
||||||
export type ReposGetResponseData = OctoOpenApiTypes['schemas']['repository'];
|
export type ReposGetResponseData = OctoOpenApiTypes['schemas']['repository'];
|
||||||
|
|
||||||
export function context(): Context {
|
|
||||||
return github.context;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function repo(token: string): Promise<ReposGetResponseData> {
|
|
||||||
return github
|
|
||||||
.getOctokit(token)
|
|
||||||
.rest.repos.get({...github.context.repo})
|
|
||||||
.then(response => response.data as ReposGetResponseData);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Jwt extends JwtPayload {
|
interface Jwt extends JwtPayload {
|
||||||
ac?: string;
|
ac?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const parseRuntimeToken = (token: string): Jwt => {
|
export class GitHub {
|
||||||
return jwt_decode<Jwt>(token);
|
private static instance?: GitHub;
|
||||||
};
|
static getInstance = (): GitHub => (GitHub.instance = GitHub.instance ?? new GitHub());
|
||||||
|
private static _gitContext: string;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
private constructor() {
|
||||||
export function fromPayload(path: string): any {
|
let ref = this.ref();
|
||||||
return util.select(github.context.payload, path);
|
if (github.context.sha && ref && !ref.startsWith('refs/')) {
|
||||||
|
ref = `refs/heads/${this.ref()}`;
|
||||||
|
}
|
||||||
|
if (github.context.sha && !ref.startsWith(`refs/pull/`)) {
|
||||||
|
ref = github.context.sha;
|
||||||
|
}
|
||||||
|
GitHub._gitContext = `${process.env.GITHUB_SERVER_URL || 'https://github.com'}/${github.context.repo.owner}/${github.context.repo.repo}.git#${ref}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public context(): Context {
|
||||||
|
return github.context;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ref(): string {
|
||||||
|
return github.context.ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
public gitContext() {
|
||||||
|
return GitHub._gitContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private payload(): Payload {
|
||||||
|
return github.context.payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public repo(token: string): Promise<ReposGetResponseData> {
|
||||||
|
return github
|
||||||
|
.getOctokit(token)
|
||||||
|
.rest.repos.get({...github.context.repo})
|
||||||
|
.then(response => response.data as ReposGetResponseData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public parseRuntimeToken(): Jwt {
|
||||||
|
return jwt_decode<Jwt>(process.env['ACTIONS_RUNTIME_TOKEN'] || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
public fromPayload(path: string): any {
|
||||||
|
return this.select(this.payload(), path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
private select(obj: any, path: string): any {
|
||||||
|
if (!obj) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const i = path.indexOf('.');
|
||||||
|
if (i < 0) {
|
||||||
|
return obj[path];
|
||||||
|
}
|
||||||
|
const key = path.slice(0, i);
|
||||||
|
return this.select(obj[key], path.slice(i + 1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
115
src/util.ts
115
src/util.ts
@ -1,92 +1,49 @@
|
|||||||
import * as fs from 'fs';
|
|
||||||
import * as os from 'os';
|
|
||||||
import * as path from 'path';
|
|
||||||
import * as tmp from 'tmp';
|
|
||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import * as github from '@actions/github';
|
|
||||||
import {parse} from 'csv-parse/sync';
|
import {parse} from 'csv-parse/sync';
|
||||||
|
|
||||||
let _defaultContext, _tmpDir: string;
|
export class Util {
|
||||||
|
public static getInputList(name: string, ignoreComma?: boolean): string[] {
|
||||||
|
const res: Array<string> = [];
|
||||||
|
|
||||||
export function defaultContext(): string {
|
const items = core.getInput(name);
|
||||||
if (!_defaultContext) {
|
if (items == '') {
|
||||||
let ref = github.context.ref;
|
return res;
|
||||||
if (github.context.sha && ref && !ref.startsWith('refs/')) {
|
|
||||||
ref = `refs/heads/${github.context.ref}`;
|
|
||||||
}
|
}
|
||||||
if (github.context.sha && !ref.startsWith(`refs/pull/`)) {
|
|
||||||
ref = github.context.sha;
|
const records = parse(items, {
|
||||||
|
columns: false,
|
||||||
|
relaxQuotes: true,
|
||||||
|
comment: '#',
|
||||||
|
relaxColumnCount: true,
|
||||||
|
skipEmptyLines: true
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const record of records as Array<string[]>) {
|
||||||
|
if (record.length == 1) {
|
||||||
|
res.push(record[0]);
|
||||||
|
continue;
|
||||||
|
} else if (!ignoreComma) {
|
||||||
|
res.push(...record);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
res.push(record.join(','));
|
||||||
}
|
}
|
||||||
_defaultContext = `${process.env.GITHUB_SERVER_URL || 'https://github.com'}/${github.context.repo.owner}/${github.context.repo.repo}.git#${ref}`;
|
|
||||||
}
|
|
||||||
return _defaultContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function tmpDir(): string {
|
return res.filter(item => item).map(pat => pat.trim());
|
||||||
if (!_tmpDir) {
|
|
||||||
_tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-actions-toolkit-')).split(path.sep).join(path.posix.sep);
|
|
||||||
}
|
|
||||||
return _tmpDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function tmpNameSync(options?: tmp.TmpNameOptions): string {
|
|
||||||
return tmp.tmpNameSync(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
export function select(obj: any, path: string): any {
|
|
||||||
if (!obj) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const i = path.indexOf('.');
|
|
||||||
if (i < 0) {
|
|
||||||
return obj[path];
|
|
||||||
}
|
|
||||||
const key = path.slice(0, i);
|
|
||||||
return select(obj[key], path.slice(i + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getInputList(name: string, ignoreComma?: boolean): string[] {
|
|
||||||
const res: Array<string> = [];
|
|
||||||
|
|
||||||
const items = core.getInput(name);
|
|
||||||
if (items == '') {
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const records = parse(items, {
|
public static async asyncForEach(array, callback) {
|
||||||
columns: false,
|
for (let index = 0; index < array.length; index++) {
|
||||||
relaxQuotes: true,
|
await callback(array[index], index, array);
|
||||||
comment: '#',
|
|
||||||
relaxColumnCount: true,
|
|
||||||
skipEmptyLines: true
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const record of records as Array<string[]>) {
|
|
||||||
if (record.length == 1) {
|
|
||||||
res.push(record[0]);
|
|
||||||
continue;
|
|
||||||
} else if (!ignoreComma) {
|
|
||||||
res.push(...record);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
res.push(record.join(','));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.filter(item => item).map(pat => pat.trim());
|
public static isValidUrl(url: string): boolean {
|
||||||
}
|
try {
|
||||||
|
new URL(url);
|
||||||
export const asyncForEach = async (array, callback) => {
|
} catch (e) {
|
||||||
for (let index = 0; index < array.length; index++) {
|
return false;
|
||||||
await callback(array[index], index, array);
|
}
|
||||||
}
|
return true;
|
||||||
};
|
}
|
||||||
|
|
||||||
export function isValidUrl(url: string): boolean {
|
|
||||||
try {
|
|
||||||
new URL(url);
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
@ -3541,6 +3541,11 @@ rimraf@^3.0.0, rimraf@^3.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
glob "^7.1.3"
|
glob "^7.1.3"
|
||||||
|
|
||||||
|
rimraf@^4.1.1:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-4.1.1.tgz#ec29817863e5d82d22bca82f9dc4325be2f1e72b"
|
||||||
|
integrity sha512-Z4Y81w8atcvaJuJuBB88VpADRH66okZAuEm+Jtaufa+s7rZmIz+Hik2G53kGaNytE7lsfXyWktTmfVz0H9xuDg==
|
||||||
|
|
||||||
run-parallel@^1.1.9:
|
run-parallel@^1.1.9:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
|
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
|
||||||
|
Loading…
Reference in New Issue
Block a user