mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-08-18 01:35:54 +08:00
Compare commits
11 Commits
v0.9.0-rc1
...
v0.8
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6224def4dd | ||
![]() |
5abee699a6 | ||
![]() |
54e497dfba | ||
![]() |
dfbd226285 | ||
![]() |
a78c2957a2 | ||
![]() |
e80890ec89 | ||
![]() |
10fe8f380c | ||
![]() |
5fac64c2c4 | ||
![]() |
24ad37a5d2 | ||
![]() |
106651877d | ||
![]() |
35bcd88f08 |
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@@ -1,10 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
open-pull-requests-limit: 10
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
labels:
|
|
||||||
- "dependencies"
|
|
||||||
- "bot"
|
|
39
.github/workflows/build.yml
vendored
39
.github/workflows/build.yml
vendored
@@ -1,9 +1,5 @@
|
|||||||
name: build
|
name: build
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
@@ -27,37 +23,29 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v2
|
||||||
-
|
-
|
||||||
name: Set up QEMU
|
name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v1
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v1
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
-
|
-
|
||||||
name: Test
|
name: Test
|
||||||
run: |
|
run: |
|
||||||
make test
|
make test
|
||||||
-
|
-
|
||||||
name: Send to Codecov
|
name: Send to Codecov
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v2
|
||||||
with:
|
with:
|
||||||
file: ./coverage/coverage.txt
|
file: ./coverage/coverage.txt
|
||||||
-
|
|
||||||
name: Expose GitHub Runtime
|
|
||||||
uses: crazy-max/ghaction-github-runtime@906832f62b7baa936e3fbef72b029308af505ee7
|
|
||||||
-
|
-
|
||||||
name: Build binaries
|
name: Build binaries
|
||||||
run: |
|
run: |
|
||||||
make release
|
make release
|
||||||
env:
|
|
||||||
CACHE_FROM: type=gha,scope=release
|
|
||||||
CACHE_TO: type=gha,scope=release
|
|
||||||
-
|
-
|
||||||
name: Upload artifacts
|
name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: buildx
|
name: buildx
|
||||||
path: ${{ env.RELEASE_OUT }}/*
|
path: ${{ env.RELEASE_OUT }}/*
|
||||||
@@ -65,7 +53,7 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Docker meta
|
name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v3
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
${{ env.REPO_SLUG }}
|
${{ env.REPO_SLUG }}
|
||||||
@@ -77,13 +65,13 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Login to DockerHub
|
name: Login to DockerHub
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v1
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
-
|
-
|
||||||
name: Build and push image
|
name: Build and push image
|
||||||
uses: docker/bake-action@v2
|
uses: docker/bake-action@v1
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
./docker-bake.hcl
|
./docker-bake.hcl
|
||||||
@@ -93,7 +81,7 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: GitHub Release
|
name: GitHub Release
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5
|
uses: softprops/action-gh-release@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
@@ -106,20 +94,19 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v2
|
||||||
-
|
-
|
||||||
name: Set up QEMU
|
name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v1
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v1
|
||||||
with:
|
with:
|
||||||
version: latest
|
|
||||||
driver-opts: image=moby/buildkit:master
|
driver-opts: image=moby/buildkit:master
|
||||||
buildkitd-flags: --debug
|
buildkitd-flags: --debug
|
||||||
-
|
-
|
||||||
# Just run a bake target to check eveything runs fine
|
# Just run a bake target to check eveything runs fine
|
||||||
name: Build
|
name: Build
|
||||||
uses: docker/bake-action@v2
|
uses: docker/bake-action@v1
|
||||||
with:
|
with:
|
||||||
targets: binaries-cross
|
targets: binaries-cross
|
||||||
|
56
.github/workflows/docs.yml
vendored
56
.github/workflows/docs.yml
vendored
@@ -1,56 +0,0 @@
|
|||||||
name: docs
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [ published ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
open-pr:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Checkout docs repo
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
|
|
||||||
repository: docker/docker.github.io
|
|
||||||
ref: master
|
|
||||||
-
|
|
||||||
name: Prepare
|
|
||||||
run: |
|
|
||||||
rm -rf ./_data/buildx/*
|
|
||||||
-
|
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
-
|
|
||||||
name: Build docs
|
|
||||||
uses: docker/bake-action@v2
|
|
||||||
with:
|
|
||||||
source: ${{ github.server_url }}/${{ github.repository }}.git#${{ github.event.release.name }}
|
|
||||||
targets: update-docs
|
|
||||||
set: |
|
|
||||||
*.output=/tmp/buildx-docs
|
|
||||||
env:
|
|
||||||
DOCS_FORMATS: yaml
|
|
||||||
-
|
|
||||||
name: Copy files
|
|
||||||
run: |
|
|
||||||
cp /tmp/buildx-docs/out/reference/*.yaml ./_data/buildx/
|
|
||||||
-
|
|
||||||
name: Commit changes
|
|
||||||
run: |
|
|
||||||
git add -A .
|
|
||||||
-
|
|
||||||
name: Create PR on docs repo
|
|
||||||
uses: peter-evans/create-pull-request@923ad837f191474af6b1721408744feb989a4c27 # v4.0.4
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
|
|
||||||
push-to-fork: docker-tools-robot/docker.github.io
|
|
||||||
commit-message: "build: update buildx reference to ${{ github.event.release.name }}"
|
|
||||||
signoff: true
|
|
||||||
branch: dispatch/buildx-ref-${{ github.event.release.name }}
|
|
||||||
delete-branch: true
|
|
||||||
title: Update buildx reference to ${{ github.event.release.name }}
|
|
||||||
body: |
|
|
||||||
Update the buildx reference documentation to keep in sync with the latest release `${{ github.event.release.name }}`
|
|
||||||
draft: false
|
|
73
.github/workflows/e2e.yml
vendored
73
.github/workflows/e2e.yml
vendored
@@ -1,9 +1,5 @@
|
|||||||
name: e2e
|
name: e2e
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
@@ -16,44 +12,8 @@ on:
|
|||||||
- 'v[0-9]*'
|
- 'v[0-9]*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
env:
|
|
||||||
BIN_OUT: ./bin
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
-
|
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
-
|
|
||||||
name: Build
|
|
||||||
uses: docker/bake-action@v2
|
|
||||||
with:
|
|
||||||
targets: binaries
|
|
||||||
set: |
|
|
||||||
*.cache-from=type=gha,scope=release
|
|
||||||
*.cache-from=type=gha,scope=binaries
|
|
||||||
*.cache-to=type=gha,scope=binaries
|
|
||||||
-
|
|
||||||
name: Rename binary
|
|
||||||
run: |
|
|
||||||
mv ${{ env.BIN_OUT }}/buildx ${{ env.BIN_OUT }}/docker-buildx
|
|
||||||
-
|
|
||||||
name: Upload artifacts
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: binary
|
|
||||||
path: ${{ env.BIN_OUT }}
|
|
||||||
if-no-files-found: error
|
|
||||||
retention-days: 7
|
|
||||||
|
|
||||||
driver:
|
driver:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
needs:
|
|
||||||
- build
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -61,7 +21,6 @@ jobs:
|
|||||||
- docker
|
- docker
|
||||||
- docker-container
|
- docker-container
|
||||||
- kubernetes
|
- kubernetes
|
||||||
- remote
|
|
||||||
buildkit:
|
buildkit:
|
||||||
- moby/buildkit:buildx-stable-1
|
- moby/buildkit:buildx-stable-1
|
||||||
- moby/buildkit:master
|
- moby/buildkit:master
|
||||||
@@ -77,8 +36,6 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- driver: kubernetes
|
- driver: kubernetes
|
||||||
driver-opt: qemu.install=true
|
driver-opt: qemu.install=true
|
||||||
- driver: remote
|
|
||||||
endpoint: tcp://localhost:1234
|
|
||||||
exclude:
|
exclude:
|
||||||
- driver: docker
|
- driver: docker
|
||||||
multi-node: mnode-true
|
multi-node: mnode-true
|
||||||
@@ -86,28 +43,18 @@ jobs:
|
|||||||
buildkit-cfg: bkcfg-true
|
buildkit-cfg: bkcfg-true
|
||||||
- driver: docker-container
|
- driver: docker-container
|
||||||
multi-node: mnode-true
|
multi-node: mnode-true
|
||||||
- driver: remote
|
|
||||||
multi-node: mnode-true
|
|
||||||
- driver: remote
|
|
||||||
buildkit-cfg: bkcfg-true
|
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v2
|
||||||
-
|
-
|
||||||
name: Set up QEMU
|
name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v1
|
||||||
if: matrix.driver == 'docker' || matrix.driver == 'docker-container'
|
if: matrix.driver == 'docker' || matrix.driver == 'docker-container'
|
||||||
-
|
-
|
||||||
name: Install buildx
|
name: Install buildx
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: binary
|
|
||||||
path: /home/runner/.docker/cli-plugins
|
|
||||||
-
|
|
||||||
name: Fix perms and check
|
|
||||||
run: |
|
run: |
|
||||||
chmod +x /home/runner/.docker/cli-plugins/docker-buildx
|
make install
|
||||||
docker buildx version
|
docker buildx version
|
||||||
-
|
-
|
||||||
name: Init env vars
|
name: Init env vars
|
||||||
@@ -129,7 +76,7 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Install k3s
|
name: Install k3s
|
||||||
if: matrix.driver == 'kubernetes'
|
if: matrix.driver == 'kubernetes'
|
||||||
uses: debianmaster/actions-k3s@b9cf3f599fd118699a3c8a0d18a2f2bda6cf4ce4
|
uses: debianmaster/actions-k3s@v1.0.3
|
||||||
id: k3s
|
id: k3s
|
||||||
with:
|
with:
|
||||||
version: v1.21.2-k3s1
|
version: v1.21.2-k3s1
|
||||||
@@ -143,17 +90,6 @@ jobs:
|
|||||||
if: matrix.driver == 'kubernetes'
|
if: matrix.driver == 'kubernetes'
|
||||||
run: |
|
run: |
|
||||||
kubectl get nodes
|
kubectl get nodes
|
||||||
-
|
|
||||||
name: Launch remote buildkitd
|
|
||||||
if: matrix.driver == 'remote'
|
|
||||||
run: |
|
|
||||||
docker run -d \
|
|
||||||
--privileged \
|
|
||||||
--name=remote-buildkit \
|
|
||||||
-p 1234:1234 \
|
|
||||||
${{ matrix.buildkit }} \
|
|
||||||
--addr unix:///run/buildkit/buildkitd.sock \
|
|
||||||
--addr tcp://0.0.0.0:1234
|
|
||||||
-
|
-
|
||||||
name: Test
|
name: Test
|
||||||
run: |
|
run: |
|
||||||
@@ -162,5 +98,4 @@ jobs:
|
|||||||
BUILDKIT_IMAGE: ${{ matrix.buildkit }}
|
BUILDKIT_IMAGE: ${{ matrix.buildkit }}
|
||||||
DRIVER: ${{ matrix.driver }}
|
DRIVER: ${{ matrix.driver }}
|
||||||
DRIVER_OPT: ${{ matrix.driver-opt }}
|
DRIVER_OPT: ${{ matrix.driver-opt }}
|
||||||
ENDPOINT: ${{ matrix.endpoint }}
|
|
||||||
PLATFORMS: ${{ matrix.platforms }}
|
PLATFORMS: ${{ matrix.platforms }}
|
||||||
|
25
.github/workflows/godev.yml
vendored
Normal file
25
.github/workflows/godev.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Workflow used to make a request to proxy.golang.org to refresh cache on https://pkg.go.dev/github.com/docker/buildx
|
||||||
|
# when a released of buildx is produced
|
||||||
|
name: godev
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Set up Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.13
|
||||||
|
-
|
||||||
|
name: Call pkg.go.dev
|
||||||
|
run: |
|
||||||
|
go get github.com/${GITHUB_REPOSITORY}@${GITHUB_REF#refs/tags/}
|
||||||
|
env:
|
||||||
|
GO111MODULE: on
|
||||||
|
GOPROXY: https://proxy.golang.org
|
18
.github/workflows/validate.yml
vendored
18
.github/workflows/validate.yml
vendored
@@ -1,9 +1,5 @@
|
|||||||
name: validate
|
name: validate
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
@@ -30,12 +26,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v2
|
||||||
-
|
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
-
|
-
|
||||||
name: Run
|
name: Run
|
||||||
run: |
|
run: |
|
||||||
@@ -48,12 +39,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v2
|
||||||
-
|
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
-
|
-
|
||||||
name: Run
|
name: Run
|
||||||
run: |
|
run: |
|
||||||
|
@@ -12,29 +12,19 @@ linters:
|
|||||||
- gofmt
|
- gofmt
|
||||||
- govet
|
- govet
|
||||||
- deadcode
|
- deadcode
|
||||||
- depguard
|
|
||||||
- goimports
|
- goimports
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- misspell
|
- misspell
|
||||||
- unused
|
- unused
|
||||||
- varcheck
|
- varcheck
|
||||||
- revive
|
- golint
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- typecheck
|
- typecheck
|
||||||
- structcheck
|
- structcheck
|
||||||
disable-all: true
|
disable-all: true
|
||||||
|
|
||||||
linters-settings:
|
|
||||||
depguard:
|
|
||||||
list-type: blacklist
|
|
||||||
include-go-root: true
|
|
||||||
packages:
|
|
||||||
# The io/ioutil package has been deprecated.
|
|
||||||
# https://go.dev/doc/go1.16#ioutil
|
|
||||||
- io/ioutil
|
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
- linters:
|
- linters:
|
||||||
- revive
|
- golint
|
||||||
text: "stutters"
|
text: "stutters"
|
||||||
|
13
.yamllint.yml
Normal file
13
.yamllint.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
ignore: |
|
||||||
|
/vendor
|
||||||
|
|
||||||
|
extends: default
|
||||||
|
|
||||||
|
yaml-files:
|
||||||
|
- '*.yaml'
|
||||||
|
- '*.yml'
|
||||||
|
|
||||||
|
rules:
|
||||||
|
truthy: disable
|
||||||
|
line-length: disable
|
||||||
|
document-start: disable
|
17
Dockerfile
17
Dockerfile
@@ -1,13 +1,12 @@
|
|||||||
# syntax=docker/dockerfile:1.4
|
# syntax=docker/dockerfile:1.3
|
||||||
|
|
||||||
ARG GO_VERSION=1.18
|
ARG GO_VERSION=1.17
|
||||||
ARG XX_VERSION=1.1.2
|
ARG DOCKERD_VERSION=20.10.8
|
||||||
ARG DOCKERD_VERSION=20.10.14
|
|
||||||
|
|
||||||
FROM docker:$DOCKERD_VERSION AS dockerd-release
|
FROM docker:$DOCKERD_VERSION AS dockerd-release
|
||||||
|
|
||||||
# xx is a helper for cross-compilation
|
# xx is a helper for cross-compilation
|
||||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.0.0 AS xx
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS golatest
|
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS golatest
|
||||||
|
|
||||||
@@ -15,7 +14,6 @@ FROM golatest AS gobase
|
|||||||
COPY --from=xx / /
|
COPY --from=xx / /
|
||||||
RUN apk add --no-cache file git
|
RUN apk add --no-cache file git
|
||||||
ENV GOFLAGS=-mod=vendor
|
ENV GOFLAGS=-mod=vendor
|
||||||
ENV CGO_ENABLED=0
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
FROM gobase AS buildx-version
|
FROM gobase AS buildx-version
|
||||||
@@ -25,6 +23,7 @@ RUN --mount=target=. \
|
|||||||
echo -n "${VERSION}" | tee /tmp/.version;
|
echo -n "${VERSION}" | tee /tmp/.version;
|
||||||
|
|
||||||
FROM gobase AS buildx-build
|
FROM gobase AS buildx-build
|
||||||
|
ENV CGO_ENABLED=0
|
||||||
ARG LDFLAGS="-w -s"
|
ARG LDFLAGS="-w -s"
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
RUN --mount=type=bind,target=. \
|
RUN --mount=type=bind,target=. \
|
||||||
@@ -34,7 +33,7 @@ RUN --mount=type=bind,target=. \
|
|||||||
set -x; xx-go build -ldflags "$(cat /tmp/.ldflags) ${LDFLAGS}" -o /usr/bin/buildx ./cmd/buildx && \
|
set -x; xx-go build -ldflags "$(cat /tmp/.ldflags) ${LDFLAGS}" -o /usr/bin/buildx ./cmd/buildx && \
|
||||||
xx-verify --static /usr/bin/buildx
|
xx-verify --static /usr/bin/buildx
|
||||||
|
|
||||||
FROM gobase AS test
|
FROM buildx-build AS test
|
||||||
RUN --mount=type=bind,target=. \
|
RUN --mount=type=bind,target=. \
|
||||||
--mount=type=cache,target=/root/.cache \
|
--mount=type=cache,target=/root/.cache \
|
||||||
--mount=type=cache,target=/go/pkg/mod \
|
--mount=type=cache,target=/go/pkg/mod \
|
||||||
@@ -45,13 +44,13 @@ FROM scratch AS test-coverage
|
|||||||
COPY --from=test /tmp/coverage.txt /coverage.txt
|
COPY --from=test /tmp/coverage.txt /coverage.txt
|
||||||
|
|
||||||
FROM scratch AS binaries-unix
|
FROM scratch AS binaries-unix
|
||||||
COPY --link --from=buildx-build /usr/bin/buildx /
|
COPY --from=buildx-build /usr/bin/buildx /
|
||||||
|
|
||||||
FROM binaries-unix AS binaries-darwin
|
FROM binaries-unix AS binaries-darwin
|
||||||
FROM binaries-unix AS binaries-linux
|
FROM binaries-unix AS binaries-linux
|
||||||
|
|
||||||
FROM scratch AS binaries-windows
|
FROM scratch AS binaries-windows
|
||||||
COPY --link --from=buildx-build /usr/bin/buildx /buildx.exe
|
COPY --from=buildx-build /usr/bin/buildx /buildx.exe
|
||||||
|
|
||||||
FROM binaries-$TARGETOS AS binaries
|
FROM binaries-$TARGETOS AS binaries
|
||||||
|
|
||||||
|
55
README.md
55
README.md
@@ -32,16 +32,14 @@ Key features:
|
|||||||
- [Building with buildx](#building-with-buildx)
|
- [Building with buildx](#building-with-buildx)
|
||||||
- [Working with builder instances](#working-with-builder-instances)
|
- [Working with builder instances](#working-with-builder-instances)
|
||||||
- [Building multi-platform images](#building-multi-platform-images)
|
- [Building multi-platform images](#building-multi-platform-images)
|
||||||
|
- [High-level build options](#high-level-build-options)
|
||||||
- [Guides](docs/guides)
|
- [Guides](docs/guides)
|
||||||
- [High-level build options with Bake](docs/guides/bake/index.md)
|
|
||||||
- [CI/CD](docs/guides/cicd.md)
|
- [CI/CD](docs/guides/cicd.md)
|
||||||
- [CNI networking](docs/guides/cni-networking.md)
|
- [CNI networking](docs/guides/cni-networking.md)
|
||||||
|
- [Registry mirror](docs/guides/registry-mirror.md)
|
||||||
|
- [Resource limiting](docs/guides/resource-limiting.md)
|
||||||
- [Using a custom network](docs/guides/custom-network.md)
|
- [Using a custom network](docs/guides/custom-network.md)
|
||||||
- [Using a custom registry configuration](docs/guides/custom-registry-config.md)
|
- [Using a custom registry configuration](docs/guides/custom-registry-config.md)
|
||||||
- [OpenTelemetry support](docs/guides/opentelemetry.md)
|
|
||||||
- [Registry mirror](docs/guides/registry-mirror.md)
|
|
||||||
- [Drivers](docs/guides/drivers/index.md)
|
|
||||||
- [Resource limiting](docs/guides/resource-limiting.md)
|
|
||||||
- [Reference](docs/reference/buildx.md)
|
- [Reference](docs/reference/buildx.md)
|
||||||
- [`buildx bake`](docs/reference/buildx_bake.md)
|
- [`buildx bake`](docs/reference/buildx_bake.md)
|
||||||
- [`buildx build`](docs/reference/buildx_build.md)
|
- [`buildx build`](docs/reference/buildx_build.md)
|
||||||
@@ -185,17 +183,23 @@ specifying target platform. In addition, Buildx also supports new features that
|
|||||||
are not yet available for regular `docker build` like building manifest lists,
|
are not yet available for regular `docker build` like building manifest lists,
|
||||||
distributed caching, and exporting build results to OCI image tarballs.
|
distributed caching, and exporting build results to OCI image tarballs.
|
||||||
|
|
||||||
Buildx is flexible and can be run in different configurations that are exposed
|
Buildx is supposed to be flexible and can be run in different configurations
|
||||||
through various "drivers". Each driver defines how and where a build should
|
that are exposed through a driver concept. Currently, we support a
|
||||||
run, and have different feature sets.
|
[`docker` driver](docs/reference/buildx_create.md#docker-driver) that uses
|
||||||
|
the BuildKit library bundled into the Docker daemon binary, a
|
||||||
|
[`docker-container` driver](docs/reference/buildx_create.md#docker-container-driver)
|
||||||
|
that automatically launches BuildKit inside a Docker container and a
|
||||||
|
[`kubernetes` driver](docs/reference/buildx_create.md#kubernetes-driver) to
|
||||||
|
spin up pods with defined BuildKit container image to build your images. We
|
||||||
|
plan to add more drivers in the future.
|
||||||
|
|
||||||
We currently support the following drivers:
|
The user experience of using buildx is very similar across drivers, but there
|
||||||
- The `docker` driver ([guide](docs/guides/drivers/docker.md), [reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver))
|
are some features that are not currently supported by the `docker` driver,
|
||||||
- The `docker-container` driver ([guide](docs/guides/drivers/docker-container.md), [reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver))
|
because the BuildKit library bundled into docker daemon currently uses a
|
||||||
- The `kubernetes` driver ([guide](docs/guides/drivers/kubernetes.md), [reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver))
|
different storage component. In contrast, all images built with `docker` driver
|
||||||
- The `remote` driver ([guide](docs/guides/drivers/remote.md))
|
are automatically added to the `docker images` view by default, whereas when
|
||||||
|
using other drivers the method for outputting an image needs to be selected
|
||||||
For more information on drivers, see the [drivers guide](docs/guides/drivers/index.md).
|
with `--output`.
|
||||||
|
|
||||||
## Working with builder instances
|
## Working with builder instances
|
||||||
|
|
||||||
@@ -311,7 +315,26 @@ cross-compilation helpers for more advanced use-cases.
|
|||||||
|
|
||||||
## High-level build options
|
## High-level build options
|
||||||
|
|
||||||
See [`docs/guides/bake/index.md`](docs/guides/bake/index.md) for more details.
|
Buildx also aims to provide support for high-level build concepts that go beyond
|
||||||
|
invoking a single build command. We want to support building all the images in
|
||||||
|
your application together and let the users define project specific reusable
|
||||||
|
build flows that can then be easily invoked by anyone.
|
||||||
|
|
||||||
|
BuildKit efficiently handles multiple concurrent build requests and
|
||||||
|
de-duplicating work. The build commands can be combined with general-purpose
|
||||||
|
command runners (for example, `make`). However, these tools generally invoke
|
||||||
|
builds in sequence and therefore cannot leverage the full potential of BuildKit
|
||||||
|
parallelization, or combine BuildKit’s output for the user. For this use case,
|
||||||
|
we have added a command called [`docker buildx bake`](docs/reference/buildx_bake.md).
|
||||||
|
|
||||||
|
The `bake` command supports building images from compose files, similar to
|
||||||
|
[`docker-compose build`](https://docs.docker.com/compose/reference/build/),
|
||||||
|
but allowing all the services to be built concurrently as part of a single
|
||||||
|
request.
|
||||||
|
|
||||||
|
There is also support for custom build rules from HCL/JSON files allowing
|
||||||
|
better code reuse and different target groups. The design of bake is in very
|
||||||
|
early stages and we are looking for feedback from users.
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
|
39
bake/bake.go
39
bake/bake.go
@@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -17,8 +17,7 @@ import (
|
|||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/build"
|
||||||
"github.com/docker/buildx/util/buildflags"
|
"github.com/docker/buildx/util/buildflags"
|
||||||
"github.com/docker/buildx/util/platformutil"
|
"github.com/docker/buildx/util/platformutil"
|
||||||
"github.com/docker/cli/cli/config"
|
"github.com/docker/docker/pkg/urlutil"
|
||||||
"github.com/docker/docker/builder/remotecontext/urlutil"
|
|
||||||
hcl "github.com/hashicorp/hcl/v2"
|
hcl "github.com/hashicorp/hcl/v2"
|
||||||
"github.com/moby/buildkit/client/llb"
|
"github.com/moby/buildkit/client/llb"
|
||||||
"github.com/moby/buildkit/session/auth/authprovider"
|
"github.com/moby/buildkit/session/auth/authprovider"
|
||||||
@@ -29,8 +28,10 @@ var (
|
|||||||
httpPrefix = regexp.MustCompile(`^https?://`)
|
httpPrefix = regexp.MustCompile(`^https?://`)
|
||||||
gitURLPathWithFragmentSuffix = regexp.MustCompile(`\.git(?:#.+)?$`)
|
gitURLPathWithFragmentSuffix = regexp.MustCompile(`\.git(?:#.+)?$`)
|
||||||
|
|
||||||
validTargetNameChars = `[a-zA-Z0-9_-]+`
|
validTargetNameChars = `[a-zA-Z0-9_-]+`
|
||||||
targetNamePattern = regexp.MustCompile(`^` + validTargetNameChars + `$`)
|
validTargetNameCharsCompose = `[a-zA-Z0-9._-]+`
|
||||||
|
targetNamePattern = regexp.MustCompile(`^` + validTargetNameChars + `$`)
|
||||||
|
targetNamePatternCompose = regexp.MustCompile(`^` + validTargetNameCharsCompose + `$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
@@ -66,12 +67,12 @@ func ReadLocalFiles(names []string) ([]File, error) {
|
|||||||
var dt []byte
|
var dt []byte
|
||||||
var err error
|
var err error
|
||||||
if n == "-" {
|
if n == "-" {
|
||||||
dt, err = io.ReadAll(os.Stdin)
|
dt, err = ioutil.ReadAll(os.Stdin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dt, err = os.ReadFile(n)
|
dt, err = ioutil.ReadFile(n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isDefault && errors.Is(err, os.ErrNotExist) {
|
if isDefault && errors.Is(err, os.ErrNotExist) {
|
||||||
continue
|
continue
|
||||||
@@ -90,10 +91,6 @@ func ReadTargets(ctx context.Context, files []File, targets, overrides []string,
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, t := range targets {
|
|
||||||
targets[i] = sanitizeTargetName(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
o, err := c.newOverrides(overrides)
|
o, err := c.newOverrides(overrides)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -337,7 +334,6 @@ func (c Config) loadLinks(name string, t *Target, m map[string]*Target, o map[st
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
t2.Outputs = nil
|
t2.Outputs = nil
|
||||||
t2.linked = true
|
|
||||||
m[target] = t2
|
m[target] = t2
|
||||||
}
|
}
|
||||||
if err := c.loadLinks(target, t2, m, o, visited); err != nil {
|
if err := c.loadLinks(target, t2, m, o, visited); err != nil {
|
||||||
@@ -533,10 +529,7 @@ type Target struct {
|
|||||||
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional"`
|
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional"`
|
||||||
NetworkMode *string `json:"-" hcl:"-"`
|
NetworkMode *string `json:"-" hcl:"-"`
|
||||||
NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional"`
|
NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional"`
|
||||||
// IMPORTANT: if you add more fields here, do not forget to update newOverrides and docs/guides/bake/file-definition.md.
|
// IMPORTANT: if you add more fields here, do not forget to update newOverrides and README.
|
||||||
|
|
||||||
// linked is a private field to mark a target used as a linked one
|
|
||||||
linked bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Target) normalize() {
|
func (t *Target) normalize() {
|
||||||
@@ -878,7 +871,6 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
|||||||
NoCacheFilter: t.NoCacheFilter,
|
NoCacheFilter: t.NoCacheFilter,
|
||||||
Pull: pull,
|
Pull: pull,
|
||||||
NetworkMode: networkMode,
|
NetworkMode: networkMode,
|
||||||
Linked: t.linked,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
platforms, err := platformutil.Parse(t.Platforms)
|
platforms, err := platformutil.Parse(t.Platforms)
|
||||||
@@ -887,8 +879,7 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
|||||||
}
|
}
|
||||||
bo.Platforms = platforms
|
bo.Platforms = platforms
|
||||||
|
|
||||||
dockerConfig := config.LoadDefaultConfigFile(os.Stderr)
|
bo.Session = append(bo.Session, authprovider.NewDockerAuthProvider(os.Stderr))
|
||||||
bo.Session = append(bo.Session, authprovider.NewDockerAuthProvider(dockerConfig))
|
|
||||||
|
|
||||||
secrets, err := buildflags.ParseSecretSpecs(t.Secrets)
|
secrets, err := buildflags.ParseSecretSpecs(t.Secrets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -980,11 +971,11 @@ func validateTargetName(name string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sanitizeTargetName(target string) string {
|
func validateTargetNameCompose(name string) error {
|
||||||
// as stipulated in compose spec, service name can contain a dot so as
|
if !targetNamePatternCompose.MatchString(name) {
|
||||||
// best-effort and to avoid any potential ambiguity, we replace the dot
|
return errors.Errorf("only %q are allowed", validTargetNameCharsCompose)
|
||||||
// with an underscore.
|
}
|
||||||
return strings.ReplaceAll(target, ".", "_")
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sliceEqual(s1, s2 []string) bool {
|
func sliceEqual(s1, s2 []string) bool {
|
||||||
|
@@ -278,19 +278,9 @@ services:
|
|||||||
`),
|
`),
|
||||||
}
|
}
|
||||||
|
|
||||||
fp3 := File{
|
|
||||||
Name: "docker-compose3.yml",
|
|
||||||
Data: []byte(
|
|
||||||
`version: "3"
|
|
||||||
services:
|
|
||||||
webapp:
|
|
||||||
entrypoint: echo 1
|
|
||||||
`),
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
|
|
||||||
m, g, err := ReadTargets(ctx, []File{fp, fp2, fp3}, []string{"default"}, nil, nil)
|
m, g, err := ReadTargets(ctx, []File{fp, fp2}, []string{"default"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, 3, len(m))
|
require.Equal(t, 3, len(m))
|
||||||
@@ -307,67 +297,6 @@ services:
|
|||||||
require.Equal(t, []string{"db", "newservice", "webapp"}, g[0].Targets)
|
require.Equal(t, []string{"db", "newservice", "webapp"}, g[0].Targets)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadTargetsWithDotCompose(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
fp := File{
|
|
||||||
Name: "docker-compose.yml",
|
|
||||||
Data: []byte(
|
|
||||||
`version: "3"
|
|
||||||
services:
|
|
||||||
web.app:
|
|
||||||
build:
|
|
||||||
dockerfile: Dockerfile.webapp
|
|
||||||
args:
|
|
||||||
buildno: 1
|
|
||||||
`),
|
|
||||||
}
|
|
||||||
|
|
||||||
fp2 := File{
|
|
||||||
Name: "docker-compose2.yml",
|
|
||||||
Data: []byte(
|
|
||||||
`version: "3"
|
|
||||||
services:
|
|
||||||
web_app:
|
|
||||||
build:
|
|
||||||
args:
|
|
||||||
buildno2: 12
|
|
||||||
`),
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.TODO()
|
|
||||||
|
|
||||||
m, _, err := ReadTargets(ctx, []File{fp}, []string{"web.app"}, nil, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 1, len(m))
|
|
||||||
_, ok := m["web_app"]
|
|
||||||
require.True(t, ok)
|
|
||||||
require.Equal(t, "Dockerfile.webapp", *m["web_app"].Dockerfile)
|
|
||||||
require.Equal(t, "1", m["web_app"].Args["buildno"])
|
|
||||||
|
|
||||||
m, _, err = ReadTargets(ctx, []File{fp2}, []string{"web_app"}, nil, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 1, len(m))
|
|
||||||
_, ok = m["web_app"]
|
|
||||||
require.True(t, ok)
|
|
||||||
require.Equal(t, "Dockerfile", *m["web_app"].Dockerfile)
|
|
||||||
require.Equal(t, "12", m["web_app"].Args["buildno2"])
|
|
||||||
|
|
||||||
m, g, err := ReadTargets(ctx, []File{fp, fp2}, []string{"default"}, nil, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 1, len(m))
|
|
||||||
_, ok = m["web_app"]
|
|
||||||
require.True(t, ok)
|
|
||||||
require.Equal(t, "Dockerfile.webapp", *m["web_app"].Dockerfile)
|
|
||||||
require.Equal(t, ".", *m["web_app"].Context)
|
|
||||||
require.Equal(t, "1", m["web_app"].Args["buildno"])
|
|
||||||
require.Equal(t, "12", m["web_app"].Args["buildno2"])
|
|
||||||
|
|
||||||
require.Equal(t, 1, len(g))
|
|
||||||
sort.Strings(g[0].Targets)
|
|
||||||
require.Equal(t, []string{"web_app"}, g[0].Targets)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHCLCwdPrefix(t *testing.T) {
|
func TestHCLCwdPrefix(t *testing.T) {
|
||||||
fp := File{
|
fp := File{
|
||||||
Name: "docker-bake.hcl",
|
Name: "docker-bake.hcl",
|
||||||
@@ -517,40 +446,6 @@ func TestReadContextFromTargetUnknown(t *testing.T) {
|
|||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Contains(t, err.Error(), "failed to find target bar")
|
require.Contains(t, err.Error(), "failed to find target bar")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadEmptyTargets(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
fp := File{
|
|
||||||
Name: "docker-bake.hcl",
|
|
||||||
Data: []byte(`target "app1" {}`),
|
|
||||||
}
|
|
||||||
|
|
||||||
fp2 := File{
|
|
||||||
Name: "docker-compose.yml",
|
|
||||||
Data: []byte(`
|
|
||||||
services:
|
|
||||||
app2: {}
|
|
||||||
`),
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.TODO()
|
|
||||||
|
|
||||||
m, _, err := ReadTargets(ctx, []File{fp, fp2}, []string{"app1", "app2"}, nil, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, 2, len(m))
|
|
||||||
_, ok := m["app1"]
|
|
||||||
require.True(t, ok)
|
|
||||||
_, ok = m["app2"]
|
|
||||||
require.True(t, ok)
|
|
||||||
|
|
||||||
require.Equal(t, "Dockerfile", *m["app1"].Dockerfile)
|
|
||||||
require.Equal(t, ".", *m["app1"].Context)
|
|
||||||
require.Equal(t, "Dockerfile", *m["app2"].Dockerfile)
|
|
||||||
require.Equal(t, ".", *m["app2"].Context)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadContextFromTargetChain(t *testing.T) {
|
func TestReadContextFromTargetChain(t *testing.T) {
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
fp := File{
|
fp := File{
|
||||||
|
234
bake/compose.go
234
bake/compose.go
@@ -3,17 +3,14 @@ package bake
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/loader"
|
"github.com/compose-spec/compose-go/loader"
|
||||||
compose "github.com/compose-spec/compose-go/types"
|
compose "github.com/compose-spec/compose-go/types"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// errComposeInvalid is returned when a compose file is invalid
|
|
||||||
var errComposeInvalid = errors.New("invalid compose file")
|
|
||||||
|
|
||||||
func parseCompose(dt []byte) (*compose.Project, error) {
|
func parseCompose(dt []byte) (*compose.Project, error) {
|
||||||
return loader.Load(compose.ConfigDetails{
|
return loader.Load(compose.ConfigDetails{
|
||||||
ConfigFiles: []compose.ConfigFile{
|
ConfigFiles: []compose.ConfigFile{
|
||||||
@@ -24,7 +21,6 @@ func parseCompose(dt []byte) (*compose.Project, error) {
|
|||||||
Environment: envMap(os.Environ()),
|
Environment: envMap(os.Environ()),
|
||||||
}, func(options *loader.Options) {
|
}, func(options *loader.Options) {
|
||||||
options.SkipNormalization = true
|
options.SkipNormalization = true
|
||||||
options.SkipConsistencyCheck = true
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,11 +41,9 @@ func ParseCompose(dt []byte) (*Config, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err = composeValidate(cfg); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var c Config
|
var c Config
|
||||||
|
var zeroBuildConfig compose.BuildConfig
|
||||||
if len(cfg.Services) > 0 {
|
if len(cfg.Services) > 0 {
|
||||||
c.Groups = []*Group{}
|
c.Groups = []*Group{}
|
||||||
c.Targets = []*Target{}
|
c.Targets = []*Target{}
|
||||||
@@ -57,13 +51,17 @@ func ParseCompose(dt []byte) (*Config, error) {
|
|||||||
g := &Group{Name: "default"}
|
g := &Group{Name: "default"}
|
||||||
|
|
||||||
for _, s := range cfg.Services {
|
for _, s := range cfg.Services {
|
||||||
if s.Build == nil {
|
|
||||||
s.Build = &compose.BuildConfig{}
|
if s.Build == nil || reflect.DeepEqual(s.Build, zeroBuildConfig) {
|
||||||
|
// if not make sure they're setting an image or it's invalid d-c.yml
|
||||||
|
if s.Image == "" {
|
||||||
|
return nil, fmt.Errorf("compose file invalid: service %s has neither an image nor a build context specified. At least one must be provided", s.Name)
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
targetName := sanitizeTargetName(s.Name)
|
if err = validateTargetNameCompose(s.Name); err != nil {
|
||||||
if err = validateTargetName(targetName); err != nil {
|
return nil, errors.Wrapf(err, "invalid service name %q", s.Name)
|
||||||
return nil, errors.Wrapf(err, "invalid service name %q", targetName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var contextPathP *string
|
var contextPathP *string
|
||||||
@@ -76,22 +74,11 @@ func ParseCompose(dt []byte) (*Config, error) {
|
|||||||
dockerfilePath := s.Build.Dockerfile
|
dockerfilePath := s.Build.Dockerfile
|
||||||
dockerfilePathP = &dockerfilePath
|
dockerfilePathP = &dockerfilePath
|
||||||
}
|
}
|
||||||
|
g.Targets = append(g.Targets, s.Name)
|
||||||
var secrets []string
|
|
||||||
for _, bs := range s.Build.Secrets {
|
|
||||||
secret, err := composeToBuildkitSecret(bs, cfg.Secrets[bs.Source])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
secrets = append(secrets, secret)
|
|
||||||
}
|
|
||||||
|
|
||||||
g.Targets = append(g.Targets, targetName)
|
|
||||||
t := &Target{
|
t := &Target{
|
||||||
Name: targetName,
|
Name: s.Name,
|
||||||
Context: contextPathP,
|
Context: contextPathP,
|
||||||
Dockerfile: dockerfilePathP,
|
Dockerfile: dockerfilePathP,
|
||||||
Tags: s.Build.Tags,
|
|
||||||
Labels: s.Build.Labels,
|
Labels: s.Build.Labels,
|
||||||
Args: flatten(s.Build.Args.Resolve(func(val string) (string, bool) {
|
Args: flatten(s.Build.Args.Resolve(func(val string) (string, bool) {
|
||||||
if val, ok := s.Environment[val]; ok && val != nil {
|
if val, ok := s.Environment[val]; ok && val != nil {
|
||||||
@@ -101,9 +88,7 @@ func ParseCompose(dt []byte) (*Config, error) {
|
|||||||
return val, ok
|
return val, ok
|
||||||
})),
|
})),
|
||||||
CacheFrom: s.Build.CacheFrom,
|
CacheFrom: s.Build.CacheFrom,
|
||||||
CacheTo: s.Build.CacheTo,
|
|
||||||
NetworkMode: &s.Build.Network,
|
NetworkMode: &s.Build.Network,
|
||||||
Secrets: secrets,
|
|
||||||
}
|
}
|
||||||
if err = t.composeExtTarget(s.Build.Extensions); err != nil {
|
if err = t.composeExtTarget(s.Build.Extensions); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -138,128 +123,89 @@ func flatten(in compose.MappingWithEquals) compose.Mapping {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// xbake Compose build extension provides fields not (yet) available in
|
|
||||||
// Compose build specification: https://github.com/compose-spec/compose-spec/blob/master/build.md
|
|
||||||
type xbake struct {
|
|
||||||
Tags stringArray `yaml:"tags,omitempty"`
|
|
||||||
CacheFrom stringArray `yaml:"cache-from,omitempty"`
|
|
||||||
CacheTo stringArray `yaml:"cache-to,omitempty"`
|
|
||||||
Secrets stringArray `yaml:"secret,omitempty"`
|
|
||||||
SSH stringArray `yaml:"ssh,omitempty"`
|
|
||||||
Platforms stringArray `yaml:"platforms,omitempty"`
|
|
||||||
Outputs stringArray `yaml:"output,omitempty"`
|
|
||||||
Pull *bool `yaml:"pull,omitempty"`
|
|
||||||
NoCache *bool `yaml:"no-cache,omitempty"`
|
|
||||||
NoCacheFilter stringArray `yaml:"no-cache-filter,omitempty"`
|
|
||||||
// don't forget to update documentation if you add a new field:
|
|
||||||
// docs/guides/bake/compose-file.md#extension-field-with-x-bake
|
|
||||||
}
|
|
||||||
|
|
||||||
type stringArray []string
|
|
||||||
|
|
||||||
func (sa *stringArray) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
||||||
var multi []string
|
|
||||||
err := unmarshal(&multi)
|
|
||||||
if err != nil {
|
|
||||||
var single string
|
|
||||||
if err := unmarshal(&single); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*sa = strings.Fields(single)
|
|
||||||
} else {
|
|
||||||
*sa = multi
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// composeExtTarget converts Compose build extension x-bake to bake Target
|
// composeExtTarget converts Compose build extension x-bake to bake Target
|
||||||
// https://github.com/compose-spec/compose-spec/blob/master/spec.md#extension
|
// https://github.com/compose-spec/compose-spec/blob/master/spec.md#extension
|
||||||
func (t *Target) composeExtTarget(exts map[string]interface{}) error {
|
func (t *Target) composeExtTarget(exts map[string]interface{}) error {
|
||||||
var xb xbake
|
if ext, ok := exts["x-bake"]; ok {
|
||||||
|
for key, val := range ext.(map[string]interface{}) {
|
||||||
ext, ok := exts["x-bake"]
|
switch key {
|
||||||
if !ok || ext == nil {
|
case "tags":
|
||||||
return nil
|
if res, k := val.(string); k {
|
||||||
}
|
t.Tags = append(t.Tags, res)
|
||||||
|
} else {
|
||||||
yb, _ := yaml.Marshal(ext)
|
for _, res := range val.([]interface{}) {
|
||||||
if err := yaml.Unmarshal(yb, &xb); err != nil {
|
t.Tags = append(t.Tags, res.(string))
|
||||||
return err
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if len(xb.Tags) > 0 {
|
|
||||||
t.Tags = dedupString(append(t.Tags, xb.Tags...))
|
|
||||||
}
|
|
||||||
if len(xb.CacheFrom) > 0 {
|
|
||||||
t.CacheFrom = dedupString(append(t.CacheFrom, xb.CacheFrom...))
|
|
||||||
}
|
|
||||||
if len(xb.CacheTo) > 0 {
|
|
||||||
t.CacheTo = dedupString(append(t.CacheTo, xb.CacheTo...))
|
|
||||||
}
|
|
||||||
if len(xb.Secrets) > 0 {
|
|
||||||
t.Secrets = dedupString(append(t.Secrets, xb.Secrets...))
|
|
||||||
}
|
|
||||||
if len(xb.SSH) > 0 {
|
|
||||||
t.SSH = dedupString(append(t.SSH, xb.SSH...))
|
|
||||||
}
|
|
||||||
if len(xb.Platforms) > 0 {
|
|
||||||
t.Platforms = dedupString(append(t.Platforms, xb.Platforms...))
|
|
||||||
}
|
|
||||||
if len(xb.Outputs) > 0 {
|
|
||||||
t.Outputs = dedupString(append(t.Outputs, xb.Outputs...))
|
|
||||||
}
|
|
||||||
if xb.Pull != nil {
|
|
||||||
t.Pull = xb.Pull
|
|
||||||
}
|
|
||||||
if xb.NoCache != nil {
|
|
||||||
t.NoCache = xb.NoCache
|
|
||||||
}
|
|
||||||
if len(xb.NoCacheFilter) > 0 {
|
|
||||||
t.NoCacheFilter = dedupString(append(t.NoCacheFilter, xb.NoCacheFilter...))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// composeValidate validates a compose file
|
|
||||||
func composeValidate(project *compose.Project) error {
|
|
||||||
for _, s := range project.Services {
|
|
||||||
if s.Build != nil {
|
|
||||||
for _, secret := range s.Build.Secrets {
|
|
||||||
if _, ok := project.Secrets[secret.Source]; !ok {
|
|
||||||
return errors.Wrap(errComposeInvalid, fmt.Sprintf("service %q refers to undefined build secret %s", sanitizeTargetName(s.Name), secret.Source))
|
|
||||||
}
|
}
|
||||||
|
case "cache-from":
|
||||||
|
t.CacheFrom = []string{} // Needed to override the main field
|
||||||
|
if res, k := val.(string); k {
|
||||||
|
t.CacheFrom = append(t.CacheFrom, res)
|
||||||
|
} else {
|
||||||
|
for _, res := range val.([]interface{}) {
|
||||||
|
t.CacheFrom = append(t.CacheFrom, res.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "cache-to":
|
||||||
|
if res, k := val.(string); k {
|
||||||
|
t.CacheTo = append(t.CacheTo, res)
|
||||||
|
} else {
|
||||||
|
for _, res := range val.([]interface{}) {
|
||||||
|
t.CacheTo = append(t.CacheTo, res.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "secret":
|
||||||
|
if res, k := val.(string); k {
|
||||||
|
t.Secrets = append(t.Secrets, res)
|
||||||
|
} else {
|
||||||
|
for _, res := range val.([]interface{}) {
|
||||||
|
t.Secrets = append(t.Secrets, res.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "ssh":
|
||||||
|
if res, k := val.(string); k {
|
||||||
|
t.SSH = append(t.SSH, res)
|
||||||
|
} else {
|
||||||
|
for _, res := range val.([]interface{}) {
|
||||||
|
t.SSH = append(t.SSH, res.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "platforms":
|
||||||
|
if res, k := val.(string); k {
|
||||||
|
t.Platforms = append(t.Platforms, res)
|
||||||
|
} else {
|
||||||
|
for _, res := range val.([]interface{}) {
|
||||||
|
t.Platforms = append(t.Platforms, res.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "output":
|
||||||
|
if res, k := val.(string); k {
|
||||||
|
t.Outputs = append(t.Outputs, res)
|
||||||
|
} else {
|
||||||
|
for _, res := range val.([]interface{}) {
|
||||||
|
t.Outputs = append(t.Outputs, res.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "pull":
|
||||||
|
if res, ok := val.(bool); ok {
|
||||||
|
t.Pull = &res
|
||||||
|
}
|
||||||
|
case "no-cache":
|
||||||
|
if res, ok := val.(bool); ok {
|
||||||
|
t.NoCache = &res
|
||||||
|
}
|
||||||
|
case "no-cache-filter":
|
||||||
|
if res, k := val.(string); k {
|
||||||
|
t.NoCacheFilter = append(t.NoCacheFilter, res)
|
||||||
|
} else {
|
||||||
|
for _, res := range val.([]interface{}) {
|
||||||
|
t.NoCacheFilter = append(t.NoCacheFilter, res.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("compose file invalid: unkwown %s field for x-bake", key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for name, secret := range project.Secrets {
|
|
||||||
if secret.External.External {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if secret.File == "" && secret.Environment == "" {
|
|
||||||
return errors.Wrap(errComposeInvalid, fmt.Sprintf("secret %q must declare either `file` or `environment`", name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// composeToBuildkitSecret converts secret from compose format to buildkit's
|
|
||||||
// csv format.
|
|
||||||
func composeToBuildkitSecret(inp compose.ServiceSecretConfig, psecret compose.SecretConfig) (string, error) {
|
|
||||||
if psecret.External.External {
|
|
||||||
return "", errors.Errorf("unsupported external secret %s", psecret.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
var bkattrs []string
|
|
||||||
if inp.Source != "" {
|
|
||||||
bkattrs = append(bkattrs, "id="+inp.Source)
|
|
||||||
}
|
|
||||||
if psecret.File != "" {
|
|
||||||
bkattrs = append(bkattrs, "src="+psecret.File)
|
|
||||||
}
|
|
||||||
if psecret.Environment != "" {
|
|
||||||
bkattrs = append(bkattrs, "env="+psecret.Environment)
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(bkattrs, ","), nil
|
|
||||||
}
|
|
||||||
|
@@ -23,18 +23,6 @@ services:
|
|||||||
none
|
none
|
||||||
args:
|
args:
|
||||||
buildno: 123
|
buildno: 123
|
||||||
cache_from:
|
|
||||||
- type=local,src=path/to/cache
|
|
||||||
cache_to:
|
|
||||||
- type=local,dest=path/to/cache
|
|
||||||
secrets:
|
|
||||||
- token
|
|
||||||
- aws
|
|
||||||
secrets:
|
|
||||||
token:
|
|
||||||
environment: ENV_TOKEN
|
|
||||||
aws:
|
|
||||||
file: /root/.aws/credentials
|
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseCompose(dt)
|
c, err := ParseCompose(dt)
|
||||||
@@ -51,20 +39,13 @@ secrets:
|
|||||||
})
|
})
|
||||||
require.Equal(t, "db", c.Targets[0].Name)
|
require.Equal(t, "db", c.Targets[0].Name)
|
||||||
require.Equal(t, "./db", *c.Targets[0].Context)
|
require.Equal(t, "./db", *c.Targets[0].Context)
|
||||||
require.Equal(t, []string{"docker.io/tonistiigi/db"}, c.Targets[0].Tags)
|
|
||||||
|
|
||||||
require.Equal(t, "webapp", c.Targets[1].Name)
|
require.Equal(t, "webapp", c.Targets[1].Name)
|
||||||
require.Equal(t, "./dir", *c.Targets[1].Context)
|
require.Equal(t, "./dir", *c.Targets[1].Context)
|
||||||
require.Equal(t, "Dockerfile-alternate", *c.Targets[1].Dockerfile)
|
require.Equal(t, "Dockerfile-alternate", *c.Targets[1].Dockerfile)
|
||||||
require.Equal(t, 1, len(c.Targets[1].Args))
|
require.Equal(t, 1, len(c.Targets[1].Args))
|
||||||
require.Equal(t, "123", c.Targets[1].Args["buildno"])
|
require.Equal(t, "123", c.Targets[1].Args["buildno"])
|
||||||
require.Equal(t, c.Targets[1].CacheFrom, []string{"type=local,src=path/to/cache"})
|
|
||||||
require.Equal(t, c.Targets[1].CacheTo, []string{"type=local,dest=path/to/cache"})
|
|
||||||
require.Equal(t, "none", *c.Targets[1].NetworkMode)
|
require.Equal(t, "none", *c.Targets[1].NetworkMode)
|
||||||
require.Equal(t, []string{
|
|
||||||
"id=token,env=ENV_TOKEN",
|
|
||||||
"id=aws,src=/root/.aws/credentials",
|
|
||||||
}, c.Targets[1].Secrets)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoBuildOutOfTreeService(t *testing.T) {
|
func TestNoBuildOutOfTreeService(t *testing.T) {
|
||||||
@@ -159,15 +140,21 @@ services:
|
|||||||
require.Equal(t, c.Targets[0].Args["BRB"], "FOO")
|
require.Equal(t, c.Targets[0].Args["BRB"], "FOO")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInconsistentComposeFile(t *testing.T) {
|
func TestBogusCompose(t *testing.T) {
|
||||||
var dt = []byte(`
|
var dt = []byte(`
|
||||||
services:
|
services:
|
||||||
|
db:
|
||||||
|
labels:
|
||||||
|
- "foo"
|
||||||
webapp:
|
webapp:
|
||||||
entrypoint: echo 1
|
build:
|
||||||
|
context: .
|
||||||
|
target: webapp
|
||||||
`)
|
`)
|
||||||
|
|
||||||
_, err := ParseCompose(dt)
|
_, err := ParseCompose(dt)
|
||||||
require.NoError(t, err)
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), "has neither an image nor a build context specified: invalid compose project")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAdvancedNetwork(t *testing.T) {
|
func TestAdvancedNetwork(t *testing.T) {
|
||||||
@@ -195,24 +182,6 @@ networks:
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTags(t *testing.T) {
|
|
||||||
var dt = []byte(`
|
|
||||||
services:
|
|
||||||
example:
|
|
||||||
image: example
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
tags:
|
|
||||||
- foo
|
|
||||||
- bar
|
|
||||||
`)
|
|
||||||
|
|
||||||
c, err := ParseCompose(dt)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, c.Targets[0].Tags, []string{"foo", "bar"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDependsOnList(t *testing.T) {
|
func TestDependsOnList(t *testing.T) {
|
||||||
var dt = []byte(`
|
var dt = []byte(`
|
||||||
version: "3.8"
|
version: "3.8"
|
||||||
@@ -259,10 +228,6 @@ services:
|
|||||||
dockerfile: ./Dockerfile
|
dockerfile: ./Dockerfile
|
||||||
cache_from:
|
cache_from:
|
||||||
- user/app:cache
|
- user/app:cache
|
||||||
cache_to:
|
|
||||||
- user/app:cache
|
|
||||||
tags:
|
|
||||||
- ct-addon:baz
|
|
||||||
args:
|
args:
|
||||||
CT_ECR: foo
|
CT_ECR: foo
|
||||||
CT_TAG: bar
|
CT_TAG: bar
|
||||||
@@ -275,8 +240,7 @@ services:
|
|||||||
- linux/arm64
|
- linux/arm64
|
||||||
cache-from:
|
cache-from:
|
||||||
- type=local,src=path/to/cache
|
- type=local,src=path/to/cache
|
||||||
cache-to:
|
cache-to: local,dest=path/to/cache
|
||||||
- type=local,dest=path/to/cache
|
|
||||||
pull: true
|
pull: true
|
||||||
|
|
||||||
aws:
|
aws:
|
||||||
@@ -303,10 +267,10 @@ services:
|
|||||||
return c.Targets[i].Name < c.Targets[j].Name
|
return c.Targets[i].Name < c.Targets[j].Name
|
||||||
})
|
})
|
||||||
require.Equal(t, c.Targets[0].Args, map[string]string{"CT_ECR": "foo", "CT_TAG": "bar"})
|
require.Equal(t, c.Targets[0].Args, map[string]string{"CT_ECR": "foo", "CT_TAG": "bar"})
|
||||||
require.Equal(t, c.Targets[0].Tags, []string{"ct-addon:baz", "ct-addon:foo", "ct-addon:alp"})
|
require.Equal(t, c.Targets[0].Tags, []string{"ct-addon:foo", "ct-addon:alp"})
|
||||||
require.Equal(t, c.Targets[0].Platforms, []string{"linux/amd64", "linux/arm64"})
|
require.Equal(t, c.Targets[0].Platforms, []string{"linux/amd64", "linux/arm64"})
|
||||||
require.Equal(t, c.Targets[0].CacheFrom, []string{"user/app:cache", "type=local,src=path/to/cache"})
|
require.Equal(t, c.Targets[0].CacheFrom, []string{"type=local,src=path/to/cache"})
|
||||||
require.Equal(t, c.Targets[0].CacheTo, []string{"user/app:cache", "type=local,dest=path/to/cache"})
|
require.Equal(t, c.Targets[0].CacheTo, []string{"local,dest=path/to/cache"})
|
||||||
require.Equal(t, c.Targets[0].Pull, newBool(true))
|
require.Equal(t, c.Targets[0].Pull, newBool(true))
|
||||||
require.Equal(t, c.Targets[1].Tags, []string{"ct-fake-aws:bar"})
|
require.Equal(t, c.Targets[1].Tags, []string{"ct-fake-aws:bar"})
|
||||||
require.Equal(t, c.Targets[1].Secrets, []string{"id=mysecret,src=/local/secret", "id=mysecret2,src=/local/secret2"})
|
require.Equal(t, c.Targets[1].Secrets, []string{"id=mysecret,src=/local/secret", "id=mysecret2,src=/local/secret2"})
|
||||||
@@ -316,37 +280,6 @@ services:
|
|||||||
require.Equal(t, c.Targets[1].NoCache, newBool(true))
|
require.Equal(t, c.Targets[1].NoCache, newBool(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestComposeExtDedup(t *testing.T) {
|
|
||||||
var dt = []byte(`
|
|
||||||
services:
|
|
||||||
webapp:
|
|
||||||
image: app:bar
|
|
||||||
build:
|
|
||||||
cache_from:
|
|
||||||
- user/app:cache
|
|
||||||
cache_to:
|
|
||||||
- user/app:cache
|
|
||||||
tags:
|
|
||||||
- ct-addon:foo
|
|
||||||
x-bake:
|
|
||||||
tags:
|
|
||||||
- ct-addon:foo
|
|
||||||
- ct-addon:baz
|
|
||||||
cache-from:
|
|
||||||
- user/app:cache
|
|
||||||
- type=local,src=path/to/cache
|
|
||||||
cache-to:
|
|
||||||
- type=local,dest=path/to/cache
|
|
||||||
`)
|
|
||||||
|
|
||||||
c, err := ParseCompose(dt)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
|
||||||
require.Equal(t, c.Targets[0].Tags, []string{"ct-addon:foo", "ct-addon:baz"})
|
|
||||||
require.Equal(t, c.Targets[0].CacheFrom, []string{"user/app:cache", "type=local,src=path/to/cache"})
|
|
||||||
require.Equal(t, c.Targets[0].CacheTo, []string{"user/app:cache", "type=local,dest=path/to/cache"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnv(t *testing.T) {
|
func TestEnv(t *testing.T) {
|
||||||
envf, err := os.CreateTemp("", "env")
|
envf, err := os.CreateTemp("", "env")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -420,6 +353,10 @@ func TestServiceName(t *testing.T) {
|
|||||||
svc: "a.b",
|
svc: "a.b",
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
svc: "a?b",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
svc: "_a",
|
svc: "_a",
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -454,69 +391,3 @@ services:
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateComposeSecret(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
dt []byte
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "secret set by file",
|
|
||||||
dt: []byte(`
|
|
||||||
secrets:
|
|
||||||
foo:
|
|
||||||
file: .secret
|
|
||||||
`),
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "secret set by environment",
|
|
||||||
dt: []byte(`
|
|
||||||
secrets:
|
|
||||||
foo:
|
|
||||||
environment: TOKEN
|
|
||||||
`),
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "external secret",
|
|
||||||
dt: []byte(`
|
|
||||||
secrets:
|
|
||||||
foo:
|
|
||||||
external: true
|
|
||||||
`),
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "unset secret",
|
|
||||||
dt: []byte(`
|
|
||||||
secrets:
|
|
||||||
foo: {}
|
|
||||||
`),
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "undefined secret",
|
|
||||||
dt: []byte(`
|
|
||||||
services:
|
|
||||||
foo:
|
|
||||||
build:
|
|
||||||
secrets:
|
|
||||||
- token
|
|
||||||
`),
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range cases {
|
|
||||||
tt := tt
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
_, err := ParseCompose(tt.dt)
|
|
||||||
if tt.wantErr {
|
|
||||||
require.Error(t, err)
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
162
bake/hcl_test.go
162
bake/hcl_test.go
@@ -620,165 +620,3 @@ func TestHCLBuiltinVars(t *testing.T) {
|
|||||||
require.Equal(t, "foo", *c.Targets[0].Context)
|
require.Equal(t, "foo", *c.Targets[0].Context)
|
||||||
require.Equal(t, "test", *c.Targets[0].Dockerfile)
|
require.Equal(t, "test", *c.Targets[0].Dockerfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCombineHCLAndJSONTargets(t *testing.T) {
|
|
||||||
c, err := ParseFiles([]File{
|
|
||||||
{
|
|
||||||
Name: "docker-bake.hcl",
|
|
||||||
Data: []byte(`
|
|
||||||
group "default" {
|
|
||||||
targets = ["a"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "metadata-a" {}
|
|
||||||
target "metadata-b" {}
|
|
||||||
|
|
||||||
target "a" {
|
|
||||||
inherits = ["metadata-a"]
|
|
||||||
context = "."
|
|
||||||
target = "a"
|
|
||||||
}
|
|
||||||
|
|
||||||
target "b" {
|
|
||||||
inherits = ["metadata-b"]
|
|
||||||
context = "."
|
|
||||||
target = "b"
|
|
||||||
}`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "metadata-a.json",
|
|
||||||
Data: []byte(`
|
|
||||||
{
|
|
||||||
"target": [{
|
|
||||||
"metadata-a": [{
|
|
||||||
"tags": [
|
|
||||||
"app/a:1.0.0",
|
|
||||||
"app/a:latest"
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
}`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "metadata-b.json",
|
|
||||||
Data: []byte(`
|
|
||||||
{
|
|
||||||
"target": [{
|
|
||||||
"metadata-b": [{
|
|
||||||
"tags": [
|
|
||||||
"app/b:1.0.0",
|
|
||||||
"app/b:latest"
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
}`),
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, 1, len(c.Groups))
|
|
||||||
require.Equal(t, "default", c.Groups[0].Name)
|
|
||||||
require.Equal(t, []string{"a"}, c.Groups[0].Targets)
|
|
||||||
|
|
||||||
require.Equal(t, 4, len(c.Targets))
|
|
||||||
|
|
||||||
require.Equal(t, c.Targets[0].Name, "metadata-a")
|
|
||||||
require.Equal(t, []string{"app/a:1.0.0", "app/a:latest"}, c.Targets[0].Tags)
|
|
||||||
|
|
||||||
require.Equal(t, c.Targets[1].Name, "metadata-b")
|
|
||||||
require.Equal(t, []string{"app/b:1.0.0", "app/b:latest"}, c.Targets[1].Tags)
|
|
||||||
|
|
||||||
require.Equal(t, c.Targets[2].Name, "a")
|
|
||||||
require.Equal(t, ".", *c.Targets[2].Context)
|
|
||||||
require.Equal(t, "a", *c.Targets[2].Target)
|
|
||||||
|
|
||||||
require.Equal(t, c.Targets[3].Name, "b")
|
|
||||||
require.Equal(t, ".", *c.Targets[3].Context)
|
|
||||||
require.Equal(t, "b", *c.Targets[3].Target)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCombineHCLAndJSONVars(t *testing.T) {
|
|
||||||
c, err := ParseFiles([]File{
|
|
||||||
{
|
|
||||||
Name: "docker-bake.hcl",
|
|
||||||
Data: []byte(`
|
|
||||||
variable "ABC" {
|
|
||||||
default = "foo"
|
|
||||||
}
|
|
||||||
variable "DEF" {
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
group "default" {
|
|
||||||
targets = ["one"]
|
|
||||||
}
|
|
||||||
target "one" {
|
|
||||||
args = {
|
|
||||||
a = "pre-${ABC}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
target "two" {
|
|
||||||
args = {
|
|
||||||
b = "pre-${DEF}"
|
|
||||||
}
|
|
||||||
}`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "foo.json",
|
|
||||||
Data: []byte(`{"variable": {"DEF": {"default": "bar"}}, "target": { "one": { "args": {"a": "pre-${ABC}-${DEF}"}} } }`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "bar.json",
|
|
||||||
Data: []byte(`{"ABC": "ghi", "DEF": "jkl"}`),
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, 1, len(c.Groups))
|
|
||||||
require.Equal(t, "default", c.Groups[0].Name)
|
|
||||||
require.Equal(t, []string{"one"}, c.Groups[0].Targets)
|
|
||||||
|
|
||||||
require.Equal(t, 2, len(c.Targets))
|
|
||||||
|
|
||||||
require.Equal(t, c.Targets[0].Name, "one")
|
|
||||||
require.Equal(t, map[string]string{"a": "pre-ghi-jkl"}, c.Targets[0].Args)
|
|
||||||
|
|
||||||
require.Equal(t, c.Targets[1].Name, "two")
|
|
||||||
require.Equal(t, map[string]string{"b": "pre-jkl"}, c.Targets[1].Args)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyVariableJSON(t *testing.T) {
|
|
||||||
dt := []byte(`{
|
|
||||||
"variable": {
|
|
||||||
"VAR": {}
|
|
||||||
}
|
|
||||||
}`)
|
|
||||||
_, err := ParseFile(dt, "docker-bake.json")
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFunctionNoParams(t *testing.T) {
|
|
||||||
dt := []byte(`
|
|
||||||
function "foo" {
|
|
||||||
result = "bar"
|
|
||||||
}
|
|
||||||
target "foo_target" {
|
|
||||||
args = {
|
|
||||||
test = foo()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
_, err := ParseFile(dt, "docker-bake.hcl")
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFunctionNoResult(t *testing.T) {
|
|
||||||
dt := []byte(`
|
|
||||||
function "foo" {
|
|
||||||
params = ["a"]
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
_, err := ParseFile(dt, "docker-bake.hcl")
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
@@ -111,13 +111,6 @@ func (p *parser) resolveFunction(name string) error {
|
|||||||
}
|
}
|
||||||
p.progressF[name] = struct{}{}
|
p.progressF[name] = struct{}{}
|
||||||
|
|
||||||
if f.Result == nil {
|
|
||||||
return errors.Errorf("empty result not allowed for %s", name)
|
|
||||||
}
|
|
||||||
if f.Params == nil {
|
|
||||||
return errors.Errorf("empty params not allowed for %s", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
paramExprs, paramsDiags := hcl.ExprList(f.Params.Expr)
|
paramExprs, paramsDiags := hcl.ExprList(f.Params.Expr)
|
||||||
if paramsDiags.HasErrors() {
|
if paramsDiags.HasErrors() {
|
||||||
return paramsDiags
|
return paramsDiags
|
||||||
@@ -263,7 +256,6 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
|||||||
if err := gohcl.DecodeBody(b, nil, &defs); err != nil {
|
if err := gohcl.DecodeBody(b, nil, &defs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defsSchema, _ := gohcl.ImpliedBodySchema(defs)
|
|
||||||
|
|
||||||
if opt.LookupVar == nil {
|
if opt.LookupVar == nil {
|
||||||
opt.LookupVar = func(string) (string, bool) {
|
opt.LookupVar = func(string) (string, bool) {
|
||||||
@@ -308,20 +300,12 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
|||||||
p.funcs[v.Name] = v
|
p.funcs[v.Name] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
content, b, diags := b.PartialContent(schema)
|
|
||||||
if diags.HasErrors() {
|
|
||||||
return diags
|
|
||||||
}
|
|
||||||
|
|
||||||
blocks, b, diags := b.PartialContent(defsSchema)
|
|
||||||
if diags.HasErrors() {
|
|
||||||
return diags
|
|
||||||
}
|
|
||||||
|
|
||||||
attrs, diags := b.JustAttributes()
|
attrs, diags := b.JustAttributes()
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
if d := removeAttributesDiags(diags, reserved, p.vars); len(d) > 0 {
|
for _, d := range diags {
|
||||||
return d
|
if d.Detail != "Blocks are not allowed here." {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,32 +361,23 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
|||||||
if diags, ok := err.(hcl.Diagnostics); ok {
|
if diags, ok := err.(hcl.Diagnostics); ok {
|
||||||
return diags
|
return diags
|
||||||
}
|
}
|
||||||
var subject *hcl.Range
|
|
||||||
var context *hcl.Range
|
|
||||||
if p.funcs[k].Params != nil {
|
|
||||||
subject = &p.funcs[k].Params.Range
|
|
||||||
context = subject
|
|
||||||
} else {
|
|
||||||
for _, block := range blocks.Blocks {
|
|
||||||
if block.Type == "function" && len(block.Labels) == 1 && block.Labels[0] == k {
|
|
||||||
subject = &block.LabelRanges[0]
|
|
||||||
context = &block.DefRange
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hcl.Diagnostics{
|
return hcl.Diagnostics{
|
||||||
&hcl.Diagnostic{
|
&hcl.Diagnostic{
|
||||||
Severity: hcl.DiagError,
|
Severity: hcl.DiagError,
|
||||||
Summary: "Invalid function",
|
Summary: "Invalid function",
|
||||||
Detail: err.Error(),
|
Detail: err.Error(),
|
||||||
Subject: subject,
|
Subject: &p.funcs[k].Params.Range,
|
||||||
Context: context,
|
Context: &p.funcs[k].Params.Range,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
content, _, diags := b.PartialContent(schema)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
|
||||||
for _, a := range content.Attributes {
|
for _, a := range content.Attributes {
|
||||||
return hcl.Diagnostics{
|
return hcl.Diagnostics{
|
||||||
&hcl.Diagnostic{
|
&hcl.Diagnostic{
|
||||||
@@ -539,33 +514,3 @@ func setLabel(v reflect.Value, lbl string) int {
|
|||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeAttributesDiags(diags hcl.Diagnostics, reserved map[string]struct{}, vars map[string]*variable) hcl.Diagnostics {
|
|
||||||
var fdiags hcl.Diagnostics
|
|
||||||
for _, d := range diags {
|
|
||||||
if fout := func(d *hcl.Diagnostic) bool {
|
|
||||||
// https://github.com/docker/buildx/pull/541
|
|
||||||
if d.Detail == "Blocks are not allowed here." {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for r := range reserved {
|
|
||||||
// JSON body objects don't handle repeated blocks like HCL but
|
|
||||||
// reserved name attributes should be allowed when multi bodies are merged.
|
|
||||||
// https://github.com/hashicorp/hcl/blob/main/json/spec.md#blocks
|
|
||||||
if strings.HasPrefix(d.Detail, fmt.Sprintf(`Argument "%s" was already set at `, r)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for v := range vars {
|
|
||||||
// Do the same for global variables
|
|
||||||
if strings.HasPrefix(d.Detail, fmt.Sprintf(`Argument "%s" was already set at `, v)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}(d); !fout {
|
|
||||||
fdiags = append(fdiags, d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fdiags
|
|
||||||
}
|
|
||||||
|
@@ -1,15 +1,12 @@
|
|||||||
package hclparser
|
package hclparser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-cty-funcs/cidr"
|
"github.com/hashicorp/go-cty-funcs/cidr"
|
||||||
"github.com/hashicorp/go-cty-funcs/crypto"
|
"github.com/hashicorp/go-cty-funcs/crypto"
|
||||||
"github.com/hashicorp/go-cty-funcs/encoding"
|
"github.com/hashicorp/go-cty-funcs/encoding"
|
||||||
"github.com/hashicorp/go-cty-funcs/uuid"
|
"github.com/hashicorp/go-cty-funcs/uuid"
|
||||||
"github.com/hashicorp/hcl/v2/ext/tryfunc"
|
"github.com/hashicorp/hcl/v2/ext/tryfunc"
|
||||||
"github.com/hashicorp/hcl/v2/ext/typeexpr"
|
"github.com/hashicorp/hcl/v2/ext/typeexpr"
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
"github.com/zclconf/go-cty/cty/function"
|
"github.com/zclconf/go-cty/cty/function"
|
||||||
"github.com/zclconf/go-cty/cty/function/stdlib"
|
"github.com/zclconf/go-cty/cty/function/stdlib"
|
||||||
)
|
)
|
||||||
@@ -99,7 +96,6 @@ var stdlibFunctions = map[string]function.Function{
|
|||||||
"substr": stdlib.SubstrFunc,
|
"substr": stdlib.SubstrFunc,
|
||||||
"subtract": stdlib.SubtractFunc,
|
"subtract": stdlib.SubtractFunc,
|
||||||
"timeadd": stdlib.TimeAddFunc,
|
"timeadd": stdlib.TimeAddFunc,
|
||||||
"timestamp": timestampFunc,
|
|
||||||
"title": stdlib.TitleFunc,
|
"title": stdlib.TitleFunc,
|
||||||
"trim": stdlib.TrimFunc,
|
"trim": stdlib.TrimFunc,
|
||||||
"trimprefix": stdlib.TrimPrefixFunc,
|
"trimprefix": stdlib.TrimPrefixFunc,
|
||||||
@@ -113,14 +109,3 @@ var stdlibFunctions = map[string]function.Function{
|
|||||||
"values": stdlib.ValuesFunc,
|
"values": stdlib.ValuesFunc,
|
||||||
"zipmap": stdlib.ZipmapFunc,
|
"zipmap": stdlib.ZipmapFunc,
|
||||||
}
|
}
|
||||||
|
|
||||||
// timestampFunc constructs a function that returns a string representation of the current date and time.
|
|
||||||
//
|
|
||||||
// This function was imported from terraform's datetime utilities.
|
|
||||||
var timestampFunc = function.New(&function.Spec{
|
|
||||||
Params: []function.Parameter{},
|
|
||||||
Type: function.StaticReturnType(cty.String),
|
|
||||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
|
||||||
return cty.StringVal(time.Now().UTC().Format(time.RFC3339)), nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
184
build/build.go
184
build/build.go
@@ -2,14 +2,13 @@ package build
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
_ "crypto/sha256" // ensure digests can be computed
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -18,8 +17,6 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containerd/containerd/content"
|
|
||||||
"github.com/containerd/containerd/content/local"
|
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/containerd/containerd/platforms"
|
"github.com/containerd/containerd/platforms"
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
@@ -30,9 +27,9 @@ import (
|
|||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/builder/remotecontext/urlutil"
|
|
||||||
dockerclient "github.com/docker/docker/client"
|
dockerclient "github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
|
"github.com/docker/docker/pkg/urlutil"
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
"github.com/moby/buildkit/client/llb"
|
"github.com/moby/buildkit/client/llb"
|
||||||
"github.com/moby/buildkit/exporter/containerimage/exptypes"
|
"github.com/moby/buildkit/exporter/containerimage/exptypes"
|
||||||
@@ -79,9 +76,6 @@ type Options struct {
|
|||||||
Tags []string
|
Tags []string
|
||||||
Target string
|
Target string
|
||||||
Ulimits *opts.UlimitOpt
|
Ulimits *opts.UlimitOpt
|
||||||
|
|
||||||
// Linked marks this target as exclusively linked (not requested by the user).
|
|
||||||
Linked bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Inputs struct {
|
type Inputs struct {
|
||||||
@@ -592,7 +586,7 @@ func toSolveOpt(ctx context.Context, di DriverInfo, multiDriver bool, opt Option
|
|||||||
so.FrontendAttrs["force-network-mode"] = opt.NetworkMode
|
so.FrontendAttrs["force-network-mode"] = opt.NetworkMode
|
||||||
case "", "default":
|
case "", "default":
|
||||||
default:
|
default:
|
||||||
return nil, nil, errors.Errorf("network mode %q not supported by buildkit - you can define a custom network for your builder using the network driver-opt in buildx create", opt.NetworkMode)
|
return nil, nil, errors.Errorf("network mode %q not supported by buildkit. You can define a custom network for your builder using the network driver-opt in buildx create.", opt.NetworkMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup extrahosts
|
// setup extrahosts
|
||||||
@@ -618,101 +612,7 @@ func toSolveOpt(ctx context.Context, di DriverInfo, multiDriver bool, opt Option
|
|||||||
return &so, releaseF, nil
|
return &so, releaseF, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerConfig is configuration for a container to run.
|
|
||||||
type ContainerConfig struct {
|
|
||||||
ResultCtx *ResultContext
|
|
||||||
Args []string
|
|
||||||
Env []string
|
|
||||||
User string
|
|
||||||
Cwd string
|
|
||||||
Tty bool
|
|
||||||
Stdin io.ReadCloser
|
|
||||||
Stdout io.WriteCloser
|
|
||||||
Stderr io.WriteCloser
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResultContext is a build result with the client that built it.
|
|
||||||
type ResultContext struct {
|
|
||||||
Client *client.Client
|
|
||||||
Res *gateway.Result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invoke invokes a build result as a container.
|
|
||||||
func Invoke(ctx context.Context, cfg ContainerConfig) error {
|
|
||||||
if cfg.ResultCtx == nil {
|
|
||||||
return errors.Errorf("result must be provided")
|
|
||||||
}
|
|
||||||
c, res := cfg.ResultCtx.Client, cfg.ResultCtx.Res
|
|
||||||
_, err := c.Build(ctx, client.SolveOpt{}, "buildx", func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
|
|
||||||
if res.Ref == nil {
|
|
||||||
return nil, errors.Errorf("no reference is registered")
|
|
||||||
}
|
|
||||||
st, err := res.Ref.ToState()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
def, err := st.Marshal(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
imgRef, err := c.Solve(ctx, gateway.SolveRequest{
|
|
||||||
Definition: def.ToPB(),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ctr, err := c.NewContainer(ctx, gateway.NewContainerRequest{
|
|
||||||
Mounts: []gateway.Mount{
|
|
||||||
{
|
|
||||||
Dest: "/",
|
|
||||||
MountType: pb.MountType_BIND,
|
|
||||||
Ref: imgRef.Ref,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer ctr.Release(ctx)
|
|
||||||
proc, err := ctr.Start(ctx, gateway.StartRequest{
|
|
||||||
Args: cfg.Args,
|
|
||||||
Env: cfg.Env,
|
|
||||||
User: cfg.User,
|
|
||||||
Cwd: cfg.Cwd,
|
|
||||||
Tty: cfg.Tty,
|
|
||||||
Stdin: cfg.Stdin,
|
|
||||||
Stdout: cfg.Stdout,
|
|
||||||
Stderr: cfg.Stderr,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("failed to start container: %v", err)
|
|
||||||
}
|
|
||||||
errCh := make(chan error)
|
|
||||||
doneCh := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
if err := proc.Wait(); err != nil {
|
|
||||||
errCh <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
close(doneCh)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-doneCh:
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
case err := <-errCh:
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}, nil)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, docker DockerAPI, configDir string, w progress.Writer) (resp map[string]*client.SolveResponse, err error) {
|
func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, docker DockerAPI, configDir string, w progress.Writer) (resp map[string]*client.SolveResponse, err error) {
|
||||||
return BuildWithResultHandler(ctx, drivers, opt, docker, configDir, w, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildWithResultHandler(ctx context.Context, drivers []DriverInfo, opt map[string]Options, docker DockerAPI, configDir string, w progress.Writer, resultHandleFunc func(driverIndex int, rCtx *ResultContext)) (resp map[string]*client.SolveResponse, err error) {
|
|
||||||
if len(drivers) == 0 {
|
if len(drivers) == 0 {
|
||||||
return nil, errors.Errorf("driver required for build")
|
return nil, errors.Errorf("driver required for build")
|
||||||
}
|
}
|
||||||
@@ -731,22 +631,12 @@ func BuildWithResultHandler(ctx context.Context, drivers []DriverInfo, opt map[s
|
|||||||
}
|
}
|
||||||
|
|
||||||
if noMobyDriver != nil && !noDefaultLoad() {
|
if noMobyDriver != nil && !noDefaultLoad() {
|
||||||
var noOutputTargets []string
|
for _, opt := range opt {
|
||||||
for name, opt := range opt {
|
if len(opt.Exports) == 0 {
|
||||||
if !opt.Linked && len(opt.Exports) == 0 {
|
logrus.Warnf("No output specified for %s driver. Build result will only remain in the build cache. To push result image into registry use --push or to load image into docker use --load", noMobyDriver.Factory().Name())
|
||||||
noOutputTargets = append(noOutputTargets, name)
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(noOutputTargets) > 0 {
|
|
||||||
var warnNoOutputBuf bytes.Buffer
|
|
||||||
warnNoOutputBuf.WriteString("No output specified ")
|
|
||||||
if len(noOutputTargets) == 1 && noOutputTargets[0] == "default" {
|
|
||||||
warnNoOutputBuf.WriteString(fmt.Sprintf("with %s driver", noMobyDriver.Factory().Name()))
|
|
||||||
} else {
|
|
||||||
warnNoOutputBuf.WriteString(fmt.Sprintf("for %s target(s) with %s driver", strings.Join(noOutputTargets, ", "), noMobyDriver.Factory().Name()))
|
|
||||||
}
|
|
||||||
logrus.Warnf("%s. Build result will only remain in the build cache. To push result image into registry use --push or to load image into docker use --load", warnNoOutputBuf.String())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m, clients, err := resolveDrivers(ctx, drivers, opt, w)
|
m, clients, err := resolveDrivers(ctx, drivers, opt, w)
|
||||||
@@ -881,7 +771,7 @@ func BuildWithResultHandler(ctx context.Context, drivers []DriverInfo, opt map[s
|
|||||||
dgst = v
|
dgst = v
|
||||||
}
|
}
|
||||||
if opt.ImageIDFile != "" {
|
if opt.ImageIDFile != "" {
|
||||||
return os.WriteFile(opt.ImageIDFile, []byte(dgst), 0644)
|
return ioutil.WriteFile(opt.ImageIDFile, []byte(dgst), 0644)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -925,26 +815,12 @@ func BuildWithResultHandler(ctx context.Context, drivers []DriverInfo, opt map[s
|
|||||||
|
|
||||||
itpull := imagetools.New(imageopt)
|
itpull := imagetools.New(imageopt)
|
||||||
|
|
||||||
ref, err := reference.ParseNormalizedNamed(names[0])
|
dt, desc, err := itpull.Combine(ctx, names[0], descs)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ref = reference.TagNameOnly(ref)
|
|
||||||
|
|
||||||
srcs := make([]*imagetools.Source, len(descs))
|
|
||||||
for i, desc := range descs {
|
|
||||||
srcs[i] = &imagetools.Source{
|
|
||||||
Desc: desc,
|
|
||||||
Ref: ref,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dt, desc, err := itpull.Combine(ctx, srcs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if opt.ImageIDFile != "" {
|
if opt.ImageIDFile != "" {
|
||||||
if err := os.WriteFile(opt.ImageIDFile, []byte(desc.Digest), 0644); err != nil {
|
if err := ioutil.WriteFile(opt.ImageIDFile, []byte(desc.Digest), 0644); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1038,16 +914,12 @@ func BuildWithResultHandler(ctx context.Context, drivers []DriverInfo, opt map[s
|
|||||||
ch, done := progress.NewChannel(pw)
|
ch, done := progress.NewChannel(pw)
|
||||||
defer func() { <-done }()
|
defer func() { <-done }()
|
||||||
|
|
||||||
cc := c
|
|
||||||
rr, err := c.Build(ctx, so, "buildx", func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
|
rr, err := c.Build(ctx, so, "buildx", func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
|
||||||
res, err := c.Solve(ctx, req)
|
res, err := c.Solve(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
results.Set(resultKey(dp.driverIndex, k), res)
|
results.Set(resultKey(dp.driverIndex, k), res)
|
||||||
if resultHandleFunc != nil {
|
|
||||||
resultHandleFunc(dp.driverIndex, &ResultContext{cc, res})
|
|
||||||
}
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}, ch)
|
}, ch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1214,7 +1086,7 @@ func remoteDigestWithMoby(ctx context.Context, d driver.Driver, name string) (st
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createTempDockerfile(r io.Reader) (string, error) {
|
func createTempDockerfile(r io.Reader) (string, error) {
|
||||||
dir, err := os.MkdirTemp("", "dockerfile")
|
dir, err := ioutil.TempDir("", "dockerfile")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -1273,7 +1145,7 @@ func LoadInputs(ctx context.Context, d driver.Driver, inp Inputs, pw progress.Wr
|
|||||||
}
|
}
|
||||||
// stdin is dockerfile
|
// stdin is dockerfile
|
||||||
dockerfileReader = buf
|
dockerfileReader = buf
|
||||||
inp.ContextPath, _ = os.MkdirTemp("", "empty-dir")
|
inp.ContextPath, _ = ioutil.TempDir("", "empty-dir")
|
||||||
toRemove = append(toRemove, inp.ContextPath)
|
toRemove = append(toRemove, inp.ContextPath)
|
||||||
target.LocalDirs["context"] = inp.ContextPath
|
target.LocalDirs["context"] = inp.ContextPath
|
||||||
}
|
}
|
||||||
@@ -1293,7 +1165,7 @@ func LoadInputs(ctx context.Context, d driver.Driver, inp Inputs, pw progress.Wr
|
|||||||
|
|
||||||
case urlutil.IsGitURL(inp.ContextPath), urlutil.IsURL(inp.ContextPath):
|
case urlutil.IsGitURL(inp.ContextPath), urlutil.IsURL(inp.ContextPath):
|
||||||
if inp.DockerfilePath == "-" {
|
if inp.DockerfilePath == "-" {
|
||||||
dockerfileReader = inp.InStream
|
return nil, errors.Errorf("Dockerfile from stdin is not supported with remote contexts")
|
||||||
}
|
}
|
||||||
target.FrontendAttrs["context"] = inp.ContextPath
|
target.FrontendAttrs["context"] = inp.ContextPath
|
||||||
default:
|
default:
|
||||||
@@ -1350,32 +1222,6 @@ func LoadInputs(ctx context.Context, d driver.Driver, inp Inputs, pw progress.Wr
|
|||||||
target.FrontendAttrs["context:"+k] = v.Path
|
target.FrontendAttrs["context:"+k] = v.Path
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle OCI layout
|
|
||||||
if strings.HasPrefix(v.Path, "oci-layout://") {
|
|
||||||
pathAlone := strings.TrimPrefix(v.Path, "oci-layout://")
|
|
||||||
parts := strings.SplitN(pathAlone, "@", 2)
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return nil, errors.Errorf("invalid oci-layout context %s, must be oci-layout:///path/to/layout@sha256:hash", v.Path)
|
|
||||||
}
|
|
||||||
localPath := parts[0]
|
|
||||||
dgst, err := digest.Parse(parts[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "invalid oci-layout context %s, does not have proper hash, must be oci-layout:///path/to/layout@sha256:hash", v.Path)
|
|
||||||
}
|
|
||||||
store, err := local.NewStore(localPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "invalid store at %s", localPath)
|
|
||||||
}
|
|
||||||
// now we can add it
|
|
||||||
if target.OCIStores == nil {
|
|
||||||
target.OCIStores = map[string]content.Store{}
|
|
||||||
}
|
|
||||||
target.OCIStores[k] = store
|
|
||||||
|
|
||||||
target.FrontendAttrs["context:"+k] = fmt.Sprintf("oci-layout:%s@%s", k, dgst.String())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
st, err := os.Stat(v.Path)
|
st, err := os.Stat(v.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "failed to get build context %v", k)
|
return nil, errors.Wrapf(err, "failed to get build context %v", k)
|
||||||
@@ -1618,13 +1464,13 @@ func tryNodeIdentifier(configDir string) (out string) {
|
|||||||
if _, err := rand.Read(b); err != nil {
|
if _, err := rand.Read(b); err != nil {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(sessionFile, []byte(hex.EncodeToString(b)), 0600); err != nil {
|
if err := ioutil.WriteFile(sessionFile, []byte(hex.EncodeToString(b)), 0600); err != nil {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dt, err := os.ReadFile(sessionFile)
|
dt, err := ioutil.ReadFile(sessionFile)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return string(dt)
|
return string(dt)
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@ package build
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
@@ -53,11 +53,11 @@ func createTempDockerfileFromURL(ctx context.Context, d driver.Driver, url strin
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
dir, err := os.MkdirTemp("", "buildx")
|
dir, err := ioutil.TempDir("", "buildx")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(filepath.Join(dir, "Dockerfile"), dt, 0600); err != nil {
|
if err := ioutil.WriteFile(filepath.Join(dir, "Dockerfile"), dt, 0600); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
out = dir
|
out = dir
|
||||||
|
@@ -15,8 +15,13 @@ import (
|
|||||||
cliflags "github.com/docker/cli/cli/flags"
|
cliflags "github.com/docker/cli/cli/flags"
|
||||||
"github.com/moby/buildkit/solver/errdefs"
|
"github.com/moby/buildkit/solver/errdefs"
|
||||||
"github.com/moby/buildkit/util/stack"
|
"github.com/moby/buildkit/util/stack"
|
||||||
|
"github.com/moby/buildkit/util/tracing/detect"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/azure"
|
_ "github.com/moby/buildkit/util/tracing/detect/delegated"
|
||||||
|
_ "github.com/moby/buildkit/util/tracing/env"
|
||||||
|
|
||||||
|
// FIXME: "k8s.io/client-go/plugin/pkg/client/auth/azure" is excluded because of compilation error
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
|
_ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
|
||||||
@@ -24,67 +29,75 @@ import (
|
|||||||
_ "github.com/docker/buildx/driver/docker"
|
_ "github.com/docker/buildx/driver/docker"
|
||||||
_ "github.com/docker/buildx/driver/docker-container"
|
_ "github.com/docker/buildx/driver/docker-container"
|
||||||
_ "github.com/docker/buildx/driver/kubernetes"
|
_ "github.com/docker/buildx/driver/kubernetes"
|
||||||
_ "github.com/docker/buildx/driver/remote"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var experimental string
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
seed.WithTimeAndRand()
|
seed.WithTimeAndRand()
|
||||||
stack.SetVersionInfo(version.Version, version.Revision)
|
stack.SetVersionInfo(version.Version, version.Revision)
|
||||||
}
|
|
||||||
|
|
||||||
func runStandalone(cmd *command.DockerCli) error {
|
detect.ServiceName = "buildx"
|
||||||
if err := cmd.Initialize(cliflags.NewClientOptions()); err != nil {
|
// do not log tracing errors to stdio
|
||||||
return err
|
otel.SetErrorHandler(skipErrors{})
|
||||||
}
|
|
||||||
rootCmd := commands.NewRootCmd(os.Args[0], false, cmd)
|
|
||||||
return rootCmd.Execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
func runPlugin(cmd *command.DockerCli) error {
|
|
||||||
rootCmd := commands.NewRootCmd("buildx", true, cmd)
|
|
||||||
return plugin.RunPlugin(cmd, rootCmd, manager.Metadata{
|
|
||||||
SchemaVersion: "0.1.0",
|
|
||||||
Vendor: "Docker Inc.",
|
|
||||||
Version: version.Version,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cmd, err := command.NewDockerCli()
|
if plugin.RunningStandalone() {
|
||||||
|
dockerCli, err := command.NewDockerCli()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
opts := cliflags.NewClientOptions()
|
||||||
|
dockerCli.Initialize(opts)
|
||||||
|
rootCmd := commands.NewRootCmd(os.Args[0], false, dockerCli)
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
dockerCli, err := command.NewDockerCli()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if plugin.RunningStandalone() {
|
p := commands.NewRootCmd("buildx", true, dockerCli)
|
||||||
err = runStandalone(cmd)
|
meta := manager.Metadata{
|
||||||
} else {
|
SchemaVersion: "0.1.0",
|
||||||
err = runPlugin(cmd)
|
Vendor: "Docker Inc.",
|
||||||
}
|
Version: version.Version,
|
||||||
if err == nil {
|
Experimental: experimental != "",
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if sterr, ok := err.(cli.StatusError); ok {
|
if err := plugin.RunPlugin(dockerCli, p, meta); err != nil {
|
||||||
if sterr.Status != "" {
|
if sterr, ok := err.(cli.StatusError); ok {
|
||||||
fmt.Fprintln(cmd.Err(), sterr.Status)
|
if sterr.Status != "" {
|
||||||
|
fmt.Fprintln(dockerCli.Err(), sterr.Status)
|
||||||
|
}
|
||||||
|
// StatusError should only be used for errors, and all errors should
|
||||||
|
// have a non-zero exit status, so never exit with 0
|
||||||
|
if sterr.StatusCode == 0 {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
os.Exit(sterr.StatusCode)
|
||||||
}
|
}
|
||||||
// StatusError should only be used for errors, and all errors should
|
for _, s := range errdefs.Sources(err) {
|
||||||
// have a non-zero exit status, so never exit with 0
|
s.Print(dockerCli.Err())
|
||||||
if sterr.StatusCode == 0 {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
os.Exit(sterr.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s := range errdefs.Sources(err) {
|
if debug.IsEnabled() {
|
||||||
s.Print(cmd.Err())
|
fmt.Fprintf(dockerCli.Err(), "error: %+v", stack.Formatter(err))
|
||||||
}
|
} else {
|
||||||
if debug.IsEnabled() {
|
fmt.Fprintf(dockerCli.Err(), "error: %v\n", err)
|
||||||
fmt.Fprintf(cmd.Err(), "ERROR: %+v", stack.Formatter(err))
|
}
|
||||||
} else {
|
|
||||||
fmt.Fprintf(cmd.Err(), "ERROR: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type skipErrors struct{}
|
||||||
|
|
||||||
|
func (skipErrors) Handle(err error) {}
|
||||||
|
@@ -1,19 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/moby/buildkit/util/tracing/detect"
|
|
||||||
"go.opentelemetry.io/otel"
|
|
||||||
|
|
||||||
_ "github.com/moby/buildkit/util/tracing/detect/delegated"
|
|
||||||
_ "github.com/moby/buildkit/util/tracing/env"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
detect.ServiceName = "buildx"
|
|
||||||
// do not log tracing errors to stdio
|
|
||||||
otel.SetErrorHandler(skipErrors{})
|
|
||||||
}
|
|
||||||
|
|
||||||
type skipErrors struct{}
|
|
||||||
|
|
||||||
func (skipErrors) Handle(err error) {}
|
|
@@ -104,8 +104,8 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error
|
|||||||
}
|
}
|
||||||
|
|
||||||
tgts, grps, err := bake.ReadTargets(ctx, files, targets, overrides, map[string]string{
|
tgts, grps, err := bake.ReadTargets(ctx, files, targets, overrides, map[string]string{
|
||||||
// don't forget to update documentation if you add a new
|
// Don't forget to update documentation if you add a new
|
||||||
// built-in variable: docs/guides/bake/file-definition.md#built-in-variables
|
// built-in variable: docs/reference/buildx_bake.md#built-in-variables
|
||||||
"BAKE_CMD_CONTEXT": cmdContext,
|
"BAKE_CMD_CONTEXT": cmdContext,
|
||||||
"BAKE_LOCAL_PLATFORM": platforms.DefaultString(),
|
"BAKE_LOCAL_PLATFORM": platforms.DefaultString(),
|
||||||
})
|
})
|
||||||
|
@@ -4,19 +4,14 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/csv"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/containerd/console"
|
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/build"
|
||||||
"github.com/docker/buildx/monitor"
|
|
||||||
"github.com/docker/buildx/util/buildflags"
|
"github.com/docker/buildx/util/buildflags"
|
||||||
"github.com/docker/buildx/util/confutil"
|
"github.com/docker/buildx/util/confutil"
|
||||||
"github.com/docker/buildx/util/platformutil"
|
"github.com/docker/buildx/util/platformutil"
|
||||||
@@ -25,7 +20,6 @@ import (
|
|||||||
"github.com/docker/cli-docs-tool/annotation"
|
"github.com/docker/cli-docs-tool/annotation"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/config"
|
|
||||||
dockeropts "github.com/docker/cli/opts"
|
dockeropts "github.com/docker/cli/opts"
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
@@ -69,7 +63,6 @@ type buildOptions struct {
|
|||||||
tags []string
|
tags []string
|
||||||
target string
|
target string
|
||||||
ulimits *dockeropts.UlimitOpt
|
ulimits *dockeropts.UlimitOpt
|
||||||
invoke string
|
|
||||||
commonOptions
|
commonOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,8 +142,7 @@ func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
|
|||||||
}
|
}
|
||||||
opts.Platforms = platforms
|
opts.Platforms = platforms
|
||||||
|
|
||||||
dockerConfig := config.LoadDefaultConfigFile(os.Stderr)
|
opts.Session = append(opts.Session, authprovider.NewDockerAuthProvider(os.Stderr))
|
||||||
opts.Session = append(opts.Session, authprovider.NewDockerAuthProvider(dockerConfig))
|
|
||||||
|
|
||||||
secrets, err := buildflags.ParseSecretSpecs(in.secrets)
|
secrets, err := buildflags.ParseSecretSpecs(in.secrets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -233,48 +225,22 @@ func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
|
|||||||
contextPathHash = in.contextPath
|
contextPathHash = in.contextPath
|
||||||
}
|
}
|
||||||
|
|
||||||
imageID, res, err := buildTargets(ctx, dockerCli, map[string]build.Options{defaultTargetName: opts}, in.progress, contextPathHash, in.builder, in.metadataFile)
|
imageID, err := buildTargets(ctx, dockerCli, map[string]build.Options{defaultTargetName: opts}, in.progress, contextPathHash, in.builder, in.metadataFile)
|
||||||
err = wrapBuildError(err, false)
|
err = wrapBuildError(err, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if in.invoke != "" {
|
|
||||||
cfg, err := parseInvokeConfig(in.invoke)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cfg.ResultCtx = res
|
|
||||||
con := console.Current()
|
|
||||||
if err := con.SetRaw(); err != nil {
|
|
||||||
return errors.Errorf("failed to configure terminal: %v", err)
|
|
||||||
}
|
|
||||||
err = monitor.RunMonitor(ctx, cfg, func(ctx context.Context) (*build.ResultContext, error) {
|
|
||||||
_, rr, err := buildTargets(ctx, dockerCli, map[string]build.Options{defaultTargetName: opts}, in.progress, contextPathHash, in.builder, in.metadataFile)
|
|
||||||
return rr, err
|
|
||||||
}, io.NopCloser(os.Stdin), nopCloser{os.Stdout}, nopCloser{os.Stderr})
|
|
||||||
if err != nil {
|
|
||||||
logrus.Warnf("failed to run monitor: %v", err)
|
|
||||||
}
|
|
||||||
con.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.quiet {
|
if in.quiet {
|
||||||
fmt.Println(imageID)
|
fmt.Println(imageID)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type nopCloser struct {
|
func buildTargets(ctx context.Context, dockerCli command.Cli, opts map[string]build.Options, progressMode, contextPathHash, instance string, metadataFile string) (imageID string, err error) {
|
||||||
io.WriteCloser
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c nopCloser) Close() error { return nil }
|
|
||||||
|
|
||||||
func buildTargets(ctx context.Context, dockerCli command.Cli, opts map[string]build.Options, progressMode, contextPathHash, instance string, metadataFile string) (imageID string, res *build.ResultContext, err error) {
|
|
||||||
dis, err := getInstanceOrDefault(ctx, dockerCli, instance, contextPathHash)
|
dis, err := getInstanceOrDefault(ctx, dockerCli, instance, contextPathHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx2, cancel := context.WithCancel(context.TODO())
|
ctx2, cancel := context.WithCancel(context.TODO())
|
||||||
@@ -282,82 +248,24 @@ func buildTargets(ctx context.Context, dockerCli command.Cli, opts map[string]bu
|
|||||||
|
|
||||||
printer := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, progressMode)
|
printer := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, progressMode)
|
||||||
|
|
||||||
var mu sync.Mutex
|
resp, err := build.Build(ctx, dis, opts, dockerAPI(dockerCli), confutil.ConfigDir(dockerCli), printer)
|
||||||
var idx int
|
|
||||||
resp, err := build.BuildWithResultHandler(ctx, dis, opts, dockerAPI(dockerCli), confutil.ConfigDir(dockerCli), printer, func(driverIndex int, gotRes *build.ResultContext) {
|
|
||||||
mu.Lock()
|
|
||||||
defer mu.Unlock()
|
|
||||||
if res == nil || driverIndex < idx {
|
|
||||||
idx, res = driverIndex, gotRes
|
|
||||||
}
|
|
||||||
})
|
|
||||||
err1 := printer.Wait()
|
err1 := printer.Wait()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = err1
|
err = err1
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(metadataFile) > 0 && resp != nil {
|
if len(metadataFile) > 0 && resp != nil {
|
||||||
if err := writeMetadataFile(metadataFile, decodeExporterResponse(resp[defaultTargetName].ExporterResponse)); err != nil {
|
if err := writeMetadataFile(metadataFile, decodeExporterResponse(resp[defaultTargetName].ExporterResponse)); err != nil {
|
||||||
return "", nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
printWarnings(os.Stderr, printer.Warnings(), progressMode)
|
printWarnings(os.Stderr, printer.Warnings(), progressMode)
|
||||||
|
|
||||||
return resp[defaultTargetName].ExporterResponse["containerimage.digest"], res, err
|
return resp[defaultTargetName].ExporterResponse["containerimage.digest"], err
|
||||||
}
|
|
||||||
|
|
||||||
func parseInvokeConfig(invoke string) (cfg build.ContainerConfig, err error) {
|
|
||||||
csvReader := csv.NewReader(strings.NewReader(invoke))
|
|
||||||
fields, err := csvReader.Read()
|
|
||||||
if err != nil {
|
|
||||||
return cfg, err
|
|
||||||
}
|
|
||||||
cfg.Tty = true
|
|
||||||
if len(fields) == 1 && !strings.Contains(fields[0], "=") {
|
|
||||||
cfg.Args = []string{fields[0]}
|
|
||||||
return cfg, nil
|
|
||||||
}
|
|
||||||
var entrypoint string
|
|
||||||
var args []string
|
|
||||||
for _, field := range fields {
|
|
||||||
parts := strings.SplitN(field, "=", 2)
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return cfg, errors.Errorf("invalid value %s", field)
|
|
||||||
}
|
|
||||||
key := strings.ToLower(parts[0])
|
|
||||||
value := parts[1]
|
|
||||||
switch key {
|
|
||||||
case "args":
|
|
||||||
args = append(args, value) // TODO: support JSON
|
|
||||||
case "entrypoint":
|
|
||||||
entrypoint = value // TODO: support JSON
|
|
||||||
case "env":
|
|
||||||
cfg.Env = append(cfg.Env, value)
|
|
||||||
case "user":
|
|
||||||
cfg.User = value
|
|
||||||
case "cwd":
|
|
||||||
cfg.Cwd = value
|
|
||||||
case "tty":
|
|
||||||
cfg.Tty, err = strconv.ParseBool(value)
|
|
||||||
if err != nil {
|
|
||||||
return cfg, errors.Errorf("failed to parse tty: %v", err)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return cfg, errors.Errorf("unknown key %q", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cfg.Args = args
|
|
||||||
if entrypoint != "" {
|
|
||||||
cfg.Args = append([]string{entrypoint}, cfg.Args...)
|
|
||||||
}
|
|
||||||
if len(cfg.Args) == 0 {
|
|
||||||
cfg.Args = []string{"sh"}
|
|
||||||
}
|
|
||||||
return cfg, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func printWarnings(w io.Writer, warnings []client.VertexWarning, mode string) {
|
func printWarnings(w io.Writer, warnings []client.VertexWarning, mode string) {
|
||||||
@@ -481,10 +389,6 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
|
|
||||||
flags.Var(options.ulimits, "ulimit", "Ulimit options")
|
flags.Var(options.ulimits, "ulimit", "Ulimit options")
|
||||||
|
|
||||||
if os.Getenv("BUILDX_EXPERIMENTAL") == "1" {
|
|
||||||
flags.StringVar(&options.invoke, "invoke", "", "Invoke a command after the build. BUILDX_EXPERIMENTAL=1 is required.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// hidden flags
|
// hidden flags
|
||||||
var ignore string
|
var ignore string
|
||||||
var ignoreSlice []string
|
var ignoreSlice []string
|
||||||
|
@@ -14,7 +14,6 @@ import (
|
|||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/store"
|
||||||
"github.com/docker/buildx/store/storeutil"
|
"github.com/docker/buildx/store/storeutil"
|
||||||
"github.com/docker/buildx/util/cobrautil"
|
"github.com/docker/buildx/util/cobrautil"
|
||||||
"github.com/docker/buildx/util/confutil"
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/google/shlex"
|
"github.com/google/shlex"
|
||||||
@@ -61,26 +60,16 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildkitHost := os.Getenv("BUILDKIT_HOST")
|
|
||||||
|
|
||||||
driverName := in.driver
|
driverName := in.driver
|
||||||
if driverName == "" {
|
if driverName == "" {
|
||||||
if len(args) == 0 && buildkitHost != "" {
|
f, err := driver.GetDefaultFactory(ctx, dockerCli.Client(), true)
|
||||||
driverName = "remote"
|
if err != nil {
|
||||||
} else {
|
return err
|
||||||
var arg string
|
|
||||||
if len(args) > 0 {
|
|
||||||
arg = args[0]
|
|
||||||
}
|
|
||||||
f, err := driver.GetDefaultFactory(ctx, arg, dockerCli.Client(), true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if f == nil {
|
|
||||||
return errors.Errorf("no valid drivers found")
|
|
||||||
}
|
|
||||||
driverName = f.Name()
|
|
||||||
}
|
}
|
||||||
|
if f == nil {
|
||||||
|
return errors.Errorf("no valid drivers found")
|
||||||
|
}
|
||||||
|
driverName = f.Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
if driver.GetFactory(driverName, true) == nil {
|
if driver.GetFactory(driverName, true) == nil {
|
||||||
@@ -101,19 +90,6 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !in.actionLeave && !in.actionAppend {
|
|
||||||
contexts, err := dockerCli.ContextStore().List()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, c := range contexts {
|
|
||||||
if c.Name == name {
|
|
||||||
logrus.Warnf("instance name %q already exists as context builder", name)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ng, err := txn.NodeGroupByName(name)
|
ng, err := txn.NodeGroupByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(errors.Cause(err)) {
|
if os.IsNotExist(errors.Cause(err)) {
|
||||||
@@ -134,11 +110,6 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOriginal := ng
|
|
||||||
if ngOriginal != nil {
|
|
||||||
ngOriginal = ngOriginal.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
if ng == nil {
|
if ng == nil {
|
||||||
ng = &store.NodeGroup{
|
ng = &store.NodeGroup{
|
||||||
Name: name,
|
Name: name,
|
||||||
@@ -158,69 +129,44 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ep string
|
var ep string
|
||||||
var setEp bool
|
|
||||||
if in.actionLeave {
|
if in.actionLeave {
|
||||||
if err := ng.Leave(in.nodeName); err != nil {
|
if err := ng.Leave(in.nodeName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch {
|
if len(args) > 0 {
|
||||||
case driverName == "kubernetes":
|
ep, err = validateEndpoint(dockerCli, args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if dockerCli.CurrentContext() == "default" && dockerCli.DockerEndpoint().TLSData != nil {
|
||||||
|
return errors.Errorf("could not create a builder instance with TLS data loaded from environment. Please use `docker context create <context-name>` to create a context for current environment and then create a builder instance with `docker buildx create <context-name>`")
|
||||||
|
}
|
||||||
|
|
||||||
|
ep, err = storeutil.GetCurrentEndpoint(dockerCli)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if in.driver == "kubernetes" {
|
||||||
// naming endpoint to make --append works
|
// naming endpoint to make --append works
|
||||||
ep = (&url.URL{
|
ep = (&url.URL{
|
||||||
Scheme: driverName,
|
Scheme: in.driver,
|
||||||
Path: "/" + in.name,
|
Path: "/" + in.name,
|
||||||
RawQuery: (&url.Values{
|
RawQuery: (&url.Values{
|
||||||
"deployment": {in.nodeName},
|
"deployment": {in.nodeName},
|
||||||
"kubeconfig": {os.Getenv("KUBECONFIG")},
|
"kubeconfig": {os.Getenv("KUBECONFIG")},
|
||||||
}).Encode(),
|
}).Encode(),
|
||||||
}).String()
|
}).String()
|
||||||
setEp = false
|
|
||||||
case driverName == "remote":
|
|
||||||
if len(args) > 0 {
|
|
||||||
ep = args[0]
|
|
||||||
} else if buildkitHost != "" {
|
|
||||||
ep = buildkitHost
|
|
||||||
} else {
|
|
||||||
return errors.Errorf("no remote endpoint provided")
|
|
||||||
}
|
|
||||||
ep, err = validateBuildkitEndpoint(ep)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
setEp = true
|
|
||||||
case len(args) > 0:
|
|
||||||
ep, err = validateEndpoint(dockerCli, args[0])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
setEp = true
|
|
||||||
default:
|
|
||||||
if dockerCli.CurrentContext() == "default" && dockerCli.DockerEndpoint().TLSData != nil {
|
|
||||||
return errors.Errorf("could not create a builder instance with TLS data loaded from environment. Please use `docker context create <context-name>` to create a context for current environment and then create a builder instance with `docker buildx create <context-name>`")
|
|
||||||
}
|
|
||||||
ep, err = storeutil.GetCurrentEndpoint(dockerCli)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
setEp = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := csvToMap(in.driverOpts)
|
m, err := csvToMap(in.driverOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := ng.Update(in.nodeName, ep, in.platform, len(args) > 0, in.actionAppend, flags, in.configFile, m); err != nil {
|
||||||
if in.configFile == "" {
|
|
||||||
// if buildkit config is not provided, check if the default one is
|
|
||||||
// available and use it
|
|
||||||
if f, ok := confutil.DefaultConfigFile(dockerCli); ok {
|
|
||||||
logrus.Warnf("Using default BuildKit config in %s", f)
|
|
||||||
in.configFile = f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ng.Update(in.nodeName, ep, in.platform, setEp, in.actionAppend, flags, in.configFile, m); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,30 +175,6 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ngi := &nginfo{ng: ng}
|
|
||||||
|
|
||||||
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err = loadNodeGroupData(timeoutCtx, dockerCli, ngi); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, info := range ngi.drivers {
|
|
||||||
if err := info.di.Err; err != nil {
|
|
||||||
err := errors.Errorf("failed to initialize builder %s (%s): %s", ng.Name, info.di.Name, err)
|
|
||||||
var err2 error
|
|
||||||
if ngOriginal == nil {
|
|
||||||
err2 = txn.Remove(ng.Name)
|
|
||||||
} else {
|
|
||||||
err2 = txn.Save(ngOriginal)
|
|
||||||
}
|
|
||||||
if err2 != nil {
|
|
||||||
logrus.Warnf("Could not rollback to previous state: %s", err2)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.use && ep != "" {
|
if in.use && ep != "" {
|
||||||
current, err := storeutil.GetCurrentEndpoint(dockerCli)
|
current, err := storeutil.GetCurrentEndpoint(dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -263,6 +185,15 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngi := &nginfo{ng: ng}
|
||||||
|
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err = loadNodeGroupData(timeoutCtx, dockerCli, ngi); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if in.bootstrap {
|
if in.bootstrap {
|
||||||
if _, err = boot(ctx, ngi); err != nil {
|
if _, err = boot(ctx, ngi); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@@ -1,26 +0,0 @@
|
|||||||
package commands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCsvToMap(t *testing.T) {
|
|
||||||
d := []string{
|
|
||||||
"\"tolerations=key=foo,value=bar;key=foo2,value=bar2\",replicas=1",
|
|
||||||
"namespace=default",
|
|
||||||
}
|
|
||||||
r, err := csvToMap(d)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Contains(t, r, "tolerations")
|
|
||||||
require.Equal(t, r["tolerations"], "key=foo,value=bar;key=foo2,value=bar2")
|
|
||||||
|
|
||||||
require.Contains(t, r, "replicas")
|
|
||||||
require.Equal(t, r["replicas"], "1")
|
|
||||||
|
|
||||||
require.Contains(t, r, "namespace")
|
|
||||||
require.Equal(t, r["namespace"], "default")
|
|
||||||
}
|
|
@@ -1,16 +1,14 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"io/ioutil"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/store"
|
||||||
"github.com/docker/buildx/store/storeutil"
|
"github.com/docker/buildx/store/storeutil"
|
||||||
"github.com/docker/buildx/util/imagetools"
|
"github.com/docker/buildx/util/imagetools"
|
||||||
"github.com/docker/buildx/util/progress"
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
"github.com/moby/buildkit/util/appcontext"
|
||||||
@@ -27,7 +25,6 @@ type createOptions struct {
|
|||||||
tags []string
|
tags []string
|
||||||
dryrun bool
|
dryrun bool
|
||||||
actionAppend bool
|
actionAppend bool
|
||||||
progress string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||||
@@ -41,7 +38,7 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
|
|
||||||
fileArgs := make([]string, len(in.files))
|
fileArgs := make([]string, len(in.files))
|
||||||
for i, f := range in.files {
|
for i, f := range in.files {
|
||||||
dt, err := os.ReadFile(f)
|
dt, err := ioutil.ReadFile(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -81,21 +78,18 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
if len(repos) == 0 {
|
if len(repos) == 0 {
|
||||||
return errors.Errorf("no repositories specified, please set a reference in tag or source")
|
return errors.Errorf("no repositories specified, please set a reference in tag or source")
|
||||||
}
|
}
|
||||||
|
if len(repos) > 1 {
|
||||||
|
return errors.Errorf("multiple repositories currently not supported, found %v", repos)
|
||||||
|
}
|
||||||
|
|
||||||
var defaultRepo *string
|
var repo string
|
||||||
if len(repos) == 1 {
|
for r := range repos {
|
||||||
for repo := range repos {
|
repo = r
|
||||||
defaultRepo = &repo
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, s := range srcs {
|
for i, s := range srcs {
|
||||||
if s.Ref == nil && s.Desc.MediaType == "" && s.Desc.Digest != "" {
|
if s.Ref == nil && s.Desc.MediaType == "" && s.Desc.Digest != "" {
|
||||||
if defaultRepo == nil {
|
n, err := reference.ParseNormalizedNamed(repo)
|
||||||
return errors.Errorf("multiple repositories specified, cannot infer repository for %q", args[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := reference.ParseNormalizedNamed(*defaultRepo)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -149,6 +143,7 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
srcs[i].Ref = nil
|
||||||
if srcs[i].Desc.Digest == "" {
|
if srcs[i].Desc.Digest == "" {
|
||||||
srcs[i].Desc = desc
|
srcs[i].Desc = desc
|
||||||
} else {
|
} else {
|
||||||
@@ -167,7 +162,12 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dt, desc, err := r.Combine(ctx, srcs)
|
descs := make([]ocispec.Descriptor, len(srcs))
|
||||||
|
for i := range descs {
|
||||||
|
descs[i] = srcs[i].Desc
|
||||||
|
}
|
||||||
|
|
||||||
|
dt, desc, err := r.Combine(ctx, repo, descs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -180,49 +180,23 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
// new resolver cause need new auth
|
// new resolver cause need new auth
|
||||||
r = imagetools.New(imageopt)
|
r = imagetools.New(imageopt)
|
||||||
|
|
||||||
ctx2, cancel := context.WithCancel(context.TODO())
|
|
||||||
defer cancel()
|
|
||||||
printer := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, in.progress)
|
|
||||||
|
|
||||||
eg, _ := errgroup.WithContext(ctx)
|
|
||||||
pw := progress.WithPrefix(printer, "internal", true)
|
|
||||||
|
|
||||||
for _, t := range tags {
|
for _, t := range tags {
|
||||||
t := t
|
if err := r.Push(ctx, t, desc, dt); err != nil {
|
||||||
eg.Go(func() error {
|
return err
|
||||||
return progress.Wrap(fmt.Sprintf("pushing %s", t.String()), pw.Write, func(sub progress.SubLogger) error {
|
}
|
||||||
eg2, _ := errgroup.WithContext(ctx)
|
fmt.Println(t.String())
|
||||||
for _, s := range srcs {
|
|
||||||
if reference.Domain(s.Ref) == reference.Domain(t) && reference.Path(s.Ref) == reference.Path(t) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s := s
|
|
||||||
eg2.Go(func() error {
|
|
||||||
sub.Log(1, []byte(fmt.Sprintf("copying %s from %s to %s\n", s.Desc.Digest.String(), s.Ref.String(), t.String())))
|
|
||||||
return r.Copy(ctx, s, t)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := eg2.Wait(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sub.Log(1, []byte(fmt.Sprintf("pushing %s to %s\n", desc.Digest.String(), t.String())))
|
|
||||||
return r.Push(ctx, t, desc, dt)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = eg.Wait()
|
return nil
|
||||||
err1 := printer.Wait()
|
|
||||||
if err == nil {
|
|
||||||
err = err1
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSources(in []string) ([]*imagetools.Source, error) {
|
type src struct {
|
||||||
out := make([]*imagetools.Source, len(in))
|
Desc ocispec.Descriptor
|
||||||
|
Ref reference.Named
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSources(in []string) ([]*src, error) {
|
||||||
|
out := make([]*src, len(in))
|
||||||
for i, in := range in {
|
for i, in := range in {
|
||||||
s, err := parseSource(in)
|
s, err := parseSource(in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -245,11 +219,11 @@ func parseRefs(in []string) ([]reference.Named, error) {
|
|||||||
return refs, nil
|
return refs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSource(in string) (*imagetools.Source, error) {
|
func parseSource(in string) (*src, error) {
|
||||||
// source can be a digest, reference or a descriptor JSON
|
// source can be a digest, reference or a descriptor JSON
|
||||||
dgst, err := digest.Parse(in)
|
dgst, err := digest.Parse(in)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return &imagetools.Source{
|
return &src{
|
||||||
Desc: ocispec.Descriptor{
|
Desc: ocispec.Descriptor{
|
||||||
Digest: dgst,
|
Digest: dgst,
|
||||||
},
|
},
|
||||||
@@ -260,14 +234,14 @@ func parseSource(in string) (*imagetools.Source, error) {
|
|||||||
|
|
||||||
ref, err := reference.ParseNormalizedNamed(in)
|
ref, err := reference.ParseNormalizedNamed(in)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return &imagetools.Source{
|
return &src{
|
||||||
Ref: ref,
|
Ref: ref,
|
||||||
}, nil
|
}, nil
|
||||||
} else if !strings.HasPrefix(in, "{") {
|
} else if !strings.HasPrefix(in, "{") {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var s imagetools.Source
|
var s src
|
||||||
if err := json.Unmarshal([]byte(in), &s.Desc); err != nil {
|
if err := json.Unmarshal([]byte(in), &s.Desc); err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
@@ -281,7 +255,7 @@ func createCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command {
|
|||||||
Use: "create [OPTIONS] [SOURCE] [SOURCE...]",
|
Use: "create [OPTIONS] [SOURCE] [SOURCE...]",
|
||||||
Short: "Create a new image based on source images",
|
Short: "Create a new image based on source images",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
options.builder = *opts.Builder
|
options.builder = opts.Builder
|
||||||
return runCreate(dockerCli, options, args)
|
return runCreate(dockerCli, options, args)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -291,7 +265,6 @@ func createCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command {
|
|||||||
flags.StringArrayVarP(&options.tags, "tag", "t", []string{}, "Set reference for new image")
|
flags.StringArrayVarP(&options.tags, "tag", "t", []string{}, "Set reference for new image")
|
||||||
flags.BoolVar(&options.dryrun, "dry-run", false, "Show final image instead of pushing")
|
flags.BoolVar(&options.dryrun, "dry-run", false, "Show final image instead of pushing")
|
||||||
flags.BoolVar(&options.actionAppend, "append", false, "Append to existing manifest")
|
flags.BoolVar(&options.actionAppend, "append", false, "Append to existing manifest")
|
||||||
flags.StringVar(&options.progress, "progress", "auto", `Set type of progress output ("auto", "plain", "tty"). Use plain to show container output`)
|
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@@ -66,7 +66,7 @@ func inspectCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
|||||||
Short: "Show details of an image in the registry",
|
Short: "Show details of an image in the registry",
|
||||||
Args: cli.ExactArgs(1),
|
Args: cli.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
options.builder = *rootOpts.Builder
|
options.builder = rootOpts.Builder
|
||||||
return runInspect(dockerCli, options, args[0])
|
return runInspect(dockerCli, options, args[0])
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type RootOptions struct {
|
type RootOptions struct {
|
||||||
Builder *string
|
Builder string
|
||||||
}
|
}
|
||||||
|
|
||||||
func RootCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command {
|
func RootCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command {
|
||||||
|
@@ -79,7 +79,6 @@ func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
|||||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||||
fmt.Fprintf(w, "Name:\t%s\n", ngi.ng.Name)
|
fmt.Fprintf(w, "Name:\t%s\n", ngi.ng.Name)
|
||||||
fmt.Fprintf(w, "Driver:\t%s\n", ngi.ng.Driver)
|
fmt.Fprintf(w, "Driver:\t%s\n", ngi.ng.Driver)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
||||||
} else if ngi.err != nil {
|
} else if ngi.err != nil {
|
||||||
@@ -95,15 +94,6 @@ func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
|||||||
}
|
}
|
||||||
fmt.Fprintf(w, "Name:\t%s\n", n.Name)
|
fmt.Fprintf(w, "Name:\t%s\n", n.Name)
|
||||||
fmt.Fprintf(w, "Endpoint:\t%s\n", n.Endpoint)
|
fmt.Fprintf(w, "Endpoint:\t%s\n", n.Endpoint)
|
||||||
|
|
||||||
var driverOpts []string
|
|
||||||
for k, v := range n.DriverOpts {
|
|
||||||
driverOpts = append(driverOpts, fmt.Sprintf("%s=%q", k, v))
|
|
||||||
}
|
|
||||||
if len(driverOpts) > 0 {
|
|
||||||
fmt.Fprintf(w, "Driver Options:\t%s\n", strings.Join(driverOpts, " "))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ngi.drivers[i].di.Err; err != nil {
|
if err := ngi.drivers[i].di.Err; err != nil {
|
||||||
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
||||||
} else if err := ngi.drivers[i].err; err != nil {
|
} else if err := ngi.drivers[i].err; err != nil {
|
||||||
|
@@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
@@ -45,30 +45,23 @@ func runLs(dockerCli command.Cli, in lsOptions) error {
|
|||||||
builders[i] = &nginfo{ng: ng}
|
builders[i] = &nginfo{ng: ng}
|
||||||
}
|
}
|
||||||
|
|
||||||
contexts, err := dockerCli.ContextStore().List()
|
list, err := dockerCli.ContextStore().List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sort.Slice(contexts, func(i, j int) bool {
|
ctxbuilders := make([]*nginfo, len(list))
|
||||||
return contexts[i].Name < contexts[j].Name
|
for i, l := range list {
|
||||||
})
|
ctxbuilders[i] = &nginfo{ng: &store.NodeGroup{
|
||||||
for _, c := range contexts {
|
Name: l.Name,
|
||||||
ngi := &nginfo{ng: &store.NodeGroup{
|
|
||||||
Name: c.Name,
|
|
||||||
Nodes: []store.Node{{
|
Nodes: []store.Node{{
|
||||||
Name: c.Name,
|
Name: l.Name,
|
||||||
Endpoint: c.Name,
|
Endpoint: l.Name,
|
||||||
}},
|
}},
|
||||||
}}
|
}}
|
||||||
// if a context has the same name as an instance from the store, do not
|
|
||||||
// add it to the builders list. An instance from the store takes
|
|
||||||
// precedence over context builders.
|
|
||||||
if hasNodeGroup(builders, ngi) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
builders = append(builders, ngi)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
builders = append(builders, ctxbuilders...)
|
||||||
|
|
||||||
eg, _ := errgroup.WithContext(ctx)
|
eg, _ := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
for _, b := range builders {
|
for _, b := range builders {
|
||||||
@@ -99,72 +92,49 @@ func runLs(dockerCli command.Cli, in lsOptions) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w := tabwriter.NewWriter(dockerCli.Out(), 0, 0, 1, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||||
fmt.Fprintf(w, "NAME/NODE\tDRIVER/ENDPOINT\tSTATUS\tBUILDKIT\tPLATFORMS\n")
|
fmt.Fprintf(w, "NAME/NODE\tDRIVER/ENDPOINT\tSTATUS\tPLATFORMS\n")
|
||||||
|
|
||||||
currentSet := false
|
currentSet := false
|
||||||
printErr := false
|
|
||||||
for _, b := range builders {
|
for _, b := range builders {
|
||||||
if !currentSet && b.ng.Name == currentName {
|
if !currentSet && b.ng.Name == currentName {
|
||||||
b.ng.Name += " *"
|
b.ng.Name += " *"
|
||||||
currentSet = true
|
currentSet = true
|
||||||
}
|
}
|
||||||
if ok := printngi(w, b); !ok {
|
printngi(w, b)
|
||||||
printErr = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Flush()
|
w.Flush()
|
||||||
|
|
||||||
if printErr {
|
|
||||||
_, _ = fmt.Fprintf(dockerCli.Err(), "\n")
|
|
||||||
for _, b := range builders {
|
|
||||||
if b.err != nil {
|
|
||||||
_, _ = fmt.Fprintf(dockerCli.Err(), "Cannot load builder %s: %s\n", b.ng.Name, strings.TrimSpace(b.err.Error()))
|
|
||||||
} else {
|
|
||||||
for idx, n := range b.ng.Nodes {
|
|
||||||
d := b.drivers[idx]
|
|
||||||
var nodeErr string
|
|
||||||
if d.err != nil {
|
|
||||||
nodeErr = d.err.Error()
|
|
||||||
} else if d.di.Err != nil {
|
|
||||||
nodeErr = d.di.Err.Error()
|
|
||||||
}
|
|
||||||
if nodeErr != "" {
|
|
||||||
_, _ = fmt.Fprintf(dockerCli.Err(), "Failed to get status for %s (%s): %s\n", b.ng.Name, n.Name, strings.TrimSpace(nodeErr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func printngi(w io.Writer, ngi *nginfo) (ok bool) {
|
func printngi(w io.Writer, ngi *nginfo) {
|
||||||
ok = true
|
|
||||||
var err string
|
var err string
|
||||||
if ngi.err != nil {
|
if ngi.err != nil {
|
||||||
ok = false
|
err = ngi.err.Error()
|
||||||
err = "error"
|
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "%s\t%s\t%s\t\t\n", ngi.ng.Name, ngi.ng.Driver, err)
|
fmt.Fprintf(w, "%s\t%s\t%s\t\n", ngi.ng.Name, ngi.ng.Driver, err)
|
||||||
if ngi.err == nil {
|
if ngi.err == nil {
|
||||||
for idx, n := range ngi.ng.Nodes {
|
for idx, n := range ngi.ng.Nodes {
|
||||||
d := ngi.drivers[idx]
|
d := ngi.drivers[idx]
|
||||||
|
var err string
|
||||||
|
if d.err != nil {
|
||||||
|
err = d.err.Error()
|
||||||
|
} else if d.di.Err != nil {
|
||||||
|
err = d.di.Err.Error()
|
||||||
|
}
|
||||||
var status string
|
var status string
|
||||||
if d.info != nil {
|
if d.info != nil {
|
||||||
status = d.info.Status.String()
|
status = d.info.Status.String()
|
||||||
}
|
}
|
||||||
if d.err != nil || d.di.Err != nil {
|
if err != "" {
|
||||||
ok = false
|
fmt.Fprintf(w, " %s\t%s\t%s\n", n.Name, n.Endpoint, err)
|
||||||
fmt.Fprintf(w, " %s\t%s\t%s\t\t\n", n.Name, n.Endpoint, "error")
|
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(w, " %s\t%s\t%s\t%s\t%s\n", n.Name, n.Endpoint, status, d.version, strings.Join(platformutil.FormatInGroups(n.Platforms, d.platforms), ", "))
|
fmt.Fprintf(w, " %s\t%s\t%s\t%s\n", n.Name, n.Endpoint, status, strings.Join(platformutil.FormatInGroups(n.Platforms, d.platforms), ", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func lsCmd(dockerCli command.Cli) *cobra.Command {
|
func lsCmd(dockerCli command.Cli) *cobra.Command {
|
||||||
|
@@ -2,7 +2,6 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/store"
|
||||||
@@ -44,41 +43,30 @@ func runRm(dockerCli command.Cli, in rmOptions) error {
|
|||||||
return rmAllInactive(ctx, txn, dockerCli, in)
|
return rmAllInactive(ctx, txn, dockerCli, in)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ng *store.NodeGroup
|
|
||||||
if in.builder != "" {
|
if in.builder != "" {
|
||||||
ng, err = storeutil.GetNodeGroup(txn, dockerCli, in.builder)
|
ng, err := storeutil.GetNodeGroup(txn, dockerCli, in.builder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
err1 := rm(ctx, dockerCli, in, ng)
|
||||||
ng, err = storeutil.GetCurrentInstance(txn, dockerCli)
|
if err := txn.Remove(ng.Name); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
|
}
|
||||||
|
return err1
|
||||||
|
}
|
||||||
|
|
||||||
|
ng, err := storeutil.GetCurrentInstance(txn, dockerCli)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ng != nil {
|
||||||
|
err1 := rm(ctx, dockerCli, in, ng)
|
||||||
|
if err := txn.Remove(ng.Name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if ng == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ctxbuilders, err := dockerCli.ContextStore().List()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, cb := range ctxbuilders {
|
|
||||||
if ng.Driver == "docker" && len(ng.Nodes) == 1 && ng.Nodes[0].Endpoint == cb.Name {
|
|
||||||
return errors.Errorf("context builder cannot be removed, run `docker context rm %s` to remove this context", cb.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err1 := rm(ctx, dockerCli, in, ng)
|
|
||||||
if err := txn.Remove(ng.Name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err1 != nil {
|
|
||||||
return err1
|
return err1
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", ng.Name)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +152,6 @@ func rmAllInactive(ctx context.Context, txn *store.Txn, dockerCli command.Cli, i
|
|||||||
if err := txn.Remove(b.ng.Name); err != nil {
|
if err := txn.Remove(b.ng.Name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", b.ng.Name)
|
|
||||||
return rmerr
|
return rmerr
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@@ -6,7 +6,6 @@ import (
|
|||||||
imagetoolscmd "github.com/docker/buildx/commands/imagetools"
|
imagetoolscmd "github.com/docker/buildx/commands/imagetools"
|
||||||
"github.com/docker/buildx/util/logutil"
|
"github.com/docker/buildx/util/logutil"
|
||||||
"github.com/docker/cli-docs-tool/annotation"
|
"github.com/docker/cli-docs-tool/annotation"
|
||||||
"github.com/docker/cli/cli"
|
|
||||||
"github.com/docker/cli/cli-plugins/plugin"
|
"github.com/docker/cli/cli-plugins/plugin"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
@@ -27,14 +26,6 @@ func NewRootCmd(name string, isPlugin bool, dockerCli command.Cli) *cobra.Comman
|
|||||||
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||||
return plugin.PersistentPreRunE(cmd, args)
|
return plugin.PersistentPreRunE(cmd, args)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// match plugin behavior for standalone mode
|
|
||||||
// https://github.com/docker/cli/blob/6c9eb708fa6d17765d71965f90e1c59cea686ee9/cli-plugins/plugin/plugin.go#L117-L127
|
|
||||||
cmd.SilenceUsage = true
|
|
||||||
cmd.SilenceErrors = true
|
|
||||||
cmd.TraverseChildren = true
|
|
||||||
cmd.DisableFlagsInUseLine = true
|
|
||||||
cli.DisableFlagsInUseLine(cmd)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.SetFormatter(&logutil.Formatter{})
|
logrus.SetFormatter(&logutil.Formatter{})
|
||||||
@@ -55,7 +46,6 @@ func NewRootCmd(name string, isPlugin bool, dockerCli command.Cli) *cobra.Comman
|
|||||||
logrus.WarnLevel,
|
logrus.WarnLevel,
|
||||||
},
|
},
|
||||||
"commandConn.CloseWrite:",
|
"commandConn.CloseWrite:",
|
||||||
"commandConn.CloseRead:",
|
|
||||||
))
|
))
|
||||||
|
|
||||||
addCommands(cmd, dockerCli)
|
addCommands(cmd, dockerCli)
|
||||||
@@ -84,7 +74,7 @@ func addCommands(cmd *cobra.Command, dockerCli command.Cli) {
|
|||||||
versionCmd(dockerCli),
|
versionCmd(dockerCli),
|
||||||
pruneCmd(dockerCli, opts),
|
pruneCmd(dockerCli, opts),
|
||||||
duCmd(dockerCli, opts),
|
duCmd(dockerCli, opts),
|
||||||
imagetoolscmd.RootCmd(dockerCli, imagetoolscmd.RootOptions{Builder: &opts.builder}),
|
imagetoolscmd.RootCmd(dockerCli, imagetoolscmd.RootOptions{Builder: opts.builder}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,7 +9,6 @@ import (
|
|||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/build"
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
ctxkube "github.com/docker/buildx/driver/kubernetes/context"
|
ctxkube "github.com/docker/buildx/driver/kubernetes/context"
|
||||||
remoteutil "github.com/docker/buildx/driver/remote/util"
|
|
||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/store"
|
||||||
"github.com/docker/buildx/store/storeutil"
|
"github.com/docker/buildx/store/storeutil"
|
||||||
"github.com/docker/buildx/util/platformutil"
|
"github.com/docker/buildx/util/platformutil"
|
||||||
@@ -19,12 +18,10 @@ import (
|
|||||||
ctxstore "github.com/docker/cli/cli/context/store"
|
ctxstore "github.com/docker/cli/cli/context/store"
|
||||||
dopts "github.com/docker/cli/opts"
|
dopts "github.com/docker/cli/opts"
|
||||||
dockerclient "github.com/docker/docker/client"
|
dockerclient "github.com/docker/docker/client"
|
||||||
"github.com/moby/buildkit/util/grpcerrors"
|
|
||||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -44,14 +41,6 @@ func validateEndpoint(dockerCli command.Cli, ep string) (string, error) {
|
|||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateBuildkitEndpoint validates that endpoint is a valid buildkit host
|
|
||||||
func validateBuildkitEndpoint(ep string) (string, error) {
|
|
||||||
if err := remoteutil.IsValidEndpoint(ep); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return ep, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// driversForNodeGroup returns drivers for a nodegroup instance
|
// driversForNodeGroup returns drivers for a nodegroup instance
|
||||||
func driversForNodeGroup(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, contextPathHash string) ([]build.DriverInfo, error) {
|
func driversForNodeGroup(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, contextPathHash string) ([]build.DriverInfo, error) {
|
||||||
eg, _ := errgroup.WithContext(ctx)
|
eg, _ := errgroup.WithContext(ctx)
|
||||||
@@ -65,21 +54,11 @@ func driversForNodeGroup(ctx context.Context, dockerCli command.Cli, ng *store.N
|
|||||||
return nil, errors.Errorf("failed to find driver %q", f)
|
return nil, errors.Errorf("failed to find driver %q", f)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// empty driver means nodegroup was implicitly created as a default
|
dockerapi, err := clientForEndpoint(dockerCli, ng.Nodes[0].Endpoint)
|
||||||
// driver for a docker context and allows falling back to a
|
|
||||||
// docker-container driver for older daemon that doesn't support
|
|
||||||
// buildkit (< 18.06).
|
|
||||||
ep := ng.Nodes[0].Endpoint
|
|
||||||
dockerapi, err := clientForEndpoint(dockerCli, ep)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// check if endpoint is healthy is needed to determine the driver type.
|
f, err = driver.GetDefaultFactory(ctx, dockerapi, false)
|
||||||
// if this fails then can't continue with driver selection.
|
|
||||||
if _, err = dockerapi.Ping(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
f, err = driver.GetDefaultFactory(ctx, ep, dockerapi, false)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -101,7 +80,6 @@ func driversForNodeGroup(ctx context.Context, dockerCli command.Cli, ng *store.N
|
|||||||
defer func() {
|
defer func() {
|
||||||
dis[i] = di
|
dis[i] = di
|
||||||
}()
|
}()
|
||||||
|
|
||||||
dockerapi, err := clientForEndpoint(dockerCli, n.Endpoint)
|
dockerapi, err := clientForEndpoint(dockerCli, n.Endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
di.Err = err
|
di.Err = err
|
||||||
@@ -140,7 +118,7 @@ func driversForNodeGroup(ctx context.Context, dockerCli command.Cli, ng *store.N
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, f, n.Endpoint, dockerapi, imageopt.Auth, kcc, n.Flags, n.Files, n.DriverOpts, n.Platforms, contextPathHash)
|
d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, f, dockerapi, imageopt.Auth, kcc, n.Flags, n.Files, n.DriverOpts, n.Platforms, contextPathHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
di.Err = err
|
di.Err = err
|
||||||
return nil
|
return nil
|
||||||
@@ -281,7 +259,7 @@ func getDefaultDrivers(ctx context.Context, dockerCli command.Cli, defaultOnly b
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
d, err := driver.GetDriver(ctx, "buildx_buildkit_default", nil, "", dockerCli.Client(), imageopt.Auth, nil, nil, nil, nil, nil, contextPathHash)
|
d, err := driver.GetDriver(ctx, "buildx_buildkit_default", nil, dockerCli.Client(), imageopt.Auth, nil, nil, nil, nil, nil, contextPathHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -317,17 +295,6 @@ func loadInfoData(ctx context.Context, d *dinfo) error {
|
|||||||
d.platforms = append(d.platforms, w.Platforms...)
|
d.platforms = append(d.platforms, w.Platforms...)
|
||||||
}
|
}
|
||||||
d.platforms = platformutil.Dedupe(d.platforms)
|
d.platforms = platformutil.Dedupe(d.platforms)
|
||||||
inf, err := c.Info(ctx)
|
|
||||||
if err != nil {
|
|
||||||
if st, ok := grpcerrors.AsGRPCStatus(err); ok && st.Code() == codes.Unimplemented {
|
|
||||||
d.version, err = d.di.Driver.Version(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "getting version")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
d.version = inf.BuildkitVersion.Version
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -396,15 +363,6 @@ func loadNodeGroupData(ctx context.Context, dockerCli command.Cli, ngi *nginfo)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasNodeGroup(list []*nginfo, ngi *nginfo) bool {
|
|
||||||
for _, l := range list {
|
|
||||||
if ngi.ng.Name == l.ng.Name {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func dockerAPI(dockerCli command.Cli) *api {
|
func dockerAPI(dockerCli command.Cli) *api {
|
||||||
return &api{dockerCli: dockerCli}
|
return &api{dockerCli: dockerCli}
|
||||||
}
|
}
|
||||||
@@ -424,7 +382,6 @@ type dinfo struct {
|
|||||||
di *build.DriverInfo
|
di *build.DriverInfo
|
||||||
info *driver.Info
|
info *driver.Info
|
||||||
platforms []specs.Platform
|
platforms []specs.Platform
|
||||||
version string
|
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
variable "GO_VERSION" {
|
variable "GO_VERSION" {
|
||||||
default = "1.18"
|
default = "1.17"
|
||||||
}
|
}
|
||||||
variable "BIN_OUT" {
|
variable "BIN_OUT" {
|
||||||
default = "./bin"
|
default = "./bin"
|
||||||
@@ -89,7 +89,11 @@ target "mod-outdated" {
|
|||||||
inherits = ["_common"]
|
inherits = ["_common"]
|
||||||
dockerfile = "./hack/dockerfiles/vendor.Dockerfile"
|
dockerfile = "./hack/dockerfiles/vendor.Dockerfile"
|
||||||
target = "outdated"
|
target = "outdated"
|
||||||
no-cache-filter = ["outdated"]
|
args = {
|
||||||
|
// used to invalidate cache for outdated run stage
|
||||||
|
// can be dropped when https://github.com/moby/buildkit/issues/1213 fixed
|
||||||
|
_RANDOM = uuidv4()
|
||||||
|
}
|
||||||
output = ["type=cacheonly"]
|
output = ["type=cacheonly"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,7 +16,6 @@ import (
|
|||||||
_ "github.com/docker/buildx/driver/docker"
|
_ "github.com/docker/buildx/driver/docker"
|
||||||
_ "github.com/docker/buildx/driver/docker-container"
|
_ "github.com/docker/buildx/driver/docker-container"
|
||||||
_ "github.com/docker/buildx/driver/kubernetes"
|
_ "github.com/docker/buildx/driver/kubernetes"
|
||||||
_ "github.com/docker/buildx/driver/remote"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultSourcePath = "docs/reference/"
|
const defaultSourcePath = "docs/reference/"
|
||||||
|
@@ -1,74 +0,0 @@
|
|||||||
# Defining additional build contexts and linking targets
|
|
||||||
|
|
||||||
In addition to the main `context` key that defines the build context each target
|
|
||||||
can also define additional named contexts with a map defined with key `contexts`.
|
|
||||||
These values map to the `--build-context` flag in the [build command](https://docs.docker.com/engine/reference/commandline/buildx_build/#build-context).
|
|
||||||
|
|
||||||
Inside the Dockerfile these contexts can be used with the `FROM` instruction or `--from` flag.
|
|
||||||
|
|
||||||
The value can be a local source directory, container image (with `docker-image://` prefix),
|
|
||||||
Git URL, HTTP URL or a name of another target in the Bake file (with `target:` prefix).
|
|
||||||
|
|
||||||
## Pinning alpine image
|
|
||||||
|
|
||||||
```dockerfile
|
|
||||||
# syntax=docker/dockerfile:1
|
|
||||||
FROM alpine
|
|
||||||
RUN echo "Hello world"
|
|
||||||
```
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
target "app" {
|
|
||||||
contexts = {
|
|
||||||
alpine = "docker-image://alpine:3.13"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using a secondary source directory
|
|
||||||
|
|
||||||
```dockerfile
|
|
||||||
# syntax=docker/dockerfile:1
|
|
||||||
FROM scratch AS src
|
|
||||||
|
|
||||||
FROM golang
|
|
||||||
COPY --from=src . .
|
|
||||||
```
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
target "app" {
|
|
||||||
contexts = {
|
|
||||||
src = "../path/to/source"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using a result of one target as a base image in another target
|
|
||||||
|
|
||||||
To use a result of one target as a build context of another, specity the target
|
|
||||||
name with `target:` prefix.
|
|
||||||
|
|
||||||
```dockerfile
|
|
||||||
# syntax=docker/dockerfile:1
|
|
||||||
FROM baseapp
|
|
||||||
RUN echo "Hello world"
|
|
||||||
```
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
target "base" {
|
|
||||||
dockerfile = "baseapp.Dockerfile"
|
|
||||||
}
|
|
||||||
|
|
||||||
target "app" {
|
|
||||||
contexts = {
|
|
||||||
baseapp = "target:base"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Please note that in most cases you should just use a single multi-stage
|
|
||||||
Dockerfile with multiple targets for similar behavior. This case is recommended
|
|
||||||
when you have multiple Dockerfiles that can't be easily merged into one.
|
|
@@ -1,219 +0,0 @@
|
|||||||
# Building from Compose file
|
|
||||||
|
|
||||||
## Specification
|
|
||||||
|
|
||||||
Bake uses the [compose-spec](https://docs.docker.com/compose/compose-file/) to
|
|
||||||
parse a compose file and translate each service to a [target](file-definition.md#target).
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# docker-compose.yml
|
|
||||||
services:
|
|
||||||
webapp-dev:
|
|
||||||
build: &build-dev
|
|
||||||
dockerfile: Dockerfile.webapp
|
|
||||||
tags:
|
|
||||||
- docker.io/username/webapp:latest
|
|
||||||
cache_from:
|
|
||||||
- docker.io/username/webapp:cache
|
|
||||||
cache_to:
|
|
||||||
- docker.io/username/webapp:cache
|
|
||||||
|
|
||||||
webapp-release:
|
|
||||||
build:
|
|
||||||
<<: *build-dev
|
|
||||||
x-bake:
|
|
||||||
platforms:
|
|
||||||
- linux/amd64
|
|
||||||
- linux/arm64
|
|
||||||
|
|
||||||
db:
|
|
||||||
image: docker.io/username/db
|
|
||||||
build:
|
|
||||||
dockerfile: Dockerfile.db
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --print
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"db",
|
|
||||||
"webapp-dev",
|
|
||||||
"webapp-release"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"db": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile.db",
|
|
||||||
"tags": [
|
|
||||||
"docker.io/username/db"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"webapp-dev": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile.webapp",
|
|
||||||
"tags": [
|
|
||||||
"docker.io/username/webapp:latest"
|
|
||||||
],
|
|
||||||
"cache-from": [
|
|
||||||
"docker.io/username/webapp:cache"
|
|
||||||
],
|
|
||||||
"cache-to": [
|
|
||||||
"docker.io/username/webapp:cache"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"webapp-release": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile.webapp",
|
|
||||||
"tags": [
|
|
||||||
"docker.io/username/webapp:latest"
|
|
||||||
],
|
|
||||||
"cache-from": [
|
|
||||||
"docker.io/username/webapp:cache"
|
|
||||||
],
|
|
||||||
"cache-to": [
|
|
||||||
"docker.io/username/webapp:cache"
|
|
||||||
],
|
|
||||||
"platforms": [
|
|
||||||
"linux/amd64",
|
|
||||||
"linux/arm64"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Unlike the [HCL format](file-definition.md#hcl-definition), there are some
|
|
||||||
limitations with the compose format:
|
|
||||||
|
|
||||||
* Specifying variables or global scope attributes is not yet supported
|
|
||||||
* `inherits` service field is not supported, but you can use [YAML anchors](https://docs.docker.com/compose/compose-file/#fragments) to reference other services like the example above
|
|
||||||
|
|
||||||
## Extension field with `x-bake`
|
|
||||||
|
|
||||||
Even if some fields are not (yet) available in the compose specification, you
|
|
||||||
can use the [special extension](https://docs.docker.com/compose/compose-file/#extension)
|
|
||||||
field `x-bake` in your compose file to evaluate extra fields:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# docker-compose.yml
|
|
||||||
services:
|
|
||||||
addon:
|
|
||||||
image: ct-addon:bar
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: ./Dockerfile
|
|
||||||
args:
|
|
||||||
CT_ECR: foo
|
|
||||||
CT_TAG: bar
|
|
||||||
x-bake:
|
|
||||||
tags:
|
|
||||||
- ct-addon:foo
|
|
||||||
- ct-addon:alp
|
|
||||||
platforms:
|
|
||||||
- linux/amd64
|
|
||||||
- linux/arm64
|
|
||||||
cache-from:
|
|
||||||
- user/app:cache
|
|
||||||
- type=local,src=path/to/cache
|
|
||||||
cache-to:
|
|
||||||
- type=local,dest=path/to/cache
|
|
||||||
pull: true
|
|
||||||
|
|
||||||
aws:
|
|
||||||
image: ct-fake-aws:bar
|
|
||||||
build:
|
|
||||||
dockerfile: ./aws.Dockerfile
|
|
||||||
args:
|
|
||||||
CT_ECR: foo
|
|
||||||
CT_TAG: bar
|
|
||||||
x-bake:
|
|
||||||
secret:
|
|
||||||
- id=mysecret,src=./secret
|
|
||||||
- id=mysecret2,src=./secret2
|
|
||||||
platforms: linux/arm64
|
|
||||||
output: type=docker
|
|
||||||
no-cache: true
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --print
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"aws",
|
|
||||||
"addon"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"addon": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "./Dockerfile",
|
|
||||||
"args": {
|
|
||||||
"CT_ECR": "foo",
|
|
||||||
"CT_TAG": "bar"
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"ct-addon:foo",
|
|
||||||
"ct-addon:alp"
|
|
||||||
],
|
|
||||||
"cache-from": [
|
|
||||||
"user/app:cache",
|
|
||||||
"type=local,src=path/to/cache"
|
|
||||||
],
|
|
||||||
"cache-to": [
|
|
||||||
"type=local,dest=path/to/cache"
|
|
||||||
],
|
|
||||||
"platforms": [
|
|
||||||
"linux/amd64",
|
|
||||||
"linux/arm64"
|
|
||||||
],
|
|
||||||
"pull": true
|
|
||||||
},
|
|
||||||
"aws": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "./aws.Dockerfile",
|
|
||||||
"args": {
|
|
||||||
"CT_ECR": "foo",
|
|
||||||
"CT_TAG": "bar"
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"ct-fake-aws:bar"
|
|
||||||
],
|
|
||||||
"secret": [
|
|
||||||
"id=mysecret,src=./secret",
|
|
||||||
"id=mysecret2,src=./secret2"
|
|
||||||
],
|
|
||||||
"platforms": [
|
|
||||||
"linux/arm64"
|
|
||||||
],
|
|
||||||
"output": [
|
|
||||||
"type=docker"
|
|
||||||
],
|
|
||||||
"no-cache": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Complete list of valid fields for `x-bake`:
|
|
||||||
|
|
||||||
* `cache-from`
|
|
||||||
* `cache-to`
|
|
||||||
* `no-cache`
|
|
||||||
* `no-cache-filter`
|
|
||||||
* `output`
|
|
||||||
* `platforms`
|
|
||||||
* `pull`
|
|
||||||
* `secret`
|
|
||||||
* `ssh`
|
|
||||||
* `tags`
|
|
@@ -1,216 +0,0 @@
|
|||||||
# Configuring builds
|
|
||||||
|
|
||||||
Bake supports loading build definition from files, but sometimes you need even
|
|
||||||
more flexibility to configure this definition.
|
|
||||||
|
|
||||||
For this use case, you can define variables inside the bake files that can be
|
|
||||||
set by the user with environment variables or by [attribute definitions](#global-scope-attributes)
|
|
||||||
in other bake files. If you wish to change a specific value for a single
|
|
||||||
invocation you can use the `--set` flag [from the command line](#from-command-line).
|
|
||||||
|
|
||||||
## Global scope attributes
|
|
||||||
|
|
||||||
You can define global scope attributes in HCL/JSON and use them for code reuse
|
|
||||||
and setting values for variables. This means you can do a "data-only" HCL file
|
|
||||||
with the values you want to set/override and use it in the list of regular
|
|
||||||
output files.
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
variable "FOO" {
|
|
||||||
default = "abc"
|
|
||||||
}
|
|
||||||
|
|
||||||
target "app" {
|
|
||||||
args = {
|
|
||||||
v1 = "pre-${FOO}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You can use this file directly:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --print app
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"app"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"app": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"args": {
|
|
||||||
"v1": "pre-abc"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Or create an override configuration file:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# env.hcl
|
|
||||||
WHOAMI="myuser"
|
|
||||||
FOO="def-${WHOAMI}"
|
|
||||||
```
|
|
||||||
|
|
||||||
And invoke bake together with both of the files:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake -f docker-bake.hcl -f env.hcl --print app
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"app"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"app": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"args": {
|
|
||||||
"v1": "pre-def-myuser"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## From command line
|
|
||||||
|
|
||||||
You can also override target configurations from the command line with the
|
|
||||||
[`--set` flag](https://docs.docker.com/engine/reference/commandline/buildx_bake/#set):
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
target "app" {
|
|
||||||
args = {
|
|
||||||
mybuildarg = "foo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --set app.args.mybuildarg=bar --set app.platform=linux/arm64 app --print
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"app"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"app": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"args": {
|
|
||||||
"mybuildarg": "bar"
|
|
||||||
},
|
|
||||||
"platforms": [
|
|
||||||
"linux/arm64"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Pattern matching syntax defined in [https://golang.org/pkg/path/#Match](https://golang.org/pkg/path/#Match)
|
|
||||||
is also supported:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --set foo*.args.mybuildarg=value # overrides build arg for all targets starting with "foo"
|
|
||||||
$ docker buildx bake --set *.platform=linux/arm64 # overrides platform for all targets
|
|
||||||
$ docker buildx bake --set foo*.no-cache # bypass caching only for targets starting with "foo"
|
|
||||||
```
|
|
||||||
|
|
||||||
Complete list of overridable fields:
|
|
||||||
|
|
||||||
* `args`
|
|
||||||
* `cache-from`
|
|
||||||
* `cache-to`
|
|
||||||
* `context`
|
|
||||||
* `dockerfile`
|
|
||||||
* `labels`
|
|
||||||
* `no-cache`
|
|
||||||
* `output`
|
|
||||||
* `platform`
|
|
||||||
* `pull`
|
|
||||||
* `secrets`
|
|
||||||
* `ssh`
|
|
||||||
* `tags`
|
|
||||||
* `target`
|
|
||||||
|
|
||||||
## Using variables in variables across files
|
|
||||||
|
|
||||||
When multiple files are specified, one file can use variables defined in
|
|
||||||
another file.
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake1.hcl
|
|
||||||
variable "FOO" {
|
|
||||||
default = upper("${BASE}def")
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "BAR" {
|
|
||||||
default = "-${FOO}-"
|
|
||||||
}
|
|
||||||
|
|
||||||
target "app" {
|
|
||||||
args = {
|
|
||||||
v1 = "pre-${BAR}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake2.hcl
|
|
||||||
variable "BASE" {
|
|
||||||
default = "abc"
|
|
||||||
}
|
|
||||||
|
|
||||||
target "app" {
|
|
||||||
args = {
|
|
||||||
v2 = "${FOO}-post"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake -f docker-bake1.hcl -f docker-bake2.hcl --print app
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"app"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"app": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"args": {
|
|
||||||
"v1": "pre--ABCDEF-",
|
|
||||||
"v2": "ABCDEF-post"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
@@ -1,440 +0,0 @@
|
|||||||
# Bake file definition
|
|
||||||
|
|
||||||
`buildx bake` supports HCL, JSON and Compose file format for defining build
|
|
||||||
[groups](#group), [targets](#target) as well as [variables](#variable) and
|
|
||||||
[functions](#functions). It looks for build definition files in the current
|
|
||||||
directory in the following order:
|
|
||||||
|
|
||||||
* `docker-compose.yml`
|
|
||||||
* `docker-compose.yaml`
|
|
||||||
* `docker-bake.json`
|
|
||||||
* `docker-bake.override.json`
|
|
||||||
* `docker-bake.hcl`
|
|
||||||
* `docker-bake.override.hcl`
|
|
||||||
|
|
||||||
## Specification
|
|
||||||
|
|
||||||
Inside a bake file you can declare group, target and variable blocks to define
|
|
||||||
project specific reusable build flows.
|
|
||||||
|
|
||||||
### Target
|
|
||||||
|
|
||||||
A target reflects a single docker build invocation with the same options that
|
|
||||||
you would specify for `docker build`:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
target "webapp-dev" {
|
|
||||||
dockerfile = "Dockerfile.webapp"
|
|
||||||
tags = ["docker.io/username/webapp:latest"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
```console
|
|
||||||
$ docker buildx bake webapp-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Note**
|
|
||||||
>
|
|
||||||
> In the case of compose files, each service corresponds to a target.
|
|
||||||
> If compose service name contains a dot it will be replaced with an underscore.
|
|
||||||
|
|
||||||
Complete list of valid target fields available for [HCL](#hcl-definition) and
|
|
||||||
[JSON](#json-definition) definitions:
|
|
||||||
|
|
||||||
| Name | Type | Description |
|
|
||||||
|---------------------|--------|-------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
||||||
| `inherits` | List | [Inherit build options](#merging-and-inheritance) from other targets |
|
|
||||||
| `args` | Map | Set build-time variables (same as [`--build-arg` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `cache-from` | List | External cache sources (same as [`--cache-from` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `cache-to` | List | Cache export destinations (same as [`--cache-to` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `context` | String | Set of files located in the specified path or URL |
|
|
||||||
| `contexts` | Map | Additional build contexts (same as [`--build-context` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `dockerfile` | String | Name of the Dockerfile (same as [`--file` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `dockerfile-inline` | String | Inline Dockerfile content |
|
|
||||||
| `labels` | Map | Set metadata for an image (same as [`--label` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `no-cache` | Bool | Do not use cache when building the image (same as [`--no-cache` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `no-cache-filter` | List | Do not cache specified stages (same as [`--no-cache-filter` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `output` | List | Output destination (same as [`--output` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `platforms` | List | Set target platforms for build (same as [`--platform` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `pull` | Bool | Always attempt to pull all referenced images (same as [`--pull` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `secret` | List | Secret to expose to the build (same as [`--secret` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `ssh` | List | SSH agent socket or keys to expose to the build (same as [`--ssh` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `tags` | List | Name and optionally a tag in the format `name:tag` (same as [`--tag` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `target` | String | Set the target build stage to build (same as [`--target` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
|
|
||||||
### Group
|
|
||||||
|
|
||||||
A group is a grouping of targets:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
group "build" {
|
|
||||||
targets = ["db", "webapp-dev"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp-dev" {
|
|
||||||
dockerfile = "Dockerfile.webapp"
|
|
||||||
tags = ["docker.io/username/webapp:latest"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "db" {
|
|
||||||
dockerfile = "Dockerfile.db"
|
|
||||||
tags = ["docker.io/username/db"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
```console
|
|
||||||
$ docker buildx bake build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Variable
|
|
||||||
|
|
||||||
Similar to how Terraform provides a way to [define variables](https://www.terraform.io/docs/configuration/variables.html#declaring-an-input-variable),
|
|
||||||
the HCL file format also supports variable block definitions. These can be used
|
|
||||||
to define variables with values provided by the current environment, or a
|
|
||||||
default value when unset:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
variable "TAG" {
|
|
||||||
default = "latest"
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp-dev" {
|
|
||||||
dockerfile = "Dockerfile.webapp"
|
|
||||||
tags = ["docker.io/username/webapp:${TAG}"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
```console
|
|
||||||
$ docker buildx bake webapp-dev # will use the default value "latest"
|
|
||||||
$ TAG=dev docker buildx bake webapp-dev # will use the TAG environment variable value
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Tip**
|
|
||||||
>
|
|
||||||
> See also the [Configuring builds](configuring-build.md) page for advanced usage.
|
|
||||||
|
|
||||||
### Functions
|
|
||||||
|
|
||||||
A [set of generally useful functions](https://github.com/docker/buildx/blob/master/bake/hclparser/stdlib.go)
|
|
||||||
provided by [go-cty](https://github.com/zclconf/go-cty/tree/main/cty/function/stdlib)
|
|
||||||
are available for use in HCL files:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
target "webapp-dev" {
|
|
||||||
dockerfile = "Dockerfile.webapp"
|
|
||||||
tags = ["docker.io/username/webapp:latest"]
|
|
||||||
args = {
|
|
||||||
buildno = "${add(123, 1)}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In addition, [user defined functions](https://github.com/hashicorp/hcl/tree/main/ext/userfunc)
|
|
||||||
are also supported:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
function "increment" {
|
|
||||||
params = [number]
|
|
||||||
result = number + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp-dev" {
|
|
||||||
dockerfile = "Dockerfile.webapp"
|
|
||||||
tags = ["docker.io/username/webapp:latest"]
|
|
||||||
args = {
|
|
||||||
buildno = "${increment(123)}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Note**
|
|
||||||
>
|
|
||||||
> See [User defined HCL functions](hcl-funcs.md) page for more details.
|
|
||||||
|
|
||||||
## Built-in variables
|
|
||||||
|
|
||||||
* `BAKE_CMD_CONTEXT` can be used to access the main `context` for bake command
|
|
||||||
from a bake file that has been [imported remotely](file-definition.md#remote-definition).
|
|
||||||
* `BAKE_LOCAL_PLATFORM` returns the current platform's default platform
|
|
||||||
specification (e.g. `linux/amd64`).
|
|
||||||
|
|
||||||
## Merging and inheritance
|
|
||||||
|
|
||||||
Multiple files can include the same target and final build options will be
|
|
||||||
determined by merging them together:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
target "webapp-dev" {
|
|
||||||
dockerfile = "Dockerfile.webapp"
|
|
||||||
tags = ["docker.io/username/webapp:latest"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
```hcl
|
|
||||||
# docker-bake2.hcl
|
|
||||||
target "webapp-dev" {
|
|
||||||
tags = ["docker.io/username/webapp:dev"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
```console
|
|
||||||
$ docker buildx bake -f docker-bake.hcl -f docker-bake2.hcl webapp-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
A group can specify its list of targets with the `targets` option. A target can
|
|
||||||
inherit build options by setting the `inherits` option to the list of targets or
|
|
||||||
groups to inherit from:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
target "webapp-dev" {
|
|
||||||
dockerfile = "Dockerfile.webapp"
|
|
||||||
tags = ["docker.io/username/webapp:${TAG}"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp-release" {
|
|
||||||
inherits = ["webapp-dev"]
|
|
||||||
platforms = ["linux/amd64", "linux/arm64"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## `default` target/group
|
|
||||||
|
|
||||||
When you invoke `bake` you specify what targets/groups you want to build. If no
|
|
||||||
arguments is specified, the group/target named `default` will be built:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
target "default" {
|
|
||||||
dockerfile = "Dockerfile.webapp"
|
|
||||||
tags = ["docker.io/username/webapp:latest"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
```console
|
|
||||||
$ docker buildx bake
|
|
||||||
```
|
|
||||||
|
|
||||||
## Definitions
|
|
||||||
|
|
||||||
### HCL definition
|
|
||||||
|
|
||||||
HCL definition file is recommended as its experience is more aligned with buildx UX
|
|
||||||
and also allows better code reuse, different target groups and extended features.
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
variable "TAG" {
|
|
||||||
default = "latest"
|
|
||||||
}
|
|
||||||
|
|
||||||
group "default" {
|
|
||||||
targets = ["db", "webapp-dev"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp-dev" {
|
|
||||||
dockerfile = "Dockerfile.webapp"
|
|
||||||
tags = ["docker.io/username/webapp:${TAG}"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp-release" {
|
|
||||||
inherits = ["webapp-dev"]
|
|
||||||
platforms = ["linux/amd64", "linux/arm64"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "db" {
|
|
||||||
dockerfile = "Dockerfile.db"
|
|
||||||
tags = ["docker.io/username/db"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### JSON definition
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"variable": {
|
|
||||||
"TAG": {
|
|
||||||
"default": "latest"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"db",
|
|
||||||
"webapp-dev"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"webapp-dev": {
|
|
||||||
"dockerfile": "Dockerfile.webapp",
|
|
||||||
"tags": [
|
|
||||||
"docker.io/username/webapp:${TAG}"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"webapp-release": {
|
|
||||||
"inherits": [
|
|
||||||
"webapp-dev"
|
|
||||||
],
|
|
||||||
"platforms": [
|
|
||||||
"linux/amd64",
|
|
||||||
"linux/arm64"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"db": {
|
|
||||||
"dockerfile": "Dockerfile.db",
|
|
||||||
"tags": [
|
|
||||||
"docker.io/username/db"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compose file
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# docker-compose.yml
|
|
||||||
services:
|
|
||||||
webapp:
|
|
||||||
image: docker.io/username/webapp:latest
|
|
||||||
build:
|
|
||||||
dockerfile: Dockerfile.webapp
|
|
||||||
|
|
||||||
db:
|
|
||||||
image: docker.io/username/db
|
|
||||||
build:
|
|
||||||
dockerfile: Dockerfile.db
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Note**
|
|
||||||
>
|
|
||||||
> See [Building from Compose file](compose-file.md) page for more details.
|
|
||||||
|
|
||||||
## Remote definition
|
|
||||||
|
|
||||||
You can also build bake files directly from a remote Git repository or HTTPS URL:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake "https://github.com/docker/cli.git#v20.10.11" --print
|
|
||||||
#1 [internal] load git source https://github.com/docker/cli.git#v20.10.11
|
|
||||||
#1 0.745 e8f1871b077b64bcb4a13334b7146492773769f7 refs/tags/v20.10.11
|
|
||||||
#1 2.022 From https://github.com/docker/cli
|
|
||||||
#1 2.022 * [new tag] v20.10.11 -> v20.10.11
|
|
||||||
#1 DONE 2.9s
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"binary"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"binary": {
|
|
||||||
"context": "https://github.com/docker/cli.git#v20.10.11",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"args": {
|
|
||||||
"BASE_VARIANT": "alpine",
|
|
||||||
"GO_STRIP": "",
|
|
||||||
"VERSION": ""
|
|
||||||
},
|
|
||||||
"target": "binary",
|
|
||||||
"platforms": [
|
|
||||||
"local"
|
|
||||||
],
|
|
||||||
"output": [
|
|
||||||
"build"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
As you can see the context is fixed to `https://github.com/docker/cli.git` even if
|
|
||||||
[no context is actually defined](https://github.com/docker/cli/blob/2776a6d694f988c0c1df61cad4bfac0f54e481c8/docker-bake.hcl#L17-L26)
|
|
||||||
in the definition.
|
|
||||||
|
|
||||||
If you want to access the main context for bake command from a bake file
|
|
||||||
that has been imported remotely, you can use the [`BAKE_CMD_CONTEXT` built-in var](#built-in-variables).
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ cat https://raw.githubusercontent.com/tonistiigi/buildx/remote-test/docker-bake.hcl
|
|
||||||
```
|
|
||||||
```hcl
|
|
||||||
target "default" {
|
|
||||||
context = BAKE_CMD_CONTEXT
|
|
||||||
dockerfile-inline = <<EOT
|
|
||||||
FROM alpine
|
|
||||||
WORKDIR /src
|
|
||||||
COPY . .
|
|
||||||
RUN ls -l && stop
|
|
||||||
EOT
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake "https://github.com/tonistiigi/buildx.git#remote-test" --print
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"target": {
|
|
||||||
"default": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"dockerfile-inline": "FROM alpine\nWORKDIR /src\nCOPY . .\nRUN ls -l \u0026\u0026 stop\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ touch foo bar
|
|
||||||
$ docker buildx bake "https://github.com/tonistiigi/buildx.git#remote-test"
|
|
||||||
```
|
|
||||||
```text
|
|
||||||
...
|
|
||||||
> [4/4] RUN ls -l && stop:
|
|
||||||
#8 0.101 total 0
|
|
||||||
#8 0.102 -rw-r--r-- 1 root root 0 Jul 27 18:47 bar
|
|
||||||
#8 0.102 -rw-r--r-- 1 root root 0 Jul 27 18:47 foo
|
|
||||||
#8 0.102 /bin/sh: stop: not found
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake "https://github.com/tonistiigi/buildx.git#remote-test" "https://github.com/docker/cli.git#v20.10.11" --print
|
|
||||||
#1 [internal] load git source https://github.com/tonistiigi/buildx.git#remote-test
|
|
||||||
#1 0.429 577303add004dd7efeb13434d69ea030d35f7888 refs/heads/remote-test
|
|
||||||
#1 CACHED
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"target": {
|
|
||||||
"default": {
|
|
||||||
"context": "https://github.com/docker/cli.git#v20.10.11",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"dockerfile-inline": "FROM alpine\nWORKDIR /src\nCOPY . .\nRUN ls -l \u0026\u0026 stop\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake "https://github.com/tonistiigi/buildx.git#remote-test" "https://github.com/docker/cli.git#v20.10.11"
|
|
||||||
```
|
|
||||||
```text
|
|
||||||
...
|
|
||||||
> [4/4] RUN ls -l && stop:
|
|
||||||
#8 0.136 drwxrwxrwx 5 root root 4096 Jul 27 18:31 kubernetes
|
|
||||||
#8 0.136 drwxrwxrwx 3 root root 4096 Jul 27 18:31 man
|
|
||||||
#8 0.136 drwxrwxrwx 2 root root 4096 Jul 27 18:31 opts
|
|
||||||
#8 0.136 -rw-rw-rw- 1 root root 1893 Jul 27 18:31 poule.yml
|
|
||||||
#8 0.136 drwxrwxrwx 7 root root 4096 Jul 27 18:31 scripts
|
|
||||||
#8 0.136 drwxrwxrwx 3 root root 4096 Jul 27 18:31 service
|
|
||||||
#8 0.136 drwxrwxrwx 2 root root 4096 Jul 27 18:31 templates
|
|
||||||
#8 0.136 drwxrwxrwx 10 root root 4096 Jul 27 18:31 vendor
|
|
||||||
#8 0.136 -rwxrwxrwx 1 root root 9620 Jul 27 18:31 vendor.conf
|
|
||||||
#8 0.136 /bin/sh: stop: not found
|
|
||||||
```
|
|
@@ -1,327 +0,0 @@
|
|||||||
# User defined HCL functions
|
|
||||||
|
|
||||||
## Using interpolation to tag an image with the git sha
|
|
||||||
|
|
||||||
As shown in the [File definition](file-definition.md#variable) page, `bake`
|
|
||||||
supports variable blocks which are assigned to matching environment variables
|
|
||||||
or default values:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
variable "TAG" {
|
|
||||||
default = "latest"
|
|
||||||
}
|
|
||||||
|
|
||||||
group "default" {
|
|
||||||
targets = ["webapp"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp" {
|
|
||||||
tags = ["docker.io/username/webapp:${TAG}"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
alternatively, in json format:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"variable": {
|
|
||||||
"TAG": {
|
|
||||||
"default": "latest"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": ["webapp"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"webapp": {
|
|
||||||
"tags": ["docker.io/username/webapp:${TAG}"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --print webapp
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"webapp"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"webapp": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"tags": [
|
|
||||||
"docker.io/username/webapp:latest"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ TAG=$(git rev-parse --short HEAD) docker buildx bake --print webapp
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"webapp"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"webapp": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"tags": [
|
|
||||||
"docker.io/username/webapp:985e9e9"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using the `add` function
|
|
||||||
|
|
||||||
You can use [`go-cty` stdlib functions](https://github.com/zclconf/go-cty/tree/main/cty/function/stdlib).
|
|
||||||
Here we are using the `add` function.
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
variable "TAG" {
|
|
||||||
default = "latest"
|
|
||||||
}
|
|
||||||
|
|
||||||
group "default" {
|
|
||||||
targets = ["webapp"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp" {
|
|
||||||
args = {
|
|
||||||
buildno = "${add(123, 1)}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --print webapp
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"webapp"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"webapp": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"args": {
|
|
||||||
"buildno": "124"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Defining an `increment` function
|
|
||||||
|
|
||||||
It also supports [user defined functions](https://github.com/hashicorp/hcl/tree/main/ext/userfunc).
|
|
||||||
The following example defines a simple an `increment` function.
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
function "increment" {
|
|
||||||
params = [number]
|
|
||||||
result = number + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
group "default" {
|
|
||||||
targets = ["webapp"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp" {
|
|
||||||
args = {
|
|
||||||
buildno = "${increment(123)}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --print webapp
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"webapp"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"webapp": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"args": {
|
|
||||||
"buildno": "124"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Only adding tags if a variable is not empty using an `notequal`
|
|
||||||
|
|
||||||
Here we are using the conditional `notequal` function which is just for
|
|
||||||
symmetry with the `equal` one.
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
variable "TAG" {default="" }
|
|
||||||
|
|
||||||
group "default" {
|
|
||||||
targets = [
|
|
||||||
"webapp",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp" {
|
|
||||||
context="."
|
|
||||||
dockerfile="Dockerfile"
|
|
||||||
tags = [
|
|
||||||
"my-image:latest",
|
|
||||||
notequal("",TAG) ? "my-image:${TAG}": "",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --print webapp
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"webapp"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"webapp": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"tags": [
|
|
||||||
"my-image:latest"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using variables in functions
|
|
||||||
|
|
||||||
You can refer variables to other variables like the target blocks can. Stdlib
|
|
||||||
functions can also be called but user functions can't at the moment.
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
variable "REPO" {
|
|
||||||
default = "user/repo"
|
|
||||||
}
|
|
||||||
|
|
||||||
function "tag" {
|
|
||||||
params = [tag]
|
|
||||||
result = ["${REPO}:${tag}"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp" {
|
|
||||||
tags = tag("v1")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --print webapp
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"webapp"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"webapp": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"tags": [
|
|
||||||
"user/repo:v1"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using typed variables
|
|
||||||
|
|
||||||
Non-string variables are also accepted. The value passed with env is parsed
|
|
||||||
into suitable type first.
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
variable "FOO" {
|
|
||||||
default = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "IS_FOO" {
|
|
||||||
default = true
|
|
||||||
}
|
|
||||||
|
|
||||||
target "app" {
|
|
||||||
args = {
|
|
||||||
v1 = FOO > 5 ? "higher" : "lower"
|
|
||||||
v2 = IS_FOO ? "yes" : "no"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --print app
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"app"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"app": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"args": {
|
|
||||||
"v1": "lower",
|
|
||||||
"v2": "yes"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
@@ -1,36 +0,0 @@
|
|||||||
# High-level build options with Bake
|
|
||||||
|
|
||||||
> This command is experimental.
|
|
||||||
>
|
|
||||||
> The design of bake is in early stages, and we are looking for [feedback from users](https://github.com/docker/buildx/issues).
|
|
||||||
{: .experimental }
|
|
||||||
|
|
||||||
Buildx also aims to provide support for high-level build concepts that go beyond
|
|
||||||
invoking a single build command. We want to support building all the images in
|
|
||||||
your application together and let the users define project specific reusable
|
|
||||||
build flows that can then be easily invoked by anyone.
|
|
||||||
|
|
||||||
[BuildKit](https://github.com/moby/buildkit) efficiently handles multiple
|
|
||||||
concurrent build requests and de-duplicating work. The build commands can be
|
|
||||||
combined with general-purpose command runners (for example, `make`). However,
|
|
||||||
these tools generally invoke builds in sequence and therefore cannot leverage
|
|
||||||
the full potential of BuildKit parallelization, or combine BuildKit's output
|
|
||||||
for the user. For this use case, we have added a command called
|
|
||||||
[`docker buildx bake`](https://docs.docker.com/engine/reference/commandline/buildx_bake/).
|
|
||||||
|
|
||||||
The `bake` command supports building images from HCL, JSON and Compose files.
|
|
||||||
This is similar to [`docker compose build`](https://docs.docker.com/compose/reference/build/),
|
|
||||||
but allowing all the services to be built concurrently as part of a single
|
|
||||||
request. If multiple files are specified they are all read and configurations are
|
|
||||||
combined.
|
|
||||||
|
|
||||||
We recommend using HCL files as its experience is more aligned with buildx UX
|
|
||||||
and also allows better code reuse, different target groups and extended features.
|
|
||||||
|
|
||||||
## Next steps
|
|
||||||
|
|
||||||
* [File definition](file-definition.md)
|
|
||||||
* [Configuring builds](configuring-build.md)
|
|
||||||
* [User defined HCL functions](hcl-funcs.md)
|
|
||||||
* [Defining additional build contexts and linking targets](build-contexts.md)
|
|
||||||
* [Building from Compose file](compose-file.md)
|
|
@@ -19,13 +19,13 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Set up QEMU
|
name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v1
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v1
|
||||||
-
|
-
|
||||||
name: Login to DockerHub
|
name: Login to DockerHub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v1
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
@@ -11,7 +11,7 @@ Now build this image:
|
|||||||
$ docker buildx build --tag buildkit-cni:local --load .
|
$ docker buildx build --tag buildkit-cni:local --load .
|
||||||
```
|
```
|
||||||
|
|
||||||
Then [create a `docker-container` builder](https://docs.docker.com/engine/reference/commandline/buildx_create/) that
|
Then [create a `docker-container` builder](../reference/buildx_create.md) that
|
||||||
will use this image:
|
will use this image:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
|
@@ -1,20 +0,0 @@
|
|||||||
# Color output controls
|
|
||||||
|
|
||||||
Buildx has support for modifying the colors that are used to output information
|
|
||||||
to the terminal. You can set the environment variable `BUILDKIT_COLORS` to
|
|
||||||
something like `run=123,20,245:error=yellow:cancel=blue:warning=white` to set
|
|
||||||
the colors that you would like to use:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Setting `NO_COLOR` to anything will disable any colorized output as recommended
|
|
||||||
by [no-color.org](https://no-color.org/):
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
> **Note**
|
|
||||||
>
|
|
||||||
> Parsing errors will be reported but ignored. This will result in default
|
|
||||||
> color values being used where needed.
|
|
||||||
|
|
||||||
See also [the list of pre-defined colors](https://github.com/moby/buildkit/blob/master/util/progress/progressui/colors.go).
|
|
@@ -7,8 +7,8 @@ named `foonet`:
|
|||||||
$ docker network create foonet
|
$ docker network create foonet
|
||||||
```
|
```
|
||||||
|
|
||||||
[Create a `docker-container` builder](https://docs.docker.com/engine/reference/commandline/buildx_create/)
|
[Create a `docker-container` builder](../reference/buildx_create.md) named
|
||||||
named `mybuilder` that will use this network:
|
`mybuilder` that will use this network:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker buildx create --use \
|
$ docker buildx create --use \
|
||||||
@@ -17,7 +17,7 @@ $ docker buildx create --use \
|
|||||||
--driver-opt "network=foonet"
|
--driver-opt "network=foonet"
|
||||||
```
|
```
|
||||||
|
|
||||||
Boot and [inspect `mybuilder`](https://docs.docker.com/engine/reference/commandline/buildx_inspect/):
|
Boot and [inspect `mybuilder`](../reference/buildx_inspect.md):
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker buildx inspect --bootstrap
|
$ docker buildx inspect --bootstrap
|
||||||
@@ -26,9 +26,23 @@ $ docker buildx inspect --bootstrap
|
|||||||
[Inspect the builder container](https://docs.docker.com/engine/reference/commandline/inspect/)
|
[Inspect the builder container](https://docs.docker.com/engine/reference/commandline/inspect/)
|
||||||
and see what network is being used:
|
and see what network is being used:
|
||||||
|
|
||||||
{% raw %}
|
|
||||||
```console
|
```console
|
||||||
$ docker inspect buildx_buildkit_mybuilder0 --format={{.NetworkSettings.Networks}}
|
$ docker inspect buildx_buildkit_mybuilder0 --format={{.NetworkSettings.Networks}}
|
||||||
map[foonet:0xc00018c0c0]
|
map[foonet:0xc00018c0c0]
|
||||||
```
|
```
|
||||||
{% endraw %}
|
|
||||||
|
## What's `buildx_buildkit_mybuilder0`?
|
||||||
|
|
||||||
|
`buildx_buildkit_mybuilder0` is the container name. It can be broken down like this:
|
||||||
|
|
||||||
|
* `buildx_buildkit_` is a hardcoded prefix
|
||||||
|
* `mybuilder0` is the name of the node (defaults to builder name + position in the list of nodes)
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx ls
|
||||||
|
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
|
||||||
|
mybuilder * docker-container
|
||||||
|
mybuilder0 unix:///var/run/docker.sock running linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
|
||||||
|
default docker
|
||||||
|
default default running linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
|
||||||
|
```
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# Using a custom registry configuration
|
# Using a custom registry configuration
|
||||||
|
|
||||||
If you [create a `docker-container` or `kubernetes` builder](https://docs.docker.com/engine/reference/commandline/buildx_create/) and
|
If you [create a `docker-container` or `kubernetes` builder](../reference/buildx_create.md) and
|
||||||
have specified certificates for registries in the [BuildKit daemon configuration](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md),
|
have specified certificates for registries in the [BuildKit daemon configuration](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md),
|
||||||
the files will be copied into the container under `/etc/buildkit/certs` and
|
the files will be copied into the container under `/etc/buildkit/certs` and
|
||||||
configuration will be updated to reflect that.
|
configuration will be updated to reflect that.
|
||||||
@@ -8,8 +8,7 @@ configuration will be updated to reflect that.
|
|||||||
Take the following `buildkitd.toml` configuration that will be used for
|
Take the following `buildkitd.toml` configuration that will be used for
|
||||||
pushing an image to this registry using self-signed certificates:
|
pushing an image to this registry using self-signed certificates:
|
||||||
|
|
||||||
```toml
|
```toml"
|
||||||
# /etc/buildkitd.toml
|
|
||||||
debug = true
|
debug = true
|
||||||
[registry."myregistry.com"]
|
[registry."myregistry.com"]
|
||||||
ca=["/etc/certs/myregistry.pem"]
|
ca=["/etc/certs/myregistry.pem"]
|
||||||
@@ -17,10 +16,11 @@ debug = true
|
|||||||
key="/etc/certs/myregistry_key.pem"
|
key="/etc/certs/myregistry_key.pem"
|
||||||
cert="/etc/certs/myregistry_cert.pem"
|
cert="/etc/certs/myregistry_cert.pem"
|
||||||
```
|
```
|
||||||
|
> `/etc/buildkitd.toml`
|
||||||
|
|
||||||
Here we have configured a self-signed certificate for `myregistry.com` registry.
|
Here we have configured a self-signed certificate for `myregistry.com` registry.
|
||||||
|
|
||||||
Now [create a `docker-container` builder](https://docs.docker.com/engine/reference/commandline/buildx_create/)
|
Now [create a `docker-container` builder](../reference/buildx_create.md)
|
||||||
that will use this BuildKit configuration:
|
that will use this BuildKit configuration:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
|
@@ -1,75 +0,0 @@
|
|||||||
# Docker container driver
|
|
||||||
|
|
||||||
The buildx docker-container driver allows creation of a managed and
|
|
||||||
customizable BuildKit environment inside a dedicated Docker container.
|
|
||||||
|
|
||||||
Using the docker-container driver has a couple of advantages over the basic
|
|
||||||
docker driver. Firstly, we can manually override the version of buildkit to
|
|
||||||
use, meaning that we can access the latest and greatest features as soon as
|
|
||||||
they're released, instead of waiting to upgrade to a newer version of Docker.
|
|
||||||
Additionally, we can access more complex features like multi-architecture
|
|
||||||
builds and the more advanced cache exporters, which are currently unsupported
|
|
||||||
in the default docker driver.
|
|
||||||
|
|
||||||
We can easily create a new builder that uses the docker-container driver:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create --name container --driver docker-container
|
|
||||||
container
|
|
||||||
```
|
|
||||||
|
|
||||||
We should then be able to see it on our list of available builders:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx ls
|
|
||||||
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
|
|
||||||
container docker-container
|
|
||||||
container0 desktop-linux inactive
|
|
||||||
default docker
|
|
||||||
default default running 20.10.17 linux/amd64, linux/386
|
|
||||||
```
|
|
||||||
|
|
||||||
If we trigger a build, the appropriate `moby/buildkit` image will be pulled
|
|
||||||
from [Docker Hub](https://hub.docker.com/u/moby/buildkit), the image started,
|
|
||||||
and our build submitted to our containerized build server.
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx build -t <image> --builder=container .
|
|
||||||
WARNING: No output specified with docker-container driver. Build result will only remain in the build cache. To push result image into registry use --push or to load image into docker use --load
|
|
||||||
#1 [internal] booting buildkit
|
|
||||||
#1 pulling image moby/buildkit:buildx-stable-1
|
|
||||||
#1 pulling image moby/buildkit:buildx-stable-1 1.9s done
|
|
||||||
#1 creating container buildx_buildkit_container0
|
|
||||||
#1 creating container buildx_buildkit_container0 0.5s done
|
|
||||||
#1 DONE 2.4s
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Note the warning "Build result will only remain in the build cache" - unlike
|
|
||||||
the `docker` driver, the built image must be explicitly loaded into the local
|
|
||||||
image store. We can use the `--load` flag for this:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx build --load -t <image> --builder=container .
|
|
||||||
...
|
|
||||||
=> exporting to oci image format 7.7s
|
|
||||||
=> => exporting layers 4.9s
|
|
||||||
=> => exporting manifest sha256:4e4ca161fa338be2c303445411900ebbc5fc086153a0b846ac12996960b479d3 0.0s
|
|
||||||
=> => exporting config sha256:adf3eec768a14b6e183a1010cb96d91155a82fd722a1091440c88f3747f1f53f 0.0s
|
|
||||||
=> => sending tarball 2.8s
|
|
||||||
=> importing to docker
|
|
||||||
```
|
|
||||||
|
|
||||||
The image should then be available in the image store:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker image ls
|
|
||||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
|
||||||
<image> latest adf3eec768a1 2 minutes ago 197MB
|
|
||||||
```
|
|
||||||
|
|
||||||
## Further reading
|
|
||||||
|
|
||||||
For more information on the docker-container driver, see the [buildx reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver).
|
|
||||||
|
|
||||||
<!--- FIXME: for 0.9, make reference link relative --->
|
|
@@ -1,50 +0,0 @@
|
|||||||
# Docker driver
|
|
||||||
|
|
||||||
The buildx docker driver is the default builtin driver, that uses the BuildKit
|
|
||||||
server components built directly into the docker engine.
|
|
||||||
|
|
||||||
No setup should be required for the docker driver - it should already be
|
|
||||||
configured for you:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx ls
|
|
||||||
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
|
|
||||||
default docker
|
|
||||||
default default running 20.10.17 linux/amd64, linux/386
|
|
||||||
```
|
|
||||||
|
|
||||||
This builder is ready to build with out-of-the-box, requiring no extra setup,
|
|
||||||
so you can get going with a `docker buildx build` as soon as you like.
|
|
||||||
|
|
||||||
Depending on your personal setup, you may find multiple builders in your list
|
|
||||||
the use the docker driver. For example, on a system that runs both a package
|
|
||||||
managed version of dockerd, as well as Docker Desktop, you might have the
|
|
||||||
following:
|
|
||||||
|
|
||||||
```console
|
|
||||||
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
|
|
||||||
default docker
|
|
||||||
default default running 20.10.17 linux/amd64, linux/386
|
|
||||||
desktop-linux * docker
|
|
||||||
desktop-linux desktop-linux running 20.10.17 linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
|
|
||||||
```
|
|
||||||
|
|
||||||
This is because the docker driver builders are automatically pulled from
|
|
||||||
the available [Docker Contexts](https://docs.docker.com/engine/context/working-with-contexts/).
|
|
||||||
When you add new contexts using `docker context create`, these will appear in
|
|
||||||
your list of buildx builders.
|
|
||||||
|
|
||||||
Unlike the [other drivers](../index.md), builders using the docker driver
|
|
||||||
cannot be manually created, and can only be automatically created from the
|
|
||||||
docker context. Additionally, they cannot be configured to a specific BuildKit
|
|
||||||
version, and cannot take any extra parameters, as these are both preset by the
|
|
||||||
Docker engine internally.
|
|
||||||
|
|
||||||
If you want the extra configuration and flexibility without too much more
|
|
||||||
overhead, then see the help page for the [docker-container driver](./docker-container.md).
|
|
||||||
|
|
||||||
## Further reading
|
|
||||||
|
|
||||||
For more information on the docker driver, see the [buildx reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver).
|
|
||||||
|
|
||||||
<!--- FIXME: for 0.9, make reference link relative --->
|
|
@@ -1,41 +0,0 @@
|
|||||||
# Buildx drivers overview
|
|
||||||
|
|
||||||
The buildx client connects out to the BuildKit backend to execute builds -
|
|
||||||
Buildx drivers allow fine-grained control over management of the backend, and
|
|
||||||
supports several different options for where and how BuildKit should run.
|
|
||||||
|
|
||||||
Currently, we support the following drivers:
|
|
||||||
|
|
||||||
- The `docker` driver, that uses the BuildKit library bundled into the Docker
|
|
||||||
daemon.
|
|
||||||
([guide](./docker.md), [reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver))
|
|
||||||
- The `docker-container` driver, that launches a dedicated BuildKit container
|
|
||||||
using Docker, for access to advanced features.
|
|
||||||
([guide](./docker-container.md), [reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver))
|
|
||||||
- The `kubernetes` driver, that launches dedicated BuildKit pods in a
|
|
||||||
remote Kubernetes cluster, for scalable builds.
|
|
||||||
([guide](./kubernetes.md), [reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver))
|
|
||||||
- The `remote` driver, that allows directly connecting to a manually managed
|
|
||||||
BuildKit daemon, for more custom setups.
|
|
||||||
([guide](./remote.md))
|
|
||||||
|
|
||||||
<!--- FIXME: for 0.9, make links relative, and add reference link for remote --->
|
|
||||||
|
|
||||||
To create a new builder that uses one of the above drivers, you can use the
|
|
||||||
[`docker buildx create`](https://docs.docker.com/engine/reference/commandline/buildx_create/) command:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create --name=<builder-name> --driver=<driver> --driver-opt=<driver-options>
|
|
||||||
```
|
|
||||||
|
|
||||||
The build experience is very similar across drivers, however, there are some
|
|
||||||
features that are not evenly supported across the board, notably, the `docker`
|
|
||||||
driver does not include support for certain output/caching types.
|
|
||||||
|
|
||||||
| Feature | `docker` | `docker-container` | `kubernetes` | `remote` |
|
|
||||||
| :---------------------------- | :-------------: | :----------------: | :----------: | :--------------------: |
|
|
||||||
| **Automatic `--load`** | ✅ | ❌ | ❌ | ❌ |
|
|
||||||
| **Cache export** | ❔ (inline only) | ✅ | ✅ | ✅ |
|
|
||||||
| **Docker/OCI tarball output** | ❌ | ✅ | ✅ | ✅ |
|
|
||||||
| **Multi-arch images** | ❌ | ✅ | ✅ | ✅ |
|
|
||||||
| **BuildKit configuration** | ❌ | ✅ | ✅ | ❔ (managed externally) |
|
|
@@ -1,238 +0,0 @@
|
|||||||
# Kubernetes driver
|
|
||||||
|
|
||||||
The buildx kubernetes driver allows connecting your local development or ci
|
|
||||||
environments to your kubernetes cluster to allow access to more powerful
|
|
||||||
and varied compute resources.
|
|
||||||
|
|
||||||
This guide assumes you already have an existing kubernetes cluster - if you don't already
|
|
||||||
have one, you can easily follow along by installing
|
|
||||||
[minikube](https://minikube.sigs.k8s.io/docs/).
|
|
||||||
|
|
||||||
Before connecting buildx to your cluster, you may want to create a dedicated
|
|
||||||
namespace using `kubectl` to keep your buildx-managed resources separate. You
|
|
||||||
can call your namespace anything you want, or use the existing `default`
|
|
||||||
namespace, but we'll create a `buildkit` namespace for now:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ kubectl create namespace buildkit
|
|
||||||
```
|
|
||||||
|
|
||||||
Then create a new buildx builder:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--bootstrap \
|
|
||||||
--name=kube \
|
|
||||||
--driver=kubernetes \
|
|
||||||
--driver-opt=namespace=buildkit
|
|
||||||
```
|
|
||||||
|
|
||||||
This assumes that the kubernetes cluster you want to connect to is currently
|
|
||||||
accessible via the kubectl command, with the `KUBECONFIG` environment variable
|
|
||||||
[set appropriately](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/#set-the-kubeconfig-environment-variable)
|
|
||||||
if neccessary.
|
|
||||||
|
|
||||||
You should now be able to see the builder in the list of buildx builders:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx ls
|
|
||||||
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
|
|
||||||
kube kubernetes
|
|
||||||
kube0-6977cdcb75-k9h9m running linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386
|
|
||||||
default * docker
|
|
||||||
default default running linux/amd64, linux/386
|
|
||||||
```
|
|
||||||
|
|
||||||
The buildx driver creates the neccessary resources on your cluster in the
|
|
||||||
specified namespace (in this case, `buildkit`), while keeping your
|
|
||||||
driver configuration locally. You can see the running pods with:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ kubectl -n buildkit get deployments
|
|
||||||
NAME READY UP-TO-DATE AVAILABLE AGE
|
|
||||||
kube0 1/1 1 1 32s
|
|
||||||
|
|
||||||
$ kubectl -n buildkit get pods
|
|
||||||
NAME READY STATUS RESTARTS AGE
|
|
||||||
kube0-6977cdcb75-k9h9m 1/1 Running 0 32s
|
|
||||||
```
|
|
||||||
|
|
||||||
You can use your new builder by including the `--builder` flag when running
|
|
||||||
buildx commands. For example (replacing `<user>` and `<image>` with your Docker
|
|
||||||
Hub username and desired image output respectively):
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx build . \
|
|
||||||
--builder=kube \
|
|
||||||
-t <user>/<image> \
|
|
||||||
--push
|
|
||||||
```
|
|
||||||
|
|
||||||
## Scaling Buildkit
|
|
||||||
|
|
||||||
One of the main advantages of the kubernetes builder is that you can easily
|
|
||||||
scale your builder up and down to handle increased build load. These controls
|
|
||||||
are exposed via the following options:
|
|
||||||
|
|
||||||
- `replicas=N`
|
|
||||||
- This scales the number of buildkit pods to the desired size. By default,
|
|
||||||
only a single pod will be created, but increasing this allows taking of
|
|
||||||
advantage of multiple nodes in your cluster.
|
|
||||||
- `requests.cpu`, `requests.memory`, `limits.cpu`, `limits.memory`
|
|
||||||
- These options allow requesting and limiting the resources available to each
|
|
||||||
buildkit pod according to the official kubernetes documentation
|
|
||||||
[here](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/).
|
|
||||||
|
|
||||||
For example, to create 4 replica buildkit pods:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--bootstrap \
|
|
||||||
--name=kube \
|
|
||||||
--driver=kubernetes \
|
|
||||||
--driver-opt=namespace=buildkit,replicas=4
|
|
||||||
```
|
|
||||||
|
|
||||||
Listing the pods, we get:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ kubectl -n buildkit get deployments
|
|
||||||
NAME READY UP-TO-DATE AVAILABLE AGE
|
|
||||||
kube0 4/4 4 4 8s
|
|
||||||
|
|
||||||
$ kubectl -n buildkit get pods
|
|
||||||
NAME READY STATUS RESTARTS AGE
|
|
||||||
kube0-6977cdcb75-48ld2 1/1 Running 0 8s
|
|
||||||
kube0-6977cdcb75-rkc6b 1/1 Running 0 8s
|
|
||||||
kube0-6977cdcb75-vb4ks 1/1 Running 0 8s
|
|
||||||
kube0-6977cdcb75-z4fzs 1/1 Running 0 8s
|
|
||||||
```
|
|
||||||
|
|
||||||
Additionally, you can use the `loadbalance=(sticky|random)` option to control
|
|
||||||
the load-balancing behavior when there are multiple replicas. While `random`
|
|
||||||
should selects random nodes from the available pool, which should provide
|
|
||||||
better balancing across all replicas, `sticky` (the default) attempts to
|
|
||||||
connect the same build performed multiple times to the same node each time,
|
|
||||||
ensuring better local cache utilization.
|
|
||||||
|
|
||||||
For more information on scalability, see the options for [buildx create](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver-opt).
|
|
||||||
|
|
||||||
## Multi-platform builds
|
|
||||||
|
|
||||||
The kubernetes buildx driver has support for creating [multi-platform images](https://docs.docker.com/build/buildx/multiplatform-images/),
|
|
||||||
for easily building for multiple platforms at once.
|
|
||||||
|
|
||||||
### QEMU
|
|
||||||
|
|
||||||
Like the other containerized driver `docker-container`, the kubernetes driver
|
|
||||||
also supports using [QEMU](https://www.qemu.org/) (user mode) to build
|
|
||||||
non-native platforms. If using a default setup like above, no extra setup
|
|
||||||
should be needed, you should just be able to start building for other
|
|
||||||
architectures, by including the `--platform` flag.
|
|
||||||
|
|
||||||
For example, to build a Linux image for `amd64` and `arm64`:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx build . \
|
|
||||||
--builder=kube \
|
|
||||||
--platform=linux/amd64,linux/arm64 \
|
|
||||||
-t <user>/<image> \
|
|
||||||
--push
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Warning**
|
|
||||||
> QEMU performs full-system emulation of non-native platforms, which is *much*
|
|
||||||
> slower than native builds. Compute-heavy tasks like compilation and
|
|
||||||
> compression/decompression will likely take a large performance hit.
|
|
||||||
|
|
||||||
Note, if you're using a custom buildkit image using the `image=<image>` driver
|
|
||||||
option, or invoking non-native binaries from within your build, you may need to
|
|
||||||
explicitly enable QEMU using the `qemu.install` option during driver creation:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--bootstrap \
|
|
||||||
--name=kube \
|
|
||||||
--driver=kubernetes \
|
|
||||||
--driver-opt=namespace=buildkit,qemu.install=true
|
|
||||||
```
|
|
||||||
|
|
||||||
### Native
|
|
||||||
|
|
||||||
If you have access to cluster nodes of different architectures, we can
|
|
||||||
configure the kubernetes driver to take advantage of these for native builds.
|
|
||||||
To do this, we need to use the `--append` feature of `docker buildx create`.
|
|
||||||
|
|
||||||
To start, we can create our builder with explicit support for a single
|
|
||||||
architecture, `amd64`:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--bootstrap \
|
|
||||||
--name=kube \
|
|
||||||
--driver=kubernetes \
|
|
||||||
--platform=linux/amd64 \
|
|
||||||
--node=builder-amd64 \
|
|
||||||
--driver-opt=namespace=buildkit,nodeselector="kubernetes.io/arch=amd64"
|
|
||||||
```
|
|
||||||
|
|
||||||
This creates a buildx builder `kube` containing a single builder node `builder-amd64`.
|
|
||||||
Note that the buildx concept of a node is not the same as the kubernetes
|
|
||||||
concept of a node - the buildx node in this case could connect multiple
|
|
||||||
kubernetes nodes of the same architecture together.
|
|
||||||
|
|
||||||
With our `kube` driver created, we can now introduce another architecture into
|
|
||||||
the mix, for example, like before we can use `arm64`:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--append \
|
|
||||||
--bootstrap \
|
|
||||||
--name=kube \
|
|
||||||
--driver=kubernetes \
|
|
||||||
--platform=linux/arm64 \
|
|
||||||
--node=builder-arm64 \
|
|
||||||
--driver-opt=namespace=buildkit,nodeselector="kubernetes.io/arch=arm64"
|
|
||||||
```
|
|
||||||
|
|
||||||
If you list builders now, you should be able to see both nodes present:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx ls
|
|
||||||
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
|
|
||||||
kube kubernetes
|
|
||||||
builder-amd64 kubernetes:///kube?deployment=builder-amd64&kubeconfig= running linux/amd64*, linux/amd64/v2, linux/amd64/v3, linux/386
|
|
||||||
builder-arm64 kubernetes:///kube?deployment=builder-arm64&kubeconfig= running linux/arm64*
|
|
||||||
```
|
|
||||||
|
|
||||||
You should now be able to build multi-arch images with `amd64` and `arm64`
|
|
||||||
combined, by specifying those platforms together in your buildx command:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx build --builder=kube --platform=linux/amd64,linux/arm64 -t <user>/<image> --push .
|
|
||||||
```
|
|
||||||
|
|
||||||
You can repeat the `buildx create --append` command for as many different
|
|
||||||
architectures that you want to support.
|
|
||||||
|
|
||||||
## Rootless mode
|
|
||||||
|
|
||||||
The kubernetes driver supports rootless mode. For more information on how
|
|
||||||
rootless mode works, and it's requirements, see [here](https://github.com/moby/buildkit/blob/master/docs/rootless.md).
|
|
||||||
|
|
||||||
To enable it in your cluster, you can use the `rootless=true` driver option:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--name=kube \
|
|
||||||
--driver=kubernetes \
|
|
||||||
--driver-opt=namespace=buildkit,rootless=true
|
|
||||||
```
|
|
||||||
|
|
||||||
This will create your pods without `securityContext.privileged`.
|
|
||||||
|
|
||||||
## Further reading
|
|
||||||
|
|
||||||
For more information on the kubernetes driver, see the [buildx reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver).
|
|
||||||
|
|
||||||
<!--- FIXME: for 0.9, make reference link relative --->
|
|
@@ -1,178 +0,0 @@
|
|||||||
# Remote driver
|
|
||||||
|
|
||||||
The buildx remote driver allows for more complex custom build workloads that
|
|
||||||
allow users to connect to external buildkit instances. This is useful for
|
|
||||||
scenarios that require manual management of the buildkit daemon, or where a
|
|
||||||
buildkit daemon is exposed from another source.
|
|
||||||
|
|
||||||
To connect to a running buildkitd instance:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--name remote \
|
|
||||||
--driver remote \
|
|
||||||
tcp://localhost:1234
|
|
||||||
```
|
|
||||||
|
|
||||||
## Remote Buildkit over Unix sockets
|
|
||||||
|
|
||||||
In this scenario, we'll create a setup with buildkitd listening on a unix
|
|
||||||
socket, and have buildx connect through it.
|
|
||||||
|
|
||||||
Firstly, ensure that [buildkit](https://github.com/moby/buildkit) is installed.
|
|
||||||
For example, you can launch an instance of buildkitd with:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ sudo ./buildkitd --group $(id -gn) --addr unix://$HOME/buildkitd.sock
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, [see here](https://github.com/moby/buildkit/blob/master/docs/rootless.md)
|
|
||||||
for running buildkitd in rootless mode or [here](https://github.com/moby/buildkit/tree/master/examples/systemd)
|
|
||||||
for examples of running it as a systemd service.
|
|
||||||
|
|
||||||
You should now have a unix socket accessible to your user, that is available to
|
|
||||||
connect to:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ ls -lh /home/user/buildkitd.sock
|
|
||||||
srw-rw---- 1 root user 0 May 5 11:04 /home/user/buildkitd.sock
|
|
||||||
```
|
|
||||||
|
|
||||||
You can then connect buildx to it with the remote driver:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--name remote-unix \
|
|
||||||
--driver remote \
|
|
||||||
unix://$HOME/buildkitd.sock
|
|
||||||
```
|
|
||||||
|
|
||||||
If you list available builders, you should then see `remote-unix` among them:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx ls
|
|
||||||
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
|
|
||||||
remote-unix remote
|
|
||||||
remote-unix0 unix:///home/.../buildkitd.sock running linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386
|
|
||||||
default * docker
|
|
||||||
default default running linux/amd64, linux/386
|
|
||||||
```
|
|
||||||
|
|
||||||
We can switch to this new builder as the default using `docker buildx use remote-unix`,
|
|
||||||
or specify it per build:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx build --builder=remote-unix -t test --load .
|
|
||||||
```
|
|
||||||
|
|
||||||
(remember that `--load` is necessary when not using the default `docker`
|
|
||||||
driver, to load the build result into the docker daemon)
|
|
||||||
|
|
||||||
## Remote Buildkit in Docker container
|
|
||||||
|
|
||||||
In this scenario, we'll create a similar setup to the `docker-container`
|
|
||||||
driver, by manually booting a buildkit docker container and connecting to it
|
|
||||||
using the buildx remote driver. In most cases you'd probably just use the
|
|
||||||
`docker-container` driver that connects to buildkit through the Docker daemon,
|
|
||||||
but in this case we manually create a container and access it via it's exposed
|
|
||||||
port.
|
|
||||||
|
|
||||||
First, we need to generate certificates for buildkit - you can use the
|
|
||||||
[create-certs.sh](https://github.com/moby/buildkit/v0.10.3/master/examples/kubernetes/create-certs.sh)
|
|
||||||
script as a starting point. Note, that while it is *possible* to expose
|
|
||||||
buildkit over TCP without using TLS, it is **not recommended**, since this will
|
|
||||||
allow arbitrary access to buildkit without credentials.
|
|
||||||
|
|
||||||
With our certificates generated in `.certs/`, we startup the container:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker run -d --rm \
|
|
||||||
--name=remote-buildkitd \
|
|
||||||
--privileged \
|
|
||||||
-p 1234:1234 \
|
|
||||||
-v $PWD/.certs:/etc/buildkit/certs \
|
|
||||||
moby/buildkit:latest \
|
|
||||||
--addr tcp://0.0.0.0:1234 \
|
|
||||||
--tlscacert /etc/buildkit/certs/ca.pem \
|
|
||||||
--tlscert /etc/buildkit/certs/daemon-cert.pem \
|
|
||||||
--tlskey /etc/buildkit/certs/daemon-key.pem
|
|
||||||
```
|
|
||||||
|
|
||||||
The above command starts a buildkit container and exposes the daemon's port
|
|
||||||
1234 to localhost.
|
|
||||||
|
|
||||||
We can now connect to this running container using buildx:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--name remote-container \
|
|
||||||
--driver remote \
|
|
||||||
--driver-opt cacert=.certs/ca.pem,cert=.certs/client-cert.pem,key=.certs/client-key.pem,servername=... \
|
|
||||||
tcp://localhost:1234
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, we could use the `docker-container://` URL scheme to connect
|
|
||||||
to the buildkit container without specifying a port:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--name remote-container \
|
|
||||||
--driver remote \
|
|
||||||
docker-container://remote-container
|
|
||||||
```
|
|
||||||
|
|
||||||
## Remote Buildkit in Kubernetes
|
|
||||||
|
|
||||||
In this scenario, we'll create a similar setup to the `kubernetes` driver by
|
|
||||||
manually creating a buildkit `Deployment`. While the `kubernetes` driver will
|
|
||||||
do this under-the-hood, it might sometimes be desirable to scale buildkit
|
|
||||||
manually. Additionally, when executing builds from inside Kubernetes pods,
|
|
||||||
the buildx builder will need to be recreated from within each pod or copied
|
|
||||||
between them.
|
|
||||||
|
|
||||||
Firstly, we can create a kubernetes deployment of buildkitd, as per the
|
|
||||||
instructions [here](https://github.com/moby/buildkit/tree/master/examples/kubernetes).
|
|
||||||
Following the guide, we setup certificates for the buildkit daemon and client
|
|
||||||
(as above using [create-certs.sh](https://github.com/moby/buildkit/blob/v0.10.3/examples/kubernetes/create-certs.sh))
|
|
||||||
and create a `Deployment` of buildkit pods with a service that connects to
|
|
||||||
them.
|
|
||||||
|
|
||||||
Assuming that the service is called `buildkitd`, we can create a remote builder
|
|
||||||
in buildx, ensuring that the listed certificate files are present:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--name remote-kubernetes \
|
|
||||||
--driver remote \
|
|
||||||
--driver-opt cacert=.certs/ca.pem,cert=.certs/client-cert.pem,key=.certs/client-key.pem \
|
|
||||||
tcp://buildkitd.default.svc:1234
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that the above will only work in-cluster (since the buildkit setup guide
|
|
||||||
only creates a ClusterIP service). To configure the builder to be accessible
|
|
||||||
remotely, you can use an appropriately configured Ingress, which is outside the
|
|
||||||
scope of this guide.
|
|
||||||
|
|
||||||
To access the service remotely, we can use the port forwarding mechanism in
|
|
||||||
kubectl:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ kubectl port-forward svc/buildkitd 1234:1234
|
|
||||||
```
|
|
||||||
|
|
||||||
Then you can simply point the remote driver at `tcp://localhost:1234`.
|
|
||||||
|
|
||||||
Alternatively, we could use the `kube-pod://` URL scheme to connect
|
|
||||||
directly to a buildkit pod through the kubernetes api (note that this method
|
|
||||||
will only connect to a single pod in the deployment):
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ kubectl get pods --selector=app=buildkitd -o json | jq -r '.items[].metadata.name
|
|
||||||
buildkitd-XXXXXXXXXX-xxxxx
|
|
||||||
$ docker buildx create \
|
|
||||||
--name remote-container \
|
|
||||||
--driver remote \
|
|
||||||
kube-pod://buildkitd-XXXXXXXXXX-xxxxx
|
|
||||||
```
|
|
||||||
|
|
||||||
<!--- FIXME: for 0.9, add further reading section with link to reference --->
|
|
@@ -9,7 +9,7 @@ First create a Jaeger container:
|
|||||||
$ docker run -d --name jaeger -p "6831:6831/udp" -p "16686:16686" jaegertracing/all-in-one
|
$ docker run -d --name jaeger -p "6831:6831/udp" -p "16686:16686" jaegertracing/all-in-one
|
||||||
```
|
```
|
||||||
|
|
||||||
Then [create a `docker-container` builder](https://docs.docker.com/engine/reference/commandline/buildx_create/)
|
Then [create a `docker-container` builder](../reference/buildx_create.md)
|
||||||
that will use the Jaeger instance via the `JAEGER_TRACE` env var:
|
that will use the Jaeger instance via the `JAEGER_TRACE` env var:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
@@ -20,7 +20,7 @@ $ docker buildx create --use \
|
|||||||
--driver-opt "env.JAEGER_TRACE=localhost:6831"
|
--driver-opt "env.JAEGER_TRACE=localhost:6831"
|
||||||
```
|
```
|
||||||
|
|
||||||
Boot and [inspect `mybuilder`](https://docs.docker.com/engine/reference/commandline/buildx_inspect/):
|
Boot and [inspect `mybuilder`](../reference/buildx_inspect.md):
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker buildx inspect --bootstrap
|
$ docker buildx inspect --bootstrap
|
||||||
@@ -28,4 +28,4 @@ $ docker buildx inspect --bootstrap
|
|||||||
|
|
||||||
Buildx commands should be traced at `http://127.0.0.1:16686/`:
|
Buildx commands should be traced at `http://127.0.0.1:16686/`:
|
||||||
|
|
||||||

|

|
||||||
|
@@ -1,21 +1,19 @@
|
|||||||
# Registry mirror
|
# Registry mirror
|
||||||
|
|
||||||
You can define a registry mirror to use for your builds by providing a [BuildKit daemon configuration](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md)
|
You can define a registry mirror to use for your builds by providing a [BuildKit daemon configuration](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md)
|
||||||
while creating a builder with the [`--config` flags](https://docs.docker.com/engine/reference/commandline/buildx_create/#config).
|
while creating a builder with the [`--config` flags](../reference/buildx_create.md#config).
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# /etc/buildkitd.toml
|
|
||||||
debug = true
|
debug = true
|
||||||
[registry."docker.io"]
|
[registry."docker.io"]
|
||||||
mirrors = ["mirror.gcr.io"]
|
mirrors = ["mirror.gcr.io"]
|
||||||
```
|
```
|
||||||
|
> `/etc/buildkitd.toml`
|
||||||
|
|
||||||
> **Note**
|
> :information_source: `debug = true` has been added to be able to debug requests
|
||||||
>
|
in the BuildKit daemon and see if the mirror is effectively used.
|
||||||
> `debug = true` has been added to be able to debug requests
|
|
||||||
> in the BuildKit daemon and see if the mirror is effectively used.
|
|
||||||
|
|
||||||
Then [create a `docker-container` builder](https://docs.docker.com/engine/reference/commandline/buildx_create/)
|
Then [create a `docker-container` builder](../reference/buildx_create.md)
|
||||||
that will use this BuildKit configuration:
|
that will use this BuildKit configuration:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
@@ -25,7 +23,7 @@ $ docker buildx create --use \
|
|||||||
--config /etc/buildkitd.toml
|
--config /etc/buildkitd.toml
|
||||||
```
|
```
|
||||||
|
|
||||||
Boot and [inspect `mybuilder`](https://docs.docker.com/engine/reference/commandline/buildx_inspect/):
|
Boot and [inspect `mybuilder`](../reference/buildx_inspect.md):
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker buildx inspect --bootstrap
|
$ docker buildx inspect --bootstrap
|
||||||
|
@@ -4,15 +4,15 @@
|
|||||||
|
|
||||||
You can limit the parallelism of the BuildKit solver, which is particularly useful
|
You can limit the parallelism of the BuildKit solver, which is particularly useful
|
||||||
for low-powered machines, using a [BuildKit daemon configuration](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md)
|
for low-powered machines, using a [BuildKit daemon configuration](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md)
|
||||||
while creating a builder with the [`--config` flags](https://docs.docker.com/engine/reference/commandline/buildx_create/#config).
|
while creating a builder with the [`--config` flags](../reference/buildx_create.md#config).
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# /etc/buildkitd.toml
|
|
||||||
[worker.oci]
|
[worker.oci]
|
||||||
max-parallelism = 4
|
max-parallelism = 4
|
||||||
```
|
```
|
||||||
|
> `/etc/buildkitd.toml`
|
||||||
|
|
||||||
Now you can [create a `docker-container` builder](https://docs.docker.com/engine/reference/commandline/buildx_create/)
|
Now you can [create a `docker-container` builder](../reference/buildx_create.md)
|
||||||
that will use this BuildKit configuration to limit parallelism.
|
that will use this BuildKit configuration to limit parallelism.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
|
@@ -9,7 +9,7 @@ Build from a file
|
|||||||
|
|
||||||
### Aliases
|
### Aliases
|
||||||
|
|
||||||
`docker buildx bake`, `docker buildx f`
|
`bake`, `f`
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
@@ -34,14 +34,12 @@ Build from a file
|
|||||||
Bake is a high-level build command. Each specified target will run in parallel
|
Bake is a high-level build command. Each specified target will run in parallel
|
||||||
as part of the build.
|
as part of the build.
|
||||||
|
|
||||||
Read [High-level build options with Bake](https://docs.docker.com/build/bake/)
|
Read [High-level build options](https://github.com/docker/buildx#high-level-build-options)
|
||||||
guide for introduction to writing bake files.
|
for introduction.
|
||||||
|
|
||||||
> **Note**
|
Please note that `buildx bake` command may receive backwards incompatible
|
||||||
>
|
features in the future if needed. We are looking for feedback on improving the
|
||||||
> `buildx bake` command may receive backwards incompatible features in the future
|
command and extending the functionality further.
|
||||||
> if needed. We are looking for feedback on improving the command and extending
|
|
||||||
> the functionality further.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -51,42 +49,166 @@ Same as [`buildx --builder`](buildx.md#builder).
|
|||||||
|
|
||||||
### <a name="file"></a> Specify a build definition file (-f, --file)
|
### <a name="file"></a> Specify a build definition file (-f, --file)
|
||||||
|
|
||||||
Use the `-f` / `--file` option to specify the build definition file to use.
|
By default, `buildx bake` looks for build definition files in the current
|
||||||
The file can be an HCL, JSON or Compose file. If multiple files are specified
|
directory, the following are parsed:
|
||||||
|
|
||||||
|
- `docker-compose.yml`
|
||||||
|
- `docker-compose.yaml`
|
||||||
|
- `docker-bake.json`
|
||||||
|
- `docker-bake.override.json`
|
||||||
|
- `docker-bake.hcl`
|
||||||
|
- `docker-bake.override.hcl`
|
||||||
|
|
||||||
|
Use the `-f` / `--file` option to specify the build definition file to use. The
|
||||||
|
file can be a Docker Compose, JSON or HCL file. If multiple files are specified
|
||||||
they are all read and configurations are combined.
|
they are all read and configurations are combined.
|
||||||
|
|
||||||
You can pass the names of the targets to build, to build only specific target(s).
|
The following example uses a Docker Compose file named `docker-compose.dev.yaml`
|
||||||
The following example builds the `db` and `webapp-release` targets that are
|
as build definition file, and builds all targets in the file:
|
||||||
defined in the `docker-bake.dev.hcl` file:
|
|
||||||
|
|
||||||
```hcl
|
```console
|
||||||
# docker-bake.dev.hcl
|
$ docker buildx bake -f docker-compose.dev.yaml
|
||||||
group "default" {
|
|
||||||
targets = ["db", "webapp-dev"]
|
[+] Building 66.3s (30/30) FINISHED
|
||||||
|
=> [frontend internal] load build definition from Dockerfile 0.1s
|
||||||
|
=> => transferring dockerfile: 36B 0.0s
|
||||||
|
=> [backend internal] load build definition from Dockerfile 0.2s
|
||||||
|
=> => transferring dockerfile: 3.73kB 0.0s
|
||||||
|
=> [database internal] load build definition from Dockerfile 0.1s
|
||||||
|
=> => transferring dockerfile: 5.77kB 0.0s
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Pass the names of the targets to build, to build only specific target(s). The
|
||||||
|
following example builds the `backend` and `database` targets that are defined
|
||||||
|
in the `docker-compose.dev.yaml` file, skipping the build for the `frontend`
|
||||||
|
target:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake -f docker-compose.dev.yaml backend database
|
||||||
|
|
||||||
|
[+] Building 2.4s (13/13) FINISHED
|
||||||
|
=> [backend internal] load build definition from Dockerfile 0.1s
|
||||||
|
=> => transferring dockerfile: 81B 0.0s
|
||||||
|
=> [database internal] load build definition from Dockerfile 0.2s
|
||||||
|
=> => transferring dockerfile: 36B 0.0s
|
||||||
|
=> [backend internal] load .dockerignore 0.3s
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use a remote `git` bake definition:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake "https://github.com/docker/cli.git#v20.10.11" --print
|
||||||
|
#1 [internal] load git source https://github.com/docker/cli.git#v20.10.11
|
||||||
|
#1 0.745 e8f1871b077b64bcb4a13334b7146492773769f7 refs/tags/v20.10.11
|
||||||
|
#1 2.022 From https://github.com/docker/cli
|
||||||
|
#1 2.022 * [new tag] v20.10.11 -> v20.10.11
|
||||||
|
#1 DONE 2.9s
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"binary"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"binary": {
|
||||||
|
"context": "https://github.com/docker/cli.git#v20.10.11",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"BASE_VARIANT": "alpine",
|
||||||
|
"GO_STRIP": "",
|
||||||
|
"VERSION": ""
|
||||||
|
},
|
||||||
|
"target": "binary",
|
||||||
|
"platforms": [
|
||||||
|
"local"
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
"build"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
target "webapp-dev" {
|
As you can see the context is fixed to `https://github.com/docker/cli.git` even if
|
||||||
dockerfile = "Dockerfile.webapp"
|
[no context is actually defined](https://github.com/docker/cli/blob/2776a6d694f988c0c1df61cad4bfac0f54e481c8/docker-bake.hcl#L17-L26)
|
||||||
tags = ["docker.io/username/webapp"]
|
in the definition.
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp-release" {
|
If you want to access the main context for bake command from a bake file
|
||||||
inherits = ["webapp-dev"]
|
that has been imported remotely, you can use the `BAKE_CMD_CONTEXT` builtin var:
|
||||||
platforms = ["linux/amd64", "linux/arm64"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "db" {
|
```console
|
||||||
dockerfile = "Dockerfile.db"
|
$ cat https://raw.githubusercontent.com/tonistiigi/buildx/remote-test/docker-bake.hcl
|
||||||
tags = ["docker.io/username/db"]
|
target "default" {
|
||||||
|
context = BAKE_CMD_CONTEXT
|
||||||
|
dockerfile-inline = <<EOT
|
||||||
|
FROM alpine
|
||||||
|
WORKDIR /src
|
||||||
|
COPY . .
|
||||||
|
RUN ls -l && stop
|
||||||
|
EOT
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker buildx bake -f docker-bake.dev.hcl db webapp-release
|
$ docker buildx bake "https://github.com/tonistiigi/buildx.git#remote-test" --print
|
||||||
|
{
|
||||||
|
"target": {
|
||||||
|
"default": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"dockerfile-inline": "FROM alpine\nWORKDIR /src\nCOPY . .\nRUN ls -l \u0026\u0026 stop\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
See our [file definition](https://docs.docker.com/build/bake/file-definition/)
|
```console
|
||||||
guide for more details.
|
$ touch foo bar
|
||||||
|
$ docker buildx bake "https://github.com/tonistiigi/buildx.git#remote-test"
|
||||||
|
...
|
||||||
|
> [4/4] RUN ls -l && stop:
|
||||||
|
#8 0.101 total 0
|
||||||
|
#8 0.102 -rw-r--r-- 1 root root 0 Jul 27 18:47 bar
|
||||||
|
#8 0.102 -rw-r--r-- 1 root root 0 Jul 27 18:47 foo
|
||||||
|
#8 0.102 /bin/sh: stop: not found
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake "https://github.com/tonistiigi/buildx.git#remote-test" "https://github.com/docker/cli.git#v20.10.11" --print
|
||||||
|
#1 [internal] load git source https://github.com/tonistiigi/buildx.git#remote-test
|
||||||
|
#1 0.429 577303add004dd7efeb13434d69ea030d35f7888 refs/heads/remote-test
|
||||||
|
#1 CACHED
|
||||||
|
{
|
||||||
|
"target": {
|
||||||
|
"default": {
|
||||||
|
"context": "https://github.com/docker/cli.git#v20.10.11",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"dockerfile-inline": "FROM alpine\nWORKDIR /src\nCOPY . .\nRUN ls -l \u0026\u0026 stop\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake "https://github.com/tonistiigi/buildx.git#remote-test" "https://github.com/docker/cli.git#v20.10.11"
|
||||||
|
...
|
||||||
|
> [4/4] RUN ls -l && stop:
|
||||||
|
#8 0.136 drwxrwxrwx 5 root root 4096 Jul 27 18:31 kubernetes
|
||||||
|
#8 0.136 drwxrwxrwx 3 root root 4096 Jul 27 18:31 man
|
||||||
|
#8 0.136 drwxrwxrwx 2 root root 4096 Jul 27 18:31 opts
|
||||||
|
#8 0.136 -rw-rw-rw- 1 root root 1893 Jul 27 18:31 poule.yml
|
||||||
|
#8 0.136 drwxrwxrwx 7 root root 4096 Jul 27 18:31 scripts
|
||||||
|
#8 0.136 drwxrwxrwx 3 root root 4096 Jul 27 18:31 service
|
||||||
|
#8 0.136 drwxrwxrwx 2 root root 4096 Jul 27 18:31 templates
|
||||||
|
#8 0.136 drwxrwxrwx 10 root root 4096 Jul 27 18:31 vendor
|
||||||
|
#8 0.136 -rwxrwxrwx 1 root root 9620 Jul 27 18:31 vendor.conf
|
||||||
|
#8 0.136 /bin/sh: stop: not found
|
||||||
|
```
|
||||||
|
|
||||||
### <a name="no-cache"></a> Do not use cache when building the image (--no-cache)
|
### <a name="no-cache"></a> Do not use cache when building the image (--no-cache)
|
||||||
|
|
||||||
@@ -121,7 +243,26 @@ $ docker buildx bake -f docker-bake.hcl --print db
|
|||||||
|
|
||||||
### <a name="progress"></a> Set type of progress output (--progress)
|
### <a name="progress"></a> Set type of progress output (--progress)
|
||||||
|
|
||||||
Same as [`build --progress`](buildx_build.md#progress).
|
Same as [`build --progress`](buildx_build.md#progress). Set type of progress
|
||||||
|
output (auto, plain, tty). Use plain to show container output (default "auto").
|
||||||
|
|
||||||
|
> You can also use the `BUILDKIT_PROGRESS` environment variable to set its value.
|
||||||
|
|
||||||
|
The following example uses `plain` output during the build:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --progress=plain
|
||||||
|
|
||||||
|
#2 [backend internal] load build definition from Dockerfile.test
|
||||||
|
#2 sha256:de70cb0bb6ed8044f7b9b1b53b67f624e2ccfb93d96bb48b70c1fba562489618
|
||||||
|
#2 ...
|
||||||
|
|
||||||
|
#1 [database internal] load build definition from Dockerfile.test
|
||||||
|
#1 sha256:453cb50abd941762900a1212657a35fc4aad107f5d180b0ee9d93d6b74481bce
|
||||||
|
#1 transferring dockerfile: 36B done
|
||||||
|
#1 DONE 0.1s
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
### <a name="pull"></a> Always attempt to pull a newer version of the image (--pull)
|
### <a name="pull"></a> Always attempt to pull a newer version of the image (--pull)
|
||||||
|
|
||||||
@@ -145,20 +286,701 @@ $ docker buildx bake --set foo*.no-cache # bypass caching only for
|
|||||||
```
|
```
|
||||||
|
|
||||||
Complete list of overridable fields:
|
Complete list of overridable fields:
|
||||||
|
`args`, `cache-from`, `cache-to`, `context`, `dockerfile`, `labels`, `no-cache`,
|
||||||
|
`output`, `platform`, `pull`, `secrets`, `ssh`, `tags`, `target`
|
||||||
|
|
||||||
* `args`
|
### File definition
|
||||||
* `cache-from`
|
|
||||||
* `cache-to`
|
In addition to compose files, bake supports a JSON and an equivalent HCL file
|
||||||
* `context`
|
format for defining build groups and targets.
|
||||||
* `dockerfile`
|
|
||||||
* `labels`
|
A target reflects a single docker build invocation with the same options that
|
||||||
* `no-cache`
|
you would specify for `docker build`. A group is a grouping of targets.
|
||||||
* `no-cache-filter`
|
|
||||||
* `output`
|
Multiple files can include the same target and final build options will be
|
||||||
* `platform`
|
determined by merging them together.
|
||||||
* `pull`
|
|
||||||
* `push`
|
In the case of compose files, each service corresponds to a target.
|
||||||
* `secrets`
|
|
||||||
* `ssh`
|
A group can specify its list of targets with the `targets` option. A target can
|
||||||
* `tags`
|
inherit build options by setting the `inherits` option to the list of targets or
|
||||||
* `target`
|
groups to inherit from.
|
||||||
|
|
||||||
|
Note: Design of bake command is work in progress, the user experience may change
|
||||||
|
based on feedback.
|
||||||
|
|
||||||
|
HCL definition example:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
group "default" {
|
||||||
|
targets = ["db", "webapp-dev"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp-dev" {
|
||||||
|
dockerfile = "Dockerfile.webapp"
|
||||||
|
tags = ["docker.io/username/webapp"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp-release" {
|
||||||
|
inherits = ["webapp-dev"]
|
||||||
|
platforms = ["linux/amd64", "linux/arm64"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "db" {
|
||||||
|
dockerfile = "Dockerfile.db"
|
||||||
|
tags = ["docker.io/username/db"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Complete list of valid target fields:
|
||||||
|
|
||||||
|
`args`, `cache-from`, `cache-to`, `context`, `contexts`, `dockerfile`, `inherits`, `labels`,
|
||||||
|
`no-cache`, `no-cache-filter`, `output`, `platform`, `pull`, `secrets`, `ssh`, `tags`, `target`
|
||||||
|
|
||||||
|
### Global scope attributes
|
||||||
|
|
||||||
|
You can define global scope attributes in HCL/JSON and use them for code reuse
|
||||||
|
and setting values for variables. This means you can do a "data-only" HCL file
|
||||||
|
with the values you want to set/override and use it in the list of regular
|
||||||
|
output files.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
variable "FOO" {
|
||||||
|
default = "abc"
|
||||||
|
}
|
||||||
|
|
||||||
|
target "app" {
|
||||||
|
args = {
|
||||||
|
v1 = "pre-${FOO}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use this file directly:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print app
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"app"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"app": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"v1": "pre-abc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or create an override configuration file:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# env.hcl
|
||||||
|
WHOAMI="myuser"
|
||||||
|
FOO="def-${WHOAMI}"
|
||||||
|
```
|
||||||
|
|
||||||
|
And invoke bake together with both of the files:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake -f docker-bake.hcl -f env.hcl --print app
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"app"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"app": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"v1": "pre-def-myuser"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### HCL variables and functions
|
||||||
|
|
||||||
|
Similar to how Terraform provides a way to [define variables](https://www.terraform.io/docs/configuration/variables.html#declaring-an-input-variable),
|
||||||
|
the HCL file format also supports variable block definitions. These can be used
|
||||||
|
to define variables with values provided by the current environment, or a
|
||||||
|
default value when unset.
|
||||||
|
|
||||||
|
A [set of generally useful functions](https://github.com/docker/buildx/blob/master/bake/hclparser/stdlib.go)
|
||||||
|
provided by [go-cty](https://github.com/zclconf/go-cty/tree/main/cty/function/stdlib)
|
||||||
|
are available for use in HCL files. In addition, [user defined functions](https://github.com/hashicorp/hcl/tree/main/ext/userfunc)
|
||||||
|
are also supported.
|
||||||
|
|
||||||
|
#### Using interpolation to tag an image with the git sha
|
||||||
|
|
||||||
|
Bake supports variable blocks which are assigned to matching environment
|
||||||
|
variables or default values.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
variable "TAG" {
|
||||||
|
default = "latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
group "default" {
|
||||||
|
targets = ["webapp"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp" {
|
||||||
|
tags = ["docker.io/username/webapp:${TAG}"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
alternatively, in json format:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"variable": {
|
||||||
|
"TAG": {
|
||||||
|
"default": "latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": ["webapp"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"webapp": {
|
||||||
|
"tags": ["docker.io/username/webapp:${TAG}"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print webapp
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"webapp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"webapp": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"tags": [
|
||||||
|
"docker.io/username/webapp:latest"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ TAG=$(git rev-parse --short HEAD) docker buildx bake --print webapp
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"webapp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"webapp": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"tags": [
|
||||||
|
"docker.io/username/webapp:985e9e9"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using the `add` function
|
||||||
|
|
||||||
|
You can use [`go-cty` stdlib functions](https://github.com/zclconf/go-cty/tree/main/cty/function/stdlib).
|
||||||
|
Here we are using the `add` function.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
variable "TAG" {
|
||||||
|
default = "latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
group "default" {
|
||||||
|
targets = ["webapp"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp" {
|
||||||
|
args = {
|
||||||
|
buildno = "${add(123, 1)}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print webapp
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"webapp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"webapp": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"buildno": "124"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Defining an `increment` function
|
||||||
|
|
||||||
|
It also supports [user defined functions](https://github.com/hashicorp/hcl/tree/main/ext/userfunc).
|
||||||
|
The following example defines a simple an `increment` function.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
function "increment" {
|
||||||
|
params = [number]
|
||||||
|
result = number + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
group "default" {
|
||||||
|
targets = ["webapp"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp" {
|
||||||
|
args = {
|
||||||
|
buildno = "${increment(123)}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print webapp
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"webapp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"webapp": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"buildno": "124"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Only adding tags if a variable is not empty using an `notequal`
|
||||||
|
|
||||||
|
Here we are using the conditional `notequal` function which is just for
|
||||||
|
symmetry with the `equal` one.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
variable "TAG" {default="" }
|
||||||
|
|
||||||
|
group "default" {
|
||||||
|
targets = [
|
||||||
|
"webapp",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp" {
|
||||||
|
context="."
|
||||||
|
dockerfile="Dockerfile"
|
||||||
|
tags = [
|
||||||
|
"my-image:latest",
|
||||||
|
notequal("",TAG) ? "my-image:${TAG}": "",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print webapp
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"webapp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"webapp": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"tags": [
|
||||||
|
"my-image:latest"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using variables in functions
|
||||||
|
|
||||||
|
You can refer variables to other variables like the target blocks can. Stdlib
|
||||||
|
functions can also be called but user functions can't at the moment.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
variable "REPO" {
|
||||||
|
default = "user/repo"
|
||||||
|
}
|
||||||
|
|
||||||
|
function "tag" {
|
||||||
|
params = [tag]
|
||||||
|
result = ["${REPO}:${tag}"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp" {
|
||||||
|
tags = tag("v1")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print webapp
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"webapp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"webapp": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"tags": [
|
||||||
|
"user/repo:v1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using variables in variables across files
|
||||||
|
|
||||||
|
When multiple files are specified, one file can use variables defined in
|
||||||
|
another file.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake1.hcl
|
||||||
|
variable "FOO" {
|
||||||
|
default = upper("${BASE}def")
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "BAR" {
|
||||||
|
default = "-${FOO}-"
|
||||||
|
}
|
||||||
|
|
||||||
|
target "app" {
|
||||||
|
args = {
|
||||||
|
v1 = "pre-${BAR}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake2.hcl
|
||||||
|
variable "BASE" {
|
||||||
|
default = "abc"
|
||||||
|
}
|
||||||
|
|
||||||
|
target "app" {
|
||||||
|
args = {
|
||||||
|
v2 = "${FOO}-post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake -f docker-bake1.hcl -f docker-bake2.hcl --print app
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"app"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"app": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"v1": "pre--ABCDEF-",
|
||||||
|
"v2": "ABCDEF-post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using typed variables
|
||||||
|
|
||||||
|
Non-string variables are also accepted. The value passed with env is parsed
|
||||||
|
into suitable type first.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
variable "FOO" {
|
||||||
|
default = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "IS_FOO" {
|
||||||
|
default = true
|
||||||
|
}
|
||||||
|
|
||||||
|
target "app" {
|
||||||
|
args = {
|
||||||
|
v1 = FOO > 5 ? "higher" : "lower"
|
||||||
|
v2 = IS_FOO ? "yes" : "no"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print app
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"app"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"app": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"v1": "lower",
|
||||||
|
"v2": "yes"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Defining additional build contexts and linking targets
|
||||||
|
|
||||||
|
In addition to the main `context` key that defines the build context each target can also define additional named contexts with a map defined with key `contexts`. These values map to the `--build-context` flag in the [build command](buildx_build.md#build-context).
|
||||||
|
|
||||||
|
Inside the Dockerfile these contexts can be used with the `FROM` instruction or `--from` flag.
|
||||||
|
|
||||||
|
The value can be a local source directory, container image (with docker-image:// prefix), Git URL, HTTP URL or a name of another target in the Bake file (with target: prefix).
|
||||||
|
|
||||||
|
#### Pinning alpine image
|
||||||
|
|
||||||
|
```Dockerfile
|
||||||
|
# Dockerfile
|
||||||
|
FROM alpine
|
||||||
|
RUN echo "Hello world"
|
||||||
|
```
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
target "app" {
|
||||||
|
contexts = {
|
||||||
|
alpine = "docker-image://alpine:3.13"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using a secondary source directory
|
||||||
|
|
||||||
|
```Dockerfile
|
||||||
|
# Dockerfile
|
||||||
|
|
||||||
|
FROM scratch AS src
|
||||||
|
|
||||||
|
FROM golang
|
||||||
|
COPY --from=src . .
|
||||||
|
```
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
target "app" {
|
||||||
|
contexts = {
|
||||||
|
src = "../path/to/source"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using a result of one target as a base image in another target
|
||||||
|
|
||||||
|
To use a result of one target as a build context of another, specity the target name with `target:` prefix.
|
||||||
|
|
||||||
|
```Dockerfile
|
||||||
|
# Dockerfile
|
||||||
|
FROM baseapp
|
||||||
|
RUN echo "Hello world"
|
||||||
|
```
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
|
||||||
|
target "base" {
|
||||||
|
dockerfile = "baseapp.Dockerfile"
|
||||||
|
}
|
||||||
|
|
||||||
|
target "app" {
|
||||||
|
contexts = {
|
||||||
|
baseapp = "target:base"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Please note that in most cases you should just use a single multi-stage Dockerfile with multiple targets for similar behavior. This case is recommended when you have multiple Dockerfiles that can't be easily merged into one.
|
||||||
|
|
||||||
|
### Extension field with Compose
|
||||||
|
|
||||||
|
[Special extension](https://github.com/compose-spec/compose-spec/blob/master/spec.md#extension)
|
||||||
|
field `x-bake` can be used in your compose file to evaluate fields that are not
|
||||||
|
(yet) available in the [build definition](https://github.com/compose-spec/compose-spec/blob/master/build.md#build-definition).
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yml
|
||||||
|
services:
|
||||||
|
addon:
|
||||||
|
image: ct-addon:bar
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./Dockerfile
|
||||||
|
args:
|
||||||
|
CT_ECR: foo
|
||||||
|
CT_TAG: bar
|
||||||
|
x-bake:
|
||||||
|
tags:
|
||||||
|
- ct-addon:foo
|
||||||
|
- ct-addon:alp
|
||||||
|
platforms:
|
||||||
|
- linux/amd64
|
||||||
|
- linux/arm64
|
||||||
|
cache-from:
|
||||||
|
- user/app:cache
|
||||||
|
- type=local,src=path/to/cache
|
||||||
|
cache-to: type=local,dest=path/to/cache
|
||||||
|
pull: true
|
||||||
|
|
||||||
|
aws:
|
||||||
|
image: ct-fake-aws:bar
|
||||||
|
build:
|
||||||
|
dockerfile: ./aws.Dockerfile
|
||||||
|
args:
|
||||||
|
CT_ECR: foo
|
||||||
|
CT_TAG: bar
|
||||||
|
x-bake:
|
||||||
|
secret:
|
||||||
|
- id=mysecret,src=./secret
|
||||||
|
- id=mysecret2,src=./secret2
|
||||||
|
platforms: linux/arm64
|
||||||
|
output: type=docker
|
||||||
|
no-cache: true
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"aws",
|
||||||
|
"addon"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"addon": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "./Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"CT_ECR": "foo",
|
||||||
|
"CT_TAG": "bar"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"ct-addon:foo",
|
||||||
|
"ct-addon:alp"
|
||||||
|
],
|
||||||
|
"cache-from": [
|
||||||
|
"user/app:cache",
|
||||||
|
"type=local,src=path/to/cache"
|
||||||
|
],
|
||||||
|
"cache-to": [
|
||||||
|
"type=local,dest=path/to/cache"
|
||||||
|
],
|
||||||
|
"platforms": [
|
||||||
|
"linux/amd64",
|
||||||
|
"linux/arm64"
|
||||||
|
],
|
||||||
|
"pull": true
|
||||||
|
},
|
||||||
|
"aws": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "./aws.Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"CT_ECR": "foo",
|
||||||
|
"CT_TAG": "bar"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"ct-fake-aws:bar"
|
||||||
|
],
|
||||||
|
"secret": [
|
||||||
|
"id=mysecret,src=./secret",
|
||||||
|
"id=mysecret2,src=./secret2"
|
||||||
|
],
|
||||||
|
"platforms": [
|
||||||
|
"linux/arm64"
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
"type=docker"
|
||||||
|
],
|
||||||
|
"no-cache": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Complete list of valid fields for `x-bake`:
|
||||||
|
|
||||||
|
`tags`, `cache-from`, `cache-to`, `secret`, `ssh`, `platforms`, `output`,
|
||||||
|
`pull`, `no-cache`, `no-cache-filter`
|
||||||
|
|
||||||
|
### Built-in variables
|
||||||
|
|
||||||
|
* `BAKE_CMD_CONTEXT` can be used to access the main `context` for bake command
|
||||||
|
from a bake file that has been [imported remotely](#file).
|
||||||
|
* `BAKE_LOCAL_PLATFORM` returns the current platform's default platform
|
||||||
|
specification (e.g. `linux/amd64`).
|
||||||
|
@@ -9,7 +9,7 @@ Start a build
|
|||||||
|
|
||||||
### Aliases
|
### Aliases
|
||||||
|
|
||||||
`docker buildx build`, `docker buildx b`
|
`build`, `b`
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ Allow extra privileged entitlement. List of entitlements:
|
|||||||
|
|
||||||
- `network.host` - Allows executions with host networking.
|
- `network.host` - Allows executions with host networking.
|
||||||
- `security.insecure` - Allows executions without sandbox. See
|
- `security.insecure` - Allows executions without sandbox. See
|
||||||
[related Dockerfile extensions](https://docs.docker.com/engine/reference/builder/#run---securitysandbox).
|
[related Dockerfile extensions](https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md#run---securityinsecuresandbox).
|
||||||
|
|
||||||
For entitlements to be enabled, the `buildkitd` daemon also needs to allow them
|
For entitlements to be enabled, the `buildkitd` daemon also needs to allow them
|
||||||
with `--allow-insecure-entitlement` (see [`create --buildkitd-flags`](buildx_create.md#buildkitd-flags))
|
with `--allow-insecure-entitlement` (see [`create --buildkitd-flags`](buildx_create.md#buildkitd-flags))
|
||||||
@@ -89,15 +89,13 @@ There are also useful built-in build args like:
|
|||||||
* `BUILDKIT_CONTEXT_KEEP_GIT_DIR=<bool>` trigger git context to keep the `.git` directory
|
* `BUILDKIT_CONTEXT_KEEP_GIT_DIR=<bool>` trigger git context to keep the `.git` directory
|
||||||
* `BUILDKIT_INLINE_BUILDINFO_ATTRS=<bool>` inline build info attributes in image config or not
|
* `BUILDKIT_INLINE_BUILDINFO_ATTRS=<bool>` inline build info attributes in image config or not
|
||||||
* `BUILDKIT_INLINE_CACHE=<bool>` inline cache metadata to image config or not
|
* `BUILDKIT_INLINE_CACHE=<bool>` inline cache metadata to image config or not
|
||||||
* `BUILDKIT_MULTI_PLATFORM=<bool>` opt into deterministic output regardless of multi-platform output or not
|
* `BUILDKIT_MULTI_PLATFORM=<bool>` opt into determnistic output regardless of multi-platform output or not
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker buildx build --build-arg BUILDKIT_MULTI_PLATFORM=1 .
|
$ docker buildx build --build-arg BUILDKIT_MULTI_PLATFORM=1 .
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note**
|
More built-in build args can be found in [dockerfile frontend docs](https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/syntax.md#built-in-build-args).
|
||||||
>
|
|
||||||
> More built-in build args can be found in [Dockerfile reference docs](https://docs.docker.com/engine/reference/builder/#buildkit-built-in-build-args).
|
|
||||||
|
|
||||||
### <a name="build-context"></a> Additional build contexts (--build-context)
|
### <a name="build-context"></a> Additional build contexts (--build-context)
|
||||||
|
|
||||||
@@ -108,7 +106,7 @@ $ docker buildx build --build-arg BUILDKIT_MULTI_PLATFORM=1 .
|
|||||||
Define additional build context with specified contents. In Dockerfile the context can be accessed when `FROM name` or `--from=name` is used.
|
Define additional build context with specified contents. In Dockerfile the context can be accessed when `FROM name` or `--from=name` is used.
|
||||||
When Dockerfile defines a stage with the same name it is overwritten.
|
When Dockerfile defines a stage with the same name it is overwritten.
|
||||||
|
|
||||||
The value can be a local source directory, [local OCI layout compliant directory](https://github.com/opencontainers/image-spec/blob/main/image-layout.md), container image (with docker-image:// prefix), Git or HTTP URL.
|
The value can be a local source directory, container image (with docker-image:// prefix), Git or HTTP URL.
|
||||||
|
|
||||||
Replace `alpine:latest` with a pinned one:
|
Replace `alpine:latest` with a pinned one:
|
||||||
|
|
||||||
@@ -128,32 +126,6 @@ FROM alpine
|
|||||||
COPY --from=project myfile /
|
COPY --from=project myfile /
|
||||||
```
|
```
|
||||||
|
|
||||||
#### <a name="source-oci-layout"></a> Source image from OCI layout directory
|
|
||||||
|
|
||||||
Source an image from a local [OCI layout compliant directory](https://github.com/opencontainers/image-spec/blob/main/image-layout.md):
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx build --build-context foo=oci-layout:///path/to/local/layout@sha256:abcd12345 .
|
|
||||||
```
|
|
||||||
|
|
||||||
```Dockerfile
|
|
||||||
FROM alpine
|
|
||||||
RUN apk add git
|
|
||||||
|
|
||||||
COPY --from=foo myfile /
|
|
||||||
|
|
||||||
FROM foo
|
|
||||||
```
|
|
||||||
|
|
||||||
The OCI layout directory must be compliant with the [OCI layout specification](https://github.com/opencontainers/image-spec/blob/main/image-layout.md). It looks _solely_ for hashes. It does not
|
|
||||||
do any form of `image:tag` resolution to find the hash of the manifest; that is up to you.
|
|
||||||
|
|
||||||
The format of the `--build-context` must be: `<context>=oci-layout://<path-to-local-layout>@sha256:<hash-of-manifest>`, where:
|
|
||||||
|
|
||||||
* `context` is the name of the build context as used in the `Dockerfile`.
|
|
||||||
* `path-to-local-layout` is the path on the local machine, where you are running `docker build`, to the spec-compliant OCI layout.
|
|
||||||
* `hash-of-manifest` is the hash of the manifest for the image. It can be a single-architecture manifest or a multi-architecture index.
|
|
||||||
|
|
||||||
### <a name="builder"></a> Override the configured builder instance (--builder)
|
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||||
|
|
||||||
Same as [`buildx --builder`](buildx.md#builder).
|
Same as [`buildx --builder`](buildx.md#builder).
|
||||||
@@ -415,9 +387,8 @@ $ docker buildx build --platform=darwin .
|
|||||||
Set type of progress output (auto, plain, tty). Use plain to show container
|
Set type of progress output (auto, plain, tty). Use plain to show container
|
||||||
output (default "auto").
|
output (default "auto").
|
||||||
|
|
||||||
> **Note**
|
> You can also use the `BUILDKIT_PROGRESS` environment variable to set
|
||||||
>
|
> its value.
|
||||||
> You can also use the `BUILDKIT_PROGRESS` environment variable to set its value.
|
|
||||||
|
|
||||||
The following example uses `plain` output during the build:
|
The following example uses `plain` output during the build:
|
||||||
|
|
||||||
@@ -434,11 +405,6 @@ $ docker buildx build --load --progress=plain .
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note**
|
|
||||||
>
|
|
||||||
> Check also our [Color output controls guide](https://docs.docker.com/build/guides/color-output/)
|
|
||||||
> for modifying the colors that are used to output information to the terminal.
|
|
||||||
|
|
||||||
### <a name="push"></a> Push the build result to a registry (--push)
|
### <a name="push"></a> Push the build result to a registry (--push)
|
||||||
|
|
||||||
Shorthand for [`--output=type=registry`](#registry). Will automatically push the
|
Shorthand for [`--output=type=registry`](#registry). Will automatically push the
|
||||||
@@ -451,7 +417,7 @@ build result to registry.
|
|||||||
```
|
```
|
||||||
|
|
||||||
Exposes secret to the build. The secret can be used by the build using
|
Exposes secret to the build. The secret can be used by the build using
|
||||||
[`RUN --mount=type=secret` mount](https://docs.docker.com/engine/reference/builder/#run---mounttypesecret).
|
[`RUN --mount=type=secret` mount](https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/syntax.md#run---mounttypesecret).
|
||||||
|
|
||||||
If `type` is unset it will be detected. Supported types are:
|
If `type` is unset it will be detected. Supported types are:
|
||||||
|
|
||||||
@@ -463,7 +429,7 @@ Attribute keys:
|
|||||||
- `src`, `source` - Secret filename. `id` used if unset.
|
- `src`, `source` - Secret filename. `id` used if unset.
|
||||||
|
|
||||||
```dockerfile
|
```dockerfile
|
||||||
# syntax=docker/dockerfile:1.4
|
# syntax=docker/dockerfile:1.3
|
||||||
FROM python:3
|
FROM python:3
|
||||||
RUN pip install awscli
|
RUN pip install awscli
|
||||||
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \
|
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \
|
||||||
@@ -482,7 +448,7 @@ Attribute keys:
|
|||||||
- `env` - Secret environment variable. `id` used if unset, otherwise will look for `src`, `source` if `id` unset.
|
- `env` - Secret environment variable. `id` used if unset, otherwise will look for `src`, `source` if `id` unset.
|
||||||
|
|
||||||
```dockerfile
|
```dockerfile
|
||||||
# syntax=docker/dockerfile:1.4
|
# syntax=docker/dockerfile:1.3
|
||||||
FROM node:alpine
|
FROM node:alpine
|
||||||
RUN --mount=type=bind,target=. \
|
RUN --mount=type=bind,target=. \
|
||||||
--mount=type=secret,id=SECRET_TOKEN \
|
--mount=type=secret,id=SECRET_TOKEN \
|
||||||
@@ -509,12 +475,12 @@ This can be useful when some commands in your Dockerfile need specific SSH
|
|||||||
authentication (e.g., cloning a private repository).
|
authentication (e.g., cloning a private repository).
|
||||||
|
|
||||||
`--ssh` exposes SSH agent socket or keys to the build and can be used with the
|
`--ssh` exposes SSH agent socket or keys to the build and can be used with the
|
||||||
[`RUN --mount=type=ssh` mount](https://docs.docker.com/engine/reference/builder/#run---mounttypessh).
|
[`RUN --mount=type=ssh` mount](https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/syntax.md#run---mounttypessh).
|
||||||
|
|
||||||
Example to access Gitlab using an SSH agent socket:
|
Example to access Gitlab using an SSH agent socket:
|
||||||
|
|
||||||
```dockerfile
|
```dockerfile
|
||||||
# syntax=docker/dockerfile:1.4
|
# syntax=docker/dockerfile:1.3
|
||||||
FROM alpine
|
FROM alpine
|
||||||
RUN apk add --no-cache openssh-client
|
RUN apk add --no-cache openssh-client
|
||||||
RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan gitlab.com >> ~/.ssh/known_hosts
|
RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan gitlab.com >> ~/.ssh/known_hosts
|
||||||
|
@@ -15,7 +15,7 @@ Create a new builder instance
|
|||||||
| `--bootstrap` | | | Boot builder after creation |
|
| `--bootstrap` | | | Boot builder after creation |
|
||||||
| [`--buildkitd-flags`](#buildkitd-flags) | `string` | | Flags for buildkitd daemon |
|
| [`--buildkitd-flags`](#buildkitd-flags) | `string` | | Flags for buildkitd daemon |
|
||||||
| [`--config`](#config) | `string` | | BuildKit config file |
|
| [`--config`](#config) | `string` | | BuildKit config file |
|
||||||
| [`--driver`](#driver) | `string` | | Driver to use (available: `docker`, `docker-container`, `kubernetes`, `remote`) |
|
| [`--driver`](#driver) | `string` | | Driver to use (available: `docker`, `docker-container`, `kubernetes`) |
|
||||||
| [`--driver-opt`](#driver-opt) | `stringArray` | | Options for the driver |
|
| [`--driver-opt`](#driver-opt) | `stringArray` | | Options for the driver |
|
||||||
| [`--leave`](#leave) | | | Remove a node from builder instead of changing it |
|
| [`--leave`](#leave) | | | Remove a node from builder instead of changing it |
|
||||||
| [`--name`](#name) | `string` | | Builder instance name |
|
| [`--name`](#name) | `string` | | Builder instance name |
|
||||||
@@ -79,11 +79,6 @@ Specifies the configuration file for the buildkitd daemon to use. The configurat
|
|||||||
can be overridden by [`--buildkitd-flags`](#buildkitd-flags).
|
can be overridden by [`--buildkitd-flags`](#buildkitd-flags).
|
||||||
See an [example buildkitd configuration file](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md).
|
See an [example buildkitd configuration file](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md).
|
||||||
|
|
||||||
If the configuration file is not specified, will look for one by default in:
|
|
||||||
* `$BUILDX_CONFIG/buildkitd.default.toml`
|
|
||||||
* `$DOCKER_CONFIG/buildx/buildkitd.default.toml`
|
|
||||||
* `~/.docker/buildx/buildkitd.default.toml`
|
|
||||||
|
|
||||||
Note that if you create a `docker-container` builder and have specified
|
Note that if you create a `docker-container` builder and have specified
|
||||||
certificates for registries in the `buildkitd.toml` configuration, the files
|
certificates for registries in the `buildkitd.toml` configuration, the files
|
||||||
will be copied into the container under `/etc/buildkit/certs` and configuration
|
will be copied into the container under `/etc/buildkit/certs` and configuration
|
||||||
@@ -123,59 +118,32 @@ Unlike `docker` driver, built images will not automatically appear in
|
|||||||
`docker images` and [`build --load`](buildx_build.md#load) needs to be used
|
`docker images` and [`build --load`](buildx_build.md#load) needs to be used
|
||||||
to achieve that.
|
to achieve that.
|
||||||
|
|
||||||
#### `remote` driver
|
|
||||||
|
|
||||||
Uses a remote instance of buildkitd over an arbitrary connection. With this
|
|
||||||
driver, you manually create and manage instances of buildkit yourself, and
|
|
||||||
configure buildx to point at it.
|
|
||||||
|
|
||||||
Unlike `docker` driver, built images will not automatically appear in
|
|
||||||
`docker images` and [`build --load`](buildx_build.md#load) needs to be used
|
|
||||||
to achieve that.
|
|
||||||
|
|
||||||
### <a name="driver-opt"></a> Set additional driver-specific options (--driver-opt)
|
### <a name="driver-opt"></a> Set additional driver-specific options (--driver-opt)
|
||||||
|
|
||||||
```
|
```
|
||||||
--driver-opt OPTIONS
|
--driver-opt OPTIONS
|
||||||
```
|
```
|
||||||
|
|
||||||
Passes additional driver-specific options.
|
Passes additional driver-specific options. Details for each driver:
|
||||||
|
|
||||||
Note: When using quoted values for example for the `nodeselector` or
|
- `docker` - No driver options
|
||||||
`tolerations` options, ensure that quotes are escaped correctly for your shell.
|
- `docker-container`
|
||||||
|
- `image=IMAGE` - Sets the container image to be used for running buildkit.
|
||||||
#### `docker` driver
|
- `network=NETMODE` - Sets the network mode for running the buildkit container.
|
||||||
|
- `cgroup-parent=CGROUP` - Sets the cgroup parent of the buildkit container if docker is using the "cgroupfs" driver. Defaults to `/docker/buildx`.
|
||||||
No driver options.
|
- `kubernetes`
|
||||||
|
- `image=IMAGE` - Sets the container image to be used for running buildkit.
|
||||||
#### `docker-container` driver
|
- `namespace=NS` - Sets the Kubernetes namespace. Defaults to the current namespace.
|
||||||
|
- `replicas=N` - Sets the number of `Pod` replicas. Defaults to 1.
|
||||||
- `image=IMAGE` - Sets the container image to be used for running buildkit.
|
- `requests.cpu` - Sets the request CPU value specified in units of Kubernetes CPU. Example `requests.cpu=100m`, `requests.cpu=2`
|
||||||
- `network=NETMODE` - Sets the network mode for running the buildkit container.
|
- `requests.memory` - Sets the request memory value specified in bytes or with a valid suffix. Example `requests.memory=500Mi`, `requests.memory=4G`
|
||||||
- `cgroup-parent=CGROUP` - Sets the cgroup parent of the buildkit container if docker is using the "cgroupfs" driver. Defaults to `/docker/buildx`.
|
- `limits.cpu` - Sets the limit CPU value specified in units of Kubernetes CPU. Example `limits.cpu=100m`, `limits.cpu=2`
|
||||||
|
- `limits.memory` - Sets the limit memory value specified in bytes or with a valid suffix. Example `limits.memory=500Mi`, `limits.memory=4G`
|
||||||
#### `kubernetes` driver
|
- `nodeselector="label1=value1,label2=value2"` - Sets the kv of `Pod` nodeSelector. No Defaults. Example `nodeselector=kubernetes.io/arch=arm64`
|
||||||
|
- `rootless=(true|false)` - Run the container as a non-root user without `securityContext.privileged`. [Using Ubuntu host kernel is recommended](https://github.com/moby/buildkit/blob/master/docs/rootless.md). Defaults to false.
|
||||||
- `image=IMAGE` - Sets the container image to be used for running buildkit.
|
- `loadbalance=(sticky|random)` - Load-balancing strategy. If set to "sticky", the pod is chosen using the hash of the context path. Defaults to "sticky"
|
||||||
- `namespace=NS` - Sets the Kubernetes namespace. Defaults to the current namespace.
|
- `qemu.install=(true|false)` - Install QEMU emulation for multi platforms support.
|
||||||
- `replicas=N` - Sets the number of `Pod` replicas. Defaults to 1.
|
- `qemu.image=IMAGE` - Sets the QEMU emulation image. Defaults to `tonistiigi/binfmt:latest`
|
||||||
- `requests.cpu` - Sets the request CPU value specified in units of Kubernetes CPU. Example `requests.cpu=100m`, `requests.cpu=2`
|
|
||||||
- `requests.memory` - Sets the request memory value specified in bytes or with a valid suffix. Example `requests.memory=500Mi`, `requests.memory=4G`
|
|
||||||
- `limits.cpu` - Sets the limit CPU value specified in units of Kubernetes CPU. Example `limits.cpu=100m`, `limits.cpu=2`
|
|
||||||
- `limits.memory` - Sets the limit memory value specified in bytes or with a valid suffix. Example `limits.memory=500Mi`, `limits.memory=4G`
|
|
||||||
- `"nodeselector=label1=value1,label2=value2"` - Sets the kv of `Pod` nodeSelector. No Defaults. Example `nodeselector=kubernetes.io/arch=arm64`
|
|
||||||
- `"tolerations=key=foo,value=bar;key=foo2,operator=exists;key=foo3,effect=NoSchedule"` - Sets the `Pod` tolerations. Accepts the same values as the kube manifest tolera>tions. Key-value pairs are separated by `,`, tolerations are separated by `;`. No Defaults. Example `tolerations=operator=exists`
|
|
||||||
- `rootless=(true|false)` - Run the container as a non-root user without `securityContext.privileged`. Needs Kubernetes 1.19 or later. [Using Ubuntu host kernel is recommended](https://github.com/moby/buildkit/blob/master/docs/rootless.md). Defaults to false.
|
|
||||||
- `loadbalance=(sticky|random)` - Load-balancing strategy. If set to "sticky", the pod is chosen using the hash of the context path. Defaults to "sticky"
|
|
||||||
- `qemu.install=(true|false)` - Install QEMU emulation for multi platforms support.
|
|
||||||
- `qemu.image=IMAGE` - Sets the QEMU emulation image. Defaults to `tonistiigi/binfmt:latest`
|
|
||||||
|
|
||||||
#### `remote` driver
|
|
||||||
|
|
||||||
- `key=KEY` - Sets the TLS client key.
|
|
||||||
- `cert=CERT` - Sets the TLS client certificate to present to buildkitd.
|
|
||||||
- `cacert=CACERT` - Sets the TLS certificate authority used for validation.
|
|
||||||
- `servername=SERVER` - Sets the TLS server name to be used in requests (defaults to the endpoint hostname).
|
|
||||||
|
|
||||||
### <a name="leave"></a> Remove a node from a builder (--leave)
|
### <a name="leave"></a> Remove a node from a builder (--leave)
|
||||||
|
|
||||||
|
@@ -15,7 +15,6 @@ Create a new image based on source images
|
|||||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
| [`--dry-run`](#dry-run) | | | Show final image instead of pushing |
|
| [`--dry-run`](#dry-run) | | | Show final image instead of pushing |
|
||||||
| [`-f`](#file), [`--file`](#file) | `stringArray` | | Read source descriptor from file |
|
| [`-f`](#file), [`--file`](#file) | `stringArray` | | Read source descriptor from file |
|
||||||
| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output |
|
|
||||||
| [`-t`](#tag), [`--tag`](#tag) | `stringArray` | | Set reference for new image |
|
| [`-t`](#tag), [`--tag`](#tag) | `stringArray` | | Set reference for new image |
|
||||||
|
|
||||||
|
|
||||||
|
@@ -43,10 +43,6 @@ name of the builder to inspect to get information about that builder.
|
|||||||
The following example shows information about a builder instance named
|
The following example shows information about a builder instance named
|
||||||
`elated_tesla`:
|
`elated_tesla`:
|
||||||
|
|
||||||
> **Note**
|
|
||||||
>
|
|
||||||
> Asterisk `*` next to node build platform(s) indicate they had been set manually during `buildx create`. Otherwise, it had been autodetected.
|
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker buildx inspect elated_tesla
|
$ docker buildx inspect elated_tesla
|
||||||
|
|
||||||
@@ -62,5 +58,5 @@ Platforms: linux/amd64
|
|||||||
Name: elated_tesla1
|
Name: elated_tesla1
|
||||||
Endpoint: ssh://ubuntu@1.2.3.4
|
Endpoint: ssh://ubuntu@1.2.3.4
|
||||||
Status: running
|
Status: running
|
||||||
Platforms: linux/arm64*, linux/arm/v7, linux/arm/v6
|
Platforms: linux/arm64, linux/arm/v7, linux/arm/v6
|
||||||
```
|
```
|
||||||
|
@@ -16,12 +16,13 @@ Lists all builder instances and the nodes for each instance
|
|||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker buildx ls
|
$ docker buildx ls
|
||||||
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
|
|
||||||
|
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
|
||||||
elated_tesla * docker-container
|
elated_tesla * docker-container
|
||||||
elated_tesla0 unix:///var/run/docker.sock running v0.10.3 linux/amd64
|
elated_tesla0 unix:///var/run/docker.sock running linux/amd64
|
||||||
elated_tesla1 ssh://ubuntu@1.2.3.4 running v0.10.3 linux/arm64*, linux/arm/v7, linux/arm/v6
|
elated_tesla1 ssh://ubuntu@1.2.3.4 running linux/arm64*, linux/arm/v7, linux/arm/v6
|
||||||
default docker
|
default docker
|
||||||
default default running 20.10.14 linux/amd64
|
default default running linux/amd64
|
||||||
```
|
```
|
||||||
|
|
||||||
Each builder has one or more nodes associated with it. The current builder's
|
Each builder has one or more nodes associated with it. The current builder's
|
||||||
|
@@ -4,11 +4,11 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
@@ -90,7 +90,7 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = io.Copy(io.Discard, rc)
|
_, err = io.Copy(ioutil.Discard, rc)
|
||||||
return err
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
// image pulling failed, check if it exists in local image store.
|
// image pulling failed, check if it exists in local image store.
|
||||||
@@ -287,29 +287,13 @@ func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Version(ctx context.Context) (string, error) {
|
|
||||||
bufStdout := &bytes.Buffer{}
|
|
||||||
bufStderr := &bytes.Buffer{}
|
|
||||||
if err := d.run(ctx, []string{"buildkitd", "--version"}, bufStdout, bufStderr); err != nil {
|
|
||||||
if bufStderr.Len() > 0 {
|
|
||||||
return "", errors.Wrap(err, bufStderr.String())
|
|
||||||
}
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
version := strings.Fields(bufStdout.String())
|
|
||||||
if len(version) != 4 {
|
|
||||||
return "", errors.Errorf("unexpected version format: %s", bufStdout.String())
|
|
||||||
}
|
|
||||||
return version[2], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) Stop(ctx context.Context, force bool) error {
|
func (d *Driver) Stop(ctx context.Context, force bool) error {
|
||||||
info, err := d.Info(ctx)
|
info, err := d.Info(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if info.Status == driver.Running {
|
if info.Status == driver.Running {
|
||||||
return d.DockerAPI.ContainerStop(ctx, d.Name, container.StopOptions{})
|
return d.DockerAPI.ContainerStop(ctx, d.Name, nil)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -29,7 +29,7 @@ func (*factory) Usage() string {
|
|||||||
return "docker-container"
|
return "docker-container"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*factory) Priority(ctx context.Context, endpoint string, api dockerclient.APIClient) int {
|
func (*factory) Priority(ctx context.Context, api dockerclient.APIClient) int {
|
||||||
if api == nil {
|
if api == nil {
|
||||||
return priorityUnsupported
|
return priorityUnsupported
|
||||||
}
|
}
|
||||||
|
@@ -29,14 +29,6 @@ func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Version(ctx context.Context) (string, error) {
|
|
||||||
v, err := d.DockerAPI.ServerVersion(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.Wrapf(driver.ErrNotConnecting, err.Error())
|
|
||||||
}
|
|
||||||
return v.Version, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) Stop(ctx context.Context, force bool) error {
|
func (d *Driver) Stop(ctx context.Context, force bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,7 @@ func (*factory) Usage() string {
|
|||||||
return "docker"
|
return "docker"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*factory) Priority(ctx context.Context, endpoint string, api dockerclient.APIClient) int {
|
func (*factory) Priority(ctx context.Context, api dockerclient.APIClient) int {
|
||||||
if api == nil {
|
if api == nil {
|
||||||
return priorityUnsupported
|
return priorityUnsupported
|
||||||
}
|
}
|
||||||
|
@@ -53,7 +53,6 @@ type Driver interface {
|
|||||||
Factory() Factory
|
Factory() Factory
|
||||||
Bootstrap(context.Context, progress.Logger) error
|
Bootstrap(context.Context, progress.Logger) error
|
||||||
Info(context.Context) (*Info, error)
|
Info(context.Context) (*Info, error)
|
||||||
Version(context.Context) (string, error)
|
|
||||||
Stop(ctx context.Context, force bool) error
|
Stop(ctx context.Context, force bool) error
|
||||||
Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error
|
Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error
|
||||||
Client(ctx context.Context) (*client.Client, error)
|
Client(ctx context.Context) (*client.Client, error)
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ var testStoreCfg = store.NewConfig(
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestSaveLoadContexts(t *testing.T) {
|
func TestSaveLoadContexts(t *testing.T) {
|
||||||
storeDir, err := os.MkdirTemp("", "test-load-save-k8-context")
|
storeDir, err := ioutil.TempDir("", "test-load-save-k8-context")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer os.RemoveAll(storeDir)
|
defer os.RemoveAll(storeDir)
|
||||||
store := store.New(storeDir, testStoreCfg)
|
store := store.New(storeDir, testStoreCfg)
|
||||||
@@ -49,7 +50,7 @@ func TestSaveLoadContexts(t *testing.T) {
|
|||||||
require.NoError(t, save(store, testEndpoint("https://test", "test", nil, nil, nil, true), "raw-notls-skip"))
|
require.NoError(t, save(store, testEndpoint("https://test", "test", nil, nil, nil, true), "raw-notls-skip"))
|
||||||
require.NoError(t, save(store, testEndpoint("https://test", "test", []byte("ca"), []byte("cert"), []byte("key"), true), "raw-tls"))
|
require.NoError(t, save(store, testEndpoint("https://test", "test", []byte("ca"), []byte("cert"), []byte("key"), true), "raw-tls"))
|
||||||
|
|
||||||
kcFile, err := os.CreateTemp(os.TempDir(), "test-load-save-k8-context")
|
kcFile, err := ioutil.TempFile(os.TempDir(), "test-load-save-k8-context")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer os.Remove(kcFile.Name())
|
defer os.Remove(kcFile.Name())
|
||||||
defer kcFile.Close()
|
defer kcFile.Close()
|
||||||
@@ -146,7 +147,7 @@ func save(s store.Writer, ep Endpoint, name string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSaveLoadGKEConfig(t *testing.T) {
|
func TestSaveLoadGKEConfig(t *testing.T) {
|
||||||
storeDir, err := os.MkdirTemp("", t.Name())
|
storeDir, err := ioutil.TempDir("", t.Name())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer os.RemoveAll(storeDir)
|
defer os.RemoveAll(storeDir)
|
||||||
store := store.New(storeDir, testStoreCfg)
|
store := store.New(storeDir, testStoreCfg)
|
||||||
@@ -171,7 +172,7 @@ func TestSaveLoadGKEConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSaveLoadEKSConfig(t *testing.T) {
|
func TestSaveLoadEKSConfig(t *testing.T) {
|
||||||
storeDir, err := os.MkdirTemp("", t.Name())
|
storeDir, err := ioutil.TempDir("", t.Name())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer os.RemoveAll(storeDir)
|
defer os.RemoveAll(storeDir)
|
||||||
store := store.New(storeDir, testStoreCfg)
|
store := store.New(storeDir, testStoreCfg)
|
||||||
@@ -196,7 +197,7 @@ func TestSaveLoadEKSConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSaveLoadK3SConfig(t *testing.T) {
|
func TestSaveLoadK3SConfig(t *testing.T) {
|
||||||
storeDir, err := os.MkdirTemp("", t.Name())
|
storeDir, err := ioutil.TempDir("", t.Name())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer os.RemoveAll(storeDir)
|
defer os.RemoveAll(storeDir)
|
||||||
store := store.New(storeDir, testStoreCfg)
|
store := store.New(storeDir, testStoreCfg)
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/context"
|
"github.com/docker/cli/cli/context"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
@@ -63,7 +63,7 @@ func FromKubeConfig(kubeconfig, kubeContext, namespaceOverride string) (Endpoint
|
|||||||
|
|
||||||
func readFileOrDefault(path string, defaultValue []byte) ([]byte, error) {
|
func readFileOrDefault(path string, defaultValue []byte) ([]byte, error) {
|
||||||
if path != "" {
|
if path != "" {
|
||||||
return os.ReadFile(path)
|
return ioutil.ReadFile(path)
|
||||||
}
|
}
|
||||||
return defaultValue, nil
|
return defaultValue, nil
|
||||||
}
|
}
|
||||||
|
@@ -160,10 +160,6 @@ func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Version(ctx context.Context) (string, error) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) Stop(ctx context.Context, force bool) error {
|
func (d *Driver) Stop(ctx context.Context, force bool) error {
|
||||||
// future version may scale the replicas to zero here
|
// future version may scale the replicas to zero here
|
||||||
return nil
|
return nil
|
||||||
|
@@ -5,8 +5,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
|
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
"github.com/docker/buildx/driver/bkimage"
|
"github.com/docker/buildx/driver/bkimage"
|
||||||
"github.com/docker/buildx/driver/kubernetes/manifest"
|
"github.com/docker/buildx/driver/kubernetes/manifest"
|
||||||
@@ -34,7 +32,7 @@ func (*factory) Usage() string {
|
|||||||
return DriverName
|
return DriverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*factory) Priority(ctx context.Context, endpoint string, api dockerclient.APIClient) int {
|
func (*factory) Priority(ctx context.Context, api dockerclient.APIClient) int {
|
||||||
if api == nil {
|
if api == nil {
|
||||||
return priorityUnsupported
|
return priorityUnsupported
|
||||||
}
|
}
|
||||||
@@ -68,9 +66,77 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
|
|||||||
clientset: clientset,
|
clientset: clientset,
|
||||||
}
|
}
|
||||||
|
|
||||||
deploymentOpt, loadbalance, namespace, err := f.processDriverOpts(deploymentName, namespace, cfg)
|
deploymentOpt := &manifest.DeploymentOpt{
|
||||||
if nil != err {
|
Name: deploymentName,
|
||||||
return nil, err
|
Image: bkimage.DefaultImage,
|
||||||
|
Replicas: 1,
|
||||||
|
BuildkitFlags: cfg.BuildkitFlags,
|
||||||
|
Rootless: false,
|
||||||
|
Platforms: cfg.Platforms,
|
||||||
|
ConfigFiles: cfg.Files,
|
||||||
|
}
|
||||||
|
|
||||||
|
deploymentOpt.Qemu.Image = bkimage.QemuImage
|
||||||
|
|
||||||
|
loadbalance := LoadbalanceSticky
|
||||||
|
|
||||||
|
for k, v := range cfg.DriverOpts {
|
||||||
|
switch k {
|
||||||
|
case "image":
|
||||||
|
if v != "" {
|
||||||
|
deploymentOpt.Image = v
|
||||||
|
}
|
||||||
|
case "namespace":
|
||||||
|
namespace = v
|
||||||
|
case "replicas":
|
||||||
|
deploymentOpt.Replicas, err = strconv.Atoi(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case "requests.cpu":
|
||||||
|
deploymentOpt.RequestsCPU = v
|
||||||
|
case "requests.memory":
|
||||||
|
deploymentOpt.RequestsMemory = v
|
||||||
|
case "limits.cpu":
|
||||||
|
deploymentOpt.LimitsCPU = v
|
||||||
|
case "limits.memory":
|
||||||
|
deploymentOpt.LimitsMemory = v
|
||||||
|
case "rootless":
|
||||||
|
deploymentOpt.Rootless, err = strconv.ParseBool(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
deploymentOpt.Image = bkimage.DefaultRootlessImage
|
||||||
|
case "nodeselector":
|
||||||
|
kvs := strings.Split(strings.Trim(v, `"`), ",")
|
||||||
|
s := map[string]string{}
|
||||||
|
for i := range kvs {
|
||||||
|
kv := strings.Split(kvs[i], "=")
|
||||||
|
if len(kv) == 2 {
|
||||||
|
s[kv[0]] = kv[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deploymentOpt.NodeSelector = s
|
||||||
|
case "loadbalance":
|
||||||
|
switch v {
|
||||||
|
case LoadbalanceSticky:
|
||||||
|
case LoadbalanceRandom:
|
||||||
|
default:
|
||||||
|
return nil, errors.Errorf("invalid loadbalance %q", v)
|
||||||
|
}
|
||||||
|
loadbalance = v
|
||||||
|
case "qemu.install":
|
||||||
|
deploymentOpt.Qemu.Install, err = strconv.ParseBool(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case "qemu.image":
|
||||||
|
if v != "" {
|
||||||
|
deploymentOpt.Qemu.Image = v
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, errors.Errorf("invalid driver option %s for driver %s", k, DriverName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d.deployment, d.configMaps, err = manifest.NewDeployment(deploymentOpt)
|
d.deployment, d.configMaps, err = manifest.NewDeployment(deploymentOpt)
|
||||||
@@ -100,121 +166,6 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
|
|||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg driver.InitConfig) (*manifest.DeploymentOpt, string, string, error) {
|
|
||||||
deploymentOpt := &manifest.DeploymentOpt{
|
|
||||||
Name: deploymentName,
|
|
||||||
Image: bkimage.DefaultImage,
|
|
||||||
Replicas: 1,
|
|
||||||
BuildkitFlags: cfg.BuildkitFlags,
|
|
||||||
Rootless: false,
|
|
||||||
Platforms: cfg.Platforms,
|
|
||||||
ConfigFiles: cfg.Files,
|
|
||||||
}
|
|
||||||
|
|
||||||
deploymentOpt.Qemu.Image = bkimage.QemuImage
|
|
||||||
|
|
||||||
loadbalance := LoadbalanceSticky
|
|
||||||
var err error
|
|
||||||
|
|
||||||
for k, v := range cfg.DriverOpts {
|
|
||||||
switch k {
|
|
||||||
case "image":
|
|
||||||
if v != "" {
|
|
||||||
deploymentOpt.Image = v
|
|
||||||
}
|
|
||||||
case "namespace":
|
|
||||||
namespace = v
|
|
||||||
case "replicas":
|
|
||||||
deploymentOpt.Replicas, err = strconv.Atoi(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", "", err
|
|
||||||
}
|
|
||||||
case "requests.cpu":
|
|
||||||
deploymentOpt.RequestsCPU = v
|
|
||||||
case "requests.memory":
|
|
||||||
deploymentOpt.RequestsMemory = v
|
|
||||||
case "limits.cpu":
|
|
||||||
deploymentOpt.LimitsCPU = v
|
|
||||||
case "limits.memory":
|
|
||||||
deploymentOpt.LimitsMemory = v
|
|
||||||
case "rootless":
|
|
||||||
deploymentOpt.Rootless, err = strconv.ParseBool(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", "", err
|
|
||||||
}
|
|
||||||
if _, isImage := cfg.DriverOpts["image"]; !isImage {
|
|
||||||
deploymentOpt.Image = bkimage.DefaultRootlessImage
|
|
||||||
}
|
|
||||||
case "nodeselector":
|
|
||||||
kvs := strings.Split(strings.Trim(v, `"`), ",")
|
|
||||||
s := map[string]string{}
|
|
||||||
for i := range kvs {
|
|
||||||
kv := strings.Split(kvs[i], "=")
|
|
||||||
if len(kv) == 2 {
|
|
||||||
s[kv[0]] = kv[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deploymentOpt.NodeSelector = s
|
|
||||||
case "tolerations":
|
|
||||||
ts := strings.Split(v, ";")
|
|
||||||
deploymentOpt.Tolerations = []corev1.Toleration{}
|
|
||||||
for i := range ts {
|
|
||||||
kvs := strings.Split(ts[i], ",")
|
|
||||||
|
|
||||||
t := corev1.Toleration{}
|
|
||||||
|
|
||||||
for j := range kvs {
|
|
||||||
kv := strings.Split(kvs[j], "=")
|
|
||||||
if len(kv) == 2 {
|
|
||||||
switch kv[0] {
|
|
||||||
case "key":
|
|
||||||
t.Key = kv[1]
|
|
||||||
case "operator":
|
|
||||||
t.Operator = corev1.TolerationOperator(kv[1])
|
|
||||||
case "value":
|
|
||||||
t.Value = kv[1]
|
|
||||||
case "effect":
|
|
||||||
t.Effect = corev1.TaintEffect(kv[1])
|
|
||||||
case "tolerationSeconds":
|
|
||||||
c, err := strconv.Atoi(kv[1])
|
|
||||||
if nil != err {
|
|
||||||
return nil, "", "", err
|
|
||||||
}
|
|
||||||
c64 := int64(c)
|
|
||||||
t.TolerationSeconds = &c64
|
|
||||||
default:
|
|
||||||
return nil, "", "", errors.Errorf("invalid tolaration %q", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deploymentOpt.Tolerations = append(deploymentOpt.Tolerations, t)
|
|
||||||
}
|
|
||||||
case "loadbalance":
|
|
||||||
switch v {
|
|
||||||
case LoadbalanceSticky:
|
|
||||||
case LoadbalanceRandom:
|
|
||||||
default:
|
|
||||||
return nil, "", "", errors.Errorf("invalid loadbalance %q", v)
|
|
||||||
}
|
|
||||||
loadbalance = v
|
|
||||||
case "qemu.install":
|
|
||||||
deploymentOpt.Qemu.Install, err = strconv.ParseBool(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", "", err
|
|
||||||
}
|
|
||||||
case "qemu.image":
|
|
||||||
if v != "" {
|
|
||||||
deploymentOpt.Qemu.Image = v
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, "", "", errors.Errorf("invalid driver option %s for driver %s", k, DriverName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return deploymentOpt, loadbalance, namespace, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *factory) AllowsInstances() bool {
|
func (f *factory) AllowsInstances() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@@ -1,230 +0,0 @@
|
|||||||
package kubernetes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/buildx/driver"
|
|
||||||
"github.com/docker/buildx/driver/bkimage"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
"k8s.io/client-go/rest"
|
|
||||||
)
|
|
||||||
|
|
||||||
type mockKubeClientConfig struct {
|
|
||||||
clientConfig *rest.Config
|
|
||||||
namespace string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *mockKubeClientConfig) ClientConfig() (*rest.Config, error) {
|
|
||||||
return r.clientConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *mockKubeClientConfig) Namespace() (string, bool, error) {
|
|
||||||
return r.namespace, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFactory_processDriverOpts(t *testing.T) {
|
|
||||||
kcc := mockKubeClientConfig{
|
|
||||||
clientConfig: &rest.Config{},
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := driver.InitConfig{
|
|
||||||
Name: "buildx_buildkit_test",
|
|
||||||
KubeClientConfig: &kcc,
|
|
||||||
}
|
|
||||||
f := factory{}
|
|
||||||
|
|
||||||
t.Run(
|
|
||||||
"ValidOptions", func(t *testing.T) {
|
|
||||||
cfg.DriverOpts = map[string]string{
|
|
||||||
"namespace": "test-ns",
|
|
||||||
"image": "test:latest",
|
|
||||||
"replicas": "2",
|
|
||||||
"requests.cpu": "100m",
|
|
||||||
"requests.memory": "32Mi",
|
|
||||||
"limits.cpu": "200m",
|
|
||||||
"limits.memory": "64Mi",
|
|
||||||
"rootless": "true",
|
|
||||||
"nodeselector": "selector1=value1,selector2=value2",
|
|
||||||
"tolerations": "key=tolerationKey1,value=tolerationValue1,operator=Equal,effect=NoSchedule,tolerationSeconds=60;key=tolerationKey2,operator=Exists",
|
|
||||||
"loadbalance": "random",
|
|
||||||
"qemu.install": "true",
|
|
||||||
"qemu.image": "qemu:latest",
|
|
||||||
}
|
|
||||||
ns := "test"
|
|
||||||
|
|
||||||
r, loadbalance, ns, err := f.processDriverOpts(cfg.Name, ns, cfg)
|
|
||||||
|
|
||||||
nodeSelectors := map[string]string{
|
|
||||||
"selector1": "value1",
|
|
||||||
"selector2": "value2",
|
|
||||||
}
|
|
||||||
|
|
||||||
ts := int64(60)
|
|
||||||
tolerations := []v1.Toleration{
|
|
||||||
{
|
|
||||||
Key: "tolerationKey1",
|
|
||||||
Operator: v1.TolerationOpEqual,
|
|
||||||
Value: "tolerationValue1",
|
|
||||||
Effect: v1.TaintEffectNoSchedule,
|
|
||||||
TolerationSeconds: &ts,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "tolerationKey2",
|
|
||||||
Operator: v1.TolerationOpExists,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "test-ns", ns)
|
|
||||||
require.Equal(t, "test:latest", r.Image)
|
|
||||||
require.Equal(t, 2, r.Replicas)
|
|
||||||
require.Equal(t, "100m", r.RequestsCPU)
|
|
||||||
require.Equal(t, "32Mi", r.RequestsMemory)
|
|
||||||
require.Equal(t, "200m", r.LimitsCPU)
|
|
||||||
require.Equal(t, "64Mi", r.LimitsMemory)
|
|
||||||
require.True(t, r.Rootless)
|
|
||||||
require.Equal(t, nodeSelectors, r.NodeSelector)
|
|
||||||
require.Equal(t, tolerations, r.Tolerations)
|
|
||||||
require.Equal(t, LoadbalanceRandom, loadbalance)
|
|
||||||
require.True(t, r.Qemu.Install)
|
|
||||||
require.Equal(t, "qemu:latest", r.Qemu.Image)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
t.Run(
|
|
||||||
"NoOptions", func(t *testing.T) {
|
|
||||||
cfg.DriverOpts = map[string]string{}
|
|
||||||
|
|
||||||
r, loadbalance, ns, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "test", ns)
|
|
||||||
require.Equal(t, bkimage.DefaultImage, r.Image)
|
|
||||||
require.Equal(t, 1, r.Replicas)
|
|
||||||
require.Equal(t, "", r.RequestsCPU)
|
|
||||||
require.Equal(t, "", r.RequestsMemory)
|
|
||||||
require.Equal(t, "", r.LimitsCPU)
|
|
||||||
require.Equal(t, "", r.LimitsMemory)
|
|
||||||
require.False(t, r.Rootless)
|
|
||||||
require.Empty(t, r.NodeSelector)
|
|
||||||
require.Empty(t, r.Tolerations)
|
|
||||||
require.Equal(t, LoadbalanceSticky, loadbalance)
|
|
||||||
require.False(t, r.Qemu.Install)
|
|
||||||
require.Equal(t, bkimage.QemuImage, r.Qemu.Image)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
t.Run(
|
|
||||||
"RootlessOverride", func(t *testing.T) {
|
|
||||||
cfg.DriverOpts = map[string]string{
|
|
||||||
"rootless": "true",
|
|
||||||
"loadbalance": "sticky",
|
|
||||||
}
|
|
||||||
|
|
||||||
r, loadbalance, ns, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "test", ns)
|
|
||||||
require.Equal(t, bkimage.DefaultRootlessImage, r.Image)
|
|
||||||
require.Equal(t, 1, r.Replicas)
|
|
||||||
require.Equal(t, "", r.RequestsCPU)
|
|
||||||
require.Equal(t, "", r.RequestsMemory)
|
|
||||||
require.Equal(t, "", r.LimitsCPU)
|
|
||||||
require.Equal(t, "", r.LimitsMemory)
|
|
||||||
require.True(t, r.Rootless)
|
|
||||||
require.Empty(t, r.NodeSelector)
|
|
||||||
require.Empty(t, r.Tolerations)
|
|
||||||
require.Equal(t, LoadbalanceSticky, loadbalance)
|
|
||||||
require.False(t, r.Qemu.Install)
|
|
||||||
require.Equal(t, bkimage.QemuImage, r.Qemu.Image)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
t.Run(
|
|
||||||
"InvalidReplicas", func(t *testing.T) {
|
|
||||||
cfg.DriverOpts = map[string]string{
|
|
||||||
"replicas": "invalid",
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
|
||||||
|
|
||||||
require.Error(t, err)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
t.Run(
|
|
||||||
"InvalidRootless", func(t *testing.T) {
|
|
||||||
cfg.DriverOpts = map[string]string{
|
|
||||||
"rootless": "invalid",
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
|
||||||
|
|
||||||
require.Error(t, err)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
t.Run(
|
|
||||||
"InvalidTolerationKeyword", func(t *testing.T) {
|
|
||||||
cfg.DriverOpts = map[string]string{
|
|
||||||
"tolerations": "key=foo,value=bar,invalid=foo2",
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
|
||||||
|
|
||||||
require.Error(t, err)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
t.Run(
|
|
||||||
"InvalidTolerationSeconds", func(t *testing.T) {
|
|
||||||
cfg.DriverOpts = map[string]string{
|
|
||||||
"tolerations": "key=foo,value=bar,tolerationSeconds=invalid",
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
|
||||||
|
|
||||||
require.Error(t, err)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
t.Run(
|
|
||||||
"InvalidLoadBalance", func(t *testing.T) {
|
|
||||||
cfg.DriverOpts = map[string]string{
|
|
||||||
"loadbalance": "invalid",
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
|
||||||
|
|
||||||
require.Error(t, err)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
t.Run(
|
|
||||||
"InvalidQemuInstall", func(t *testing.T) {
|
|
||||||
cfg.DriverOpts = map[string]string{
|
|
||||||
"qemu.install": "invalid",
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
|
||||||
|
|
||||||
require.Error(t, err)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
t.Run(
|
|
||||||
"InvalidOption", func(t *testing.T) {
|
|
||||||
cfg.DriverOpts = map[string]string{
|
|
||||||
"invalid": "foo",
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
|
||||||
|
|
||||||
require.Error(t, err)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
@@ -32,7 +32,6 @@ type DeploymentOpt struct {
|
|||||||
|
|
||||||
Rootless bool
|
Rootless bool
|
||||||
NodeSelector map[string]string
|
NodeSelector map[string]string
|
||||||
Tolerations []corev1.Toleration
|
|
||||||
RequestsCPU string
|
RequestsCPU string
|
||||||
RequestsMemory string
|
RequestsMemory string
|
||||||
LimitsCPU string
|
LimitsCPU string
|
||||||
@@ -160,10 +159,6 @@ func NewDeployment(opt *DeploymentOpt) (d *appsv1.Deployment, c []*corev1.Config
|
|||||||
d.Spec.Template.Spec.NodeSelector = opt.NodeSelector
|
d.Spec.Template.Spec.NodeSelector = opt.NodeSelector
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(opt.Tolerations) > 0 {
|
|
||||||
d.Spec.Template.Spec.Tolerations = opt.Tolerations
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt.RequestsCPU != "" {
|
if opt.RequestsCPU != "" {
|
||||||
reqCPU, err := resource.ParseQuantity(opt.RequestsCPU)
|
reqCPU, err := resource.ParseQuantity(opt.RequestsCPU)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -204,15 +199,12 @@ func toRootless(d *appsv1.Deployment) error {
|
|||||||
d.Spec.Template.Spec.Containers[0].Args,
|
d.Spec.Template.Spec.Containers[0].Args,
|
||||||
"--oci-worker-no-process-sandbox",
|
"--oci-worker-no-process-sandbox",
|
||||||
)
|
)
|
||||||
d.Spec.Template.Spec.Containers[0].SecurityContext = &corev1.SecurityContext{
|
d.Spec.Template.Spec.Containers[0].SecurityContext = nil
|
||||||
SeccompProfile: &corev1.SeccompProfile{
|
|
||||||
Type: corev1.SeccompProfileTypeUnconfined,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if d.Spec.Template.ObjectMeta.Annotations == nil {
|
if d.Spec.Template.ObjectMeta.Annotations == nil {
|
||||||
d.Spec.Template.ObjectMeta.Annotations = make(map[string]string, 1)
|
d.Spec.Template.ObjectMeta.Annotations = make(map[string]string, 2)
|
||||||
}
|
}
|
||||||
d.Spec.Template.ObjectMeta.Annotations["container.apparmor.security.beta.kubernetes.io/"+containerName] = "unconfined"
|
d.Spec.Template.ObjectMeta.Annotations["container.apparmor.security.beta.kubernetes.io/"+containerName] = "unconfined"
|
||||||
|
d.Spec.Template.ObjectMeta.Annotations["container.seccomp.security.alpha.kubernetes.io/"+containerName] = "unconfined"
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,7 +6,6 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/serialx/hashring"
|
"github.com/serialx/hashring"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
@@ -30,9 +29,6 @@ func (pc *RandomPodChooser) ChoosePod(ctx context.Context) (*corev1.Pod, error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(pods) == 0 {
|
|
||||||
return nil, errors.New("no running buildkit pods found")
|
|
||||||
}
|
|
||||||
randSource := pc.RandSource
|
randSource := pc.RandSource
|
||||||
if randSource == nil {
|
if randSource == nil {
|
||||||
randSource = rand.NewSource(time.Now().Unix())
|
randSource = rand.NewSource(time.Now().Unix())
|
||||||
|
@@ -2,7 +2,7 @@ package driver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
"io/ioutil"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
type Factory interface {
|
type Factory interface {
|
||||||
Name() string
|
Name() string
|
||||||
Usage() string
|
Usage() string
|
||||||
Priority(ctx context.Context, endpoint string, api dockerclient.APIClient) int
|
Priority(context.Context, dockerclient.APIClient) int
|
||||||
New(ctx context.Context, cfg InitConfig) (Driver, error)
|
New(ctx context.Context, cfg InitConfig) (Driver, error)
|
||||||
AllowsInstances() bool
|
AllowsInstances() bool
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ func (k KubeClientConfigInCluster) ClientConfig() (*rest.Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (k KubeClientConfigInCluster) Namespace() (string, bool, error) {
|
func (k KubeClientConfigInCluster) Namespace() (string, bool, error) {
|
||||||
namespace, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
|
namespace, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false, err
|
return "", false, err
|
||||||
}
|
}
|
||||||
@@ -50,7 +50,6 @@ func (k KubeClientConfigInCluster) Namespace() (string, bool, error) {
|
|||||||
type InitConfig struct {
|
type InitConfig struct {
|
||||||
// This object needs updates to be generic for different drivers
|
// This object needs updates to be generic for different drivers
|
||||||
Name string
|
Name string
|
||||||
EndpointAddr string
|
|
||||||
DockerAPI dockerclient.APIClient
|
DockerAPI dockerclient.APIClient
|
||||||
KubeClientConfig KubeClientConfig
|
KubeClientConfig KubeClientConfig
|
||||||
BuildkitFlags []string
|
BuildkitFlags []string
|
||||||
@@ -71,7 +70,7 @@ func Register(f Factory) {
|
|||||||
drivers[f.Name()] = f
|
drivers[f.Name()] = f
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultFactory(ctx context.Context, ep string, c dockerclient.APIClient, instanceRequired bool) (Factory, error) {
|
func GetDefaultFactory(ctx context.Context, c dockerclient.APIClient, instanceRequired bool) (Factory, error) {
|
||||||
if len(drivers) == 0 {
|
if len(drivers) == 0 {
|
||||||
return nil, errors.Errorf("no drivers available")
|
return nil, errors.Errorf("no drivers available")
|
||||||
}
|
}
|
||||||
@@ -84,7 +83,7 @@ func GetDefaultFactory(ctx context.Context, ep string, c dockerclient.APIClient,
|
|||||||
if instanceRequired && !f.AllowsInstances() {
|
if instanceRequired && !f.AllowsInstances() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dd = append(dd, p{f: f, priority: f.Priority(ctx, ep, c)})
|
dd = append(dd, p{f: f, priority: f.Priority(ctx, c)})
|
||||||
}
|
}
|
||||||
sort.Slice(dd, func(i, j int) bool {
|
sort.Slice(dd, func(i, j int) bool {
|
||||||
return dd[i].priority < dd[j].priority
|
return dd[i].priority < dd[j].priority
|
||||||
@@ -104,9 +103,8 @@ func GetFactory(name string, instanceRequired bool) Factory {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDriver(ctx context.Context, name string, f Factory, endpointAddr string, api dockerclient.APIClient, auth Auth, kcc KubeClientConfig, flags []string, files map[string][]byte, do map[string]string, platforms []specs.Platform, contextPathHash string) (Driver, error) {
|
func GetDriver(ctx context.Context, name string, f Factory, api dockerclient.APIClient, auth Auth, kcc KubeClientConfig, flags []string, files map[string][]byte, do map[string]string, platforms []specs.Platform, contextPathHash string) (Driver, error) {
|
||||||
ic := InitConfig{
|
ic := InitConfig{
|
||||||
EndpointAddr: endpointAddr,
|
|
||||||
DockerAPI: api,
|
DockerAPI: api,
|
||||||
KubeClientConfig: kcc,
|
KubeClientConfig: kcc,
|
||||||
Name: name,
|
Name: name,
|
||||||
@@ -119,7 +117,7 @@ func GetDriver(ctx context.Context, name string, f Factory, endpointAddr string,
|
|||||||
}
|
}
|
||||||
if f == nil {
|
if f == nil {
|
||||||
var err error
|
var err error
|
||||||
f, err = GetDefaultFactory(ctx, endpointAddr, api, false)
|
f, err = GetDefaultFactory(ctx, api, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -1,106 +0,0 @@
|
|||||||
package remote
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/buildx/driver"
|
|
||||||
"github.com/docker/buildx/util/progress"
|
|
||||||
"github.com/moby/buildkit/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Driver struct {
|
|
||||||
factory driver.Factory
|
|
||||||
driver.InitConfig
|
|
||||||
*tlsOpts
|
|
||||||
}
|
|
||||||
|
|
||||||
type tlsOpts struct {
|
|
||||||
serverName string
|
|
||||||
caCert string
|
|
||||||
cert string
|
|
||||||
key string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error {
|
|
||||||
for i := 0; ; i++ {
|
|
||||||
info, err := d.Info(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if info.Status != driver.Inactive {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
default:
|
|
||||||
if i > 10 {
|
|
||||||
i = 10
|
|
||||||
}
|
|
||||||
time.Sleep(time.Duration(i) * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
|
|
||||||
c, err := d.Client(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return &driver.Info{
|
|
||||||
Status: driver.Inactive,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := c.ListWorkers(ctx); err != nil {
|
|
||||||
return &driver.Info{
|
|
||||||
Status: driver.Inactive,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &driver.Info{
|
|
||||||
Status: driver.Running,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) Version(ctx context.Context) (string, error) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) Stop(ctx context.Context, force bool) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
|
||||||
opts := []client.ClientOpt{}
|
|
||||||
if d.tlsOpts != nil {
|
|
||||||
opts = append(opts, client.WithCredentials(d.tlsOpts.serverName, d.tlsOpts.caCert, d.tlsOpts.cert, d.tlsOpts.key))
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.New(ctx, d.InitConfig.EndpointAddr, opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) Features() map[driver.Feature]bool {
|
|
||||||
return map[driver.Feature]bool{
|
|
||||||
driver.OCIExporter: true,
|
|
||||||
driver.DockerExporter: false,
|
|
||||||
driver.CacheExport: true,
|
|
||||||
driver.MultiPlatform: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) Factory() driver.Factory {
|
|
||||||
return d.factory
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) IsMobyDriver() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) Config() driver.InitConfig {
|
|
||||||
return d.InitConfig
|
|
||||||
}
|
|
@@ -1,118 +0,0 @@
|
|||||||
package remote
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/url"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
// import connhelpers for special url schemes
|
|
||||||
_ "github.com/moby/buildkit/client/connhelper/dockercontainer"
|
|
||||||
_ "github.com/moby/buildkit/client/connhelper/kubepod"
|
|
||||||
_ "github.com/moby/buildkit/client/connhelper/ssh"
|
|
||||||
|
|
||||||
"github.com/docker/buildx/driver"
|
|
||||||
util "github.com/docker/buildx/driver/remote/util"
|
|
||||||
dockerclient "github.com/docker/docker/client"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const prioritySupported = 20
|
|
||||||
const priorityUnsupported = 90
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
driver.Register(&factory{})
|
|
||||||
}
|
|
||||||
|
|
||||||
type factory struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*factory) Name() string {
|
|
||||||
return "remote"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*factory) Usage() string {
|
|
||||||
return "remote"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*factory) Priority(ctx context.Context, endpoint string, api dockerclient.APIClient) int {
|
|
||||||
if util.IsValidEndpoint(endpoint) != nil {
|
|
||||||
return priorityUnsupported
|
|
||||||
}
|
|
||||||
return prioritySupported
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver, error) {
|
|
||||||
if len(cfg.Files) > 0 {
|
|
||||||
return nil, errors.Errorf("setting config file is not supported for remote driver")
|
|
||||||
}
|
|
||||||
if len(cfg.BuildkitFlags) > 0 {
|
|
||||||
return nil, errors.Errorf("setting buildkit flags is not supported for remote driver")
|
|
||||||
}
|
|
||||||
|
|
||||||
d := &Driver{
|
|
||||||
factory: f,
|
|
||||||
InitConfig: cfg,
|
|
||||||
}
|
|
||||||
|
|
||||||
tls := &tlsOpts{}
|
|
||||||
tlsEnabled := false
|
|
||||||
for k, v := range cfg.DriverOpts {
|
|
||||||
switch k {
|
|
||||||
case "servername":
|
|
||||||
tls.serverName = v
|
|
||||||
tlsEnabled = true
|
|
||||||
case "cacert":
|
|
||||||
if !filepath.IsAbs(v) {
|
|
||||||
return nil, errors.Errorf("non-absolute path '%s' provided for %s", v, k)
|
|
||||||
}
|
|
||||||
tls.caCert = v
|
|
||||||
tlsEnabled = true
|
|
||||||
case "cert":
|
|
||||||
if !filepath.IsAbs(v) {
|
|
||||||
return nil, errors.Errorf("non-absolute path '%s' provided for %s", v, k)
|
|
||||||
}
|
|
||||||
tls.cert = v
|
|
||||||
tlsEnabled = true
|
|
||||||
case "key":
|
|
||||||
if !filepath.IsAbs(v) {
|
|
||||||
return nil, errors.Errorf("non-absolute path '%s' provided for %s", v, k)
|
|
||||||
}
|
|
||||||
tls.key = v
|
|
||||||
tlsEnabled = true
|
|
||||||
default:
|
|
||||||
return nil, errors.Errorf("invalid driver option %s for remote driver", k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tlsEnabled {
|
|
||||||
if tls.serverName == "" {
|
|
||||||
// guess servername as hostname of target address
|
|
||||||
uri, err := url.Parse(cfg.EndpointAddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tls.serverName = uri.Hostname()
|
|
||||||
}
|
|
||||||
missing := []string{}
|
|
||||||
if tls.caCert == "" {
|
|
||||||
missing = append(missing, "cacert")
|
|
||||||
}
|
|
||||||
if tls.cert == "" {
|
|
||||||
missing = append(missing, "cert")
|
|
||||||
}
|
|
||||||
if tls.key == "" {
|
|
||||||
missing = append(missing, "key")
|
|
||||||
}
|
|
||||||
if len(missing) > 0 {
|
|
||||||
return nil, errors.Errorf("tls enabled, but missing keys %s", strings.Join(missing, ", "))
|
|
||||||
}
|
|
||||||
d.tlsOpts = tls
|
|
||||||
}
|
|
||||||
|
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *factory) AllowsInstances() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
@@ -1,26 +0,0 @@
|
|||||||
package remote
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
var schemes = map[string]struct{}{
|
|
||||||
"tcp": {},
|
|
||||||
"unix": {},
|
|
||||||
"ssh": {},
|
|
||||||
"docker-container": {},
|
|
||||||
"kube-pod": {},
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsValidEndpoint(ep string) error {
|
|
||||||
endpoint, err := url.Parse(ep)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to parse endpoint %s", ep)
|
|
||||||
}
|
|
||||||
if _, ok := schemes[endpoint.Scheme]; !ok {
|
|
||||||
return errors.Errorf("unrecognized url scheme %s", endpoint.Scheme)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
170
go.mod
170
go.mod
@@ -1,162 +1,64 @@
|
|||||||
module github.com/docker/buildx
|
module github.com/docker/buildx
|
||||||
|
|
||||||
go 1.17
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/compose-spec/compose-go v1.3.0
|
|
||||||
github.com/containerd/console v1.0.3
|
|
||||||
github.com/containerd/containerd v1.6.6
|
|
||||||
github.com/docker/cli v20.10.17+incompatible // v22.06.x - see "replace" for the actual version
|
|
||||||
github.com/docker/cli-docs-tool v0.5.0
|
|
||||||
github.com/docker/distribution v2.8.1+incompatible
|
|
||||||
github.com/docker/docker v20.10.17+incompatible // v22.06.x - see "replace" for the actual version
|
|
||||||
github.com/docker/go-units v0.4.0
|
|
||||||
github.com/gofrs/flock v0.7.3
|
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
|
||||||
github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840
|
|
||||||
github.com/hashicorp/hcl/v2 v2.8.2
|
|
||||||
github.com/moby/buildkit v0.10.1-0.20220721175135-c75998aec3d4
|
|
||||||
github.com/morikuni/aec v1.0.0
|
|
||||||
github.com/opencontainers/go-digest v1.0.0
|
|
||||||
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799
|
|
||||||
github.com/pelletier/go-toml v1.9.4
|
|
||||||
github.com/pkg/errors v0.9.1
|
|
||||||
github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002
|
|
||||||
github.com/sirupsen/logrus v1.9.0
|
|
||||||
github.com/spf13/cobra v1.5.0
|
|
||||||
github.com/spf13/pflag v1.0.5
|
|
||||||
github.com/stretchr/testify v1.8.0
|
|
||||||
github.com/zclconf/go-cty v1.10.0
|
|
||||||
go.opentelemetry.io/otel v1.4.1
|
|
||||||
go.opentelemetry.io/otel/trace v1.4.1
|
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
|
||||||
google.golang.org/grpc v1.45.0
|
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
|
||||||
k8s.io/api v0.23.5
|
|
||||||
k8s.io/apimachinery v0.23.5
|
|
||||||
k8s.io/client-go v0.23.5
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
cloud.google.com/go v0.81.0 // indirect
|
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
|
||||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
|
||||||
github.com/Azure/go-autorest/autorest v0.11.24 // indirect
|
|
||||||
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
|
|
||||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
|
||||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
|
||||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
|
||||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
|
||||||
github.com/agext/levenshtein v1.2.3 // indirect
|
|
||||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
|
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
|
||||||
github.com/apparentlymart/go-cidr v1.0.1 // indirect
|
|
||||||
github.com/apparentlymart/go-textseg/v12 v12.0.0 // indirect
|
|
||||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
|
||||||
github.com/bugsnag/bugsnag-go v1.4.1 // indirect
|
github.com/bugsnag/bugsnag-go v1.4.1 // indirect
|
||||||
github.com/bugsnag/panicwrap v1.2.0 // indirect
|
github.com/bugsnag/panicwrap v1.2.0 // indirect
|
||||||
github.com/cenkalti/backoff v2.1.1+incompatible // indirect
|
github.com/cenkalti/backoff v2.1.1+incompatible // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.1.2 // indirect
|
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
|
||||||
github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e // indirect
|
github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e // indirect
|
||||||
github.com/containerd/continuity v0.3.0 // indirect
|
github.com/compose-spec/compose-go v1.2.1
|
||||||
github.com/containerd/ttrpc v1.1.0 // indirect
|
github.com/containerd/console v1.0.3
|
||||||
github.com/containerd/typeurl v1.0.2 // indirect
|
github.com/containerd/containerd v1.6.1
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/docker/cli v20.10.12+incompatible
|
||||||
github.com/distribution/distribution/v3 v3.0.0-20220725133111-4bf3547399eb // indirect
|
github.com/docker/cli-docs-tool v0.4.0
|
||||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
github.com/docker/distribution v2.8.0+incompatible
|
||||||
|
github.com/docker/docker v20.10.7+incompatible
|
||||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
github.com/docker/go-units v0.4.0
|
||||||
github.com/docker/go-metrics v0.0.1 // indirect
|
|
||||||
github.com/docker/libtrust v0.0.0-20150526203908-9cbd2a1374f4 // indirect
|
github.com/docker/libtrust v0.0.0-20150526203908-9cbd2a1374f4 // indirect
|
||||||
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484 // indirect
|
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.2 // indirect
|
|
||||||
github.com/fvbommel/sortorder v1.0.1 // indirect
|
github.com/fvbommel/sortorder v1.0.1 // indirect
|
||||||
github.com/go-logr/logr v1.2.2 // indirect
|
github.com/gofrs/flock v0.7.3
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/gofrs/uuid v3.3.0+incompatible // indirect
|
||||||
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
|
||||||
github.com/gogo/googleapis v1.4.1 // indirect
|
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
|
||||||
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
|
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
|
||||||
github.com/google/certificate-transparency-go v1.0.21 // indirect
|
github.com/google/certificate-transparency-go v1.0.21 // indirect
|
||||||
github.com/google/go-cmp v0.5.8 // indirect
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
github.com/google/gofuzz v1.2.0 // indirect
|
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
|
||||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
|
||||||
github.com/gorilla/mux v1.8.0 // indirect
|
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
|
||||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
|
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
|
||||||
github.com/imdario/mergo v0.3.13 // indirect
|
github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/hashicorp/hcl/v2 v2.8.2
|
||||||
github.com/jinzhu/gorm v1.9.2 // indirect
|
github.com/jinzhu/gorm v1.9.2 // indirect
|
||||||
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect
|
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||||
github.com/klauspost/compress v1.15.1 // indirect
|
github.com/moby/buildkit v0.10.1-0.20220403220257-10e6f94bf90d
|
||||||
github.com/kr/pretty v0.3.0 // indirect
|
github.com/morikuni/aec v1.0.0
|
||||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5
|
||||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
github.com/pelletier/go-toml v1.9.4
|
||||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002
|
||||||
github.com/moby/locker v1.0.1 // indirect
|
github.com/sirupsen/logrus v1.8.1
|
||||||
github.com/moby/spdystream v0.2.0 // indirect
|
github.com/spf13/cobra v1.2.1
|
||||||
github.com/moby/sys/signal v0.6.0 // indirect
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
|
||||||
github.com/opencontainers/runc v1.1.3 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/prometheus/client_golang v1.12.1 // indirect
|
|
||||||
github.com/prometheus/client_model v0.2.0 // indirect
|
|
||||||
github.com/prometheus/common v0.32.1 // indirect
|
|
||||||
github.com/prometheus/procfs v0.7.3 // indirect
|
|
||||||
github.com/rogpeppe/go-internal v1.8.1 // indirect
|
|
||||||
github.com/theupdateframework/notary v0.6.1 // indirect
|
github.com/theupdateframework/notary v0.6.1 // indirect
|
||||||
github.com/tonistiigi/fsutil v0.0.0-20220510150904-0dbf3a8a7d58 // indirect
|
github.com/tonistiigi/fsutil v0.0.0-20220315205639-9ed612626da3 // indirect
|
||||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
github.com/zclconf/go-cty v1.10.0
|
||||||
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect
|
go.opentelemetry.io/otel v1.4.1
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
go.opentelemetry.io/otel/trace v1.4.1
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
google.golang.org/grpc v1.44.0
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.29.0 // indirect
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.29.0 // indirect
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1 // indirect
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 // indirect
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.1 // indirect
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.4.1 // indirect
|
|
||||||
go.opentelemetry.io/otel/internal/metric v0.27.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/metric v0.27.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/sdk v1.4.1 // indirect
|
|
||||||
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
|
|
||||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
|
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
|
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
|
||||||
golang.org/x/text v0.3.7 // indirect
|
|
||||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
|
||||||
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect
|
|
||||||
google.golang.org/protobuf v1.27.1 // indirect
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
|
||||||
gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect
|
gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect
|
||||||
gopkg.in/fatih/pool.v2 v2.0.0 // indirect
|
gopkg.in/fatih/pool.v2 v2.0.0 // indirect
|
||||||
gopkg.in/gorethink/gorethink.v3 v3.0.5 // indirect
|
gopkg.in/gorethink/gorethink.v3 v3.0.5 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
k8s.io/api v0.23.4
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
k8s.io/apimachinery v0.23.4
|
||||||
k8s.io/klog/v2 v2.30.0 // indirect
|
k8s.io/client-go v0.23.4
|
||||||
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect
|
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
|
|
||||||
sigs.k8s.io/yaml v1.2.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
github.com/docker/cli => github.com/docker/cli v20.10.3-0.20220721163225-f1615facb1ca+incompatible // master (v22.06-dev)
|
github.com/docker/cli => github.com/docker/cli v20.10.3-0.20220226190722-8667ccd1124c+incompatible
|
||||||
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220720171342-a60b458179aa+incompatible // 22.06 branch (v22.06-dev)
|
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220121014307-40bb9831756f+incompatible
|
||||||
k8s.io/api => k8s.io/api v0.22.4
|
k8s.io/api => k8s.io/api v0.22.4
|
||||||
k8s.io/apimachinery => k8s.io/apimachinery v0.22.4
|
k8s.io/apimachinery => k8s.io/apimachinery v0.22.4
|
||||||
k8s.io/client-go => k8s.io/client-go v0.22.4
|
k8s.io/client-go => k8s.io/client-go v0.22.4
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
# syntax=docker/dockerfile:1.4
|
# syntax=docker/dockerfile:1.3-labs
|
||||||
|
|
||||||
FROM alpine:3.14 AS gen
|
FROM alpine:3.14 AS gen
|
||||||
RUN apk add --no-cache git
|
RUN apk add --no-cache git
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:1.4
|
# syntax=docker/dockerfile:1.3-labs
|
||||||
|
|
||||||
ARG GO_VERSION=1.18
|
ARG GO_VERSION=1.17
|
||||||
ARG FORMATS=md,yaml
|
ARG FORMATS=md,yaml
|
||||||
|
|
||||||
FROM golang:${GO_VERSION}-alpine AS docsgen
|
FROM golang:${GO_VERSION}-alpine AS docsgen
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
# syntax=docker/dockerfile:1.4
|
# syntax=docker/dockerfile:1.3
|
||||||
|
|
||||||
ARG GO_VERSION=1.18
|
ARG GO_VERSION=1.17
|
||||||
|
|
||||||
FROM golang:${GO_VERSION}-alpine
|
FROM golang:${GO_VERSION}-alpine
|
||||||
RUN apk add --no-cache git gcc musl-dev
|
RUN apk add --no-cache gcc musl-dev yamllint
|
||||||
RUN wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.45.2
|
RUN wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.36.0
|
||||||
WORKDIR /go/src/github.com/docker/buildx
|
WORKDIR /go/src/github.com/docker/buildx
|
||||||
RUN --mount=target=/go/src/github.com/docker/buildx --mount=target=/root/.cache,type=cache \
|
RUN --mount=target=/go/src/github.com/docker/buildx --mount=target=/root/.cache,type=cache \
|
||||||
golangci-lint run
|
golangci-lint run
|
||||||
|
RUN --mount=target=/go/src/github.com/docker/buildx --mount=target=/root/.cache,type=cache \
|
||||||
|
yamllint -c .yamllint.yml --strict .
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:1.4
|
# syntax=docker/dockerfile:1.3-labs
|
||||||
|
|
||||||
ARG GO_VERSION=1.18
|
ARG GO_VERSION=1.17
|
||||||
ARG MODOUTDATED_VERSION=v0.8.0
|
ARG MODOUTDATED_VERSION=v0.8.0
|
||||||
|
|
||||||
FROM golang:${GO_VERSION}-alpine AS base
|
FROM golang:${GO_VERSION}-alpine AS base
|
||||||
@@ -13,7 +13,7 @@ RUN --mount=target=/context \
|
|||||||
--mount=target=/go/pkg/mod,type=cache <<EOT
|
--mount=target=/go/pkg/mod,type=cache <<EOT
|
||||||
set -e
|
set -e
|
||||||
rsync -a /context/. .
|
rsync -a /context/. .
|
||||||
go mod tidy -compat=1.17
|
go mod tidy
|
||||||
go mod vendor
|
go mod vendor
|
||||||
mkdir /out
|
mkdir /out
|
||||||
cp -r go.mod go.sum vendor /out
|
cp -r go.mod go.sum vendor /out
|
||||||
@@ -39,6 +39,7 @@ EOT
|
|||||||
|
|
||||||
FROM psampaz/go-mod-outdated:${MODOUTDATED_VERSION} AS go-mod-outdated
|
FROM psampaz/go-mod-outdated:${MODOUTDATED_VERSION} AS go-mod-outdated
|
||||||
FROM base AS outdated
|
FROM base AS outdated
|
||||||
|
ARG _RANDOM
|
||||||
RUN --mount=target=.,ro \
|
RUN --mount=target=.,ro \
|
||||||
--mount=target=/go/pkg/mod,type=cache \
|
--mount=target=/go/pkg/mod,type=cache \
|
||||||
--mount=from=go-mod-outdated,source=/home/go-mod-outdated,target=/usr/bin/go-mod-outdated \
|
--mount=from=go-mod-outdated,source=/home/go-mod-outdated,target=/usr/bin/go-mod-outdated \
|
||||||
|
23
hack/release
23
hack/release
@@ -4,22 +4,9 @@ set -eu -o pipefail
|
|||||||
|
|
||||||
: ${BUILDX_CMD=docker buildx}
|
: ${BUILDX_CMD=docker buildx}
|
||||||
: ${RELEASE_OUT=./release-out}
|
: ${RELEASE_OUT=./release-out}
|
||||||
: ${CACHE_FROM=}
|
|
||||||
: ${CACHE_TO=}
|
|
||||||
|
|
||||||
if [ -n "$CACHE_FROM" ]; then
|
|
||||||
for cfrom in $CACHE_FROM; do
|
|
||||||
cacheFlags+=(--set "*.cache-from=$cfrom")
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
if [ -n "$CACHE_TO" ]; then
|
|
||||||
for cto in $CACHE_TO; do
|
|
||||||
cacheFlags+=(--set "*.cache-to=$cto")
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
# release
|
# release
|
||||||
(set -x ; ${BUILDX_CMD} bake "${cacheFlags[@]}" --set "*.output=$RELEASE_OUT" release)
|
(set -x ; ${BUILDX_CMD} bake --set "*.output=$RELEASE_OUT" release)
|
||||||
|
|
||||||
# wrap binaries
|
# wrap binaries
|
||||||
mv -f ./${RELEASE_OUT}/**/* ./${RELEASE_OUT}/
|
mv -f ./${RELEASE_OUT}/**/* ./${RELEASE_OUT}/
|
||||||
@@ -30,10 +17,4 @@ if ! type shasum > /dev/null 2>&1; then
|
|||||||
echo >&2 "ERROR: shasum is required"
|
echo >&2 "ERROR: shasum is required"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
find ./${RELEASE_OUT}/ -type f \( -iname "buildx-*" ! -iname "*darwin*" \) -print0 | sort -z | xargs -r0 shasum -a 256 -b | sed 's# .*/# #' > ./${RELEASE_OUT}/checksums.txt
|
find ./${RELEASE_OUT}/ -type f \( -iname "buildx-*" ! -iname "*darwin*" \) -print0 | sort -z | xargs -r0 shasum -a 256 -b | sed 's# .*/# #' > ./${RELEASE_OUT}/checksums.txt
|
||||||
|
|
||||||
# verify
|
|
||||||
(
|
|
||||||
cd ./${RELEASE_OUT}
|
|
||||||
shasum -a 256 -U -c checksums.txt
|
|
||||||
)
|
|
||||||
|
@@ -7,7 +7,6 @@ set -eu -o pipefail
|
|||||||
: ${BUILDKIT_CFG=}
|
: ${BUILDKIT_CFG=}
|
||||||
: ${DRIVER=docker-container}
|
: ${DRIVER=docker-container}
|
||||||
: ${DRIVER_OPT=}
|
: ${DRIVER_OPT=}
|
||||||
: ${ENDPOINT=}
|
|
||||||
: ${MULTI_NODE=0}
|
: ${MULTI_NODE=0}
|
||||||
: ${PLATFORMS=linux/amd64,linux/arm64}
|
: ${PLATFORMS=linux/amd64,linux/arm64}
|
||||||
|
|
||||||
@@ -35,11 +34,9 @@ else
|
|||||||
buildPlatformFlag=--platform="${PLATFORMS}"
|
buildPlatformFlag=--platform="${PLATFORMS}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$DRIVER" != "remote" ]; then
|
driverOpt=image=${BUILDKIT_IMAGE}
|
||||||
driverOpt=${driverOpt:+"${driverOpt},"}image=${BUILDKIT_IMAGE}
|
|
||||||
fi
|
|
||||||
if [ -n "$DRIVER_OPT" ]; then
|
if [ -n "$DRIVER_OPT" ]; then
|
||||||
driverOpt=${driverOpt:+"${driverOpt},"}$DRIVER_OPT
|
driverOpt=$driverOpt,$DRIVER_OPT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# create builder except for docker driver
|
# create builder except for docker driver
|
||||||
@@ -55,13 +52,11 @@ if [ "$DRIVER" != "docker" ]; then
|
|||||||
createFlags="$createFlags --append"
|
createFlags="$createFlags --append"
|
||||||
fi
|
fi
|
||||||
buildxCmd create ${createFlags} \
|
buildxCmd create ${createFlags} \
|
||||||
--bootstrap \
|
|
||||||
--name="${builderName}" \
|
--name="${builderName}" \
|
||||||
--node="${builderName}-${platform/\//-}" \
|
--node="${builderName}-${platform/\//-}" \
|
||||||
--platform="${platform}" \
|
|
||||||
--driver="${DRIVER}" \
|
--driver="${DRIVER}" \
|
||||||
${driverOpt:+"--driver-opt=${driverOpt}"} \
|
--driver-opt="${driverOpt}" \
|
||||||
${ENDPOINT}
|
--platform="${platform}"
|
||||||
firstNode=0
|
firstNode=0
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
@@ -70,12 +65,10 @@ if [ "$DRIVER" != "docker" ]; then
|
|||||||
createFlags="$createFlags --config=${BUILDKIT_CFG}"
|
createFlags="$createFlags --config=${BUILDKIT_CFG}"
|
||||||
fi
|
fi
|
||||||
buildxCmd create ${createFlags} \
|
buildxCmd create ${createFlags} \
|
||||||
--bootstrap \
|
|
||||||
--name="${builderName}" \
|
--name="${builderName}" \
|
||||||
--platform="${PLATFORMS}" \
|
|
||||||
--driver="${DRIVER}" \
|
--driver="${DRIVER}" \
|
||||||
${driverOpt:+"--driver-opt=${driverOpt}"} \
|
--driver-opt="${driverOpt}" \
|
||||||
${ENDPOINT}
|
--platform="${PLATFORMS}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@@ -1,486 +0,0 @@
|
|||||||
package monitor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/docker/buildx/build"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"golang.org/x/term"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RunMonitor provides an interactive session for running and managing containers via specified IO.
|
|
||||||
func RunMonitor(ctx context.Context, containerConfig build.ContainerConfig, reloadFunc func(context.Context) (*build.ResultContext, error), stdin io.ReadCloser, stdout, stderr io.WriteCloser) error {
|
|
||||||
monitorIn, monitorOut := ioSetPipe()
|
|
||||||
defer monitorIn.Close()
|
|
||||||
monitorEnableCh := make(chan struct{})
|
|
||||||
monitorDisableCh := make(chan struct{})
|
|
||||||
monitorOutCtx := ioSetOutContext{monitorOut,
|
|
||||||
func() { monitorEnableCh <- struct{}{} },
|
|
||||||
func() { monitorDisableCh <- struct{}{} },
|
|
||||||
}
|
|
||||||
|
|
||||||
containerIn, containerOut := ioSetPipe()
|
|
||||||
defer containerIn.Close()
|
|
||||||
containerOutCtx := ioSetOutContext{containerOut,
|
|
||||||
// send newline to hopefully get the prompt; TODO: better UI (e.g. reprinting the last line)
|
|
||||||
func() { containerOut.stdin.Write([]byte("\n")) },
|
|
||||||
func() {},
|
|
||||||
}
|
|
||||||
|
|
||||||
m := &monitor{
|
|
||||||
invokeIO: newIOForwarder(containerIn),
|
|
||||||
muxIO: newMuxIO(ioSetIn{stdin, stdout, stderr}, []ioSetOutContext{monitorOutCtx, containerOutCtx}, 1, "Switched IO\n"),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start container automatically
|
|
||||||
go func() {
|
|
||||||
m.rollback(ctx, containerConfig)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Serve monitor commands
|
|
||||||
monitorForwarder := newIOForwarder(monitorIn)
|
|
||||||
for {
|
|
||||||
<-monitorEnableCh
|
|
||||||
in, out := ioSetPipe()
|
|
||||||
monitorForwarder.setDestination(&out)
|
|
||||||
doneCh, errCh := make(chan struct{}), make(chan error)
|
|
||||||
go func() {
|
|
||||||
defer close(doneCh)
|
|
||||||
defer in.Close()
|
|
||||||
t := term.NewTerminal(readWriter{in.stdin, in.stdout}, "(buildx) ")
|
|
||||||
for {
|
|
||||||
l, err := t.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
if err != io.EOF {
|
|
||||||
errCh <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch l {
|
|
||||||
case "":
|
|
||||||
// nop
|
|
||||||
case "reload":
|
|
||||||
res, err := reloadFunc(ctx)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("failed to reload: %v\n", err)
|
|
||||||
} else {
|
|
||||||
// rollback the running container with the new result
|
|
||||||
containerConfig.ResultCtx = res
|
|
||||||
m.rollback(ctx, containerConfig)
|
|
||||||
}
|
|
||||||
case "rollback":
|
|
||||||
m.rollback(ctx, containerConfig)
|
|
||||||
case "exit":
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
fmt.Printf("unknown command: %q\n", l)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-doneCh:
|
|
||||||
return nil
|
|
||||||
case err := <-errCh:
|
|
||||||
return err
|
|
||||||
case <-monitorDisableCh:
|
|
||||||
}
|
|
||||||
monitorForwarder.setDestination(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type readWriter struct {
|
|
||||||
io.Reader
|
|
||||||
io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
type monitor struct {
|
|
||||||
muxIO *muxIO
|
|
||||||
invokeIO *ioForwarder
|
|
||||||
curInvokeCancel func()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *monitor) rollback(ctx context.Context, cfg build.ContainerConfig) {
|
|
||||||
if m.curInvokeCancel != nil {
|
|
||||||
m.curInvokeCancel() // Finish the running container if exists
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
// Start a new container
|
|
||||||
if err := m.invoke(ctx, cfg); err != nil {
|
|
||||||
logrus.Debugf("invoke error: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *monitor) invoke(ctx context.Context, cfg build.ContainerConfig) error {
|
|
||||||
m.muxIO.enable(1)
|
|
||||||
defer m.muxIO.disable(1)
|
|
||||||
invokeCtx, invokeCancel := context.WithCancel(ctx)
|
|
||||||
|
|
||||||
containerIn, containerOut := ioSetPipe()
|
|
||||||
m.invokeIO.setDestination(&containerOut)
|
|
||||||
waitInvokeDoneCh := make(chan struct{})
|
|
||||||
var cancelOnce sync.Once
|
|
||||||
curInvokeCancel := func() {
|
|
||||||
cancelOnce.Do(func() {
|
|
||||||
containerIn.Close()
|
|
||||||
m.invokeIO.setDestination(nil)
|
|
||||||
invokeCancel()
|
|
||||||
})
|
|
||||||
<-waitInvokeDoneCh
|
|
||||||
}
|
|
||||||
defer curInvokeCancel()
|
|
||||||
m.curInvokeCancel = curInvokeCancel
|
|
||||||
|
|
||||||
cfg.Stdin = containerIn.stdin
|
|
||||||
cfg.Stdout = containerIn.stdout
|
|
||||||
cfg.Stderr = containerIn.stderr
|
|
||||||
err := build.Invoke(invokeCtx, cfg)
|
|
||||||
close(waitInvokeDoneCh)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type ioForwarder struct {
|
|
||||||
curIO *ioSetOut
|
|
||||||
mu sync.Mutex
|
|
||||||
updateCh chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newIOForwarder(in ioSetIn) *ioForwarder {
|
|
||||||
f := &ioForwarder{
|
|
||||||
updateCh: make(chan struct{}),
|
|
||||||
}
|
|
||||||
doneCh := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
f.mu.Lock()
|
|
||||||
w := f.curIO
|
|
||||||
f.mu.Unlock()
|
|
||||||
if w != nil && w.stdout != nil && w.stderr != nil {
|
|
||||||
go func() {
|
|
||||||
if _, err := io.Copy(in.stdout, w.stdout); err != nil && err != io.ErrClosedPipe {
|
|
||||||
// ErrClosedPipe is OK as we close this read end during setDestination.
|
|
||||||
logrus.WithError(err).Warnf("failed to forward stdout: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
if _, err := io.Copy(in.stderr, w.stderr); err != nil && err != io.ErrClosedPipe {
|
|
||||||
// ErrClosedPipe is OK as we close this read end during setDestination.
|
|
||||||
logrus.WithError(err).Warnf("failed to forward stderr: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-f.updateCh:
|
|
||||||
case <-doneCh:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
if err := copyToFunc(in.stdin, func() (io.Writer, error) {
|
|
||||||
f.mu.Lock()
|
|
||||||
w := f.curIO
|
|
||||||
f.mu.Unlock()
|
|
||||||
if w != nil {
|
|
||||||
return w.stdin, nil
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}); err != nil && err != io.ErrClosedPipe {
|
|
||||||
logrus.WithError(err).Warnf("failed to forward IO: %v", err)
|
|
||||||
}
|
|
||||||
close(doneCh)
|
|
||||||
|
|
||||||
if w := f.curIO; w != nil {
|
|
||||||
// Propagate close
|
|
||||||
if err := w.Close(); err != nil {
|
|
||||||
logrus.WithError(err).Warnf("failed to forwarded stdin IO: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *ioForwarder) setDestination(out *ioSetOut) {
|
|
||||||
f.mu.Lock()
|
|
||||||
if f.curIO != nil {
|
|
||||||
// close all stream on the current IO no to mix with the new IO
|
|
||||||
f.curIO.Close()
|
|
||||||
}
|
|
||||||
f.curIO = out
|
|
||||||
f.mu.Unlock()
|
|
||||||
f.updateCh <- struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ioSetOutContext struct {
|
|
||||||
ioSetOut
|
|
||||||
enableHook func()
|
|
||||||
disableHook func()
|
|
||||||
}
|
|
||||||
|
|
||||||
// newMuxIO forwards IO stream to/from "in" and "outs".
|
|
||||||
// "outs" are closed automatically when "in" reaches EOF.
|
|
||||||
// "in" doesn't closed automatically so the caller needs to explicitly close it.
|
|
||||||
func newMuxIO(in ioSetIn, out []ioSetOutContext, initIdx int, toggleMessage string) *muxIO {
|
|
||||||
m := &muxIO{
|
|
||||||
enabled: make(map[int]struct{}),
|
|
||||||
in: in,
|
|
||||||
out: out,
|
|
||||||
closedCh: make(chan struct{}),
|
|
||||||
toggleMessage: toggleMessage,
|
|
||||||
}
|
|
||||||
for i := range out {
|
|
||||||
m.enabled[i] = struct{}{}
|
|
||||||
}
|
|
||||||
m.maxCur = len(out)
|
|
||||||
m.cur = initIdx
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
var mu sync.Mutex
|
|
||||||
for i, o := range out {
|
|
||||||
i, o := i, o
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := copyToFunc(o.stdout, func() (io.Writer, error) {
|
|
||||||
if m.cur == i {
|
|
||||||
return in.stdout, nil
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}); err != nil {
|
|
||||||
logrus.WithField("output index", i).WithError(err).Warnf("failed to write stdout")
|
|
||||||
}
|
|
||||||
if err := o.stdout.Close(); err != nil {
|
|
||||||
logrus.WithField("output index", i).WithError(err).Warnf("failed to close stdout")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := copyToFunc(o.stderr, func() (io.Writer, error) {
|
|
||||||
if m.cur == i {
|
|
||||||
return in.stderr, nil
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}); err != nil {
|
|
||||||
logrus.WithField("output index", i).WithError(err).Warnf("failed to write stderr")
|
|
||||||
}
|
|
||||||
if err := o.stderr.Close(); err != nil {
|
|
||||||
logrus.WithField("output index", i).WithError(err).Warnf("failed to close stderr")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
errToggle := errors.Errorf("toggle IO")
|
|
||||||
for {
|
|
||||||
prevIsControlSequence := false
|
|
||||||
if err := copyToFunc(traceReader(in.stdin, func(r rune) (bool, error) {
|
|
||||||
// Toggle IO when it detects C-a-c
|
|
||||||
// TODO: make it configurable if needed
|
|
||||||
if int(r) == 1 {
|
|
||||||
prevIsControlSequence = true
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
defer func() { prevIsControlSequence = false }()
|
|
||||||
if prevIsControlSequence {
|
|
||||||
if string(r) == "c" {
|
|
||||||
return false, errToggle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}), func() (io.Writer, error) {
|
|
||||||
mu.Lock()
|
|
||||||
o := out[m.cur]
|
|
||||||
mu.Unlock()
|
|
||||||
return o.stdin, nil
|
|
||||||
}); !errors.Is(err, errToggle) {
|
|
||||||
if err != nil {
|
|
||||||
logrus.WithError(err).Warnf("failed to read stdin")
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
m.toggleIO()
|
|
||||||
}
|
|
||||||
|
|
||||||
// propagate stdin EOF
|
|
||||||
for i, o := range out {
|
|
||||||
if err := o.stdin.Close(); err != nil {
|
|
||||||
logrus.WithError(err).Warnf("failed to close stdin of %d", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
close(m.closedCh)
|
|
||||||
}()
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
type muxIO struct {
|
|
||||||
cur int
|
|
||||||
maxCur int
|
|
||||||
enabled map[int]struct{}
|
|
||||||
mu sync.Mutex
|
|
||||||
in ioSetIn
|
|
||||||
out []ioSetOutContext
|
|
||||||
closedCh chan struct{}
|
|
||||||
toggleMessage string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *muxIO) waitClosed() {
|
|
||||||
<-m.closedCh
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *muxIO) enable(i int) {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
m.enabled[i] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *muxIO) disable(i int) error {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
if i == 0 {
|
|
||||||
return errors.Errorf("disabling 0th io is prohibited")
|
|
||||||
}
|
|
||||||
delete(m.enabled, i)
|
|
||||||
if m.cur == i {
|
|
||||||
m.toggleIO()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *muxIO) toggleIO() {
|
|
||||||
if m.out[m.cur].disableHook != nil {
|
|
||||||
m.out[m.cur].disableHook()
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
if m.cur+1 >= m.maxCur {
|
|
||||||
m.cur = 0
|
|
||||||
} else {
|
|
||||||
m.cur++
|
|
||||||
}
|
|
||||||
if _, ok := m.enabled[m.cur]; !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if m.out[m.cur].enableHook != nil {
|
|
||||||
m.out[m.cur].enableHook()
|
|
||||||
}
|
|
||||||
fmt.Fprintf(m.in.stdout, m.toggleMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func traceReader(r io.ReadCloser, f func(rune) (bool, error)) io.ReadCloser {
|
|
||||||
pr, pw := io.Pipe()
|
|
||||||
go func() {
|
|
||||||
br := bufio.NewReader(r)
|
|
||||||
for {
|
|
||||||
rn, _, err := br.ReadRune()
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
pw.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pw.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if isWrite, err := f(rn); err != nil {
|
|
||||||
pw.CloseWithError(err)
|
|
||||||
return
|
|
||||||
} else if !isWrite {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, err := pw.Write([]byte(string(rn))); err != nil {
|
|
||||||
pw.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return &readerWithClose{
|
|
||||||
Reader: pr,
|
|
||||||
closeFunc: func() error {
|
|
||||||
pr.Close()
|
|
||||||
return r.Close()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyToFunc(r io.Reader, wFunc func() (io.Writer, error)) error {
|
|
||||||
buf := make([]byte, 4096)
|
|
||||||
for {
|
|
||||||
n, readErr := r.Read(buf)
|
|
||||||
if readErr != nil && readErr != io.EOF {
|
|
||||||
return readErr
|
|
||||||
}
|
|
||||||
w, err := wFunc()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if w != nil {
|
|
||||||
if _, err := w.Write(buf[:n]); err != nil {
|
|
||||||
logrus.WithError(err).Debugf("failed to copy")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if readErr == io.EOF {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ioSetPipe() (ioSetIn, ioSetOut) {
|
|
||||||
r1, w1 := io.Pipe()
|
|
||||||
r2, w2 := io.Pipe()
|
|
||||||
r3, w3 := io.Pipe()
|
|
||||||
return ioSetIn{r1, w2, w3}, ioSetOut{w1, r2, r3}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ioSetIn struct {
|
|
||||||
stdin io.ReadCloser
|
|
||||||
stdout io.WriteCloser
|
|
||||||
stderr io.WriteCloser
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s ioSetIn) Close() (retErr error) {
|
|
||||||
if err := s.stdin.Close(); err != nil {
|
|
||||||
retErr = err
|
|
||||||
}
|
|
||||||
if err := s.stdout.Close(); err != nil {
|
|
||||||
retErr = err
|
|
||||||
}
|
|
||||||
if err := s.stderr.Close(); err != nil {
|
|
||||||
retErr = err
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type ioSetOut struct {
|
|
||||||
stdin io.WriteCloser
|
|
||||||
stdout io.ReadCloser
|
|
||||||
stderr io.ReadCloser
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s ioSetOut) Close() (retErr error) {
|
|
||||||
if err := s.stdin.Close(); err != nil {
|
|
||||||
retErr = err
|
|
||||||
}
|
|
||||||
if err := s.stdout.Close(); err != nil {
|
|
||||||
retErr = err
|
|
||||||
}
|
|
||||||
if err := s.stderr.Close(); err != nil {
|
|
||||||
retErr = err
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type readerWithClose struct {
|
|
||||||
io.Reader
|
|
||||||
closeFunc func() error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *readerWithClose) Close() error {
|
|
||||||
return r.closeFunc()
|
|
||||||
}
|
|
@@ -1,321 +0,0 @@
|
|||||||
package monitor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestMuxIO tests muxIO
|
|
||||||
func TestMuxIO(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
inputs []instruction
|
|
||||||
initIdx int
|
|
||||||
outputsNum int
|
|
||||||
wants []string
|
|
||||||
|
|
||||||
// Everytime string is written to the mux stdin, the output end
|
|
||||||
// that received the string write backs to the string that is masked with
|
|
||||||
// its index number. This is useful to check if writeback is written from the
|
|
||||||
// expected output destination.
|
|
||||||
wantsMaskedOutput string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "single output",
|
|
||||||
inputs: []instruction{
|
|
||||||
input("foo\nbar\n"),
|
|
||||||
toggle(),
|
|
||||||
input("1234"),
|
|
||||||
toggle(),
|
|
||||||
input("456"),
|
|
||||||
},
|
|
||||||
initIdx: 0,
|
|
||||||
outputsNum: 1,
|
|
||||||
wants: []string{"foo\nbar\n1234456"},
|
|
||||||
wantsMaskedOutput: `^0+$`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "multi output",
|
|
||||||
inputs: []instruction{
|
|
||||||
input("foo\nbar\n"),
|
|
||||||
toggle(),
|
|
||||||
input("12" + string([]rune{rune(1)}) + "34abc"),
|
|
||||||
toggle(),
|
|
||||||
input("456"),
|
|
||||||
},
|
|
||||||
initIdx: 0,
|
|
||||||
outputsNum: 3,
|
|
||||||
wants: []string{"foo\nbar\n", "1234abc", "456"},
|
|
||||||
wantsMaskedOutput: `^0+1+2+$`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "multi output with nonzero index",
|
|
||||||
inputs: []instruction{
|
|
||||||
input("foo\nbar\n"),
|
|
||||||
toggle(),
|
|
||||||
input("1234"),
|
|
||||||
toggle(),
|
|
||||||
input("456"),
|
|
||||||
},
|
|
||||||
initIdx: 1,
|
|
||||||
outputsNum: 3,
|
|
||||||
wants: []string{"456", "foo\nbar\n", "1234"},
|
|
||||||
wantsMaskedOutput: `^1+2+0+$`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "multi output many toggles",
|
|
||||||
inputs: []instruction{
|
|
||||||
input("foo\nbar\n"),
|
|
||||||
toggle(),
|
|
||||||
input("1234"),
|
|
||||||
toggle(),
|
|
||||||
toggle(),
|
|
||||||
input("456"),
|
|
||||||
toggle(),
|
|
||||||
input("%%%%"),
|
|
||||||
toggle(),
|
|
||||||
toggle(),
|
|
||||||
toggle(),
|
|
||||||
input("aaaa"),
|
|
||||||
},
|
|
||||||
initIdx: 0,
|
|
||||||
outputsNum: 3,
|
|
||||||
wants: []string{"foo\nbar\n456", "1234%%%%aaaa", ""},
|
|
||||||
wantsMaskedOutput: `^0+1+0+1+$`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "enable disable",
|
|
||||||
inputs: []instruction{
|
|
||||||
input("foo\nbar\n"),
|
|
||||||
toggle(),
|
|
||||||
input("1234"),
|
|
||||||
toggle(),
|
|
||||||
input("456"),
|
|
||||||
disable(2),
|
|
||||||
input("%%%%"),
|
|
||||||
enable(2),
|
|
||||||
toggle(),
|
|
||||||
toggle(),
|
|
||||||
input("aaa"),
|
|
||||||
disable(2),
|
|
||||||
disable(1),
|
|
||||||
input("1111"),
|
|
||||||
toggle(),
|
|
||||||
input("2222"),
|
|
||||||
toggle(),
|
|
||||||
input("3333"),
|
|
||||||
},
|
|
||||||
initIdx: 0,
|
|
||||||
outputsNum: 3,
|
|
||||||
wants: []string{"foo\nbar\n%%%%111122223333", "1234", "456aaa"},
|
|
||||||
wantsMaskedOutput: `^0+1+2+0+2+0+$`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
inBuf, end, in := newTestIn(t)
|
|
||||||
var outBufs []*outBuf
|
|
||||||
var outs []ioSetOutContext
|
|
||||||
if tt.outputsNum != len(tt.wants) {
|
|
||||||
t.Fatalf("wants != outputsNum")
|
|
||||||
}
|
|
||||||
for i := 0; i < tt.outputsNum; i++ {
|
|
||||||
outBuf, out := newTestOut(t, i)
|
|
||||||
outBufs = append(outBufs, outBuf)
|
|
||||||
outs = append(outs, ioSetOutContext{out, nil, nil})
|
|
||||||
}
|
|
||||||
mio := newMuxIO(in, outs, tt.initIdx, "")
|
|
||||||
for _, i := range tt.inputs {
|
|
||||||
// Add input to muxIO
|
|
||||||
istr, writeback := i(mio)
|
|
||||||
if _, err := end.stdin.Write([]byte(istr)); err != nil {
|
|
||||||
t.Fatalf("failed to write data to stdin: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for writeback of this input
|
|
||||||
var eg errgroup.Group
|
|
||||||
eg.Go(func() error {
|
|
||||||
outbuf := make([]byte, len(writeback))
|
|
||||||
if _, err := io.ReadAtLeast(end.stdout, outbuf, len(outbuf)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
eg.Go(func() error {
|
|
||||||
errbuf := make([]byte, len(writeback))
|
|
||||||
if _, err := io.ReadAtLeast(end.stderr, errbuf, len(errbuf)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err := eg.Wait(); err != nil {
|
|
||||||
t.Fatalf("failed to wait for output: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close stdin on this muxIO
|
|
||||||
end.stdin.Close()
|
|
||||||
|
|
||||||
// Wait for all output ends reach EOF
|
|
||||||
mio.waitClosed()
|
|
||||||
|
|
||||||
// Close stdout/stderr as well
|
|
||||||
in.Close()
|
|
||||||
|
|
||||||
// Check if each output end received expected string
|
|
||||||
<-inBuf.doneCh
|
|
||||||
for i, o := range outBufs {
|
|
||||||
<-o.doneCh
|
|
||||||
if o.stdin != tt.wants[i] {
|
|
||||||
t.Fatalf("output[%d]: got %q; wanted %q", i, o.stdin, tt.wants[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if expected string is returned from expected outputs
|
|
||||||
if !regexp.MustCompile(tt.wantsMaskedOutput).MatchString(inBuf.stdout) {
|
|
||||||
t.Fatalf("stdout: got %q; wanted %q", inBuf.stdout, tt.wantsMaskedOutput)
|
|
||||||
}
|
|
||||||
if !regexp.MustCompile(tt.wantsMaskedOutput).MatchString(inBuf.stderr) {
|
|
||||||
t.Fatalf("stderr: got %q; wanted %q", inBuf.stderr, tt.wantsMaskedOutput)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type instruction func(m *muxIO) (intput string, writeBackView string)
|
|
||||||
|
|
||||||
func input(s string) instruction {
|
|
||||||
return func(m *muxIO) (string, string) {
|
|
||||||
return s, strings.ReplaceAll(s, string([]rune{rune(1)}), "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func toggle() instruction {
|
|
||||||
return func(m *muxIO) (string, string) {
|
|
||||||
return string([]rune{rune(1)}) + "c", ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func enable(i int) instruction {
|
|
||||||
return func(m *muxIO) (string, string) {
|
|
||||||
m.enable(i)
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func disable(i int) instruction {
|
|
||||||
return func(m *muxIO) (string, string) {
|
|
||||||
m.disable(i)
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type inBuf struct {
|
|
||||||
stdout string
|
|
||||||
stderr string
|
|
||||||
doneCh chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestIn(t *testing.T) (*inBuf, ioSetOut, ioSetIn) {
|
|
||||||
ti := &inBuf{
|
|
||||||
doneCh: make(chan struct{}),
|
|
||||||
}
|
|
||||||
gotOutR, gotOutW := io.Pipe()
|
|
||||||
gotErrR, gotErrW := io.Pipe()
|
|
||||||
outR, outW := io.Pipe()
|
|
||||||
var eg errgroup.Group
|
|
||||||
eg.Go(func() error {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
if _, err := io.Copy(io.MultiWriter(gotOutW, buf), outR); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ti.stdout = buf.String()
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
errR, errW := io.Pipe()
|
|
||||||
eg.Go(func() error {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
if _, err := io.Copy(io.MultiWriter(gotErrW, buf), errR); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ti.stderr = buf.String()
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
go func() {
|
|
||||||
eg.Wait()
|
|
||||||
close(ti.doneCh)
|
|
||||||
}()
|
|
||||||
inR, inW := io.Pipe()
|
|
||||||
return ti, ioSetOut{inW, gotOutR, gotErrR}, ioSetIn{inR, outW, errW}
|
|
||||||
}
|
|
||||||
|
|
||||||
type outBuf struct {
|
|
||||||
idx int
|
|
||||||
stdin string
|
|
||||||
doneCh chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestOut(t *testing.T, idx int) (*outBuf, ioSetOut) {
|
|
||||||
to := &outBuf{
|
|
||||||
idx: idx,
|
|
||||||
doneCh: make(chan struct{}),
|
|
||||||
}
|
|
||||||
inR, inW := io.Pipe()
|
|
||||||
outR, outW := io.Pipe()
|
|
||||||
errR, errW := io.Pipe()
|
|
||||||
go func() {
|
|
||||||
defer inR.Close()
|
|
||||||
defer outW.Close()
|
|
||||||
defer errW.Close()
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
mw := io.MultiWriter(buf,
|
|
||||||
writeMasked(outW, fmt.Sprintf("%d", to.idx)),
|
|
||||||
writeMasked(errW, fmt.Sprintf("%d", to.idx)),
|
|
||||||
)
|
|
||||||
if _, err := io.Copy(mw, inR); err != nil {
|
|
||||||
inR.CloseWithError(err)
|
|
||||||
outW.CloseWithError(err)
|
|
||||||
errW.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
to.stdin = string(buf.Bytes())
|
|
||||||
outW.Close()
|
|
||||||
errW.Close()
|
|
||||||
close(to.doneCh)
|
|
||||||
}()
|
|
||||||
return to, ioSetOut{inW, outR, errR}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeMasked(w io.Writer, s string) io.Writer {
|
|
||||||
buf := make([]byte, 4096)
|
|
||||||
pr, pw := io.Pipe()
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
n, readErr := pr.Read(buf)
|
|
||||||
if readErr != nil && readErr != io.EOF {
|
|
||||||
pr.CloseWithError(readErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var masked string
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
masked += s
|
|
||||||
}
|
|
||||||
if _, err := w.Write([]byte(masked)); err != nil {
|
|
||||||
pr.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if readErr == io.EOF {
|
|
||||||
pr.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return pw
|
|
||||||
}
|
|
@@ -110,44 +110,6 @@ func (ng *NodeGroup) Update(name, endpoint string, platforms []string, endpoints
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ng *NodeGroup) Copy() *NodeGroup {
|
|
||||||
nodes := make([]Node, len(ng.Nodes))
|
|
||||||
for i, node := range ng.Nodes {
|
|
||||||
nodes[i] = *node.Copy()
|
|
||||||
}
|
|
||||||
return &NodeGroup{
|
|
||||||
Name: ng.Name,
|
|
||||||
Driver: ng.Driver,
|
|
||||||
Nodes: nodes,
|
|
||||||
Dynamic: ng.Dynamic,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Node) Copy() *Node {
|
|
||||||
platforms := []specs.Platform{}
|
|
||||||
copy(platforms, n.Platforms)
|
|
||||||
flags := []string{}
|
|
||||||
copy(flags, n.Flags)
|
|
||||||
driverOpts := map[string]string{}
|
|
||||||
for k, v := range n.DriverOpts {
|
|
||||||
driverOpts[k] = v
|
|
||||||
}
|
|
||||||
files := map[string][]byte{}
|
|
||||||
for k, v := range n.Files {
|
|
||||||
vv := []byte{}
|
|
||||||
copy(vv, v)
|
|
||||||
files[k] = vv
|
|
||||||
}
|
|
||||||
return &Node{
|
|
||||||
Name: n.Name,
|
|
||||||
Endpoint: n.Endpoint,
|
|
||||||
Platforms: platforms,
|
|
||||||
Flags: flags,
|
|
||||||
DriverOpts: driverOpts,
|
|
||||||
Files: files,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ng *NodeGroup) validateDuplicates(ep string, idx int) error {
|
func (ng *NodeGroup) validateDuplicates(ep string, idx int) error {
|
||||||
i := 0
|
i := 0
|
||||||
for _, n := range ng.Nodes {
|
for _, n := range ng.Nodes {
|
||||||
|
@@ -2,6 +2,7 @@ package store
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -45,7 +46,7 @@ type Txn struct {
|
|||||||
|
|
||||||
func (t *Txn) List() ([]*NodeGroup, error) {
|
func (t *Txn) List() ([]*NodeGroup, error) {
|
||||||
pp := filepath.Join(t.s.root, "instances")
|
pp := filepath.Join(t.s.root, "instances")
|
||||||
fis, err := os.ReadDir(pp)
|
fis, err := ioutil.ReadDir(pp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -74,7 +75,7 @@ func (t *Txn) NodeGroupByName(name string) (*NodeGroup, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
dt, err := os.ReadFile(filepath.Join(t.s.root, "instances", name))
|
dt, err := ioutil.ReadFile(filepath.Join(t.s.root, "instances", name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -143,7 +144,7 @@ func (t *Txn) reset(key string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Txn) Current(key string) (*NodeGroup, error) {
|
func (t *Txn) Current(key string) (*NodeGroup, error) {
|
||||||
dt, err := os.ReadFile(filepath.Join(t.s.root, "current"))
|
dt, err := ioutil.ReadFile(filepath.Join(t.s.root, "current"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -174,7 +175,7 @@ func (t *Txn) Current(key string) (*NodeGroup, error) {
|
|||||||
|
|
||||||
h := toHash(key)
|
h := toHash(key)
|
||||||
|
|
||||||
dt, err = os.ReadFile(filepath.Join(t.s.root, "defaults", h))
|
dt, err = ioutil.ReadFile(filepath.Join(t.s.root, "defaults", h))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
t.reset(key)
|
t.reset(key)
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -11,7 +12,7 @@ import (
|
|||||||
|
|
||||||
func TestEmptyStartup(t *testing.T) {
|
func TestEmptyStartup(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
tmpdir, err := os.MkdirTemp("", "buildx-store")
|
tmpdir, err := ioutil.TempDir("", "buildx-store")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer os.RemoveAll(tmpdir)
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ func TestEmptyStartup(t *testing.T) {
|
|||||||
|
|
||||||
func TestNodeLocking(t *testing.T) {
|
func TestNodeLocking(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
tmpdir, err := os.MkdirTemp("", "buildx-store")
|
tmpdir, err := ioutil.TempDir("", "buildx-store")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer os.RemoveAll(tmpdir)
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
@@ -64,7 +65,7 @@ func TestNodeLocking(t *testing.T) {
|
|||||||
|
|
||||||
func TestNodeManagement(t *testing.T) {
|
func TestNodeManagement(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
tmpdir, err := os.MkdirTemp("", "buildx-store")
|
tmpdir, err := ioutil.TempDir("", "buildx-store")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer os.RemoveAll(tmpdir)
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
|
@@ -2,7 +2,6 @@ package confutil
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
@@ -25,15 +24,6 @@ func ConfigDir(dockerCli command.Cli) string {
|
|||||||
return buildxConfig
|
return buildxConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultConfigFile returns the default BuildKit configuration file path
|
|
||||||
func DefaultConfigFile(dockerCli command.Cli) (string, bool) {
|
|
||||||
f := path.Join(ConfigDir(dockerCli), "buildkitd.default.toml")
|
|
||||||
if _, err := os.Stat(f); err == nil {
|
|
||||||
return f, true
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadConfigTree loads BuildKit config toml tree
|
// loadConfigTree loads BuildKit config toml tree
|
||||||
func loadConfigTree(fp string) (*toml.Tree, error) {
|
func loadConfigTree(fp string) (*toml.Tree, error) {
|
||||||
f, err := os.Open(fp)
|
f, err := os.Open(fp)
|
||||||
|
@@ -4,15 +4,12 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/containerd/containerd/content"
|
"github.com/containerd/containerd/content"
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/containerd/containerd/platforms"
|
"github.com/containerd/containerd/platforms"
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/moby/buildkit/util/contentutil"
|
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
"github.com/opencontainers/image-spec/specs-go"
|
"github.com/opencontainers/image-spec/specs-go"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
@@ -20,46 +17,46 @@ import (
|
|||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Source struct {
|
func (r *Resolver) Combine(ctx context.Context, in string, descs []ocispec.Descriptor) ([]byte, ocispec.Descriptor, error) {
|
||||||
Desc ocispec.Descriptor
|
ref, err := parseRef(in)
|
||||||
Ref reference.Named
|
if err != nil {
|
||||||
}
|
return nil, ocispec.Descriptor{}, err
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Resolver) Combine(ctx context.Context, srcs []*Source) ([]byte, ocispec.Descriptor, error) {
|
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
dts := make([][]byte, len(srcs))
|
dts := make([][]byte, len(descs))
|
||||||
for i := range dts {
|
for i := range dts {
|
||||||
func(i int) {
|
func(i int) {
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
dt, err := r.GetDescriptor(ctx, srcs[i].Ref.String(), srcs[i].Desc)
|
dt, err := r.GetDescriptor(ctx, ref.String(), descs[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dts[i] = dt
|
dts[i] = dt
|
||||||
|
|
||||||
if srcs[i].Desc.MediaType == "" {
|
if descs[i].MediaType == "" {
|
||||||
mt, err := detectMediaType(dt)
|
mt, err := detectMediaType(dt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
srcs[i].Desc.MediaType = mt
|
descs[i].MediaType = mt
|
||||||
}
|
}
|
||||||
|
|
||||||
mt := srcs[i].Desc.MediaType
|
mt := descs[i].MediaType
|
||||||
|
|
||||||
switch mt {
|
switch mt {
|
||||||
case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
|
case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
|
||||||
p := srcs[i].Desc.Platform
|
p := descs[i].Platform
|
||||||
if srcs[i].Desc.Platform == nil {
|
if descs[i].Platform == nil {
|
||||||
p = &ocispec.Platform{}
|
p = &ocispec.Platform{}
|
||||||
}
|
}
|
||||||
if p.OS == "" || p.Architecture == "" {
|
if p.OS == "" || p.Architecture == "" {
|
||||||
if err := r.loadPlatform(ctx, p, srcs[i].Ref.String(), dt); err != nil {
|
if err := r.loadPlatform(ctx, p, in, dt); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
srcs[i].Desc.Platform = p
|
descs[i].Platform = p
|
||||||
case images.MediaTypeDockerSchema1Manifest:
|
case images.MediaTypeDockerSchema1Manifest:
|
||||||
return errors.Errorf("schema1 manifests are not allowed in manifest lists")
|
return errors.Errorf("schema1 manifests are not allowed in manifest lists")
|
||||||
}
|
}
|
||||||
@@ -74,14 +71,14 @@ func (r *Resolver) Combine(ctx context.Context, srcs []*Source) ([]byte, ocispec
|
|||||||
}
|
}
|
||||||
|
|
||||||
// on single source, return original bytes
|
// on single source, return original bytes
|
||||||
if len(srcs) == 1 {
|
if len(descs) == 1 {
|
||||||
if mt := srcs[0].Desc.MediaType; mt == images.MediaTypeDockerSchema2ManifestList || mt == ocispec.MediaTypeImageIndex {
|
if mt := descs[0].MediaType; mt == images.MediaTypeDockerSchema2ManifestList || mt == ocispec.MediaTypeImageIndex {
|
||||||
return dts[0], srcs[0].Desc, nil
|
return dts[0], descs[0], nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m := map[digest.Digest]int{}
|
m := map[digest.Digest]int{}
|
||||||
newDescs := make([]ocispec.Descriptor, 0, len(srcs))
|
newDescs := make([]ocispec.Descriptor, 0, len(descs))
|
||||||
|
|
||||||
addDesc := func(d ocispec.Descriptor) {
|
addDesc := func(d ocispec.Descriptor) {
|
||||||
idx, ok := m[d.Digest]
|
idx, ok := m[d.Digest]
|
||||||
@@ -106,8 +103,8 @@ func (r *Resolver) Combine(ctx context.Context, srcs []*Source) ([]byte, ocispec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, src := range srcs {
|
for i, desc := range descs {
|
||||||
switch src.Desc.MediaType {
|
switch desc.MediaType {
|
||||||
case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
|
case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
|
||||||
var mfst ocispec.Index
|
var mfst ocispec.Index
|
||||||
if err := json.Unmarshal(dts[i], &mfst); err != nil {
|
if err := json.Unmarshal(dts[i], &mfst); err != nil {
|
||||||
@@ -117,7 +114,7 @@ func (r *Resolver) Combine(ctx context.Context, srcs []*Source) ([]byte, ocispec
|
|||||||
addDesc(d)
|
addDesc(d)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
addDesc(src.Desc)
|
addDesc(desc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,37 +169,6 @@ func (r *Resolver) Push(ctx context.Context, ref reference.Named, desc ocispec.D
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) Copy(ctx context.Context, src *Source, dest reference.Named) error {
|
|
||||||
dest = reference.TagNameOnly(dest)
|
|
||||||
p, err := r.resolver().Pusher(ctx, dest.String())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
srcRef := reference.TagNameOnly(src.Ref)
|
|
||||||
f, err := r.resolver().Fetcher(ctx, srcRef.String())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
refspec := reference.TrimNamed(src.Ref).String()
|
|
||||||
u, err := url.Parse("dummy://" + refspec)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
source, repo := u.Hostname(), strings.TrimPrefix(u.Path, "/")
|
|
||||||
if src.Desc.Annotations == nil {
|
|
||||||
src.Desc.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
src.Desc.Annotations["containerd.io/distribution.source."+source] = repo
|
|
||||||
|
|
||||||
err = contentutil.CopyChain(ctx, contentutil.FromPusher(p), contentutil.FromFetcher(f), src.Desc)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) loadPlatform(ctx context.Context, p2 *ocispec.Platform, in string, dt []byte) error {
|
func (r *Resolver) loadPlatform(ctx context.Context, p2 *ocispec.Platform, in string, dt []byte) error {
|
||||||
var manifest ocispec.Manifest
|
var manifest ocispec.Manifest
|
||||||
if err := json.Unmarshal(dt, &manifest); err != nil {
|
if err := json.Unmarshal(dt, &manifest); err != nil {
|
||||||
|
@@ -2,6 +2,7 @@ package imagetools
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -15,9 +16,9 @@ import (
|
|||||||
"github.com/containerd/containerd/platforms"
|
"github.com/containerd/containerd/platforms"
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
binfotypes "github.com/moby/buildkit/util/buildinfo/types"
|
binfotypes "github.com/moby/buildkit/util/buildinfo/types"
|
||||||
"github.com/moby/buildkit/util/imageutil"
|
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -59,9 +60,7 @@ func NewPrinter(ctx context.Context, opt Opt, name string, format string) (*Prin
|
|||||||
switch manifest.MediaType {
|
switch manifest.MediaType {
|
||||||
case images.MediaTypeDockerSchema2ManifestList, ocispecs.MediaTypeImageIndex:
|
case images.MediaTypeDockerSchema2ManifestList, ocispecs.MediaTypeImageIndex:
|
||||||
for _, m := range index.Manifests {
|
for _, m := range index.Manifests {
|
||||||
if m.Platform != nil {
|
pforms = append(pforms, *m.Platform)
|
||||||
pforms = append(pforms, *m.Platform)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
pforms = append(pforms, platforms.DefaultSpec())
|
pforms = append(pforms, platforms.DefaultSpec())
|
||||||
@@ -124,7 +123,7 @@ func (p *Printer) Print(raw bool, out io.Writer) error {
|
|||||||
} else if img != nil {
|
} else if img != nil {
|
||||||
imageconfigs[platforms.Format(platform)] = img
|
imageconfigs[platforms.Format(platform)] = img
|
||||||
}
|
}
|
||||||
if bi, err := imageutil.BuildInfo(dtic); err != nil {
|
if bi, err := p.getBuildInfo(dtic); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if bi != nil {
|
} else if bi != nil {
|
||||||
buildinfos[platforms.Format(platform)] = bi
|
buildinfos[platforms.Format(platform)] = bi
|
||||||
@@ -336,3 +335,23 @@ func (p *Printer) getImageConfig(platform *ocispecs.Platform) (*ocispecs.Image,
|
|||||||
}
|
}
|
||||||
return img, dtic, nil
|
return img, dtic, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Printer) getBuildInfo(dtic []byte) (*binfotypes.BuildInfo, error) {
|
||||||
|
var binfo *binfotypes.BuildInfo
|
||||||
|
if len(dtic) > 0 {
|
||||||
|
var biconfig binfotypes.ImageConfig
|
||||||
|
if err := json.Unmarshal(dtic, &biconfig); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to unmarshal image config")
|
||||||
|
}
|
||||||
|
if len(biconfig.BuildInfo) > 0 {
|
||||||
|
dtbi, err := base64.StdEncoding.DecodeString(biconfig.BuildInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to decode build info")
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(dtbi, &binfo); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to unmarshal build info")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return binfo, nil
|
||||||
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package logutil
|
package logutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io/ioutil"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
func NewFilter(levels []logrus.Level, filters ...string) logrus.Hook {
|
func NewFilter(levels []logrus.Level, filters ...string) logrus.Hook {
|
||||||
dl := logrus.New()
|
dl := logrus.New()
|
||||||
dl.SetOutput(io.Discard)
|
dl.SetOutput(ioutil.Discard)
|
||||||
return &logsFilter{
|
return &logsFilter{
|
||||||
levels: levels,
|
levels: levels,
|
||||||
filters: filters,
|
filters: filters,
|
||||||
|
@@ -2,6 +2,7 @@ package progress
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
@@ -23,7 +24,7 @@ func FromReader(w Writer, name string, rc io.ReadCloser) {
|
|||||||
Vertexes: []*client.Vertex{&vtx},
|
Vertexes: []*client.Vertex{&vtx},
|
||||||
})
|
})
|
||||||
|
|
||||||
_, err := io.Copy(io.Discard, rc)
|
_, err := io.Copy(ioutil.Discard, rc)
|
||||||
|
|
||||||
tm2 := time.Now()
|
tm2 := time.Now()
|
||||||
vtx2 := vtx
|
vtx2 := vtx
|
||||||
|
@@ -3,6 +3,7 @@ package progress
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -87,7 +88,7 @@ func NewPrinter(ctx context.Context, w io.Writer, out console.File, mode string)
|
|||||||
var c console.Console
|
var c console.Console
|
||||||
switch mode {
|
switch mode {
|
||||||
case PrinterModeQuiet:
|
case PrinterModeQuiet:
|
||||||
w = io.Discard
|
w = ioutil.Discard
|
||||||
case PrinterModeAuto, PrinterModeTty:
|
case PrinterModeAuto, PrinterModeTty:
|
||||||
if cons, err := console.ConsoleFromFile(out); err == nil {
|
if cons, err := console.ConsoleFromFile(out); err == nil {
|
||||||
c = cons
|
c = cons
|
||||||
|
5
vendor/github.com/Azure/go-ansiterm/go.mod
generated
vendored
Normal file
5
vendor/github.com/Azure/go-ansiterm/go.mod
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module github.com/Azure/go-ansiterm
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require golang.org/x/sys v0.0.0-20210616094352-59db8d763f22
|
2
vendor/github.com/Azure/go-ansiterm/go.sum
generated
vendored
Normal file
2
vendor/github.com/Azure/go-ansiterm/go.sum
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
|
||||||
|
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
32
vendor/github.com/Azure/go-autorest/.gitignore
generated
vendored
32
vendor/github.com/Azure/go-autorest/.gitignore
generated
vendored
@@ -1,32 +0,0 @@
|
|||||||
# The standard Go .gitignore file follows. (Sourced from: github.com/github/gitignore/master/Go.gitignore)
|
|
||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
|
||||||
*.o
|
|
||||||
*.a
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Folders
|
|
||||||
_obj
|
|
||||||
_test
|
|
||||||
.DS_Store
|
|
||||||
.idea/
|
|
||||||
.vscode/
|
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
|
||||||
*.cgo2.c
|
|
||||||
_cgo_defun.c
|
|
||||||
_cgo_gotypes.go
|
|
||||||
_cgo_export.*
|
|
||||||
|
|
||||||
_testmain.go
|
|
||||||
|
|
||||||
*.exe
|
|
||||||
*.test
|
|
||||||
*.prof
|
|
||||||
|
|
||||||
# go-autorest specific
|
|
||||||
vendor/
|
|
||||||
autorest/azure/example/example
|
|
1004
vendor/github.com/Azure/go-autorest/CHANGELOG.md
generated
vendored
1004
vendor/github.com/Azure/go-autorest/CHANGELOG.md
generated
vendored
File diff suppressed because it is too large
Load Diff
23
vendor/github.com/Azure/go-autorest/GNUmakefile
generated
vendored
23
vendor/github.com/Azure/go-autorest/GNUmakefile
generated
vendored
@@ -1,23 +0,0 @@
|
|||||||
DIR?=./autorest/
|
|
||||||
|
|
||||||
default: build
|
|
||||||
|
|
||||||
build: fmt
|
|
||||||
go install $(DIR)
|
|
||||||
|
|
||||||
test:
|
|
||||||
go test $(DIR) || exit 1
|
|
||||||
|
|
||||||
vet:
|
|
||||||
@echo "go vet ."
|
|
||||||
@go vet $(DIR)... ; if [ $$? -eq 1 ]; then \
|
|
||||||
echo ""; \
|
|
||||||
echo "Vet found suspicious constructs. Please check the reported constructs"; \
|
|
||||||
echo "and fix them if necessary before submitting the code for review."; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
fmt:
|
|
||||||
gofmt -w $(DIR)
|
|
||||||
|
|
||||||
.PHONY: build test vet fmt
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user