mirror of
https://github.com/docker/setup-buildx-action.git
synced 2024-11-25 07:40:56 -05:00
commit
7117987c01
10 changed files with 320 additions and 89 deletions
140
.github/workflows/ci.yml
vendored
140
.github/workflows/ci.yml
vendored
|
@ -30,21 +30,9 @@ jobs:
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
id: buildx
|
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
version: ${{ matrix.buildx-version }}
|
version: ${{ matrix.buildx-version }}
|
||||||
-
|
|
||||||
name: Inspect builder
|
|
||||||
run: |
|
|
||||||
echo "Name: ${{ steps.buildx.outputs.name }}"
|
|
||||||
echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}"
|
|
||||||
echo "Status: ${{ steps.buildx.outputs.status }}"
|
|
||||||
echo "Flags: ${{ steps.buildx.outputs.flags }}"
|
|
||||||
echo "Platforms: ${{ steps.buildx.outputs.platforms }}"
|
|
||||||
-
|
|
||||||
name: Dump context
|
|
||||||
uses: crazy-max/ghaction-dump-context@v1
|
|
||||||
|
|
||||||
multi:
|
multi:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -54,28 +42,10 @@ jobs:
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx 1
|
name: Set up Docker Buildx 1
|
||||||
id: buildx1
|
|
||||||
uses: ./
|
uses: ./
|
||||||
-
|
|
||||||
name: Inspect builder 1
|
|
||||||
run: |
|
|
||||||
echo "Name: ${{ steps.buildx1.outputs.name }}"
|
|
||||||
echo "Endpoint: ${{ steps.buildx1.outputs.endpoint }}"
|
|
||||||
echo "Status: ${{ steps.buildx1.outputs.status }}"
|
|
||||||
echo "Flags: ${{ steps.buildx1.outputs.flags }}"
|
|
||||||
echo "Platforms: ${{ steps.buildx1.outputs.platforms }}"
|
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx 2
|
name: Set up Docker Buildx 2
|
||||||
id: buildx2
|
|
||||||
uses: ./
|
uses: ./
|
||||||
-
|
|
||||||
name: Inspect builder 2
|
|
||||||
run: |
|
|
||||||
echo "Name: ${{ steps.buildx2.outputs.name }}"
|
|
||||||
echo "Endpoint: ${{ steps.buildx2.outputs.endpoint }}"
|
|
||||||
echo "Status: ${{ steps.buildx2.outputs.status }}"
|
|
||||||
echo "Flags: ${{ steps.buildx2.outputs.flags }}"
|
|
||||||
echo "Platforms: ${{ steps.buildx2.outputs.platforms }}"
|
|
||||||
|
|
||||||
error:
|
error:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -328,18 +298,9 @@ jobs:
|
||||||
platforms: ${{ matrix.qemu-platforms }}
|
platforms: ${{ matrix.qemu-platforms }}
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
id: buildx
|
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
version: ${{ matrix.buildx-version }}
|
version: ${{ matrix.buildx-version }}
|
||||||
-
|
|
||||||
name: Inspect builder
|
|
||||||
run: |
|
|
||||||
echo "Name: ${{ steps.buildx.outputs.name }}"
|
|
||||||
echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}"
|
|
||||||
echo "Status: ${{ steps.buildx.outputs.status }}"
|
|
||||||
echo "Flags: ${{ steps.buildx.outputs.flags }}"
|
|
||||||
echo "Platforms: ${{ steps.buildx.outputs.platforms }}"
|
|
||||||
|
|
||||||
build-ref:
|
build-ref:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -360,10 +321,6 @@ jobs:
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
version: https://github.com/docker/buildx.git#${{ matrix.ref }}
|
version: https://github.com/docker/buildx.git#${{ matrix.ref }}
|
||||||
-
|
|
||||||
name: Check version
|
|
||||||
run: |
|
|
||||||
docker buildx version
|
|
||||||
-
|
-
|
||||||
name: Create Dockerfile
|
name: Create Dockerfile
|
||||||
run: |
|
run: |
|
||||||
|
@ -375,3 +332,100 @@ jobs:
|
||||||
uses: docker/build-push-action@master
|
uses: docker/build-push-action@master
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
|
|
||||||
|
standalone:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
buildx-version:
|
||||||
|
- latest
|
||||||
|
- ""
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
-
|
||||||
|
name: Uninstall docker cli
|
||||||
|
run: |
|
||||||
|
sudo apt-get purge -y moby-cli moby-buildx
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
version: ${{ matrix.buildx-version }}
|
||||||
|
-
|
||||||
|
name: Check available in path
|
||||||
|
if: matrix.standalone
|
||||||
|
run: |
|
||||||
|
buildx version
|
||||||
|
|
||||||
|
standalone-install-error:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
-
|
||||||
|
name: Uninstall docker cli
|
||||||
|
run: |
|
||||||
|
sudo apt-get purge -y moby-cli moby-buildx
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
id: buildx
|
||||||
|
continue-on-error: true
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
install: true
|
||||||
|
-
|
||||||
|
name: Check
|
||||||
|
run: |
|
||||||
|
echo "${{ toJson(steps.buildx) }}"
|
||||||
|
if [ "${{ steps.buildx.outcome }}" != "failure" ] || [ "${{ steps.buildx.conclusion }}" != "success" ]; then
|
||||||
|
echo "::error::Should have failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
standalone-kubernetes:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
-
|
||||||
|
name: Uninstall moby
|
||||||
|
run: |
|
||||||
|
sudo apt-get purge -y moby-engine moby-cli moby-buildx
|
||||||
|
-
|
||||||
|
name: Setup k8s cluster
|
||||||
|
run: |
|
||||||
|
set -x
|
||||||
|
sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
|
||||||
|
echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y kubelet kubeadm kubectl
|
||||||
|
sudo swapoff -a
|
||||||
|
sudo kubeadm init --cri-socket /run/containerd/containerd.sock
|
||||||
|
mkdir -p $HOME/.kube/
|
||||||
|
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
|
||||||
|
sudo chown $USER $HOME/.kube/config
|
||||||
|
kubectl taint nodes --all node-role.kubernetes.io/master-
|
||||||
|
kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml
|
||||||
|
kubectl wait --for=condition=ready --timeout=30s node --all
|
||||||
|
kubectl get nodes -o wide
|
||||||
|
-
|
||||||
|
name: Create Dockerfile
|
||||||
|
run: |
|
||||||
|
cat > ./Dockerfile <<EOL
|
||||||
|
FROM alpine
|
||||||
|
RUN echo hello
|
||||||
|
EOL
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
driver: kubernetes
|
||||||
|
-
|
||||||
|
name: Build
|
||||||
|
run: |
|
||||||
|
buildx build .
|
||||||
|
|
34
README.md
34
README.md
|
@ -24,6 +24,7 @@ ___
|
||||||
* [BuildKit daemon configuration](#buildkit-daemon-configuration)
|
* [BuildKit daemon configuration](#buildkit-daemon-configuration)
|
||||||
* [Registry mirror](#registry-mirror)
|
* [Registry mirror](#registry-mirror)
|
||||||
* [Max parallelism](#max-parallelism)
|
* [Max parallelism](#max-parallelism)
|
||||||
|
* [Standalone mode](#standalone-mode)
|
||||||
* [Customizing](#customizing)
|
* [Customizing](#customizing)
|
||||||
* [inputs](#inputs)
|
* [inputs](#inputs)
|
||||||
* [outputs](#outputs)
|
* [outputs](#outputs)
|
||||||
|
@ -180,6 +181,33 @@ jobs:
|
||||||
config: .github/buildkitd.toml
|
config: .github/buildkitd.toml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Standalone mode
|
||||||
|
|
||||||
|
If you don't have the Docker CLI installed on the GitHub Runner, buildx binary
|
||||||
|
is invoked directly, instead of calling it as a docker plugin. This can be
|
||||||
|
useful if you want to use the `kubernetes` driver in your self-hosted runner:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
buildx:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
with:
|
||||||
|
driver: kubernetes
|
||||||
|
-
|
||||||
|
name: Build
|
||||||
|
run: |
|
||||||
|
buildx build .
|
||||||
|
```
|
||||||
|
|
||||||
## Customizing
|
## Customizing
|
||||||
|
|
||||||
### inputs
|
### inputs
|
||||||
|
@ -195,10 +223,10 @@ Following inputs can be used as `step.with` keys
|
||||||
| `install` | Bool | Sets up `docker build` command as an alias to `docker buildx` (default `false`) |
|
| `install` | Bool | Sets up `docker build` command as an alias to `docker buildx` (default `false`) |
|
||||||
| `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-inline`¹ | String | Same as `config` but inline |
|
||||||
|
|
||||||
> `config` and `config-inline` are mutually exclusive.
|
> * ¹ `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
|
||||||
|
|
|
@ -32,6 +32,17 @@ describe('isAvailable', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('isAvailable standalone', () => {
|
||||||
|
const execSpy = jest.spyOn(exec, 'getExecOutput');
|
||||||
|
buildx.isAvailable(true);
|
||||||
|
|
||||||
|
// eslint-disable-next-line jest/no-standalone-expect
|
||||||
|
expect(execSpy).toHaveBeenCalledWith(`buildx`, [], {
|
||||||
|
silent: true,
|
||||||
|
ignoreReturnCode: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('getVersion', () => {
|
describe('getVersion', () => {
|
||||||
it('valid', async () => {
|
it('valid', async () => {
|
||||||
const version = await buildx.getVersion();
|
const version = await buildx.getVersion();
|
||||||
|
@ -75,29 +86,32 @@ describe('build', () => {
|
||||||
|
|
||||||
// eslint-disable-next-line jest/no-disabled-tests
|
// eslint-disable-next-line jest/no-disabled-tests
|
||||||
it.skip('builds refs/pull/648/head', async () => {
|
it.skip('builds refs/pull/648/head', async () => {
|
||||||
const buildxBin = await buildx.build('https://github.com/docker/buildx.git#refs/pull/648/head', tmpDir);
|
const buildxBin = await buildx.build('https://github.com/docker/buildx.git#refs/pull/648/head', tmpDir, false);
|
||||||
expect(fs.existsSync(buildxBin)).toBe(true);
|
expect(fs.existsSync(buildxBin)).toBe(true);
|
||||||
}, 100000);
|
}, 100000);
|
||||||
|
|
||||||
// eslint-disable-next-line jest/no-disabled-tests
|
// eslint-disable-next-line jest/no-disabled-tests
|
||||||
it.skip('builds 67bd6f4dc82a9cd96f34133dab3f6f7af803bb14', async () => {
|
it.skip('builds 67bd6f4dc82a9cd96f34133dab3f6f7af803bb14', async () => {
|
||||||
const buildxBin = await buildx.build('https://github.com/docker/buildx.git#67bd6f4dc82a9cd96f34133dab3f6f7af803bb14', tmpDir);
|
const buildxBin = await buildx.build('https://github.com/docker/buildx.git#67bd6f4dc82a9cd96f34133dab3f6f7af803bb14', tmpDir, false);
|
||||||
expect(fs.existsSync(buildxBin)).toBe(true);
|
expect(fs.existsSync(buildxBin)).toBe(true);
|
||||||
}, 100000);
|
}, 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-'));
|
||||||
|
test.each([
|
||||||
it('acquires v0.4.1 version of buildx', async () => {
|
['v0.4.1', false],
|
||||||
const buildxBin = await buildx.install('v0.4.1', tmpDir);
|
['latest', false],
|
||||||
|
['v0.4.1', true],
|
||||||
|
['latest', true]
|
||||||
|
])(
|
||||||
|
'acquires %p of buildx (standalone: %p)',
|
||||||
|
async (version, standalone) => {
|
||||||
|
const buildxBin = await buildx.install(version, tmpDir, standalone);
|
||||||
expect(fs.existsSync(buildxBin)).toBe(true);
|
expect(fs.existsSync(buildxBin)).toBe(true);
|
||||||
}, 100000);
|
},
|
||||||
|
100000
|
||||||
it('acquires latest version of buildx', async () => {
|
);
|
||||||
const buildxBin = await buildx.install('latest', tmpDir);
|
|
||||||
expect(fs.existsSync(buildxBin)).toBe(true);
|
|
||||||
}, 100000);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getConfig', () => {
|
describe('getConfig', () => {
|
||||||
|
|
16
__tests__/docker.test.ts
Normal file
16
__tests__/docker.test.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import {describe, expect, it, jest} from '@jest/globals';
|
||||||
|
import * as docker from '../src/docker';
|
||||||
|
import * as exec from '@actions/exec';
|
||||||
|
|
||||||
|
describe('isAvailable', () => {
|
||||||
|
it('cli', () => {
|
||||||
|
const execSpy = jest.spyOn(exec, 'getExecOutput');
|
||||||
|
docker.isAvailable();
|
||||||
|
|
||||||
|
// eslint-disable-next-line jest/no-standalone-expect
|
||||||
|
expect(execSpy).toHaveBeenCalledWith(`docker`, undefined, {
|
||||||
|
silent: true,
|
||||||
|
ignoreReturnCode: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
4
dist/index.js
generated
vendored
4
dist/index.js
generated
vendored
File diff suppressed because one or more lines are too long
2
dist/index.js.map
generated
vendored
2
dist/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
|
@ -41,9 +41,10 @@ export async function getConfig(s: string, file: boolean): Promise<string> {
|
||||||
return configFile;
|
return configFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isAvailable(): Promise<boolean> {
|
export async function isAvailable(standalone?: boolean): Promise<boolean> {
|
||||||
|
const cmd = getCommand([], standalone);
|
||||||
return await exec
|
return await exec
|
||||||
.getExecOutput('docker', ['buildx'], {
|
.getExecOutput(cmd.commandLine, cmd.args, {
|
||||||
ignoreReturnCode: true,
|
ignoreReturnCode: true,
|
||||||
silent: true
|
silent: true
|
||||||
})
|
})
|
||||||
|
@ -52,12 +53,17 @@ export async function isAvailable(): Promise<boolean> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return res.exitCode == 0;
|
return res.exitCode == 0;
|
||||||
|
})
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
.catch(error => {
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getVersion(): Promise<string> {
|
export async function getVersion(standalone?: boolean): Promise<string> {
|
||||||
|
const cmd = getCommand(['version'], standalone);
|
||||||
return await exec
|
return await exec
|
||||||
.getExecOutput('docker', ['buildx', 'version'], {
|
.getExecOutput(cmd.commandLine, cmd.args, {
|
||||||
ignoreReturnCode: true,
|
ignoreReturnCode: true,
|
||||||
silent: true
|
silent: true
|
||||||
})
|
})
|
||||||
|
@ -81,9 +87,10 @@ export function satisfies(version: string, range: string): boolean {
|
||||||
return semver.satisfies(version, range) || /^[0-9a-f]{7}$/.exec(version) !== null;
|
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, standalone?: boolean): Promise<Builder> {
|
||||||
|
const cmd = getCommand(['inspect', name], standalone);
|
||||||
return await exec
|
return await exec
|
||||||
.getExecOutput(`docker`, ['buildx', 'inspect', name], {
|
.getExecOutput(cmd.commandLine, cmd.args, {
|
||||||
ignoreReturnCode: true,
|
ignoreReturnCode: true,
|
||||||
silent: true
|
silent: true
|
||||||
})
|
})
|
||||||
|
@ -133,7 +140,7 @@ export async function inspect(name: string): Promise<Builder> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function build(inputBuildRef: string, dockerConfigHome: string): Promise<string> {
|
export async function build(inputBuildRef: string, dest: string, standalone: boolean): Promise<string> {
|
||||||
// eslint-disable-next-line prefer-const
|
// eslint-disable-next-line prefer-const
|
||||||
let [repo, ref] = inputBuildRef.split('#');
|
let [repo, ref] = inputBuildRef.split('#');
|
||||||
if (ref.length == 0) {
|
if (ref.length == 0) {
|
||||||
|
@ -152,8 +159,27 @@ export async function build(inputBuildRef: string, dockerConfigHome: string): Pr
|
||||||
toolPath = tc.find('buildx', vspec);
|
toolPath = tc.find('buildx', vspec);
|
||||||
if (!toolPath) {
|
if (!toolPath) {
|
||||||
const outFolder = path.join(context.tmpDir(), 'out').split(path.sep).join(path.posix.sep);
|
const outFolder = path.join(context.tmpDir(), 'out').split(path.sep).join(path.posix.sep);
|
||||||
|
let buildWithStandalone = false;
|
||||||
|
const standaloneFound = await isAvailable(true);
|
||||||
|
const pluginFound = await isAvailable(false);
|
||||||
|
if (standalone && standaloneFound) {
|
||||||
|
core.debug(`Buildx standalone found, build with it`);
|
||||||
|
buildWithStandalone = true;
|
||||||
|
} else if (!standalone && pluginFound) {
|
||||||
|
core.debug(`Buildx plugin found, build with it`);
|
||||||
|
buildWithStandalone = false;
|
||||||
|
} else if (standaloneFound) {
|
||||||
|
core.debug(`Buildx plugin not found, but standalone found so trying to build with it`);
|
||||||
|
buildWithStandalone = true;
|
||||||
|
} else if (pluginFound) {
|
||||||
|
core.debug(`Buildx standalone not found, but plugin found so trying to build with it`);
|
||||||
|
buildWithStandalone = false;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Neither buildx standalone or plugin have been found to build from ref`);
|
||||||
|
}
|
||||||
|
const buildCmd = getCommand(['build', '--target', 'binaries', '--build-arg', 'BUILDKIT_CONTEXT_KEEP_GIT_DIR=1', '--output', `type=local,dest=${outFolder}`, inputBuildRef], buildWithStandalone);
|
||||||
toolPath = await exec
|
toolPath = await exec
|
||||||
.getExecOutput('docker', ['buildx', 'build', '--target', 'binaries', '--build-arg', 'BUILDKIT_CONTEXT_KEEP_GIT_DIR=1', '--output', `type=local,dest=${outFolder}`, inputBuildRef], {
|
.getExecOutput(buildCmd.commandLine, buildCmd.args, {
|
||||||
ignoreReturnCode: true
|
ignoreReturnCode: true
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
|
@ -164,10 +190,13 @@ export async function build(inputBuildRef: string, dockerConfigHome: string): Pr
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return setPlugin(toolPath, dockerConfigHome);
|
if (standalone) {
|
||||||
|
return setStandalone(toolPath, dest);
|
||||||
|
}
|
||||||
|
return setPlugin(toolPath, dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function install(inputVersion: string, dockerConfigHome: string): Promise<string> {
|
export async function install(inputVersion: string, dest: string, standalone: boolean): Promise<string> {
|
||||||
const release: github.GitHubRelease | null = await github.getRelease(inputVersion);
|
const release: github.GitHubRelease | null = await github.getRelease(inputVersion);
|
||||||
if (!release) {
|
if (!release) {
|
||||||
throw new Error(`Cannot find buildx ${inputVersion} release`);
|
throw new Error(`Cannot find buildx ${inputVersion} release`);
|
||||||
|
@ -185,10 +214,40 @@ export async function install(inputVersion: string, dockerConfigHome: string): P
|
||||||
toolPath = await download(version);
|
toolPath = await download(version);
|
||||||
}
|
}
|
||||||
|
|
||||||
return setPlugin(toolPath, dockerConfigHome);
|
if (standalone) {
|
||||||
|
return setStandalone(toolPath, dest);
|
||||||
|
}
|
||||||
|
return setPlugin(toolPath, dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setStandalone(toolPath: string, dest: string): Promise<string> {
|
||||||
|
core.info('Standalone mode');
|
||||||
|
const toolBinPath = path.join(toolPath, context.osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx');
|
||||||
|
|
||||||
|
const binDir = path.join(dest, 'bin');
|
||||||
|
core.debug(`Bin dir is ${binDir}`);
|
||||||
|
if (!fs.existsSync(binDir)) {
|
||||||
|
fs.mkdirSync(binDir, {recursive: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
const filename: string = context.osPlat == 'win32' ? 'buildx.exe' : 'buildx';
|
||||||
|
const buildxPath: string = path.join(binDir, filename);
|
||||||
|
core.debug(`Bin path is ${buildxPath}`);
|
||||||
|
fs.copyFileSync(toolBinPath, buildxPath);
|
||||||
|
|
||||||
|
core.info('Fixing perms');
|
||||||
|
fs.chmodSync(buildxPath, '0755');
|
||||||
|
|
||||||
|
core.addPath(binDir);
|
||||||
|
core.info('Added buildx to the path');
|
||||||
|
|
||||||
|
return buildxPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setPlugin(toolPath: string, dockerConfigHome: string): Promise<string> {
|
async function setPlugin(toolPath: string, dockerConfigHome: string): Promise<string> {
|
||||||
|
core.info('Docker plugin mode');
|
||||||
|
const toolBinPath = path.join(toolPath, context.osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx');
|
||||||
|
|
||||||
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)) {
|
||||||
|
@ -198,7 +257,7 @@ async function setPlugin(toolPath: string, dockerConfigHome: string): Promise<st
|
||||||
const filename: string = context.osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx';
|
const filename: string = context.osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx';
|
||||||
const pluginPath: string = path.join(pluginsDir, filename);
|
const pluginPath: string = path.join(pluginsDir, filename);
|
||||||
core.debug(`Plugin path is ${pluginPath}`);
|
core.debug(`Plugin path is ${pluginPath}`);
|
||||||
fs.copyFileSync(path.join(toolPath, filename), pluginPath);
|
fs.copyFileSync(toolBinPath, pluginPath);
|
||||||
|
|
||||||
core.info('Fixing perms');
|
core.info('Fixing perms');
|
||||||
fs.chmodSync(pluginPath, '0755');
|
fs.chmodSync(pluginPath, '0755');
|
||||||
|
@ -269,3 +328,10 @@ export async function getBuildKitVersion(containerID: string): Promise<string> {
|
||||||
return bkitimage.stdout.trim();
|
return bkitimage.stdout.trim();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCommand(args: Array<string>, standalone?: boolean) {
|
||||||
|
return {
|
||||||
|
commandLine: standalone ? 'buildx' : 'docker',
|
||||||
|
args: standalone ? args : ['buildx', ...args]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
19
src/docker.ts
Normal file
19
src/docker.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import * as exec from '@actions/exec';
|
||||||
|
|
||||||
|
export async function isAvailable(): Promise<boolean> {
|
||||||
|
return await exec
|
||||||
|
.getExecOutput('docker', undefined, {
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
65
src/main.ts
65
src/main.ts
|
@ -3,6 +3,7 @@ import * as path from 'path';
|
||||||
import * as uuid from 'uuid';
|
import * as uuid from 'uuid';
|
||||||
import * as buildx from './buildx';
|
import * as buildx from './buildx';
|
||||||
import * as context from './context';
|
import * as context from './context';
|
||||||
|
import * as docker from './docker';
|
||||||
import * as stateHelper from './state-helper';
|
import * as stateHelper from './state-helper';
|
||||||
import * as util from './util';
|
import * as util from './util';
|
||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
|
@ -10,32 +11,54 @@ import * as exec from '@actions/exec';
|
||||||
|
|
||||||
async function run(): Promise<void> {
|
async function run(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
core.startGroup(`Docker info`);
|
|
||||||
await exec.exec('docker', ['version']);
|
|
||||||
await exec.exec('docker', ['info']);
|
|
||||||
core.endGroup();
|
|
||||||
|
|
||||||
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 (util.isValidUrl(inputs.version)) {
|
// standalone if docker cli not available
|
||||||
core.startGroup(`Build and install buildx`);
|
const standalone = !(await docker.isAvailable());
|
||||||
await buildx.build(inputs.version, dockerConfigHome);
|
stateHelper.setStandalone(standalone);
|
||||||
|
|
||||||
|
core.startGroup(`Docker info`);
|
||||||
|
if (standalone) {
|
||||||
|
core.info(`Docker info skipped in standalone mode`);
|
||||||
|
} else {
|
||||||
|
await exec.exec('docker', ['version'], {
|
||||||
|
failOnStdErr: false
|
||||||
|
});
|
||||||
|
await exec.exec('docker', ['info'], {
|
||||||
|
failOnStdErr: false
|
||||||
|
});
|
||||||
|
}
|
||||||
core.endGroup();
|
core.endGroup();
|
||||||
} else if (!(await buildx.isAvailable()) || inputs.version) {
|
|
||||||
|
if (util.isValidUrl(inputs.version)) {
|
||||||
|
if (standalone) {
|
||||||
|
throw new Error(`Cannot build from source without the Docker CLI`);
|
||||||
|
}
|
||||||
|
core.startGroup(`Build and install buildx`);
|
||||||
|
await buildx.build(inputs.version, dockerConfigHome, standalone);
|
||||||
|
core.endGroup();
|
||||||
|
} else if (!(await buildx.isAvailable(standalone)) || inputs.version) {
|
||||||
core.startGroup(`Download and install buildx`);
|
core.startGroup(`Download and install buildx`);
|
||||||
await buildx.install(inputs.version || 'latest', dockerConfigHome);
|
await buildx.install(inputs.version || 'latest', standalone ? context.tmpDir() : dockerConfigHome, standalone);
|
||||||
core.endGroup();
|
core.endGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildxVersion = await buildx.getVersion();
|
const buildxVersion = await buildx.getVersion(standalone);
|
||||||
|
await core.group(`Buildx version`, async () => {
|
||||||
|
const versionCmd = buildx.getCommand(['version'], standalone);
|
||||||
|
await exec.exec(versionCmd.commandLine, versionCmd.args, {
|
||||||
|
failOnStdErr: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const builderName: string = inputs.driver == 'docker' ? 'default' : `builder-${uuid.v4()}`;
|
const builderName: string = inputs.driver == 'docker' ? 'default' : `builder-${uuid.v4()}`;
|
||||||
context.setOutput('name', builderName);
|
context.setOutput('name', builderName);
|
||||||
stateHelper.setBuilderName(builderName);
|
stateHelper.setBuilderName(builderName);
|
||||||
|
|
||||||
if (inputs.driver !== 'docker') {
|
if (inputs.driver !== 'docker') {
|
||||||
core.startGroup(`Creating a new builder instance`);
|
core.startGroup(`Creating a new builder instance`);
|
||||||
const createArgs: Array<string> = ['buildx', 'create', '--name', builderName, '--driver', inputs.driver];
|
const createArgs: Array<string> = ['create', '--name', builderName, '--driver', inputs.driver];
|
||||||
if (buildx.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);
|
||||||
|
@ -55,26 +78,31 @@ async function run(): Promise<void> {
|
||||||
} else if (inputs.configInline) {
|
} else if (inputs.configInline) {
|
||||||
createArgs.push('--config', await buildx.getConfigInline(inputs.configInline));
|
createArgs.push('--config', await buildx.getConfigInline(inputs.configInline));
|
||||||
}
|
}
|
||||||
await exec.exec('docker', createArgs);
|
const createCmd = buildx.getCommand(createArgs, standalone);
|
||||||
|
await exec.exec(createCmd.commandLine, createCmd.args);
|
||||||
core.endGroup();
|
core.endGroup();
|
||||||
|
|
||||||
core.startGroup(`Booting builder`);
|
core.startGroup(`Booting builder`);
|
||||||
const bootstrapArgs: Array<string> = ['buildx', 'inspect', '--bootstrap'];
|
const bootstrapArgs: Array<string> = ['inspect', '--bootstrap'];
|
||||||
if (buildx.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);
|
const bootstrapCmd = buildx.getCommand(bootstrapArgs, standalone);
|
||||||
|
await exec.exec(bootstrapCmd.commandLine, bootstrapCmd.args);
|
||||||
core.endGroup();
|
core.endGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputs.install) {
|
if (inputs.install) {
|
||||||
|
if (standalone) {
|
||||||
|
throw new Error(`Cannot set buildx as default builder without the Docker CLI`);
|
||||||
|
}
|
||||||
core.startGroup(`Setting buildx as default builder`);
|
core.startGroup(`Setting buildx as default builder`);
|
||||||
await exec.exec('docker', ['buildx', 'install']);
|
await exec.exec('docker', ['buildx', 'install']);
|
||||||
core.endGroup();
|
core.endGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
core.startGroup(`Inspect builder`);
|
core.startGroup(`Inspect builder`);
|
||||||
const builder = await buildx.inspect(builderName);
|
const builder = await buildx.inspect(builderName, standalone);
|
||||||
core.info(JSON.stringify(builder, undefined, 2));
|
core.info(JSON.stringify(builder, undefined, 2));
|
||||||
context.setOutput('driver', builder.driver);
|
context.setOutput('driver', builder.driver);
|
||||||
context.setOutput('endpoint', builder.node_endpoint);
|
context.setOutput('endpoint', builder.node_endpoint);
|
||||||
|
@ -83,7 +111,7 @@ async function run(): Promise<void> {
|
||||||
context.setOutput('platforms', builder.node_platforms);
|
context.setOutput('platforms', builder.node_platforms);
|
||||||
core.endGroup();
|
core.endGroup();
|
||||||
|
|
||||||
if (inputs.driver == 'docker-container') {
|
if (!standalone && inputs.driver == 'docker-container') {
|
||||||
stateHelper.setContainerName(`buildx_buildkit_${builder.node_name}`);
|
stateHelper.setContainerName(`buildx_buildkit_${builder.node_name}`);
|
||||||
core.startGroup(`BuildKit version`);
|
core.startGroup(`BuildKit version`);
|
||||||
core.info(await buildx.getBuildKitVersion(`buildx_buildkit_${builder.node_name}`));
|
core.info(await buildx.getBuildKitVersion(`buildx_buildkit_${builder.node_name}`));
|
||||||
|
@ -114,8 +142,9 @@ async function cleanup(): Promise<void> {
|
||||||
|
|
||||||
if (stateHelper.builderName.length > 0) {
|
if (stateHelper.builderName.length > 0) {
|
||||||
core.startGroup(`Removing builder`);
|
core.startGroup(`Removing builder`);
|
||||||
|
const rmCmd = buildx.getCommand(['rm', stateHelper.builderName], /true/i.test(stateHelper.standalone));
|
||||||
await exec
|
await exec
|
||||||
.getExecOutput('docker', ['buildx', 'rm', `${stateHelper.builderName}`], {
|
.getExecOutput(rmCmd.commandLine, rmCmd.args, {
|
||||||
ignoreReturnCode: true
|
ignoreReturnCode: true
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import * as core from '@actions/core';
|
||||||
|
|
||||||
export const IsPost = !!process.env['STATE_isPost'];
|
export const IsPost = !!process.env['STATE_isPost'];
|
||||||
export const IsDebug = !!process.env['STATE_isDebug'];
|
export const IsDebug = !!process.env['STATE_isDebug'];
|
||||||
|
export const standalone = process.env['STATE_standalone'] || '';
|
||||||
export const builderName = process.env['STATE_builderName'] || '';
|
export const builderName = process.env['STATE_builderName'] || '';
|
||||||
export const containerName = process.env['STATE_containerName'] || '';
|
export const containerName = process.env['STATE_containerName'] || '';
|
||||||
|
|
||||||
|
@ -9,6 +10,10 @@ export function setDebug(debug: string) {
|
||||||
core.saveState('isDebug', debug);
|
core.saveState('isDebug', debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setStandalone(standalone: boolean) {
|
||||||
|
core.saveState('standalone', standalone);
|
||||||
|
}
|
||||||
|
|
||||||
export function setBuilderName(builderName: string) {
|
export function setBuilderName(builderName: string) {
|
||||||
core.saveState('builderName', builderName);
|
core.saveState('builderName', builderName);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue