Compare commits

..

16 Commits

Author SHA1 Message Date
94ab11c41e Merge pull request #106 from crazy-max/config-inline
Add `config-inline` input
2021-09-03 22:23:23 +02:00
34e94a5fed Add config-inline input
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-09-03 22:21:33 +02:00
ee7ac3140a Merge pull request #104 from docker/dependabot/npm_and_yarn/actions/core-1.5.0
Bump @actions/core from 1.4.0 to 1.5.0
2021-08-20 10:58:12 +02:00
93fe949311 Update generated content
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-08-20 10:56:04 +02:00
75abbe0a7b Bump @actions/core from 1.4.0 to 1.5.0
Bumps [@actions/core](https://github.com/actions/toolkit/tree/HEAD/packages/core) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/actions/toolkit/releases)
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/core/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/core)

---
updated-dependencies:
- dependency-name: "@actions/core"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-20 04:04:09 +00:00
e639814ab4 Merge pull request #101 from docker/dependabot/github_actions/codecov/codecov-action-2.0.1
Bump codecov/codecov-action from 1 to 2.0.1
2021-07-23 13:01:36 +02:00
96016fa2cf Use major
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-07-23 12:56:44 +02:00
cddc6485b4 Merge pull request #102 from crazy-max/update-deps
Use official images
2021-07-20 10:35:27 +02:00
1e85bf381a Use official images
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-07-20 10:29:24 +02:00
7c99741146 Bump codecov/codecov-action from 1 to 2.0.1
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 1 to 2.0.1.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v1...v2.0.1)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-20 04:02:19 +00:00
abe5d8f79a Merge pull request #100 from crazy-max/explicit-semver
Explicit version spec for caching
2021-07-12 13:23:18 +02:00
580d5c72a2 Explicit version spec for caching
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-07-11 22:42:24 +02:00
e673438944 Merge pull request #99 from crazy-max/build-ref
Allow building buildx from source
2021-07-02 10:49:07 +02:00
f40e8894f1 Allow building buildx from source
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-07-02 07:02:33 +02:00
a1c666d855 Merge pull request #97 from crazy-max/fix-get-buildkit-version
Fix `docker: invalid reference format`
2021-06-29 14:04:53 +02:00
d5b70f51d8 Fix docker: invalid reference format
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-06-29 14:02:48 +02:00
21 changed files with 5170 additions and 85 deletions

View File

@ -4,19 +4,15 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
time: "06:00"
timezone: "Europe/Paris"
labels: labels:
- ":game_die: dependencies" - "dependencies"
- ":robot: bot" - "bot"
- package-ecosystem: "npm" - package-ecosystem: "npm"
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
time: "06:00"
timezone: "Europe/Paris"
allow: allow:
- dependency-type: "production" - dependency-type: "production"
labels: labels:
- ":game_die: dependencies" - "dependencies"
- ":robot: bot" - "bot"

View File

@ -278,6 +278,33 @@ jobs:
with: with:
context: . context: .
config-inline:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Create Dockerfile
run: |
cat > ./Dockerfile <<EOL
FROM alpine
EOL
-
name: Set up Docker Buildx
uses: ./
with:
buildkitd-flags: --debug
config-inline: |
debug = true
[registry."docker.io"]
mirrors = ["mirror.gcr.io"]
-
name: Build
uses: docker/build-push-action@v2
with:
context: .
with-qemu: with-qemu:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
@ -313,3 +340,38 @@ jobs:
echo "Status: ${{ steps.buildx.outputs.status }}" echo "Status: ${{ steps.buildx.outputs.status }}"
echo "Flags: ${{ steps.buildx.outputs.flags }}" echo "Flags: ${{ steps.buildx.outputs.flags }}"
echo "Platforms: ${{ steps.buildx.outputs.platforms }}" echo "Platforms: ${{ steps.buildx.outputs.platforms }}"
build-ref:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
ref:
- master
- refs/tags/v0.5.1
- refs/pull/731/head
- cb185f095fd3d9444e0aa605d3789e9e05f2a1e7
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up Docker Buildx
uses: ./
with:
version: https://github.com/docker/buildx.git#${{ matrix.ref }}
-
name: Check version
run: |
docker buildx version
-
name: Create Dockerfile
run: |
cat > ./Dockerfile <<EOL
FROM alpine
EOL
-
name: Build
uses: docker/build-push-action@master
with:
context: .

View File

@ -32,6 +32,6 @@ jobs:
targets: test targets: test
- -
name: Upload coverage name: Upload coverage
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v2
with: with:
file: ./coverage/clover.xml file: ./coverage/clover.xml

View File

@ -1,5 +1,5 @@
{ {
"printWidth": 120, "printWidth": 240,
"tabWidth": 2, "tabWidth": 2,
"useTabs": false, "useTabs": false,
"semi": true, "semi": true,

View File

@ -21,6 +21,9 @@ ___
* [Quick start](#quick-start) * [Quick start](#quick-start)
* [With QEMU](#with-qemu) * [With QEMU](#with-qemu)
* [Install by default](#install-by-default) * [Install by default](#install-by-default)
* [BuildKit daemon configuration](#buildkit-daemon-configuration)
* [Registry mirror](#registry-mirror)
* [Max parallelism](#max-parallelism)
* [Customizing](#customizing) * [Customizing](#customizing)
* [inputs](#inputs) * [inputs](#inputs)
* [outputs](#outputs) * [outputs](#outputs)
@ -91,8 +94,6 @@ jobs:
### Install by default ### Install by default
Implemented with https://github.com/docker/buildx#setting-buildx-as-default-builder-in-docker-1903
```yaml ```yaml
name: ci name: ci
@ -117,6 +118,68 @@ jobs:
docker build . # will run buildx docker build . # will run buildx
``` ```
### BuildKit daemon configuration
You can provide a [BuildKit configuration](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md)
to your builder if you're using the [`docker-container` driver](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#driver)
(default) with the `config` or `config-inline` inputs:
#### Registry mirror
You can configure a registry mirror using an inline block directly in your
workflow with the `config-inline` input:
```yaml
name: ci
on:
push:
jobs:
buildx:
runs-on: ubuntu-latest
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
config-inline: |
[registry."docker.io"]
mirrors = ["mirror.gcr.io"]
```
#### Max parallelism
You can limit the parallelism of the BuildKit solver which is particularly
useful for low-powered machines.
You can use the `config-inline` input like the
previous example, or you can use a dedicated BuildKit config file from your
repo if you want with the `config` input:
```toml
# .github/buildkitd.toml
[worker.oci]
max-parallelism = 4
```
```yaml
name: ci
on:
push:
jobs:
buildx:
runs-on: ubuntu-latest
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
config: .github/buildkitd.toml
```
## Customizing ## Customizing
### inputs ### inputs
@ -125,7 +188,7 @@ Following inputs can be used as `step.with` keys
| Name | Type | Description | | Name | Type | Description |
|--------------------|---------|-----------------------------------| |--------------------|---------|-----------------------------------|
| `version` | String | [Buildx](https://github.com/docker/buildx) version. (eg. `v0.3.0`, `latest`) | | `version` | String | [buildx](https://github.com/docker/buildx) version. (eg. `v0.3.0`, `latest`, `https://github.com/docker/buildx.git#master`) |
| `driver` | String | Sets the [builder driver](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#driver) to be used (default `docker-container`) | | `driver` | String | Sets the [builder driver](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#driver) to be used (default `docker-container`) |
| `driver-opts` | CSV | List of additional [driver-specific options](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#driver-opt) (eg. `image=moby/buildkit:master`) | | `driver-opts` | CSV | List of additional [driver-specific options](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#driver-opt) (eg. `image=moby/buildkit:master`) |
| `buildkitd-flags` | String | [Flags for buildkitd](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md) daemon (since [buildx v0.3.0](https://github.com/docker/buildx/releases/tag/v0.3.0)) | | `buildkitd-flags` | String | [Flags for buildkitd](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md) daemon (since [buildx v0.3.0](https://github.com/docker/buildx/releases/tag/v0.3.0)) |
@ -133,6 +196,9 @@ Following inputs can be used as `step.with` keys
| `use` | Bool | Switch to this builder instance (default `true`) | | `use` | Bool | Switch to this builder instance (default `true`) |
| `endpoint` | String | [Optional address for docker socket](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#description) or context from `docker context ls` | | `endpoint` | String | [Optional address for docker socket](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#description) or context from `docker context ls` |
| `config` | String | [BuildKit config file](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#config) | | `config` | String | [BuildKit config file](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#config) |
| `config-inline` | String | Same as `config` but inline |
> `config` and `config-inline` are mutually exclusive.
> `CSV` type must be a newline-delimited string > `CSV` type must be a newline-delimited string
> ```yaml > ```yaml

View File

@ -1,10 +1,25 @@
import fs = require('fs'); import * as fs from 'fs';
import * as buildx from '../src/buildx';
import * as path from 'path';
import * as os from 'os'; import * as os from 'os';
import * as path from 'path';
import * as buildx from '../src/buildx';
import * as context from '../src/context';
import * as semver from 'semver'; import * as semver from 'semver';
import * as exec from '@actions/exec'; import * as exec from '@actions/exec';
const tmpNameSync = path.join('/tmp/.docker-setup-buildx-jest', '.tmpname-jest').split(path.sep).join(path.posix.sep);
jest.spyOn(context, 'tmpDir').mockImplementation((): string => {
const tmpDir = path.join('/tmp/.docker-setup-buildx-jest').split(path.sep).join(path.posix.sep);
if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir, {recursive: true});
}
return tmpDir;
});
jest.spyOn(context, 'tmpNameSync').mockImplementation((): string => {
return tmpNameSync;
});
describe('isAvailable', () => { describe('isAvailable', () => {
const execSpy: jest.SpyInstance = jest.spyOn(exec, 'getExecOutput'); const execSpy: jest.SpyInstance = jest.spyOn(exec, 'getExecOutput');
buildx.isAvailable(); buildx.isAvailable();
@ -23,7 +38,7 @@ describe('getVersion', () => {
silent: true silent: true
}) })
.then(res => { .then(res => {
return !res.stdout.includes(' ') && res.exitCode == 0; return !res.stdout.trim().includes(' ') && res.exitCode == 0;
}); });
} }
(isDaemonRunning() ? it : it.skip)( (isDaemonRunning() ? it : it.skip)(
@ -41,9 +56,20 @@ describe('parseVersion', () => {
test.each([ test.each([
['github.com/docker/buildx 0.4.1+azure bda4882a65349ca359216b135896bddc1d92461c', '0.4.1'], ['github.com/docker/buildx 0.4.1+azure bda4882a65349ca359216b135896bddc1d92461c', '0.4.1'],
['github.com/docker/buildx v0.4.1 bda4882a65349ca359216b135896bddc1d92461c', '0.4.1'], ['github.com/docker/buildx v0.4.1 bda4882a65349ca359216b135896bddc1d92461c', '0.4.1'],
['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']
])('given %p', async (stdout, expected) => { ])('given %p', async (stdout, expected) => {
expect(await buildx.parseVersion(stdout)).toEqual(expected); expect(buildx.parseVersion(stdout)).toEqual(expected);
});
});
describe('satisfies', () => {
test.each([
['0.4.1', '>=0.3.2', true],
['bda4882a65349ca359216b135896bddc1d92461c', '>0.1.0', false],
['f117971', '>0.6.0', true]
])('given %p', async (version, range, expected) => {
expect(buildx.satisfies(version, range)).toBe(expected);
}); });
}); });
@ -55,7 +81,7 @@ describe('inspect', () => {
silent: true silent: true
}) })
.then(res => { .then(res => {
return !res.stdout.includes(' ') && res.exitCode == 0; return !res.stdout.trim().includes(' ') && res.exitCode == 0;
}); });
} }
(isDaemonRunning() ? it : it.skip)( (isDaemonRunning() ? it : it.skip)(
@ -72,6 +98,20 @@ describe('inspect', () => {
); );
}); });
describe('build', () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'setup-buildx-'));
it.skip('builds refs/pull/648/head', async () => {
const buildxBin = await buildx.build('https://github.com/docker/buildx.git#refs/pull/648/head', tmpDir);
console.log(buildxBin);
expect(fs.existsSync(buildxBin)).toBe(true);
}, 100000);
it.skip('builds 67bd6f4dc82a9cd96f34133dab3f6f7af803bb14', async () => {
const buildxBin = await buildx.build('https://github.com/docker/buildx.git#67bd6f4dc82a9cd96f34133dab3f6f7af803bb14', tmpDir);
console.log(buildxBin);
expect(fs.existsSync(buildxBin)).toBe(true);
}, 100000);
});
describe('install', () => { describe('install', () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'setup-buildx-')); const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'setup-buildx-'));
it('acquires v0.4.1 version of buildx', async () => { it('acquires v0.4.1 version of buildx', async () => {
@ -85,3 +125,37 @@ describe('install', () => {
expect(fs.existsSync(buildxBin)).toBe(true); expect(fs.existsSync(buildxBin)).toBe(true);
}, 100000); }, 100000);
}); });
describe('getConfig', () => {
test.each([
['debug = true', false, 'debug = true', false],
[`notfound.toml`, true, '', true],
[
`${path.join(__dirname, 'fixtures', 'buildkitd.toml').split(path.sep).join(path.posix.sep)}`,
true,
`debug = true
[registry."docker.io"]
mirrors = ["mirror.gcr.io"]
`,
false
]
])('given %p config', async (val, file, exValue, invalid) => {
try {
let config: string;
if (file) {
config = await buildx.getConfigFile(val);
} else {
config = await buildx.getConfigInline(val);
}
expect(true).toBe(!invalid);
console.log(`config: ${config}`);
expect(config).toEqual(`${tmpNameSync}`);
const configValue = await fs.readFileSync(tmpNameSync, 'utf-8');
console.log(`configValue: ${configValue}`);
expect(configValue).toEqual(exValue);
} catch (err) {
console.log(err);
expect(true).toBe(invalid);
}
});
});

View File

@ -1,6 +1,20 @@
import * as fs from 'fs';
import * as os from 'os'; import * as os from 'os';
import * as path from 'path';
import * as context from '../src/context'; import * as context from '../src/context';
jest.spyOn(context, 'tmpDir').mockImplementation((): string => {
const tmpDir = path.join('/tmp/.docker-setup-buildx-jest').split(path.sep).join(path.posix.sep);
if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir, {recursive: true});
}
return tmpDir;
});
jest.spyOn(context, 'tmpNameSync').mockImplementation((): string => {
return path.join('/tmp/.docker-setup-buildx-jest', '.tmpname-jest').split(path.sep).join(path.posix.sep);
});
describe('getInputList', () => { describe('getInputList', () => {
it('handles single line correctly', async () => { it('handles single line correctly', async () => {
await setInput('foo', 'bar'); await setInput('foo', 'bar');

View File

@ -0,0 +1,3 @@
debug = true
[registry."docker.io"]
mirrors = ["mirror.gcr.io"]

9
__tests__/git.test.ts Normal file
View File

@ -0,0 +1,9 @@
import * as git from '../src/git';
describe('git', () => {
it('returns git remote ref', async () => {
const ref: string = await git.getRemoteSha('https://github.com/docker/buildx.git', 'refs/pull/648/head');
console.log(`ref: ${ref}`);
expect(ref).toEqual('f11797113e5a9b86bd976329c5dbb8a8bfdfadfa');
});
});

11
__tests__/util.test.ts Normal file
View File

@ -0,0 +1,11 @@
import * as util from '../src/util';
describe('isValidUrl', () => {
test.each([
['https://github.com/docker/buildx.git', true],
['https://github.com/docker/buildx.git#refs/pull/648/head', true],
['v0.4.1', false]
])('given %p', async (url, expected) => {
expect(util.isValidUrl(url)).toEqual(expected);
});
});

View File

@ -35,6 +35,9 @@ inputs:
config: config:
description: 'BuildKit config file' description: 'BuildKit config file'
required: false required: false
config-inline:
description: 'Inline BuildKit config'
required: false
outputs: outputs:
name: name:

3
codecov.yml Normal file
View File

@ -0,0 +1,3 @@
comment: false
github_checks:
annotations: false

4782
dist/index.js generated vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,10 @@
# syntax=docker/dockerfile:1.2 # syntax=docker/dockerfile:1.2
ARG NODE_VERSION ARG NODE_VERSION
ARG DOCKER_VERSION=20.10.7
ARG BUILDX_VERSION=0.6.0
FROM docker:${DOCKER_VERSION} as docker
FROM docker/buildx-bin:${BUILDX_VERSION} as buildx
FROM node:${NODE_VERSION}-alpine AS base FROM node:${NODE_VERSION}-alpine AS base
RUN apk add --no-cache git RUN apk add --no-cache git
@ -15,8 +20,8 @@ ENV RUNNER_TEMP=/tmp/github_runner
ENV RUNNER_TOOL_CACHE=/tmp/github_tool_cache ENV RUNNER_TOOL_CACHE=/tmp/github_tool_cache
RUN --mount=type=bind,target=.,rw \ RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \ --mount=type=cache,target=/src/node_modules \
--mount=type=bind,from=crazymax/docker,source=/usr/libexec/docker/cli-plugins/docker-buildx,target=/usr/libexec/docker/cli-plugins/docker-buildx \ --mount=type=bind,from=docker,source=/usr/local/bin/docker,target=/usr/bin/docker \
--mount=type=bind,from=crazymax/docker,source=/usr/local/bin/docker,target=/usr/bin/docker \ --mount=type=bind,from=buildx,source=/buildx,target=/usr/libexec/docker/cli-plugins/docker-buildx \
yarn run test --coverageDirectory=/tmp/coverage yarn run test --coverageDirectory=/tmp/coverage
FROM scratch AS test-coverage FROM scratch AS test-coverage

View File

@ -27,11 +27,12 @@
], ],
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@actions/core": "^1.4.0", "@actions/core": "^1.5.0",
"@actions/exec": "^1.1.0", "@actions/exec": "^1.1.0",
"@actions/http-client": "^1.0.11", "@actions/http-client": "^1.0.11",
"@actions/tool-cache": "^1.7.1", "@actions/tool-cache": "^1.7.1",
"semver": "^7.3.5", "semver": "^7.3.5",
"tmp": "^0.2.1",
"uuid": "^8.3.2" "uuid": "^8.3.2"
}, },
"devDependencies": { "devDependencies": {

View File

@ -3,6 +3,7 @@ import * as path from 'path';
import * as semver from 'semver'; import * as semver from 'semver';
import * as util from 'util'; import * as util from 'util';
import * as context from './context'; import * as context from './context';
import * as git from './git';
import * as github from './github'; import * as github from './github';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as exec from '@actions/exec'; import * as exec from '@actions/exec';
@ -18,6 +19,28 @@ export type Builder = {
node_platforms?: string; node_platforms?: string;
}; };
export async function getConfigInline(s: string): Promise<string> {
return getConfig(s, false);
}
export async function getConfigFile(s: string): Promise<string> {
return getConfig(s, true);
}
export async function getConfig(s: string, file: boolean): Promise<string> {
if (file) {
if (!fs.existsSync(s)) {
throw new Error(`config file ${s} not found`);
}
s = fs.readFileSync(s, {encoding: 'utf-8'});
}
const configFile = context.tmpNameSync({
tmpdir: context.tmpDir()
});
fs.writeFileSync(configFile, s);
return configFile;
}
export async function isAvailable(): Promise<Boolean> { export async function isAvailable(): Promise<Boolean> {
return await exec return await exec
.getExecOutput('docker', ['buildx'], { .getExecOutput('docker', ['buildx'], {
@ -42,16 +65,20 @@ export async function getVersion(): Promise<string> {
if (res.stderr.length > 0 && res.exitCode != 0) { if (res.stderr.length > 0 && res.exitCode != 0) {
throw new Error(res.stderr.trim()); throw new Error(res.stderr.trim());
} }
return parseVersion(res.stdout); return parseVersion(res.stdout.trim());
}); });
} }
export async function parseVersion(stdout: string): Promise<string> { export function parseVersion(stdout: string): string {
const matches = /\sv?([0-9.]+)/.exec(stdout); const matches = /\sv?([0-9a-f]{7}|[0-9.]+)/.exec(stdout);
if (!matches) { if (!matches) {
throw new Error(`Cannot parse buildx version`); throw new Error(`Cannot parse buildx version`);
} }
return semver.clean(matches[1]); return matches[1];
}
export function satisfies(version: string, range: string): boolean {
return semver.satisfies(version, range) || /^[0-9a-f]{7}$/.exec(version) !== null;
} }
export async function inspect(name: string): Promise<Builder> { export async function inspect(name: string): Promise<Builder> {
@ -106,6 +133,39 @@ export async function inspect(name: string): Promise<Builder> {
}); });
} }
export async function build(inputBuildRef: string, dockerConfigHome: string): Promise<string> {
let [repo, ref] = inputBuildRef.split('#');
if (ref.length == 0) {
ref = 'master';
}
let vspec: string;
if (ref.match(/^[0-9a-fA-F]{40}$/)) {
vspec = ref;
} else {
vspec = await git.getRemoteSha(repo, ref);
}
core.debug(`Tool version spec ${vspec}`);
let toolPath: string;
toolPath = tc.find('buildx', vspec);
if (!toolPath) {
const outFolder = path.join(context.tmpDir(), 'out').split(path.sep).join(path.posix.sep);
toolPath = await exec
.getExecOutput('docker', ['buildx', 'build', '--target', 'binaries', '--build-arg', 'BUILDKIT_CONTEXT_KEEP_GIT_DIR=1', '--output', `type=local,dest=${outFolder}`, inputBuildRef], {
ignoreReturnCode: true
})
.then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
core.warning(res.stderr.trim());
}
return tc.cacheFile(`${outFolder}/buildx`, context.osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx', 'buildx', vspec);
});
}
return setPlugin(toolPath, dockerConfigHome);
}
export async function install(inputVersion: string, dockerConfigHome: string): Promise<string> { export async function install(inputVersion: string, dockerConfigHome: string): Promise<string> {
const release: github.GitHubRelease | null = await github.getRelease(inputVersion); const release: github.GitHubRelease | null = await github.getRelease(inputVersion);
if (!release) { if (!release) {
@ -124,6 +184,10 @@ export async function install(inputVersion: string, dockerConfigHome: string): P
toolPath = await download(version); toolPath = await download(version);
} }
return setPlugin(toolPath, dockerConfigHome);
}
async function setPlugin(toolPath: string, dockerConfigHome: string): Promise<string> {
const pluginsDir: string = path.join(dockerConfigHome, 'cli-plugins'); const pluginsDir: string = path.join(dockerConfigHome, 'cli-plugins');
core.debug(`Plugins dir is ${pluginsDir}`); core.debug(`Plugins dir is ${pluginsDir}`);
if (!fs.existsSync(pluginsDir)) { if (!fs.existsSync(pluginsDir)) {
@ -143,11 +207,7 @@ export async function install(inputVersion: string, dockerConfigHome: string): P
async function download(version: string): Promise<string> { async function download(version: string): Promise<string> {
const targetFile: string = context.osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx'; const targetFile: string = context.osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx';
const downloadUrl = util.format( const downloadUrl = util.format('https://github.com/docker/buildx/releases/download/v%s/%s', version, await filename(version));
'https://github.com/docker/buildx/releases/download/v%s/%s',
version,
await filename(version)
);
let downloadPath: string; let downloadPath: string;
try { try {
@ -196,21 +256,21 @@ export async function getBuildKitVersion(containerID: string): Promise<string> {
.then(bkitimage => { .then(bkitimage => {
if (bkitimage.exitCode == 0 && bkitimage.stdout.length > 0) { if (bkitimage.exitCode == 0 && bkitimage.stdout.length > 0) {
return exec return exec
.getExecOutput(`docker`, ['run', '--rm', bkitimage.stdout, '--version'], { .getExecOutput(`docker`, ['run', '--rm', bkitimage.stdout.trim(), '--version'], {
ignoreReturnCode: true, ignoreReturnCode: true,
silent: true silent: true
}) })
.then(bkitversion => { .then(bkitversion => {
if (bkitversion.exitCode == 0 && bkitversion.stdout.length > 0) { if (bkitversion.exitCode == 0 && bkitversion.stdout.length > 0) {
return `${bkitimage.stdout} => ${bkitversion.stdout}`; return `${bkitimage.stdout.trim()} => ${bkitversion.stdout.trim()}`;
} else if (bkitversion.stderr.length > 0) { } else if (bkitversion.stderr.length > 0) {
core.warning(bkitversion.stderr.trim()); core.warning(bkitversion.stderr.trim());
} }
return bkitversion.stdout; return bkitversion.stdout.trim();
}); });
} else if (bkitimage.stderr.length > 0) { } else if (bkitimage.stderr.length > 0) {
core.warning(bkitimage.stderr.trim()); core.warning(bkitimage.stderr.trim());
} }
return bkitimage.stdout; return bkitimage.stdout.trim();
}); });
} }

View File

@ -1,10 +1,25 @@
import fs from 'fs';
import * as os from 'os'; import * as os from 'os';
import path from 'path';
import * as tmp from 'tmp';
import * as core from '@actions/core'; import * as core from '@actions/core';
import {issueCommand} from '@actions/core/lib/command'; import {issueCommand} from '@actions/core/lib/command';
let _tmpDir: string;
export const osPlat: string = os.platform(); export const osPlat: string = os.platform();
export const osArch: string = os.arch(); export const osArch: string = os.arch();
export function tmpDir(): string {
if (!_tmpDir) {
_tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-setup-buildx-')).split(path.sep).join(path.posix.sep);
}
return _tmpDir;
}
export function tmpNameSync(options?: tmp.TmpNameOptions): string {
return tmp.tmpNameSync(options);
}
export interface Inputs { export interface Inputs {
version: string; version: string;
driver: string; driver: string;
@ -14,6 +29,7 @@ export interface Inputs {
use: boolean; use: boolean;
endpoint: string; endpoint: string;
config: string; config: string;
configInline: string;
} }
export async function getInputs(): Promise<Inputs> { export async function getInputs(): Promise<Inputs> {
@ -21,13 +37,12 @@ export async function getInputs(): Promise<Inputs> {
version: core.getInput('version'), version: core.getInput('version'),
driver: core.getInput('driver') || 'docker-container', driver: core.getInput('driver') || 'docker-container',
driverOpts: await getInputList('driver-opts', true), driverOpts: await getInputList('driver-opts', true),
buildkitdFlags: buildkitdFlags: core.getInput('buildkitd-flags') || '--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host',
core.getInput('buildkitd-flags') ||
'--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host',
install: core.getBooleanInput('install'), install: core.getBooleanInput('install'),
use: core.getBooleanInput('use'), use: core.getBooleanInput('use'),
endpoint: core.getInput('endpoint'), endpoint: core.getInput('endpoint'),
config: core.getInput('config') config: core.getInput('config'),
configInline: core.getInput('config-inline')
}; };
} }
@ -39,10 +54,7 @@ export async function getInputList(name: string, ignoreComma?: boolean): Promise
return items return items
.split(/\r?\n/) .split(/\r?\n/)
.filter(x => x) .filter(x => x)
.reduce<string[]>( .reduce<string[]>((acc, line) => acc.concat(!ignoreComma ? line.split(',').filter(x => x) : line).map(pat => pat.trim()), []);
(acc, line) => acc.concat(!ignoreComma ? line.split(',').filter(x => x) : line).map(pat => pat.trim()),
[]
);
} }
export const asyncForEach = async (array, callback) => { export const asyncForEach = async (array, callback) => {

19
src/git.ts Normal file
View File

@ -0,0 +1,19 @@
import * as exec from '@actions/exec';
export async function getRemoteSha(repo: string, ref: string): Promise<string> {
return await exec
.getExecOutput(`git`, ['ls-remote', repo, ref], {
ignoreReturnCode: true,
silent: true
})
.then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
throw new Error(res.stderr);
}
const [rsha, rref] = res.stdout.trim().split(/[\s\t]/);
if (rsha.length == 0) {
throw new Error(`Cannot find remote ref for ${repo}#${ref}`);
}
return rsha;
});
}

View File

@ -1,9 +1,9 @@
import * as os from 'os'; import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import * as semver from 'semver';
import * as buildx from './buildx'; import * as buildx from './buildx';
import * as context from './context'; import * as context from './context';
import * as stateHelper from './state-helper'; import * as stateHelper from './state-helper';
import * as util from './util';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as exec from '@actions/exec'; import * as exec from '@actions/exec';
@ -17,8 +17,12 @@ async function run(): Promise<void> {
const inputs: context.Inputs = await context.getInputs(); const inputs: context.Inputs = await context.getInputs();
const dockerConfigHome: string = process.env.DOCKER_CONFIG || path.join(os.homedir(), '.docker'); const dockerConfigHome: string = process.env.DOCKER_CONFIG || path.join(os.homedir(), '.docker');
if (!(await buildx.isAvailable()) || inputs.version) { if (util.isValidUrl(inputs.version)) {
core.startGroup(`Installing buildx`); core.startGroup(`Build and install buildx`);
await buildx.build(inputs.version, dockerConfigHome);
core.endGroup();
} else if (!(await buildx.isAvailable()) || inputs.version) {
core.startGroup(`Download and install buildx`);
await buildx.install(inputs.version || 'latest', dockerConfigHome); await buildx.install(inputs.version || 'latest', dockerConfigHome);
core.endGroup(); core.endGroup();
} }
@ -31,7 +35,7 @@ async function run(): Promise<void> {
if (inputs.driver !== 'docker') { if (inputs.driver !== 'docker') {
core.startGroup(`Creating a new builder instance`); core.startGroup(`Creating a new builder instance`);
let createArgs: Array<string> = ['buildx', 'create', '--name', builderName, '--driver', inputs.driver]; let createArgs: Array<string> = ['buildx', 'create', '--name', builderName, '--driver', inputs.driver];
if (semver.satisfies(buildxVersion, '>=0.3.0')) { if (buildx.satisfies(buildxVersion, '>=0.3.0')) {
await context.asyncForEach(inputs.driverOpts, async driverOpt => { await context.asyncForEach(inputs.driverOpts, async driverOpt => {
createArgs.push('--driver-opt', driverOpt); createArgs.push('--driver-opt', driverOpt);
}); });
@ -46,14 +50,16 @@ async function run(): Promise<void> {
createArgs.push(inputs.endpoint); createArgs.push(inputs.endpoint);
} }
if (inputs.config) { if (inputs.config) {
createArgs.push('--config', inputs.config); createArgs.push('--config', await buildx.getConfigFile(inputs.config));
} else if (inputs.configInline) {
createArgs.push('--config', await buildx.getConfigInline(inputs.configInline));
} }
await exec.exec('docker', createArgs); await exec.exec('docker', createArgs);
core.endGroup(); core.endGroup();
core.startGroup(`Booting builder`); core.startGroup(`Booting builder`);
let bootstrapArgs: Array<string> = ['buildx', 'inspect', '--bootstrap']; let bootstrapArgs: Array<string> = ['buildx', 'inspect', '--bootstrap'];
if (semver.satisfies(buildxVersion, '>=0.4.0')) { if (buildx.satisfies(buildxVersion, '>=0.4.0')) {
bootstrapArgs.push('--builder', builderName); bootstrapArgs.push('--builder', builderName);
} }
await exec.exec('docker', bootstrapArgs); await exec.exec('docker', bootstrapArgs);

8
src/util.ts Normal file
View File

@ -0,0 +1,8 @@
export function isValidUrl(url: string): boolean {
try {
new URL(url);
} catch (e) {
return false;
}
return true;
}

View File

@ -2,10 +2,10 @@
# yarn lockfile v1 # yarn lockfile v1
"@actions/core@^1.2.6", "@actions/core@^1.4.0": "@actions/core@^1.2.6", "@actions/core@^1.5.0":
version "1.4.0" version "1.5.0"
resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.4.0.tgz#cf2e6ee317e314b03886adfeb20e448d50d6e524" resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.5.0.tgz#885b864700001a1b9a6fba247833a036e75ad9d3"
integrity sha512-CGx2ilGq5i7zSLgiiGUtBCxhRRxibJYU6Fim0Q1Wg2aQL2LTnF27zbqZOrxfvFQ55eSBW0L8uVStgtKMpa0Qlg== integrity sha512-eDOLH1Nq9zh+PJlYLqEMkS/jLQxhksPNmUGNBHfa4G+tQmnIhzpctxmchETtVGyBOvXgOVVpYuE40+eS4cUnwQ==
"@actions/exec@^1.0.0", "@actions/exec@^1.1.0": "@actions/exec@^1.0.0", "@actions/exec@^1.1.0":
version "1.1.0" version "1.1.0"
@ -3253,6 +3253,13 @@ throat@^5.0.0:
resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b"
integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==
tmp@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==
dependencies:
rimraf "^3.0.0"
tmpl@1.0.x: tmpl@1.0.x:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"