Compare commits
5 Commits
v0.10.0-rc
...
v0.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
266c0eac61 | ||
|
|
0b320faf34 | ||
|
|
944fd444f6 | ||
|
|
de7dfb925d | ||
|
|
43e51fd089 |
@@ -1 +1,3 @@
|
||||
bin/
|
||||
cross-out/
|
||||
release-out/
|
||||
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"
|
||||
268
.github/workflows/build.yml
vendored
268
.github/workflows/build.yml
vendored
@@ -1,9 +1,5 @@
|
||||
name: build
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
@@ -16,119 +12,172 @@ on:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'v[0-9]*'
|
||||
paths-ignore:
|
||||
- 'README.md'
|
||||
- 'docs/**'
|
||||
|
||||
env:
|
||||
REPO_SLUG: "docker/buildx-bin"
|
||||
DESTDIR: "./bin"
|
||||
REPO_SLUG_ORIGIN: "moby/buildkit:master"
|
||||
CACHEKEY_BINARIES: "binaries"
|
||||
PLATFORMS: "linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/s390x,linux/ppc64le,linux/riscv64"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
base:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Cache ${{ env.CACHEKEY_BINARIES }}
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
key: ${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
version: latest
|
||||
driver-opts: image=${{ env.REPO_SLUG_ORIGIN }}
|
||||
-
|
||||
name: Test
|
||||
uses: docker/bake-action@v2
|
||||
with:
|
||||
targets: test
|
||||
set: |
|
||||
*.cache-from=type=gha,scope=test
|
||||
*.cache-to=type=gha,scope=test
|
||||
name: Build ${{ env.CACHEKEY_BINARIES }}
|
||||
run: |
|
||||
./hack/build_ci_first_pass binaries
|
||||
env:
|
||||
CACHEDIR_FROM: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
CACHEDIR_TO: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}-new
|
||||
-
|
||||
name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
directory: ${{ env.DESTDIR }}/coverage
|
||||
# FIXME: Temp fix for https://github.com/moby/buildkit/issues/1850
|
||||
name: Move cache
|
||||
run: |
|
||||
rm -rf /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
mv /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}-new /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
|
||||
prepare:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.platforms.outputs.matrix }}
|
||||
needs: [base]
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Create matrix
|
||||
id: platforms
|
||||
run: |
|
||||
echo ::set-output name=matrix::$(docker buildx bake binaries-cross --print | jq -cr '.target."binaries-cross".platforms')
|
||||
name: Cache ${{ env.CACHEKEY_BINARIES }}
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
key: ${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-
|
||||
-
|
||||
name: Show matrix
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
driver-opts: image=${{ env.REPO_SLUG_ORIGIN }}
|
||||
-
|
||||
name: Test
|
||||
run: |
|
||||
echo ${{ steps.platforms.outputs.matrix }}
|
||||
make test
|
||||
env:
|
||||
TEST_COVERAGE: 1
|
||||
TESTFLAGS: -v --parallel=6 --timeout=20m
|
||||
CACHEDIR_FROM: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
-
|
||||
name: Send to Codecov
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
file: ./coverage/coverage.txt
|
||||
|
||||
cross:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [base]
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Cache ${{ env.CACHEKEY_BINARIES }}
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
key: ${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
driver-opts: image=${{ env.REPO_SLUG_ORIGIN }}
|
||||
-
|
||||
name: Cross
|
||||
run: |
|
||||
make cross
|
||||
env:
|
||||
TARGETPLATFORM: ${{ env.PLATFORMS }},darwin/amd64,darwin/arm64,windows/amd64,windows/arm64
|
||||
CACHEDIR_FROM: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
|
||||
binaries:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- prepare
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: ${{ fromJson(needs.prepare.outputs.matrix) }}
|
||||
needs: [test, cross]
|
||||
env:
|
||||
RELEASE_OUT: ./release-out
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Prepare
|
||||
id: prep
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
TAG=pr
|
||||
if [[ $GITHUB_REF == refs/tags/v* ]]; then
|
||||
TAG=${GITHUB_REF#refs/tags/}
|
||||
elif [[ $GITHUB_REF == refs/heads/* ]]; then
|
||||
TAG=$(echo ${GITHUB_REF#refs/heads/} | sed -r 's#/+#-#g')
|
||||
fi
|
||||
echo ::set-output name=tag::${TAG}
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
name: Cache ${{ env.CACHEKEY_BINARIES }}
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
key: ${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
version: latest
|
||||
driver-opts: image=${{ env.REPO_SLUG_ORIGIN }}
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@v2
|
||||
with:
|
||||
targets: release
|
||||
set: |
|
||||
*.platform=${{ matrix.platform }}
|
||||
*.cache-from=type=gha,scope=binaries-${{ env.PLATFORM_PAIR }}
|
||||
*.cache-to=type=gha,scope=binaries-${{ env.PLATFORM_PAIR }},mode=max
|
||||
name: Build ${{ steps.prep.outputs.tag }}
|
||||
run: |
|
||||
./hack/release ${{ env.RELEASE_OUT }}
|
||||
env:
|
||||
PLATFORMS: ${{ env.PLATFORMS }},darwin/amd64,darwin/arm64,windows/amd64,windows/arm64
|
||||
CACHEDIR_FROM: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: buildx
|
||||
path: ${{ env.DESTDIR }}/release/*
|
||||
path: ${{ env.RELEASE_OUT }}/*
|
||||
if-no-files-found: error
|
||||
|
||||
bin-image:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: latest
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: |
|
||||
${{ env.REPO_SLUG }}
|
||||
@@ -136,82 +185,31 @@ jobs:
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
bake-target: meta-helper
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Build and push image
|
||||
uses: docker/bake-action@v2
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
files: |
|
||||
./docker-bake.hcl
|
||||
${{ steps.meta.outputs.bake-file }}
|
||||
targets: image-cross
|
||||
context: .
|
||||
target: binaries
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
set: |
|
||||
*.cache-from=type=gha,scope=bin-image
|
||||
*.cache-to=type=gha,scope=bin-image,mode=max
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- binaries
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Download binaries
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: buildx
|
||||
path: ${{ env.DESTDIR }}
|
||||
-
|
||||
name: Create checksums
|
||||
run: ./hack/hash-files
|
||||
-
|
||||
name: List artifacts
|
||||
run: |
|
||||
tree -nh ${{ env.DESTDIR }}
|
||||
-
|
||||
name: Check artifacts
|
||||
run: |
|
||||
find ${{ env.DESTDIR }} -type f -exec file -e ascii -- {} +
|
||||
cache-from: type=local,src=/tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
platforms: ${{ env.PLATFORMS }},darwin/amd64,darwin/arm64,windows/amd64,windows/arm64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
-
|
||||
name: GitHub Release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15
|
||||
uses: softprops/action-gh-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
draft: true
|
||||
files: ${{ env.DESTDIR }}/*
|
||||
|
||||
buildkit-edge:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: latest
|
||||
driver-opts: image=moby/buildkit:master
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
# Just run a bake target to check eveything runs fine
|
||||
name: Build
|
||||
uses: docker/bake-action@v2
|
||||
with:
|
||||
targets: binaries
|
||||
files: ${{ env.RELEASE_OUT }}/*
|
||||
name: ${{ steps.prep.outputs.tag }}
|
||||
|
||||
56
.github/workflows/docs-release.yml
vendored
56
.github/workflows/docs-release.yml
vendored
@@ -1,56 +0,0 @@
|
||||
name: docs-release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ released ]
|
||||
|
||||
jobs:
|
||||
open-pr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout docs repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
|
||||
repository: docker/docs
|
||||
ref: main
|
||||
-
|
||||
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@2b011faafdcbc9ceb11414d64d0573f37c774b04
|
||||
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
|
||||
61
.github/workflows/docs-upstream.yml
vendored
61
.github/workflows/docs-upstream.yml
vendored
@@ -1,61 +0,0 @@
|
||||
# this workflow runs the remote validate bake target from docker/docker.github.io
|
||||
# to check if yaml reference docs and markdown files used in this repo are still valid
|
||||
# https://github.com/docker/docker.github.io/blob/98c7c9535063ae4cd2cd0a31478a21d16d2f07a3/docker-bake.hcl#L34-L36
|
||||
name: docs-upstream
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'v[0-9]*'
|
||||
paths:
|
||||
- '.github/workflows/docs-upstream.yml'
|
||||
- 'docs/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/docs-upstream.yml'
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
docs-yaml:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: latest
|
||||
-
|
||||
name: Build reference YAML docs
|
||||
uses: docker/bake-action@v2
|
||||
with:
|
||||
targets: update-docs
|
||||
set: |
|
||||
*.output=/tmp/buildx-docs
|
||||
*.cache-from=type=gha,scope=docs-yaml
|
||||
*.cache-to=type=gha,scope=docs-yaml,mode=max
|
||||
env:
|
||||
DOCS_FORMATS: yaml
|
||||
-
|
||||
name: Upload reference YAML docs
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docs-yaml
|
||||
path: /tmp/buildx-docs/out/reference
|
||||
retention-days: 1
|
||||
|
||||
validate:
|
||||
uses: docker/docs/.github/workflows/validate-upstream.yml@main
|
||||
needs:
|
||||
- docs-yaml
|
||||
with:
|
||||
repo: https://github.com/${{ github.repository }}
|
||||
data-files-id: docs-yaml
|
||||
data-files-folder: buildx
|
||||
170
.github/workflows/e2e.yml
vendored
170
.github/workflows/e2e.yml
vendored
@@ -1,170 +0,0 @@
|
||||
name: e2e
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'v[0-9]*'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'v[0-9]*'
|
||||
paths-ignore:
|
||||
- 'README.md'
|
||||
- 'docs/**'
|
||||
|
||||
env:
|
||||
DESTDIR: "./bin"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
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.DESTDIR }}/build/buildx ${{ env.DESTDIR }}/build/docker-buildx
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: binary
|
||||
path: ${{ env.DESTDIR }}/build
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
driver:
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
driver:
|
||||
- docker
|
||||
- docker-container
|
||||
- kubernetes
|
||||
- remote
|
||||
buildkit:
|
||||
- moby/buildkit:buildx-stable-1
|
||||
- moby/buildkit:master
|
||||
buildkit-cfg:
|
||||
- bkcfg-false
|
||||
- bkcfg-true
|
||||
multi-node:
|
||||
- mnode-false
|
||||
- mnode-true
|
||||
platforms:
|
||||
- linux/amd64
|
||||
- linux/amd64,linux/arm64
|
||||
include:
|
||||
- driver: kubernetes
|
||||
driver-opt: qemu.install=true
|
||||
- driver: remote
|
||||
endpoint: tcp://localhost:1234
|
||||
exclude:
|
||||
- driver: docker
|
||||
multi-node: mnode-true
|
||||
- driver: docker
|
||||
buildkit-cfg: bkcfg-true
|
||||
- driver: docker-container
|
||||
multi-node: mnode-true
|
||||
- driver: remote
|
||||
multi-node: mnode-true
|
||||
- driver: remote
|
||||
buildkit-cfg: bkcfg-true
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
if: matrix.driver == 'docker' || matrix.driver == 'docker-container'
|
||||
-
|
||||
name: Install buildx
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: binary
|
||||
path: /home/runner/.docker/cli-plugins
|
||||
-
|
||||
name: Fix perms and check
|
||||
run: |
|
||||
chmod +x /home/runner/.docker/cli-plugins/docker-buildx
|
||||
docker buildx version
|
||||
-
|
||||
name: Init env vars
|
||||
run: |
|
||||
# BuildKit cfg
|
||||
if [ "${{ matrix.buildkit-cfg }}" = "bkcfg-true" ]; then
|
||||
cat > "/tmp/buildkitd.toml" <<EOL
|
||||
[worker.oci]
|
||||
max-parallelism = 2
|
||||
EOL
|
||||
echo "BUILDKIT_CFG=/tmp/buildkitd.toml" >> $GITHUB_ENV
|
||||
fi
|
||||
# Multi node
|
||||
if [ "${{ matrix.multi-node }}" = "mnode-true" ]; then
|
||||
echo "MULTI_NODE=1" >> $GITHUB_ENV
|
||||
else
|
||||
echo "MULTI_NODE=0" >> $GITHUB_ENV
|
||||
fi
|
||||
-
|
||||
name: Install k3s
|
||||
if: matrix.driver == 'kubernetes'
|
||||
uses: debianmaster/actions-k3s@b9cf3f599fd118699a3c8a0d18a2f2bda6cf4ce4
|
||||
id: k3s
|
||||
with:
|
||||
version: v1.21.2-k3s1
|
||||
-
|
||||
name: Config k3s
|
||||
if: matrix.driver == 'kubernetes'
|
||||
run: |
|
||||
(set -x ; cat ${{ steps.k3s.outputs.kubeconfig }})
|
||||
-
|
||||
name: Check k3s nodes
|
||||
if: matrix.driver == 'kubernetes'
|
||||
run: |
|
||||
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
|
||||
run: |
|
||||
make test-driver
|
||||
env:
|
||||
BUILDKIT_IMAGE: ${{ matrix.buildkit }}
|
||||
DRIVER: ${{ matrix.driver }}
|
||||
DRIVER_OPT: ${{ matrix.driver-opt }}
|
||||
ENDPOINT: ${{ matrix.endpoint }}
|
||||
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
|
||||
13
.github/workflows/validate.yml
vendored
13
.github/workflows/validate.yml
vendored
@@ -1,9 +1,5 @@
|
||||
name: validate
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
@@ -17,6 +13,9 @@ on:
|
||||
- 'master'
|
||||
- 'v[0-9]*'
|
||||
|
||||
env:
|
||||
REPO_SLUG_ORIGIN: "moby/buildkit:master"
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -30,12 +29,12 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
version: latest
|
||||
driver-opts: image=${{ env.REPO_SLUG_ORIGIN }}
|
||||
-
|
||||
name: Run
|
||||
run: |
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1 +1,4 @@
|
||||
/bin
|
||||
bin
|
||||
coverage
|
||||
cross-out
|
||||
release-out
|
||||
|
||||
@@ -12,29 +12,19 @@ linters:
|
||||
- gofmt
|
||||
- govet
|
||||
- deadcode
|
||||
- depguard
|
||||
- goimports
|
||||
- ineffassign
|
||||
- misspell
|
||||
- unused
|
||||
- varcheck
|
||||
- revive
|
||||
- golint
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- nolintlint
|
||||
- structcheck
|
||||
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:
|
||||
exclude-rules:
|
||||
- linters:
|
||||
- revive
|
||||
- golint
|
||||
text: "stutters"
|
||||
|
||||
9
.mailmap
9
.mailmap
@@ -1,13 +1,6 @@
|
||||
# This file lists all individuals having contributed content to the repository.
|
||||
# For how it is generated, see hack/dockerfiles/authors.Dockerfile.
|
||||
# For how it is generated, see `hack/generate-authors`.
|
||||
|
||||
CrazyMax <github@crazymax.dev>
|
||||
CrazyMax <github@crazymax.dev> <1951866+crazy-max@users.noreply.github.com>
|
||||
CrazyMax <github@crazymax.dev> <crazy-max@users.noreply.github.com>
|
||||
Sebastiaan van Stijn <github@gone.nl>
|
||||
Sebastiaan van Stijn <github@gone.nl> <thaJeztah@users.noreply.github.com>
|
||||
Tibor Vass <tibor@docker.com>
|
||||
Tibor Vass <tibor@docker.com> <tiborvass@users.noreply.github.com>
|
||||
Tõnis Tiigi <tonistiigi@gmail.com>
|
||||
Ulysses Souza <ulyssessouza@gmail.com>
|
||||
Wang Jinglei <morlay.null@gmail.com>
|
||||
|
||||
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
|
||||
40
AUTHORS
40
AUTHORS
@@ -1,45 +1,7 @@
|
||||
# This file lists all individuals having contributed content to the repository.
|
||||
# For how it is generated, see hack/dockerfiles/authors.Dockerfile.
|
||||
# For how it is generated, see `scripts/generate-authors.sh`.
|
||||
|
||||
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
|
||||
Alex Couture-Beil <alex@earthly.dev>
|
||||
Andrew Haines <andrew.haines@zencargo.com>
|
||||
Andy MacKinlay <admackin@users.noreply.github.com>
|
||||
Anthony Poschen <zanven42@gmail.com>
|
||||
Artur Klauser <Artur.Klauser@computer.org>
|
||||
Batuhan Apaydın <developerguy2@gmail.com>
|
||||
Bin Du <bindu@microsoft.com>
|
||||
Brandon Philips <brandon@ifup.org>
|
||||
Brian Goff <cpuguy83@gmail.com>
|
||||
CrazyMax <github@crazymax.dev>
|
||||
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
||||
Devin Bayer <dev@doubly.so>
|
||||
Djordje Lukic <djordje.lukic@docker.com>
|
||||
Dmytro Makovey <dmytro.makovey@docker.com>
|
||||
Donghui Wang <977675308@qq.com>
|
||||
faust <faustin@fala.red>
|
||||
Felipe Santos <felipecassiors@gmail.com>
|
||||
Fernando Miguel <github@FernandoMiguel.net>
|
||||
gfrancesco <gfrancesco@users.noreply.github.com>
|
||||
gracenoah <gracenoahgh@gmail.com>
|
||||
Hollow Man <hollowman@hollowman.ml>
|
||||
Ilya Dmitrichenko <errordeveloper@gmail.com>
|
||||
Jack Laxson <jackjrabbit@gmail.com>
|
||||
Jean-Yves Gastaud <jygastaud@gmail.com>
|
||||
khs1994 <khs1994@khs1994.com>
|
||||
Kotaro Adachi <k33asby@gmail.com>
|
||||
l00397676 <lujingxiao@huawei.com>
|
||||
Michal Augustyn <michal.augustyn@mail.com>
|
||||
Patrick Van Stee <patrick@vanstee.me>
|
||||
Saul Shanabrook <s.shanabrook@gmail.com>
|
||||
Sebastiaan van Stijn <github@gone.nl>
|
||||
SHIMA Tatsuya <ts1s1andn@gmail.com>
|
||||
Silvin Lubecki <silvin.lubecki@docker.com>
|
||||
Solomon Hykes <sh.github.6811@hykes.org>
|
||||
Sune Keller <absukl@almbrand.dk>
|
||||
Tibor Vass <tibor@docker.com>
|
||||
Tõnis Tiigi <tonistiigi@gmail.com>
|
||||
Ulysses Souza <ulyssessouza@gmail.com>
|
||||
Wang Jinglei <morlay.null@gmail.com>
|
||||
Xiang Dai <764524258@qq.com>
|
||||
zelahi <elahi.zuhayr@gmail.com>
|
||||
|
||||
86
Dockerfile
86
Dockerfile
@@ -1,90 +1,90 @@
|
||||
# syntax=docker/dockerfile:1.4
|
||||
# syntax=docker/dockerfile:1.2
|
||||
|
||||
ARG GO_VERSION=1.19
|
||||
ARG XX_VERSION=1.1.2
|
||||
ARG DOCKERD_VERSION=20.10.14
|
||||
ARG DOCKERD_VERSION=19.03
|
||||
ARG CLI_VERSION=19.03
|
||||
|
||||
FROM docker:$DOCKERD_VERSION AS dockerd-release
|
||||
|
||||
# xx is a helper for cross-compilation
|
||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx@sha256:21a61be4744f6531cb5f33b0e6f40ede41fa3a1b8c82d5946178f80cc84bfc04 AS xx
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS golatest
|
||||
FROM --platform=$BUILDPLATFORM golang:1.16-alpine AS golatest
|
||||
|
||||
FROM golatest AS gobase
|
||||
FROM golatest AS go-linux
|
||||
FROM golatest AS go-darwin
|
||||
FROM golatest AS go-windows-amd64
|
||||
FROM golatest AS go-windows-386
|
||||
FROM golatest AS go-windows-arm
|
||||
FROM --platform=$BUILDPLATFORM golang:1.17beta1-alpine AS go-windows-arm64
|
||||
FROM go-windows-${TARGETARCH} AS go-windows
|
||||
|
||||
FROM go-${TARGETOS} AS gobase
|
||||
COPY --from=xx / /
|
||||
RUN apk add --no-cache file git
|
||||
ENV GOFLAGS=-mod=vendor
|
||||
ENV CGO_ENABLED=0
|
||||
WORKDIR /src
|
||||
|
||||
FROM gobase AS buildx-version
|
||||
RUN --mount=type=bind,target=. <<EOT
|
||||
set -e
|
||||
mkdir /buildx-version
|
||||
echo -n "$(./hack/git-meta version)" | tee /buildx-version/version
|
||||
echo -n "$(./hack/git-meta revision)" | tee /buildx-version/revision
|
||||
EOT
|
||||
RUN --mount=target=. \
|
||||
PKG=github.com/docker/buildx VERSION=$(git describe --match 'v[0-9]*' --dirty='.m' --always --tags) REVISION=$(git rev-parse HEAD)$(if ! git diff --no-ext-diff --quiet --exit-code; then echo .m; fi); \
|
||||
echo "-X ${PKG}/version.Version=${VERSION} -X ${PKG}/version.Revision=${REVISION} -X ${PKG}/version.Package=${PKG}" | tee /tmp/.ldflags; \
|
||||
echo -n "${VERSION}" | tee /tmp/.version;
|
||||
|
||||
FROM gobase AS buildx-build
|
||||
ENV CGO_ENABLED=0
|
||||
ARG TARGETPLATFORM
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=bind,from=buildx-version,source=/buildx-version,target=/buildx-version <<EOT
|
||||
set -e
|
||||
xx-go --wrap
|
||||
DESTDIR=/usr/bin VERSION=$(cat /buildx-version/version) REVISION=$(cat /buildx-version/revision) GO_EXTRA_LDFLAGS="-s -w" ./hack/build
|
||||
xx-verify --static /usr/bin/docker-buildx
|
||||
EOT
|
||||
RUN --mount=target=. --mount=target=/root/.cache,type=cache \
|
||||
--mount=target=/go/pkg/mod,type=cache \
|
||||
--mount=source=/tmp/.ldflags,target=/tmp/.ldflags,from=buildx-version \
|
||||
set -x; xx-go build -ldflags "$(cat /tmp/.ldflags)" -o /usr/bin/buildx ./cmd/buildx && \
|
||||
xx-verify --static /usr/bin/buildx
|
||||
|
||||
FROM gobase AS test
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
go test -v -coverprofile=/tmp/coverage.txt -covermode=atomic ./... && \
|
||||
go tool cover -func=/tmp/coverage.txt
|
||||
FROM buildx-build AS integration-tests
|
||||
COPY . .
|
||||
|
||||
FROM scratch AS test-coverage
|
||||
COPY --from=test /tmp/coverage.txt /coverage.txt
|
||||
# FROM golang:1.12-alpine AS docker-cli-build
|
||||
# RUN apk add -U git bash coreutils gcc musl-dev
|
||||
# ENV CGO_ENABLED=0
|
||||
# ARG REPO=github.com/tiborvass/cli
|
||||
# ARG BRANCH=cli-plugin-aliases
|
||||
# ARG CLI_VERSION
|
||||
# WORKDIR /go/src/github.com/docker/cli
|
||||
# RUN git clone git://$REPO . && git checkout $BRANCH
|
||||
# RUN ./scripts/build/binary
|
||||
|
||||
FROM scratch AS binaries-unix
|
||||
COPY --link --from=buildx-build /usr/bin/docker-buildx /buildx
|
||||
COPY --from=buildx-build /usr/bin/buildx /
|
||||
|
||||
FROM binaries-unix AS binaries-darwin
|
||||
FROM binaries-unix AS binaries-linux
|
||||
|
||||
FROM scratch AS binaries-windows
|
||||
COPY --link --from=buildx-build /usr/bin/docker-buildx /buildx.exe
|
||||
COPY --from=buildx-build /usr/bin/buildx /buildx.exe
|
||||
|
||||
FROM binaries-$TARGETOS AS binaries
|
||||
|
||||
# Release
|
||||
FROM --platform=$BUILDPLATFORM alpine AS releaser
|
||||
WORKDIR /work
|
||||
ARG TARGETPLATFORM
|
||||
RUN --mount=from=binaries \
|
||||
--mount=type=bind,from=buildx-version,source=/buildx-version,target=/buildx-version <<EOT
|
||||
set -e
|
||||
mkdir -p /out
|
||||
cp buildx* "/out/buildx-$(cat /buildx-version/version).$(echo $TARGETPLATFORM | sed 's/\//-/g')$(ls buildx* | sed -e 's/^buildx//')"
|
||||
EOT
|
||||
--mount=source=/tmp/.version,target=/tmp/.version,from=buildx-version \
|
||||
mkdir -p /out && cp buildx* "/out/buildx-$(cat /tmp/.version).$(echo $TARGETPLATFORM | sed 's/\//-/g')$(ls buildx* | sed -e 's/^buildx//')"
|
||||
|
||||
FROM scratch AS release
|
||||
COPY --from=releaser /out/ /
|
||||
|
||||
# Shell
|
||||
FROM docker:$DOCKERD_VERSION AS dockerd-release
|
||||
FROM alpine AS shell
|
||||
FROM alpine AS demo-env
|
||||
RUN apk add --no-cache iptables tmux git vim less openssh
|
||||
RUN mkdir -p /usr/local/lib/docker/cli-plugins && ln -s /usr/local/bin/buildx /usr/local/lib/docker/cli-plugins/docker-buildx
|
||||
COPY ./hack/demo-env/entrypoint.sh /usr/local/bin
|
||||
COPY ./hack/demo-env/tmux.conf /root/.tmux.conf
|
||||
COPY --from=dockerd-release /usr/local/bin /usr/local/bin
|
||||
#COPY --from=docker-cli-build /go/src/github.com/docker/cli/build/docker /usr/local/bin
|
||||
|
||||
WORKDIR /work
|
||||
COPY ./hack/demo-env/examples .
|
||||
COPY --from=binaries / /usr/local/bin/
|
||||
VOLUME /var/lib/docker
|
||||
ENTRYPOINT ["entrypoint.sh"]
|
||||
|
||||
FROM binaries
|
||||
FROM binaries
|
||||
@@ -152,7 +152,6 @@ made through a pull request.
|
||||
people = [
|
||||
"akihirosuda",
|
||||
"crazy-max",
|
||||
"jedevc",
|
||||
"tiborvass",
|
||||
"tonistiigi",
|
||||
]
|
||||
@@ -189,11 +188,6 @@ made through a pull request.
|
||||
Email = "contact@crazymax.dev"
|
||||
GitHub = "crazy-max"
|
||||
|
||||
[people.jedevc]
|
||||
Name = "Justin Chadwell"
|
||||
Email = "me@jedevc.com"
|
||||
GitHub = "jedevc"
|
||||
|
||||
[people.thajeztah]
|
||||
Name = "Sebastiaan van Stijn"
|
||||
Email = "github@gone.nl"
|
||||
|
||||
80
Makefile
80
Makefile
@@ -1,80 +1,40 @@
|
||||
ifneq (, $(BUILDX_BIN))
|
||||
export BUILDX_CMD = $(BUILDX_BIN)
|
||||
else ifneq (, $(shell docker buildx version))
|
||||
export BUILDX_CMD = docker buildx
|
||||
else ifneq (, $(shell which buildx))
|
||||
export BUILDX_CMD = $(which buildx)
|
||||
endif
|
||||
|
||||
export BUILDX_CMD ?= docker buildx
|
||||
|
||||
.PHONY: all
|
||||
all: binaries
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
./hack/build
|
||||
|
||||
.PHONY: shell
|
||||
shell:
|
||||
./hack/shell
|
||||
|
||||
.PHONY: binaries
|
||||
binaries:
|
||||
$(BUILDX_CMD) bake binaries
|
||||
./hack/binaries
|
||||
|
||||
.PHONY: binaries-cross
|
||||
binaries-cross:
|
||||
$(BUILDX_CMD) bake binaries-cross
|
||||
EXPORT_LOCAL=cross-out ./hack/cross
|
||||
|
||||
cross:
|
||||
./hack/cross
|
||||
|
||||
.PHONY: install
|
||||
install: binaries
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
install bin/build/buildx ~/.docker/cli-plugins/docker-buildx
|
||||
install bin/buildx ~/.docker/cli-plugins/docker-buildx
|
||||
|
||||
.PHONY: release
|
||||
release:
|
||||
./hack/release
|
||||
lint:
|
||||
./hack/lint
|
||||
|
||||
.PHONY: validate-all
|
||||
test:
|
||||
./hack/test
|
||||
|
||||
validate-vendor:
|
||||
./hack/validate-vendor
|
||||
|
||||
validate-docs:
|
||||
./hack/validate-docs
|
||||
|
||||
validate-all: lint test validate-vendor validate-docs
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
$(BUILDX_CMD) bake lint
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
$(BUILDX_CMD) bake test
|
||||
|
||||
.PHONY: validate-vendor
|
||||
validate-vendor:
|
||||
$(BUILDX_CMD) bake validate-vendor
|
||||
|
||||
.PHONY: validate-docs
|
||||
validate-docs:
|
||||
$(BUILDX_CMD) bake validate-docs
|
||||
|
||||
.PHONY: validate-authors
|
||||
validate-authors:
|
||||
$(BUILDX_CMD) bake validate-authors
|
||||
|
||||
.PHONY: test-driver
|
||||
test-driver:
|
||||
./hack/test-driver
|
||||
|
||||
.PHONY: vendor
|
||||
vendor:
|
||||
./hack/update-vendor
|
||||
|
||||
.PHONY: docs
|
||||
docs:
|
||||
./hack/update-docs
|
||||
|
||||
.PHONY: authors
|
||||
authors:
|
||||
$(BUILDX_CMD) bake update-authors
|
||||
generate-authors:
|
||||
./hack/generate-authors
|
||||
|
||||
.PHONY: mod-outdated
|
||||
mod-outdated:
|
||||
$(BUILDX_CMD) bake mod-outdated
|
||||
.PHONY: vendor lint shell binaries install binaries-cross validate-all generate-authors validate-docs docs
|
||||
|
||||
302
README.md
302
README.md
@@ -1,13 +1,11 @@
|
||||
# buildx
|
||||
|
||||
[](https://github.com/docker/buildx/releases/latest)
|
||||
[](https://pkg.go.dev/github.com/docker/buildx)
|
||||
[](https://github.com/docker/buildx/actions?query=workflow%3Abuild)
|
||||
[](https://goreportcard.com/report/github.com/docker/buildx)
|
||||
[](https://codecov.io/gh/docker/buildx)
|
||||
[](https://pkg.go.dev/github.com/docker/buildx)
|
||||
[](https://github.com/docker/buildx/actions?query=workflow%3Abuild)
|
||||
[](https://goreportcard.com/report/github.com/docker/buildx)
|
||||
[](https://codecov.io/gh/docker/buildx)
|
||||
|
||||
`buildx` is a Docker CLI plugin for extended build capabilities with
|
||||
[BuildKit](https://github.com/moby/buildkit).
|
||||
`buildx` is a Docker CLI plugin for extended build capabilities with [BuildKit](https://github.com/moby/buildkit).
|
||||
|
||||
Key features:
|
||||
|
||||
@@ -22,140 +20,74 @@ Key features:
|
||||
# Table of Contents
|
||||
|
||||
- [Installing](#installing)
|
||||
- [Windows and macOS](#windows-and-macos)
|
||||
- [Linux packages](#linux-packages)
|
||||
- [Manual download](#manual-download)
|
||||
- [Dockerfile](#dockerfile)
|
||||
- [Set buildx as the default builder](#set-buildx-as-the-default-builder)
|
||||
- [Docker](#docker)
|
||||
- [Binary release](#binary-release)
|
||||
- [From `Dockerfile`](#from-dockerfile)
|
||||
- [Building](#building)
|
||||
- [with Docker 18.09+](#with-docker-1809)
|
||||
- [with buildx or Docker 19.03](#with-buildx-or-docker-1903)
|
||||
- [Getting started](#getting-started)
|
||||
- [Building with buildx](#building-with-buildx)
|
||||
- [Working with builder instances](#working-with-builder-instances)
|
||||
- [Building multi-platform images](#building-multi-platform-images)
|
||||
- [Manuals](docs/manuals)
|
||||
- [High-level build options with Bake](docs/manuals/bake/index.md)
|
||||
- [Drivers](docs/manuals/drivers/index.md)
|
||||
- [Exporters](docs/manuals/exporters/index.md)
|
||||
- [Cache backends](docs/manuals/cache/backends/index.md)
|
||||
- [Guides](docs/guides)
|
||||
- [CI/CD](docs/guides/cicd.md)
|
||||
- [CNI networking](docs/guides/cni-networking.md)
|
||||
- [Using a custom network](docs/guides/custom-network.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)
|
||||
- [Resource limiting](docs/guides/resource-limiting.md)
|
||||
- [Reference](docs/reference/buildx.md)
|
||||
- [`buildx bake`](docs/reference/buildx_bake.md)
|
||||
- [`buildx build`](docs/reference/buildx_build.md)
|
||||
- [`buildx create`](docs/reference/buildx_create.md)
|
||||
- [`buildx du`](docs/reference/buildx_du.md)
|
||||
- [`buildx imagetools`](docs/reference/buildx_imagetools.md)
|
||||
- [`buildx imagetools create`](docs/reference/buildx_imagetools_create.md)
|
||||
- [`buildx imagetools inspect`](docs/reference/buildx_imagetools_inspect.md)
|
||||
- [`buildx inspect`](docs/reference/buildx_inspect.md)
|
||||
- [`buildx install`](docs/reference/buildx_install.md)
|
||||
- [High-level build options](#high-level-build-options)
|
||||
- [Documentation](docs/reference)
|
||||
- [`buildx build [OPTIONS] PATH | URL | -`](docs/reference/buildx_build.md)
|
||||
- [`buildx create [OPTIONS] [CONTEXT|ENDPOINT]`](docs/reference/buildx_create.md)
|
||||
- [`buildx use NAME`](docs/reference/buildx_use.md)
|
||||
- [`buildx inspect [NAME]`](docs/reference/buildx_inspect.md)
|
||||
- [`buildx ls`](docs/reference/buildx_ls.md)
|
||||
- [`buildx prune`](docs/reference/buildx_prune.md)
|
||||
- [`buildx rm`](docs/reference/buildx_rm.md)
|
||||
- [`buildx stop`](docs/reference/buildx_stop.md)
|
||||
- [`buildx uninstall`](docs/reference/buildx_uninstall.md)
|
||||
- [`buildx use`](docs/reference/buildx_use.md)
|
||||
- [`buildx version`](docs/reference/buildx_version.md)
|
||||
- [`buildx stop [NAME]`](docs/reference/buildx_stop.md)
|
||||
- [`buildx rm [NAME]`](docs/reference/buildx_rm.md)
|
||||
- [`buildx bake [OPTIONS] [TARGET...]`](docs/reference/buildx_bake.md)
|
||||
- [`buildx imagetools create [OPTIONS] [SOURCE] [SOURCE...]`](docs/reference/buildx_imagetools_create.md)
|
||||
- [`buildx imagetools inspect NAME`](docs/reference/buildx_imagetools_inspect.md)
|
||||
- [Setting buildx as default builder in Docker 19.03+](#setting-buildx-as-default-builder-in-docker-1903)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
|
||||
# Installing
|
||||
|
||||
Using `buildx` as a docker CLI plugin requires using Docker 19.03 or newer.
|
||||
A limited set of functionality works with older versions of Docker when
|
||||
invoking the binary directly.
|
||||
Using `buildx` as a docker CLI plugin requires using Docker 19.03 or newer. A limited set of functionality works with older versions of Docker when invoking the binary directly.
|
||||
|
||||
## Windows and macOS
|
||||
### Docker
|
||||
|
||||
Docker Buildx is included in [Docker Desktop](https://docs.docker.com/desktop/)
|
||||
for Windows and macOS.
|
||||
`buildx` comes bundled with Docker Desktop and in latest Docker CE packages, but may not be included in all Linux distros (in which case follow the binary release instructions).
|
||||
|
||||
## Linux packages
|
||||
### Binary release
|
||||
|
||||
Docker Linux packages also include Docker Buildx when installed using the
|
||||
[DEB or RPM packages](https://docs.docker.com/engine/install/).
|
||||
Download the latest binary release from https://github.com/docker/buildx/releases/latest and copy it to `~/.docker/cli-plugins` folder with name `docker-buildx`.
|
||||
|
||||
## Manual download
|
||||
Change the permission to execute:
|
||||
```sh
|
||||
chmod a+x ~/.docker/cli-plugins/docker-buildx
|
||||
```
|
||||
|
||||
> **Important**
|
||||
>
|
||||
> This section is for unattended installation of the buildx component. These
|
||||
> instructions are mostly suitable for testing purposes. We do not recommend
|
||||
> installing buildx using manual download in production environments as they
|
||||
> will not be updated automatically with security updates.
|
||||
>
|
||||
> On Windows and macOS, we recommend that you install [Docker Desktop](https://docs.docker.com/desktop/)
|
||||
> instead. For Linux, we recommend that you follow the [instructions specific for your distribution](#linux-packages).
|
||||
### From `Dockerfile`
|
||||
|
||||
You can also download the latest binary from the [GitHub releases page](https://github.com/docker/buildx/releases/latest).
|
||||
Here is how to use buildx inside a Dockerfile through the [`docker/buildx-bin`](https://hub.docker.com/r/docker/buildx-bin) image:
|
||||
|
||||
Rename the relevant binary and copy it to the destination matching your OS:
|
||||
|
||||
| OS | Binary name | Destination folder |
|
||||
| -------- | -------------------- | -----------------------------------------|
|
||||
| Linux | `docker-buildx` | `$HOME/.docker/cli-plugins` |
|
||||
| macOS | `docker-buildx` | `$HOME/.docker/cli-plugins` |
|
||||
| Windows | `docker-buildx.exe` | `%USERPROFILE%\.docker\cli-plugins` |
|
||||
|
||||
Or copy it into one of these folders for installing it system-wide.
|
||||
|
||||
On Unix environments:
|
||||
|
||||
* `/usr/local/lib/docker/cli-plugins` OR `/usr/local/libexec/docker/cli-plugins`
|
||||
* `/usr/lib/docker/cli-plugins` OR `/usr/libexec/docker/cli-plugins`
|
||||
|
||||
On Windows:
|
||||
|
||||
* `C:\ProgramData\Docker\cli-plugins`
|
||||
* `C:\Program Files\Docker\cli-plugins`
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> On Unix environments, it may also be necessary to make it executable with `chmod +x`:
|
||||
> ```shell
|
||||
> $ chmod +x ~/.docker/cli-plugins/docker-buildx
|
||||
> ```
|
||||
|
||||
## Dockerfile
|
||||
|
||||
Here is how to install and use Buildx inside a Dockerfile through the
|
||||
[`docker/buildx-bin`](https://hub.docker.com/r/docker/buildx-bin) image:
|
||||
|
||||
```dockerfile
|
||||
# syntax=docker/dockerfile:1
|
||||
```Dockerfile
|
||||
FROM docker
|
||||
COPY --from=docker/buildx-bin /buildx /usr/libexec/docker/cli-plugins/docker-buildx
|
||||
COPY --from=docker/buildx-bin:latest /buildx /usr/libexec/docker/cli-plugins/docker-buildx
|
||||
RUN docker buildx version
|
||||
```
|
||||
|
||||
# Set buildx as the default builder
|
||||
|
||||
Running the command [`docker buildx install`](docs/reference/buildx_install.md)
|
||||
sets up docker builder command as an alias to `docker buildx build`. This
|
||||
results in the ability to have `docker build` use the current buildx builder.
|
||||
|
||||
To remove this alias, run [`docker buildx uninstall`](docs/reference/buildx_uninstall.md).
|
||||
|
||||
# Building
|
||||
|
||||
```console
|
||||
# Buildx 0.6+
|
||||
$ docker buildx bake "https://github.com/docker/buildx.git"
|
||||
$ mkdir -p ~/.docker/cli-plugins
|
||||
$ mv ./bin/buildx ~/.docker/cli-plugins/docker-buildx
|
||||
|
||||
# Docker 19.03+
|
||||
$ DOCKER_BUILDKIT=1 docker build --platform=local -o . "https://github.com/docker/buildx.git"
|
||||
### with buildx or Docker 19.03+
|
||||
```
|
||||
$ export DOCKER_BUILDKIT=1
|
||||
$ docker build --platform=local -o . git://github.com/docker/buildx
|
||||
$ mkdir -p ~/.docker/cli-plugins
|
||||
$ mv buildx ~/.docker/cli-plugins/docker-buildx
|
||||
```
|
||||
|
||||
# Local
|
||||
$ git clone https://github.com/docker/buildx.git && cd buildx
|
||||
### with Docker 18.09+
|
||||
```
|
||||
$ git clone git://github.com/docker/buildx && cd buildx
|
||||
$ make install
|
||||
```
|
||||
|
||||
@@ -163,146 +95,65 @@ $ make install
|
||||
|
||||
## Building with buildx
|
||||
|
||||
Buildx is a Docker CLI plugin that extends the `docker build` command with the
|
||||
full support of the features provided by [Moby BuildKit](https://github.com/moby/buildkit)
|
||||
builder toolkit. It provides the same user experience as `docker build` with
|
||||
many new features like creating scoped builder instances and building against
|
||||
multiple nodes concurrently.
|
||||
Buildx is a Docker CLI plugin that extends the `docker build` command with the full support of the features provided by [Moby BuildKit](https://github.com/moby/buildkit) builder toolkit. It provides the same user experience as `docker build` with many new features like creating scoped builder instances and building against multiple nodes concurrently.
|
||||
|
||||
After installation, buildx can be accessed through the `docker buildx` command
|
||||
with Docker 19.03. `docker buildx build` is the command for starting a new
|
||||
build. With Docker versions older than 19.03 buildx binary can be called
|
||||
directly to access the `docker buildx` subcommands.
|
||||
After installation, buildx can be accessed through the `docker buildx` command with Docker 19.03. `docker buildx build` is the command for starting a new build. With Docker versions older than 19.03 buildx binary can be called directly to access the `docker buildx` subcommands.
|
||||
|
||||
```console
|
||||
```
|
||||
$ docker buildx build .
|
||||
[+] Building 8.4s (23/32)
|
||||
=> ...
|
||||
```
|
||||
|
||||
Buildx will always build using the BuildKit engine and does not require
|
||||
`DOCKER_BUILDKIT=1` environment variable for starting builds.
|
||||
|
||||
The `docker buildx build` command supports features available for `docker build`,
|
||||
including features such as outputs configuration, inline build caching, and
|
||||
specifying target platform. In addition, Buildx also supports new features that
|
||||
are not yet available for regular `docker build` like building manifest lists,
|
||||
distributed caching, and exporting build results to OCI image tarballs.
|
||||
Buildx will always build using the BuildKit engine and does not require `DOCKER_BUILDKIT=1` environment variable for starting builds.
|
||||
|
||||
Buildx is flexible and can be run in different configurations that are exposed
|
||||
through various "drivers". Each driver defines how and where a build should
|
||||
run, and have different feature sets.
|
||||
Buildx build command supports the features available for `docker build` including the new features in Docker 19.03 such as outputs configuration, inline build caching or specifying target platform. In addition, buildx supports new features not yet available for regular `docker build` like building manifest lists, distributed caching, exporting build results to OCI image tarballs etc.
|
||||
|
||||
We currently support the following drivers:
|
||||
- The `docker` driver ([guide](docs/manuals/drivers/docker.md), [reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver))
|
||||
- The `docker-container` driver ([guide](docs/manuals/drivers/docker-container.md), [reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver))
|
||||
- The `kubernetes` driver ([guide](docs/manuals/drivers/kubernetes.md), [reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver))
|
||||
- The `remote` driver ([guide](docs/manuals/drivers/remote.md))
|
||||
Buildx is supposed to be flexible and can be run in different configurations that are exposed through a driver concept. Currently, we support a "docker" driver that uses the BuildKit library bundled into the Docker daemon binary, and a "docker-container" driver that automatically launches BuildKit inside a Docker container. We plan to add more drivers in the future, for example, one that would allow running buildx inside an (unprivileged) container.
|
||||
|
||||
The user experience of using buildx is very similar across drivers, but there are some features that are not currently supported by the "docker" driver, because the BuildKit library bundled into docker daemon currently uses a different storage component. In contrast, all images built with "docker" driver 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 with `--output`.
|
||||
|
||||
For more information on drivers, see the [drivers guide](docs/manuals/drivers/index.md).
|
||||
|
||||
## Working with builder instances
|
||||
|
||||
By default, buildx will initially use the `docker` driver if it is supported,
|
||||
providing a very similar user experience to the native `docker build`. Note that
|
||||
you must use a local shared daemon to build your applications.
|
||||
By default, buildx will initially use the "docker" driver if it is supported, providing a very similar user experience to the native `docker build`. But using a local shared daemon is only one way to build your applications.
|
||||
|
||||
Buildx allows you to create new instances of isolated builders. This can be
|
||||
used for getting a scoped environment for your CI builds that does not change
|
||||
the state of the shared daemon or for isolating the builds for different
|
||||
projects. You can create a new instance for a set of remote nodes, forming a
|
||||
build farm, and quickly switch between them.
|
||||
Buildx allows you to create new instances of isolated builders. This can be used for getting a scoped environment for your CI builds that does not change the state of the shared daemon or for isolating the builds for different projects. You can create a new instance for a set of remote nodes, forming a build farm, and quickly switch between them.
|
||||
|
||||
You can create new instances using the [`docker buildx create`](docs/reference/buildx_create.md)
|
||||
command. This creates a new builder instance with a single node based on your
|
||||
current configuration.
|
||||
New instances can be created with `docker buildx create` command. This will create a new builder instance with a single node based on your current configuration. To use a remote node you can specify the `DOCKER_HOST` or remote context name while creating the new builder. After creating a new instance you can manage its lifecycle with the `inspect`, `stop` and `rm` commands and list all available builders with `ls`. After creating a new builder you can also append new nodes to it.
|
||||
|
||||
To use a remote node you can specify the `DOCKER_HOST` or the remote context name
|
||||
while creating the new builder. After creating a new instance, you can manage its
|
||||
lifecycle using the [`docker buildx inspect`](docs/reference/buildx_inspect.md),
|
||||
[`docker buildx stop`](docs/reference/buildx_stop.md), and
|
||||
[`docker buildx rm`](docs/reference/buildx_rm.md) commands. To list all
|
||||
available builders, use [`buildx ls`](docs/reference/buildx_ls.md). After
|
||||
creating a new builder you can also append new nodes to it.
|
||||
To switch between different builders, use `docker buildx use <name>`. After running this command the build commands would automatically keep using this builder.
|
||||
|
||||
To switch between different builders, use [`docker buildx use <name>`](docs/reference/buildx_use.md).
|
||||
After running this command, the build commands will automatically use this
|
||||
builder.
|
||||
|
||||
Docker also features a [`docker context`](https://docs.docker.com/engine/reference/commandline/context/)
|
||||
command that can be used for giving names for remote Docker API endpoints.
|
||||
Buildx integrates with `docker context` so that all of your contexts
|
||||
automatically get a default builder instance. While creating a new builder
|
||||
instance or when adding a node to it you can also set the context name as the
|
||||
target.
|
||||
Docker 19.03 also features a new `docker context` command that can be used for giving names for remote Docker API endpoints. Buildx integrates with `docker context` so that all of your contexts automatically get a default builder instance. While creating a new builder instance or when adding a node to it you can also set the context name as the target.
|
||||
|
||||
## Building multi-platform images
|
||||
|
||||
BuildKit is designed to work well for building for multiple platforms and not
|
||||
only for the architecture and operating system that the user invoking the build
|
||||
happens to run.
|
||||
BuildKit is designed to work well for building for multiple platforms and not only for the architecture and operating system that the user invoking the build happens to run.
|
||||
|
||||
When you invoke a build, you can set the `--platform` flag to specify the target
|
||||
platform for the build output, (for example, `linux/amd64`, `linux/arm64`, or
|
||||
`darwin/amd64`).
|
||||
When invoking a build, the `--platform` flag can be used to specify the target platform for the build output, (e.g. linux/amd64, linux/arm64, darwin/amd64). When the current builder instance is backed by the "docker-container" driver, multiple platforms can be specified together. In this case, a manifest list will be built, containing images for all of the specified architectures. When this image is used in `docker run` or `docker service`, Docker will pick the correct image based on the node’s platform.
|
||||
|
||||
When the current builder instance is backed by the `docker-container` or
|
||||
`kubernetes` driver, you can specify multiple platforms together. In this case,
|
||||
it builds a manifest list which contains images for all specified architectures.
|
||||
When you use this image in [`docker run`](https://docs.docker.com/engine/reference/commandline/run/)
|
||||
or [`docker service`](https://docs.docker.com/engine/reference/commandline/service/),
|
||||
Docker picks the correct image based on the node's platform.
|
||||
Multi-platform images can be built by mainly three different strategies that are all supported by buildx and Dockerfiles. You can use the QEMU emulation support in the kernel, build on multiple native nodes using the same builder instance or use a stage in Dockerfile to cross-compile to different architectures.
|
||||
|
||||
You can build multi-platform images using three different strategies that are
|
||||
supported by Buildx and Dockerfiles:
|
||||
QEMU is the easiest way to get started if your node already supports it (e.g. if you are using Docker Desktop). It requires no changes to your Dockerfile and BuildKit will automatically detect the secondary architectures that are available. When BuildKit needs to run a binary for a different architecture it will automatically load it through a binary registered in the binfmt_misc handler. For QEMU binaries registered with binfmt_misc on the host OS to work transparently inside containers they must be registered with the fix_binary flag. This requires a kernel >= 4.8 and binfmt-support >= 2.1.7. You can check for proper registration by checking if `F` is among the flags in `/proc/sys/fs/binfmt_misc/qemu-*`. While Docker Desktop comes preconfigured with binfmt_misc support for additional platforms, for other installations it likely needs to be installed using [`tonistiigi/binfmt`](https://github.com/tonistiigi/binfmt) image.
|
||||
|
||||
1. Using the QEMU emulation support in the kernel
|
||||
2. Building on multiple native nodes using the same builder instance
|
||||
3. Using a stage in Dockerfile to cross-compile to different architectures
|
||||
|
||||
QEMU is the easiest way to get started if your node already supports it (for
|
||||
example. if you are using Docker Desktop). It requires no changes to your
|
||||
Dockerfile and BuildKit automatically detects the secondary architectures that
|
||||
are available. When BuildKit needs to run a binary for a different architecture,
|
||||
it automatically loads it through a binary registered in the `binfmt_misc`
|
||||
handler.
|
||||
|
||||
For QEMU binaries registered with `binfmt_misc` on the host OS to work
|
||||
transparently inside containers they must be registered with the `fix_binary`
|
||||
flag. This requires a kernel >= 4.8 and binfmt-support >= 2.1.7. You can check
|
||||
for proper registration by checking if `F` is among the flags in
|
||||
`/proc/sys/fs/binfmt_misc/qemu-*`. While Docker Desktop comes preconfigured
|
||||
with `binfmt_misc` support for additional platforms, for other installations
|
||||
it likely needs to be installed using [`tonistiigi/binfmt`](https://github.com/tonistiigi/binfmt)
|
||||
image.
|
||||
|
||||
```console
|
||||
```
|
||||
$ docker run --privileged --rm tonistiigi/binfmt --install all
|
||||
```
|
||||
|
||||
Using multiple native nodes provide better support for more complicated cases
|
||||
that are not handled by QEMU and generally have better performance. You can
|
||||
add additional nodes to the builder instance using the `--append` flag.
|
||||
Using multiple native nodes provides better support for more complicated cases not handled by QEMU and generally have better performance. Additional nodes can be added to the builder instance with `--append` flag.
|
||||
|
||||
Assuming contexts `node-amd64` and `node-arm64` exist in `docker context ls`;
|
||||
|
||||
```console
|
||||
```
|
||||
# assuming contexts node-amd64 and node-arm64 exist in "docker context ls"
|
||||
$ docker buildx create --use --name mybuild node-amd64
|
||||
mybuild
|
||||
$ docker buildx create --append --name mybuild node-arm64
|
||||
$ docker buildx build --platform linux/amd64,linux/arm64 .
|
||||
```
|
||||
|
||||
Finally, depending on your project, the language that you use may have good
|
||||
support for cross-compilation. In that case, multi-stage builds in Dockerfiles
|
||||
can be effectively used to build binaries for the platform specified with
|
||||
`--platform` using the native architecture of the build node. A list of build
|
||||
arguments like `BUILDPLATFORM` and `TARGETPLATFORM` is available automatically
|
||||
inside your Dockerfile and can be leveraged by the processes running as part
|
||||
of your build.
|
||||
Finally, depending on your project, the language that you use may have good support for cross-compilation. In that case, multi-stage builds in Dockerfiles can be effectively used to build binaries for the platform specified with `--platform` using the native architecture of the build node. List of build arguments like `BUILDPLATFORM` and `TARGETPLATFORM` are available automatically inside your Dockerfile and can be leveraged by the processes running as part of your build.
|
||||
|
||||
```dockerfile
|
||||
# syntax=docker/dockerfile:1
|
||||
```
|
||||
FROM --platform=$BUILDPLATFORM golang:alpine AS build
|
||||
ARG TARGETPLATFORM
|
||||
ARG BUILDPLATFORM
|
||||
@@ -311,12 +162,25 @@ FROM alpine
|
||||
COPY --from=build /log /log
|
||||
```
|
||||
|
||||
You can also use [`tonistiigi/xx`](https://github.com/tonistiigi/xx) Dockerfile
|
||||
cross-compilation helpers for more advanced use-cases.
|
||||
|
||||
## High-level build options
|
||||
|
||||
See [`docs/manuals/bake/index.md`](docs/manuals/bake/index.md) for more details.
|
||||
Buildx also aims to provide support for higher 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 has great support for efficiently handling multiple concurrent build requests and deduplicating work. While build commands can be combined with general-purpose command runners (eg. make), these tools generally invoke builds in sequence and therefore can’t 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`.
|
||||
|
||||
Currently, the bake command supports building images from compose files, similar to `compose 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.
|
||||
|
||||
[`buildx bake` Reference Docs](docs/reference/buildx_bake.md)
|
||||
|
||||
# Setting buildx as default builder in Docker 19.03+
|
||||
|
||||
Running `docker buildx install` sets up `docker builder` command as an alias to `docker buildx`. This results in the ability to have `docker build` use the current buildx builder.
|
||||
|
||||
To remove this alias, you can run `docker buildx uninstall`.
|
||||
|
||||
|
||||
# Contributing
|
||||
|
||||
|
||||
682
bake/bake.go
682
bake/bake.go
@@ -2,14 +2,11 @@ package bake
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -17,32 +14,21 @@ import (
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/util/buildflags"
|
||||
"github.com/docker/buildx/util/platformutil"
|
||||
"github.com/docker/cli/cli/config"
|
||||
"github.com/docker/docker/builder/remotecontext/urlutil"
|
||||
"github.com/docker/docker/pkg/urlutil"
|
||||
hcl "github.com/hashicorp/hcl/v2"
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/session/auth/authprovider"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
httpPrefix = regexp.MustCompile(`^https?://`)
|
||||
gitURLPathWithFragmentSuffix = regexp.MustCompile(`\.git(?:#.+)?$`)
|
||||
|
||||
validTargetNameChars = `[a-zA-Z0-9_-]+`
|
||||
targetNamePattern = regexp.MustCompile(`^` + validTargetNameChars + `$`)
|
||||
)
|
||||
var httpPrefix = regexp.MustCompile(`^https?://`)
|
||||
var gitURLPathWithFragmentSuffix = regexp.MustCompile(`\.git(?:#.+)?$`)
|
||||
|
||||
type File struct {
|
||||
Name string
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type Override struct {
|
||||
Value string
|
||||
ArrValue []string
|
||||
}
|
||||
|
||||
func defaultFilenames() []string {
|
||||
return []string{
|
||||
"docker-compose.yml", // support app
|
||||
@@ -63,132 +49,41 @@ func ReadLocalFiles(names []string) ([]File, error) {
|
||||
out := make([]File, 0, len(names))
|
||||
|
||||
for _, n := range names {
|
||||
var dt []byte
|
||||
var err error
|
||||
if n == "-" {
|
||||
dt, err = io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
dt, err = os.ReadFile(n)
|
||||
if err != nil {
|
||||
if isDefault && errors.Is(err, os.ErrNotExist) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
dt, err := ioutil.ReadFile(n)
|
||||
if err != nil {
|
||||
if isDefault && errors.Is(err, os.ErrNotExist) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, File{Name: n, Data: dt})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func ReadTargets(ctx context.Context, files []File, targets, overrides []string, defaults map[string]string) (map[string]*Target, map[string]*Group, error) {
|
||||
func ReadTargets(ctx context.Context, files []File, targets, overrides []string, defaults map[string]string) (map[string]*Target, error) {
|
||||
c, err := ParseFiles(files, defaults)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for i, t := range targets {
|
||||
targets[i] = sanitizeTargetName(t)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
o, err := c.newOverrides(overrides)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
m := map[string]*Target{}
|
||||
n := map[string]*Group{}
|
||||
for _, target := range targets {
|
||||
ts, gs := c.ResolveGroup(target)
|
||||
for _, tname := range ts {
|
||||
t, err := c.ResolveTarget(tname, o)
|
||||
for _, n := range targets {
|
||||
for _, n := range c.ResolveGroup(n) {
|
||||
t, err := c.ResolveTarget(n, o)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
if t != nil {
|
||||
m[tname] = t
|
||||
}
|
||||
}
|
||||
for _, gname := range gs {
|
||||
for _, group := range c.Groups {
|
||||
if group.Name == gname {
|
||||
n[gname] = group
|
||||
break
|
||||
}
|
||||
m[n] = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, target := range targets {
|
||||
if target == "default" {
|
||||
continue
|
||||
}
|
||||
if _, ok := n["default"]; !ok {
|
||||
n["default"] = &Group{Name: "default"}
|
||||
}
|
||||
n["default"].Targets = append(n["default"].Targets, target)
|
||||
}
|
||||
if g, ok := n["default"]; ok {
|
||||
g.Targets = dedupSlice(g.Targets)
|
||||
}
|
||||
|
||||
for name, t := range m {
|
||||
if err := c.loadLinks(name, t, m, o, nil); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return m, n, nil
|
||||
}
|
||||
|
||||
func dedupSlice(s []string) []string {
|
||||
if len(s) == 0 {
|
||||
return s
|
||||
}
|
||||
var res []string
|
||||
seen := make(map[string]struct{})
|
||||
for _, val := range s {
|
||||
if _, ok := seen[val]; !ok {
|
||||
res = append(res, val)
|
||||
seen[val] = struct{}{}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func dedupMap(ms ...map[string]string) map[string]string {
|
||||
if len(ms) == 0 {
|
||||
return nil
|
||||
}
|
||||
res := map[string]string{}
|
||||
for _, m := range ms {
|
||||
if len(m) == 0 {
|
||||
continue
|
||||
}
|
||||
for k, v := range m {
|
||||
if _, ok := res[k]; !ok {
|
||||
res[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func sliceToMap(env []string) (res map[string]string) {
|
||||
res = make(map[string]string)
|
||||
for _, s := range env {
|
||||
kv := strings.SplitN(s, "=", 2)
|
||||
key := kv[0]
|
||||
switch {
|
||||
case len(kv) == 1:
|
||||
res[key] = ""
|
||||
default:
|
||||
res[key] = kv[1]
|
||||
}
|
||||
}
|
||||
return
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func ParseFiles(files []File, defaults map[string]string) (_ *Config, err error) {
|
||||
@@ -197,15 +92,15 @@ func ParseFiles(files []File, defaults map[string]string) (_ *Config, err error)
|
||||
}()
|
||||
|
||||
var c Config
|
||||
var composeFiles []File
|
||||
var hclFiles []*hcl.File
|
||||
var fs []*hcl.File
|
||||
for _, f := range files {
|
||||
isCompose, composeErr := validateComposeFile(f.Data, f.Name)
|
||||
cfg, isCompose, composeErr := ParseComposeFile(f.Data, f.Name)
|
||||
if isCompose {
|
||||
if composeErr != nil {
|
||||
return nil, composeErr
|
||||
}
|
||||
composeFiles = append(composeFiles, f)
|
||||
c = mergeConfig(c, *cfg)
|
||||
c = dedupeConfig(c)
|
||||
}
|
||||
if !isCompose {
|
||||
hf, isHCL, err := ParseHCLFile(f.Data, f.Name)
|
||||
@@ -213,7 +108,7 @@ func ParseFiles(files []File, defaults map[string]string) (_ *Config, err error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hclFiles = append(hclFiles, hf)
|
||||
fs = append(fs, hf)
|
||||
} else if composeErr != nil {
|
||||
return nil, fmt.Errorf("failed to parse %s: parsing yaml: %v, parsing hcl: %w", f.Name, composeErr, err)
|
||||
} else {
|
||||
@@ -222,43 +117,26 @@ func ParseFiles(files []File, defaults map[string]string) (_ *Config, err error)
|
||||
}
|
||||
}
|
||||
|
||||
if len(composeFiles) > 0 {
|
||||
cfg, cmperr := ParseComposeFiles(composeFiles)
|
||||
if cmperr != nil {
|
||||
return nil, errors.Wrap(cmperr, "failed to parse compose file")
|
||||
}
|
||||
c = mergeConfig(c, *cfg)
|
||||
c = dedupeConfig(c)
|
||||
}
|
||||
|
||||
if len(hclFiles) > 0 {
|
||||
if err := hclparser.Parse(hcl.MergeFiles(hclFiles), hclparser.Opt{
|
||||
LookupVar: os.LookupEnv,
|
||||
Vars: defaults,
|
||||
ValidateLabel: validateTargetName,
|
||||
if len(fs) > 0 {
|
||||
if err := hclparser.Parse(hcl.MergeFiles(fs), hclparser.Opt{
|
||||
LookupVar: os.LookupEnv,
|
||||
Vars: defaults,
|
||||
}, &c); err.HasErrors() {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func dedupeConfig(c Config) Config {
|
||||
c2 := c
|
||||
c2.Groups = make([]*Group, 0, len(c2.Groups))
|
||||
for _, g := range c.Groups {
|
||||
g1 := *g
|
||||
g1.Targets = dedupSlice(g1.Targets)
|
||||
c2.Groups = append(c2.Groups, &g1)
|
||||
}
|
||||
c2.Targets = make([]*Target, 0, len(c2.Targets))
|
||||
mt := map[string]*Target{}
|
||||
m := map[string]*Target{}
|
||||
for _, t := range c.Targets {
|
||||
if t2, ok := mt[t.Name]; ok {
|
||||
if t2, ok := m[t.Name]; ok {
|
||||
t2.Merge(t)
|
||||
} else {
|
||||
mt[t.Name] = t
|
||||
m[t.Name] = t
|
||||
c2.Targets = append(c2.Targets, t)
|
||||
}
|
||||
}
|
||||
@@ -269,9 +147,22 @@ func ParseFile(dt []byte, fn string) (*Config, error) {
|
||||
return ParseFiles([]File{{Data: dt, Name: fn}}, nil)
|
||||
}
|
||||
|
||||
func ParseComposeFile(dt []byte, fn string) (*Config, bool, error) {
|
||||
fnl := strings.ToLower(fn)
|
||||
if strings.HasSuffix(fnl, ".yml") || strings.HasSuffix(fnl, ".yaml") {
|
||||
cfg, err := ParseCompose(dt)
|
||||
return cfg, true, err
|
||||
}
|
||||
if strings.HasSuffix(fnl, ".json") || strings.HasSuffix(fnl, ".hcl") {
|
||||
return nil, false, nil
|
||||
}
|
||||
cfg, err := ParseCompose(dt)
|
||||
return cfg, err == nil, err
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Groups []*Group `json:"group" hcl:"group,block" cty:"group"`
|
||||
Targets []*Target `json:"target" hcl:"target,block" cty:"target"`
|
||||
Groups []*Group `json:"group" hcl:"group,block"`
|
||||
Targets []*Target `json:"target" hcl:"target,block"`
|
||||
}
|
||||
|
||||
func mergeConfig(c1, c2 Config) Config {
|
||||
@@ -349,46 +240,10 @@ func (c Config) expandTargets(pattern string) ([]string, error) {
|
||||
return names, nil
|
||||
}
|
||||
|
||||
func (c Config) loadLinks(name string, t *Target, m map[string]*Target, o map[string]map[string]Override, visited []string) error {
|
||||
visited = append(visited, name)
|
||||
for _, v := range t.Contexts {
|
||||
if strings.HasPrefix(v, "target:") {
|
||||
target := strings.TrimPrefix(v, "target:")
|
||||
if target == t.Name {
|
||||
return errors.Errorf("target %s cannot link to itself", target)
|
||||
}
|
||||
for _, v := range visited {
|
||||
if v == target {
|
||||
return errors.Errorf("infinite loop from %s to %s", name, target)
|
||||
}
|
||||
}
|
||||
t2, ok := m[target]
|
||||
if !ok {
|
||||
var err error
|
||||
t2, err = c.ResolveTarget(target, o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t2.Outputs = nil
|
||||
t2.linked = true
|
||||
m[target] = t2
|
||||
}
|
||||
if err := c.loadLinks(target, t2, m, o, visited); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(t.Platforms) > 1 && len(t2.Platforms) > 1 {
|
||||
if !sliceEqual(t.Platforms, t2.Platforms) {
|
||||
return errors.Errorf("target %s can't be used by %s because it is defined for different platforms %v and %v", target, name, t2.Platforms, t.Platforms)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Config) newOverrides(v []string) (map[string]map[string]Override, error) {
|
||||
m := map[string]map[string]Override{}
|
||||
func (c Config) newOverrides(v []string) (map[string]*Target, error) {
|
||||
m := map[string]*Target{}
|
||||
for _, v := range v {
|
||||
|
||||
parts := strings.SplitN(v, "=", 2)
|
||||
keys := strings.SplitN(parts[0], ".", 3)
|
||||
if len(keys) < 2 {
|
||||
@@ -405,64 +260,85 @@ func (c Config) newOverrides(v []string) (map[string]map[string]Override, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kk := strings.SplitN(parts[0], ".", 2)
|
||||
|
||||
for _, name := range names {
|
||||
t, ok := m[name]
|
||||
if !ok {
|
||||
t = map[string]Override{}
|
||||
m[name] = t
|
||||
t = &Target{}
|
||||
}
|
||||
|
||||
o := t[kk[1]]
|
||||
|
||||
switch keys[1] {
|
||||
case "output", "cache-to", "cache-from", "tags", "platform", "secrets", "ssh", "attest":
|
||||
if len(parts) == 2 {
|
||||
o.ArrValue = append(o.ArrValue, parts[1])
|
||||
}
|
||||
case "context":
|
||||
t.Context = &parts[1]
|
||||
case "dockerfile":
|
||||
t.Dockerfile = &parts[1]
|
||||
case "args":
|
||||
if len(keys) != 3 {
|
||||
return nil, errors.Errorf("invalid key %s, args requires name", parts[0])
|
||||
}
|
||||
if t.Args == nil {
|
||||
t.Args = map[string]string{}
|
||||
}
|
||||
if len(parts) < 2 {
|
||||
v, ok := os.LookupEnv(keys[2])
|
||||
if !ok {
|
||||
continue
|
||||
if ok {
|
||||
t.Args[keys[2]] = v
|
||||
}
|
||||
o.Value = v
|
||||
} else {
|
||||
t.Args[keys[2]] = parts[1]
|
||||
}
|
||||
fallthrough
|
||||
case "contexts":
|
||||
case "labels":
|
||||
if len(keys) != 3 {
|
||||
return nil, errors.Errorf("invalid key %s, contexts requires name", parts[0])
|
||||
return nil, errors.Errorf("invalid key %s, lanels requires name", parts[0])
|
||||
}
|
||||
fallthrough
|
||||
if t.Labels == nil {
|
||||
t.Labels = map[string]string{}
|
||||
}
|
||||
t.Labels[keys[2]] = parts[1]
|
||||
case "tags":
|
||||
t.Tags = append(t.Tags, parts[1])
|
||||
case "cache-from":
|
||||
t.CacheFrom = append(t.CacheFrom, parts[1])
|
||||
case "cache-to":
|
||||
t.CacheTo = append(t.CacheTo, parts[1])
|
||||
case "target":
|
||||
s := parts[1]
|
||||
t.Target = &s
|
||||
case "secrets":
|
||||
t.Secrets = append(t.Secrets, parts[1])
|
||||
case "ssh":
|
||||
t.SSH = append(t.SSH, parts[1])
|
||||
case "platform":
|
||||
t.Platforms = append(t.Platforms, parts[1])
|
||||
case "output":
|
||||
t.Outputs = append(t.Outputs, parts[1])
|
||||
case "no-cache":
|
||||
noCache, err := strconv.ParseBool(parts[1])
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("invalid value %s for boolean key no-cache", parts[1])
|
||||
}
|
||||
t.NoCache = &noCache
|
||||
case "pull":
|
||||
pull, err := strconv.ParseBool(parts[1])
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("invalid value %s for boolean key pull", parts[1])
|
||||
}
|
||||
t.Pull = &pull
|
||||
default:
|
||||
if len(parts) == 2 {
|
||||
o.Value = parts[1]
|
||||
}
|
||||
return nil, errors.Errorf("unknown key: %s", keys[1])
|
||||
}
|
||||
|
||||
t[kk[1]] = o
|
||||
m[name] = t
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c Config) ResolveGroup(name string) ([]string, []string) {
|
||||
targets, groups := c.group(name, map[string]visit{})
|
||||
return dedupSlice(targets), dedupSlice(groups)
|
||||
func (c Config) ResolveGroup(name string) []string {
|
||||
return c.group(name, map[string]struct{}{})
|
||||
}
|
||||
|
||||
type visit struct {
|
||||
target []string
|
||||
group []string
|
||||
}
|
||||
|
||||
func (c Config) group(name string, visited map[string]visit) ([]string, []string) {
|
||||
if v, ok := visited[name]; ok {
|
||||
return v.target, v.group
|
||||
func (c Config) group(name string, visited map[string]struct{}) []string {
|
||||
if _, ok := visited[name]; ok {
|
||||
return nil
|
||||
}
|
||||
var g *Group
|
||||
for _, group := range c.Groups {
|
||||
@@ -472,32 +348,21 @@ func (c Config) group(name string, visited map[string]visit) ([]string, []string
|
||||
}
|
||||
}
|
||||
if g == nil {
|
||||
return []string{name}, nil
|
||||
return []string{name}
|
||||
}
|
||||
visited[name] = visit{}
|
||||
visited[name] = struct{}{}
|
||||
targets := make([]string, 0, len(g.Targets))
|
||||
groups := []string{name}
|
||||
for _, t := range g.Targets {
|
||||
ttarget, tgroup := c.group(t, visited)
|
||||
if len(ttarget) > 0 {
|
||||
targets = append(targets, ttarget...)
|
||||
} else {
|
||||
targets = append(targets, t)
|
||||
}
|
||||
if len(tgroup) > 0 {
|
||||
groups = append(groups, tgroup...)
|
||||
}
|
||||
targets = append(targets, c.group(t, visited)...)
|
||||
}
|
||||
visited[name] = visit{target: targets, group: groups}
|
||||
return targets, groups
|
||||
return targets
|
||||
}
|
||||
|
||||
func (c Config) ResolveTarget(name string, overrides map[string]map[string]Override) (*Target, error) {
|
||||
t, err := c.target(name, map[string]*Target{}, overrides)
|
||||
func (c Config) ResolveTarget(name string, overrides map[string]*Target) (*Target, error) {
|
||||
t, err := c.target(name, map[string]struct{}{}, overrides)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.Inherits = nil
|
||||
if t.Context == nil {
|
||||
s := "."
|
||||
t.Context = &s
|
||||
@@ -509,11 +374,11 @@ func (c Config) ResolveTarget(name string, overrides map[string]map[string]Overr
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (c Config) target(name string, visited map[string]*Target, overrides map[string]map[string]Override) (*Target, error) {
|
||||
if t, ok := visited[name]; ok {
|
||||
return t, nil
|
||||
func (c Config) target(name string, visited map[string]struct{}, overrides map[string]*Target) (*Target, error) {
|
||||
if _, ok := visited[name]; ok {
|
||||
return nil, nil
|
||||
}
|
||||
visited[name] = nil
|
||||
visited[name] = struct{}{}
|
||||
var t *Target
|
||||
for _, target := range c.Targets {
|
||||
if target.Name == name {
|
||||
@@ -534,57 +399,50 @@ func (c Config) target(name string, visited map[string]*Target, overrides map[st
|
||||
tt.Merge(t)
|
||||
}
|
||||
}
|
||||
t.Inherits = nil
|
||||
m := defaultTarget()
|
||||
m.Merge(tt)
|
||||
m.Merge(t)
|
||||
tt = m
|
||||
if err := tt.AddOverrides(overrides[name]); err != nil {
|
||||
return nil, err
|
||||
if override, ok := overrides[name]; ok {
|
||||
tt.Merge(override)
|
||||
}
|
||||
tt.normalize()
|
||||
visited[name] = tt
|
||||
return tt, nil
|
||||
}
|
||||
|
||||
type Group struct {
|
||||
Name string `json:"-" hcl:"name,label" cty:"name"`
|
||||
Targets []string `json:"targets" hcl:"targets" cty:"targets"`
|
||||
Name string `json:"-" hcl:"name,label"`
|
||||
Targets []string `json:"targets" hcl:"targets"`
|
||||
// Target // TODO?
|
||||
}
|
||||
|
||||
type Target struct {
|
||||
Name string `json:"-" hcl:"name,label" cty:"name"`
|
||||
Name string `json:"-" hcl:"name,label"`
|
||||
|
||||
// Inherits is the only field that cannot be overridden with --set
|
||||
Attest []string `json:"attest,omitempty" hcl:"attest,optional" cty:"attest"`
|
||||
Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional" cty:"inherits"`
|
||||
Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional"`
|
||||
|
||||
Context *string `json:"context,omitempty" hcl:"context,optional" cty:"context"`
|
||||
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional" cty:"contexts"`
|
||||
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional" cty:"dockerfile"`
|
||||
DockerfileInline *string `json:"dockerfile-inline,omitempty" hcl:"dockerfile-inline,optional" cty:"dockerfile-inline"`
|
||||
Args map[string]string `json:"args,omitempty" hcl:"args,optional" cty:"args"`
|
||||
Labels map[string]string `json:"labels,omitempty" hcl:"labels,optional" cty:"labels"`
|
||||
Tags []string `json:"tags,omitempty" hcl:"tags,optional" cty:"tags"`
|
||||
CacheFrom []string `json:"cache-from,omitempty" hcl:"cache-from,optional" cty:"cache-from"`
|
||||
CacheTo []string `json:"cache-to,omitempty" hcl:"cache-to,optional" cty:"cache-to"`
|
||||
Target *string `json:"target,omitempty" hcl:"target,optional" cty:"target"`
|
||||
Secrets []string `json:"secret,omitempty" hcl:"secret,optional" cty:"secret"`
|
||||
SSH []string `json:"ssh,omitempty" hcl:"ssh,optional" cty:"ssh"`
|
||||
Platforms []string `json:"platforms,omitempty" hcl:"platforms,optional" cty:"platforms"`
|
||||
Outputs []string `json:"output,omitempty" hcl:"output,optional" cty:"output"`
|
||||
Pull *bool `json:"pull,omitempty" hcl:"pull,optional" cty:"pull"`
|
||||
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional" cty:"no-cache"`
|
||||
NetworkMode *string `json:"-" hcl:"-" cty:"-"`
|
||||
NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional" cty:"no-cache-filter"`
|
||||
// IMPORTANT: if you add more fields here, do not forget to update newOverrides and docs/manuals/bake/file-definition.md.
|
||||
Context *string `json:"context,omitempty" hcl:"context,optional"`
|
||||
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional"`
|
||||
DockerfileInline *string `json:"dockerfile-inline,omitempty" hcl:"dockerfile-inline,optional"`
|
||||
Args map[string]string `json:"args,omitempty" hcl:"args,optional"`
|
||||
Labels map[string]string `json:"labels,omitempty" hcl:"labels,optional"`
|
||||
Tags []string `json:"tags,omitempty" hcl:"tags,optional"`
|
||||
CacheFrom []string `json:"cache-from,omitempty" hcl:"cache-from,optional"`
|
||||
CacheTo []string `json:"cache-to,omitempty" hcl:"cache-to,optional"`
|
||||
Target *string `json:"target,omitempty" hcl:"target,optional"`
|
||||
Secrets []string `json:"secret,omitempty" hcl:"secret,optional"`
|
||||
SSH []string `json:"ssh,omitempty" hcl:"ssh,optional"`
|
||||
Platforms []string `json:"platforms,omitempty" hcl:"platforms,optional"`
|
||||
Outputs []string `json:"output,omitempty" hcl:"output,optional"`
|
||||
Pull *bool `json:"pull,omitempty" hcl:"pull,optional"`
|
||||
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional"`
|
||||
|
||||
// linked is a private field to mark a target used as a linked one
|
||||
linked bool
|
||||
// IMPORTANT: if you add more fields here, do not forget to update newOverrides and README.
|
||||
}
|
||||
|
||||
func (t *Target) normalize() {
|
||||
t.Attest = removeDupes(t.Attest)
|
||||
t.Tags = removeDupes(t.Tags)
|
||||
t.Secrets = removeDupes(t.Secrets)
|
||||
t.SSH = removeDupes(t.SSH)
|
||||
@@ -592,16 +450,6 @@ func (t *Target) normalize() {
|
||||
t.CacheFrom = removeDupes(t.CacheFrom)
|
||||
t.CacheTo = removeDupes(t.CacheTo)
|
||||
t.Outputs = removeDupes(t.Outputs)
|
||||
t.NoCacheFilter = removeDupes(t.NoCacheFilter)
|
||||
|
||||
for k, v := range t.Contexts {
|
||||
if v == "" {
|
||||
delete(t.Contexts, k)
|
||||
}
|
||||
}
|
||||
if len(t.Contexts) == 0 {
|
||||
t.Contexts = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Target) Merge(t2 *Target) {
|
||||
@@ -620,12 +468,6 @@ func (t *Target) Merge(t2 *Target) {
|
||||
}
|
||||
t.Args[k] = v
|
||||
}
|
||||
for k, v := range t2.Contexts {
|
||||
if t.Contexts == nil {
|
||||
t.Contexts = map[string]string{}
|
||||
}
|
||||
t.Contexts[k] = v
|
||||
}
|
||||
for k, v := range t2.Labels {
|
||||
if t.Labels == nil {
|
||||
t.Labels = map[string]string{}
|
||||
@@ -638,9 +480,6 @@ func (t *Target) Merge(t2 *Target) {
|
||||
if t2.Target != nil {
|
||||
t.Target = t2.Target
|
||||
}
|
||||
if t2.Attest != nil { // merge
|
||||
t.Attest = append(t.Attest, t2.Attest...)
|
||||
}
|
||||
if t2.Secrets != nil { // merge
|
||||
t.Secrets = append(t.Secrets, t2.Secrets...)
|
||||
}
|
||||
@@ -665,101 +504,9 @@ func (t *Target) Merge(t2 *Target) {
|
||||
if t2.NoCache != nil {
|
||||
t.NoCache = t2.NoCache
|
||||
}
|
||||
if t2.NetworkMode != nil {
|
||||
t.NetworkMode = t2.NetworkMode
|
||||
}
|
||||
if t2.NoCacheFilter != nil { // merge
|
||||
t.NoCacheFilter = append(t.NoCacheFilter, t2.NoCacheFilter...)
|
||||
}
|
||||
t.Inherits = append(t.Inherits, t2.Inherits...)
|
||||
}
|
||||
|
||||
func (t *Target) AddOverrides(overrides map[string]Override) error {
|
||||
for key, o := range overrides {
|
||||
value := o.Value
|
||||
keys := strings.SplitN(key, ".", 2)
|
||||
switch keys[0] {
|
||||
case "context":
|
||||
t.Context = &value
|
||||
case "dockerfile":
|
||||
t.Dockerfile = &value
|
||||
case "args":
|
||||
if len(keys) != 2 {
|
||||
return errors.Errorf("args require name")
|
||||
}
|
||||
if t.Args == nil {
|
||||
t.Args = map[string]string{}
|
||||
}
|
||||
t.Args[keys[1]] = value
|
||||
case "contexts":
|
||||
if len(keys) != 2 {
|
||||
return errors.Errorf("contexts require name")
|
||||
}
|
||||
if t.Contexts == nil {
|
||||
t.Contexts = map[string]string{}
|
||||
}
|
||||
t.Contexts[keys[1]] = value
|
||||
case "labels":
|
||||
if len(keys) != 2 {
|
||||
return errors.Errorf("labels require name")
|
||||
}
|
||||
if t.Labels == nil {
|
||||
t.Labels = map[string]string{}
|
||||
}
|
||||
t.Labels[keys[1]] = value
|
||||
case "tags":
|
||||
t.Tags = o.ArrValue
|
||||
case "cache-from":
|
||||
t.CacheFrom = o.ArrValue
|
||||
case "cache-to":
|
||||
t.CacheTo = o.ArrValue
|
||||
case "target":
|
||||
t.Target = &value
|
||||
case "secrets":
|
||||
t.Secrets = o.ArrValue
|
||||
case "ssh":
|
||||
t.SSH = o.ArrValue
|
||||
case "platform":
|
||||
t.Platforms = o.ArrValue
|
||||
case "output":
|
||||
t.Outputs = o.ArrValue
|
||||
case "attest":
|
||||
t.Attest = append(t.Attest, o.ArrValue...)
|
||||
case "no-cache":
|
||||
noCache, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return errors.Errorf("invalid value %s for boolean key no-cache", value)
|
||||
}
|
||||
t.NoCache = &noCache
|
||||
case "no-cache-filter":
|
||||
t.NoCacheFilter = o.ArrValue
|
||||
case "pull":
|
||||
pull, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return errors.Errorf("invalid value %s for boolean key pull", value)
|
||||
}
|
||||
t.Pull = &pull
|
||||
case "push":
|
||||
_, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return errors.Errorf("invalid value %s for boolean key push", value)
|
||||
}
|
||||
if len(t.Outputs) == 0 {
|
||||
t.Outputs = append(t.Outputs, "type=image,push=true")
|
||||
} else {
|
||||
for i, output := range t.Outputs {
|
||||
if typ := parseOutputType(output); typ == "image" || typ == "registry" {
|
||||
t.Outputs[i] = t.Outputs[i] + ",push=" + value
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
return errors.Errorf("unknown key: %s", keys[0])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TargetsToBuildOpt(m map[string]*Target, inp *Input) (map[string]build.Options, error) {
|
||||
m2 := make(map[string]build.Options, len(m))
|
||||
for k, v := range m {
|
||||
@@ -776,21 +523,6 @@ func updateContext(t *build.Inputs, inp *Input) {
|
||||
if inp == nil || inp.State == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range t.NamedContexts {
|
||||
if v.Path == "." {
|
||||
t.NamedContexts[k] = build.NamedContext{Path: inp.URL}
|
||||
}
|
||||
if strings.HasPrefix(v.Path, "cwd://") || strings.HasPrefix(v.Path, "target:") || strings.HasPrefix(v.Path, "docker-image:") {
|
||||
continue
|
||||
}
|
||||
if IsRemoteURL(v.Path) {
|
||||
continue
|
||||
}
|
||||
st := llb.Scratch().File(llb.Copy(*inp.State, v.Path, "/"), llb.WithCustomNamef("set context %s to %s", k, v.Path))
|
||||
t.NamedContexts[k] = build.NamedContext{State: &st}
|
||||
}
|
||||
|
||||
if t.ContextPath == "." {
|
||||
t.ContextPath = inp.URL
|
||||
return
|
||||
@@ -805,59 +537,6 @@ func updateContext(t *build.Inputs, inp *Input) {
|
||||
t.ContextState = &st
|
||||
}
|
||||
|
||||
// validateContextsEntitlements is a basic check to ensure contexts do not
|
||||
// escape local directories when loaded from remote sources. This is to be
|
||||
// replaced with proper entitlements support in the future.
|
||||
func validateContextsEntitlements(t build.Inputs, inp *Input) error {
|
||||
if inp == nil || inp.State == nil {
|
||||
return nil
|
||||
}
|
||||
if v, ok := os.LookupEnv("BAKE_ALLOW_REMOTE_FS_ACCESS"); ok {
|
||||
if vv, _ := strconv.ParseBool(v); vv {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if t.ContextState == nil {
|
||||
if err := checkPath(t.ContextPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, v := range t.NamedContexts {
|
||||
if v.State != nil {
|
||||
continue
|
||||
}
|
||||
if err := checkPath(v.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPath(p string) error {
|
||||
if IsRemoteURL(p) || strings.HasPrefix(p, "target:") || strings.HasPrefix(p, "docker-image:") {
|
||||
return nil
|
||||
}
|
||||
p, err := filepath.EvalSymlinks(p)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rel, err := filepath.Rel(wd, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
||||
return errors.Errorf("path %s is outside of the working directory, please set BAKE_ALLOW_REMOTE_FS_ACCESS=1", p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
||||
if v := t.Context; v != nil && *v == "-" {
|
||||
return nil, errors.Errorf("context from stdin not allowed in bake")
|
||||
@@ -890,15 +569,10 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
||||
if t.Pull != nil {
|
||||
pull = *t.Pull
|
||||
}
|
||||
networkMode := ""
|
||||
if t.NetworkMode != nil {
|
||||
networkMode = *t.NetworkMode
|
||||
}
|
||||
|
||||
bi := build.Inputs{
|
||||
ContextPath: contextPath,
|
||||
DockerfilePath: dockerfilePath,
|
||||
NamedContexts: toNamedContexts(t.Contexts),
|
||||
}
|
||||
if t.DockerfileInline != nil {
|
||||
bi.DockerfileInline = *t.DockerfileInline
|
||||
@@ -907,28 +581,16 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
||||
if strings.HasPrefix(bi.ContextPath, "cwd://") {
|
||||
bi.ContextPath = path.Clean(strings.TrimPrefix(bi.ContextPath, "cwd://"))
|
||||
}
|
||||
for k, v := range bi.NamedContexts {
|
||||
if strings.HasPrefix(v.Path, "cwd://") {
|
||||
bi.NamedContexts[k] = build.NamedContext{Path: path.Clean(strings.TrimPrefix(v.Path, "cwd://"))}
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateContextsEntitlements(bi, inp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.Context = &bi.ContextPath
|
||||
|
||||
bo := &build.Options{
|
||||
Inputs: bi,
|
||||
Tags: t.Tags,
|
||||
BuildArgs: t.Args,
|
||||
Labels: t.Labels,
|
||||
NoCache: noCache,
|
||||
NoCacheFilter: t.NoCacheFilter,
|
||||
Pull: pull,
|
||||
NetworkMode: networkMode,
|
||||
Linked: t.linked,
|
||||
Inputs: bi,
|
||||
Tags: t.Tags,
|
||||
BuildArgs: t.Args,
|
||||
Labels: t.Labels,
|
||||
NoCache: noCache,
|
||||
Pull: pull,
|
||||
}
|
||||
|
||||
platforms, err := platformutil.Parse(t.Platforms)
|
||||
@@ -937,8 +599,7 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
||||
}
|
||||
bo.Platforms = platforms
|
||||
|
||||
dockerConfig := config.LoadDefaultConfigFile(os.Stderr)
|
||||
bo.Session = append(bo.Session, authprovider.NewDockerAuthProvider(dockerConfig))
|
||||
bo.Session = append(bo.Session, authprovider.NewDockerAuthProvider(os.Stderr))
|
||||
|
||||
secrets, err := buildflags.ParseSecretSpecs(t.Secrets)
|
||||
if err != nil {
|
||||
@@ -978,12 +639,6 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
||||
}
|
||||
bo.Exports = outputs
|
||||
|
||||
attests, err := buildflags.ParseAttests(t.Attest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bo.Attests = attests
|
||||
|
||||
return bo, nil
|
||||
}
|
||||
|
||||
@@ -1011,56 +666,3 @@ func removeDupes(s []string) []string {
|
||||
func isRemoteResource(str string) bool {
|
||||
return urlutil.IsGitURL(str) || urlutil.IsURL(str)
|
||||
}
|
||||
|
||||
func parseOutputType(str string) string {
|
||||
csvReader := csv.NewReader(strings.NewReader(str))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, field := range fields {
|
||||
parts := strings.SplitN(field, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
if parts[0] == "type" {
|
||||
return parts[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func validateTargetName(name string) error {
|
||||
if !targetNamePattern.MatchString(name) {
|
||||
return errors.Errorf("only %q are allowed", validTargetNameChars)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sanitizeTargetName(target string) string {
|
||||
// as stipulated in compose spec, service name can contain a dot so as
|
||||
// best-effort and to avoid any potential ambiguity, we replace the dot
|
||||
// with an underscore.
|
||||
return strings.ReplaceAll(target, ".", "_")
|
||||
}
|
||||
|
||||
func sliceEqual(s1, s2 []string) bool {
|
||||
if len(s1) != len(s2) {
|
||||
return false
|
||||
}
|
||||
sort.Strings(s1)
|
||||
sort.Strings(s2)
|
||||
for i := range s1 {
|
||||
if s1[i] != s2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func toNamedContexts(m map[string]string) map[string]build.NamedContext {
|
||||
m2 := make(map[string]build.NamedContext, len(m))
|
||||
for k, v := range m {
|
||||
m2[k] = build.NamedContext{Path: v}
|
||||
}
|
||||
return m2
|
||||
}
|
||||
|
||||
1077
bake/bake_test.go
1077
bake/bake_test.go
File diff suppressed because it is too large
Load Diff
280
bake/compose.go
280
bake/compose.go
@@ -1,44 +1,48 @@
|
||||
package bake
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/dotenv"
|
||||
"github.com/compose-spec/compose-go/loader"
|
||||
compose "github.com/compose-spec/compose-go/types"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func ParseComposeFiles(fs []File) (*Config, error) {
|
||||
envs, err := composeEnv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cfgs []compose.ConfigFile
|
||||
for _, f := range fs {
|
||||
cfgs = append(cfgs, compose.ConfigFile{
|
||||
Filename: f.Name,
|
||||
Content: f.Data,
|
||||
})
|
||||
}
|
||||
return ParseCompose(cfgs, envs)
|
||||
}
|
||||
|
||||
func ParseCompose(cfgs []compose.ConfigFile, envs map[string]string) (*Config, error) {
|
||||
cfg, err := loader.Load(compose.ConfigDetails{
|
||||
ConfigFiles: cfgs,
|
||||
Environment: envs,
|
||||
func parseCompose(dt []byte) (*compose.Project, error) {
|
||||
return loader.Load(compose.ConfigDetails{
|
||||
ConfigFiles: []compose.ConfigFile{
|
||||
{
|
||||
Content: dt,
|
||||
},
|
||||
},
|
||||
Environment: envMap(os.Environ()),
|
||||
}, func(options *loader.Options) {
|
||||
options.SkipNormalization = true
|
||||
})
|
||||
}
|
||||
|
||||
func envMap(env []string) map[string]string {
|
||||
result := make(map[string]string, len(env))
|
||||
for _, s := range env {
|
||||
kv := strings.SplitN(s, "=", 2)
|
||||
if len(kv) != 2 {
|
||||
continue
|
||||
}
|
||||
result[kv[0]] = kv[1]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ParseCompose(dt []byte) (*Config, error) {
|
||||
cfg, err := parseCompose(dt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var c Config
|
||||
var zeroBuildConfig compose.BuildConfig
|
||||
if len(cfg.Services) > 0 {
|
||||
c.Groups = []*Group{}
|
||||
c.Targets = []*Target{}
|
||||
@@ -46,13 +50,13 @@ func ParseCompose(cfgs []compose.ConfigFile, envs map[string]string) (*Config, e
|
||||
g := &Group{Name: "default"}
|
||||
|
||||
for _, s := range cfg.Services {
|
||||
if s.Build == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
targetName := sanitizeTargetName(s.Name)
|
||||
if err = validateTargetName(targetName); err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid service name %q", targetName)
|
||||
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
|
||||
}
|
||||
|
||||
var contextPathP *string
|
||||
@@ -65,43 +69,24 @@ func ParseCompose(cfgs []compose.ConfigFile, envs map[string]string) (*Config, e
|
||||
dockerfilePath := s.Build.Dockerfile
|
||||
dockerfilePathP = &dockerfilePath
|
||||
}
|
||||
|
||||
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)
|
||||
g.Targets = append(g.Targets, s.Name)
|
||||
t := &Target{
|
||||
Name: targetName,
|
||||
Name: s.Name,
|
||||
Context: contextPathP,
|
||||
Dockerfile: dockerfilePathP,
|
||||
Tags: s.Build.Tags,
|
||||
Labels: s.Build.Labels,
|
||||
Args: flatten(s.Build.Args.Resolve(func(val string) (string, bool) {
|
||||
if val, ok := s.Environment[val]; ok && val != nil {
|
||||
return *val, true
|
||||
}
|
||||
val, ok := cfg.Environment[val]
|
||||
return val, ok
|
||||
})),
|
||||
CacheFrom: s.Build.CacheFrom,
|
||||
CacheTo: s.Build.CacheTo,
|
||||
NetworkMode: &s.Build.Network,
|
||||
Secrets: secrets,
|
||||
}
|
||||
if err = t.composeExtTarget(s.Build.Extensions); err != nil {
|
||||
return nil, err
|
||||
CacheFrom: s.Build.CacheFrom,
|
||||
// TODO: add platforms
|
||||
}
|
||||
if s.Build.Target != "" {
|
||||
target := s.Build.Target
|
||||
t.Target = &target
|
||||
}
|
||||
if len(t.Tags) == 0 && s.Image != "" {
|
||||
if s.Image != "" {
|
||||
t.Tags = []string{s.Image}
|
||||
}
|
||||
c.Targets = append(c.Targets, t)
|
||||
@@ -113,86 +98,6 @@ func ParseCompose(cfgs []compose.ConfigFile, envs map[string]string) (*Config, e
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func validateComposeFile(dt []byte, fn string) (bool, error) {
|
||||
envs, err := composeEnv()
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
fnl := strings.ToLower(fn)
|
||||
if strings.HasSuffix(fnl, ".yml") || strings.HasSuffix(fnl, ".yaml") {
|
||||
return true, validateCompose(dt, envs)
|
||||
}
|
||||
if strings.HasSuffix(fnl, ".json") || strings.HasSuffix(fnl, ".hcl") {
|
||||
return false, nil
|
||||
}
|
||||
err = validateCompose(dt, envs)
|
||||
return err == nil, err
|
||||
}
|
||||
|
||||
func validateCompose(dt []byte, envs map[string]string) error {
|
||||
_, err := loader.Load(compose.ConfigDetails{
|
||||
ConfigFiles: []compose.ConfigFile{
|
||||
{
|
||||
Content: dt,
|
||||
},
|
||||
},
|
||||
Environment: envs,
|
||||
}, func(options *loader.Options) {
|
||||
options.SkipNormalization = true
|
||||
// consistency is checked later in ParseCompose to ensure multiple
|
||||
// compose files can be merged together
|
||||
options.SkipConsistencyCheck = true
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func composeEnv() (map[string]string, error) {
|
||||
envs := sliceToMap(os.Environ())
|
||||
if wd, err := os.Getwd(); err == nil {
|
||||
envs, err = loadDotEnv(envs, wd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return envs, nil
|
||||
}
|
||||
|
||||
func loadDotEnv(curenv map[string]string, workingDir string) (map[string]string, error) {
|
||||
if curenv == nil {
|
||||
curenv = make(map[string]string)
|
||||
}
|
||||
|
||||
ef, err := filepath.Abs(filepath.Join(workingDir, ".env"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err = os.Stat(ef); os.IsNotExist(err) {
|
||||
return curenv, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dt, err := os.ReadFile(ef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
envs, err := dotenv.UnmarshalBytes(dt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k, v := range envs {
|
||||
if _, set := curenv[k]; set {
|
||||
continue
|
||||
}
|
||||
curenv[k] = v
|
||||
}
|
||||
|
||||
return curenv, nil
|
||||
}
|
||||
|
||||
func flatten(in compose.MappingWithEquals) compose.Mapping {
|
||||
if len(in) == 0 {
|
||||
return nil
|
||||
@@ -206,112 +111,3 @@ func flatten(in compose.MappingWithEquals) compose.Mapping {
|
||||
}
|
||||
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"`
|
||||
Contexts stringMap `yaml:"contexts,omitempty"`
|
||||
// don't forget to update documentation if you add a new field:
|
||||
// docs/manuals/bake/compose-file.md#extension-field-with-x-bake
|
||||
}
|
||||
|
||||
type stringMap map[string]string
|
||||
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
|
||||
// https://github.com/compose-spec/compose-spec/blob/master/spec.md#extension
|
||||
func (t *Target) composeExtTarget(exts map[string]interface{}) error {
|
||||
var xb xbake
|
||||
|
||||
ext, ok := exts["x-bake"]
|
||||
if !ok || ext == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
yb, _ := yaml.Marshal(ext)
|
||||
if err := yaml.Unmarshal(yb, &xb); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(xb.Tags) > 0 {
|
||||
t.Tags = dedupSlice(append(t.Tags, xb.Tags...))
|
||||
}
|
||||
if len(xb.CacheFrom) > 0 {
|
||||
t.CacheFrom = dedupSlice(append(t.CacheFrom, xb.CacheFrom...))
|
||||
}
|
||||
if len(xb.CacheTo) > 0 {
|
||||
t.CacheTo = dedupSlice(append(t.CacheTo, xb.CacheTo...))
|
||||
}
|
||||
if len(xb.Secrets) > 0 {
|
||||
t.Secrets = dedupSlice(append(t.Secrets, xb.Secrets...))
|
||||
}
|
||||
if len(xb.SSH) > 0 {
|
||||
t.SSH = dedupSlice(append(t.SSH, xb.SSH...))
|
||||
}
|
||||
if len(xb.Platforms) > 0 {
|
||||
t.Platforms = dedupSlice(append(t.Platforms, xb.Platforms...))
|
||||
}
|
||||
if len(xb.Outputs) > 0 {
|
||||
t.Outputs = dedupSlice(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 = dedupSlice(append(t.NoCacheFilter, xb.NoCacheFilter...))
|
||||
}
|
||||
if len(xb.Contexts) > 0 {
|
||||
t.Contexts = dedupMap(t.Contexts, xb.Contexts)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -2,12 +2,9 @@ package bake
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
compose "github.com/compose-spec/compose-go/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -22,29 +19,15 @@ services:
|
||||
build:
|
||||
context: ./dir
|
||||
dockerfile: Dockerfile-alternate
|
||||
network:
|
||||
none
|
||||
args:
|
||||
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([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
c, err := ParseCompose(dt)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 1, len(c.Groups))
|
||||
require.Equal(t, "default", c.Groups[0].Name)
|
||||
require.Equal(t, c.Groups[0].Name, "default")
|
||||
sort.Strings(c.Groups[0].Targets)
|
||||
require.Equal(t, []string{"db", "webapp"}, c.Groups[0].Targets)
|
||||
|
||||
@@ -54,20 +37,12 @@ secrets:
|
||||
})
|
||||
require.Equal(t, "db", c.Targets[0].Name)
|
||||
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, "./dir", *c.Targets[1].Context)
|
||||
require.Equal(t, "Dockerfile-alternate", *c.Targets[1].Dockerfile)
|
||||
require.Equal(t, 1, len(c.Targets[1].Args))
|
||||
require.Equal(t, "123", c.Targets[1].Args["buildno"])
|
||||
require.Equal(t, []string{"type=local,src=path/to/cache"}, c.Targets[1].CacheFrom)
|
||||
require.Equal(t, []string{"type=local,dest=path/to/cache"}, c.Targets[1].CacheTo)
|
||||
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) {
|
||||
@@ -78,10 +53,9 @@ services:
|
||||
webapp:
|
||||
build: ./db
|
||||
`)
|
||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
c, err := ParseCompose(dt)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(c.Groups))
|
||||
require.Equal(t, 1, len(c.Targets))
|
||||
}
|
||||
|
||||
func TestParseComposeTarget(t *testing.T) {
|
||||
@@ -97,7 +71,7 @@ services:
|
||||
target: webapp
|
||||
`)
|
||||
|
||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
c, err := ParseCompose(dt)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 2, len(c.Targets))
|
||||
@@ -122,15 +96,15 @@ services:
|
||||
target: webapp
|
||||
`)
|
||||
|
||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
c, err := ParseCompose(dt)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(c.Targets))
|
||||
sort.Slice(c.Targets, func(i, j int) bool {
|
||||
return c.Targets[i].Name < c.Targets[j].Name
|
||||
})
|
||||
require.Equal(t, "db", c.Targets[0].Name)
|
||||
require.Equal(t, c.Targets[0].Name, "db")
|
||||
require.Equal(t, "db", *c.Targets[0].Target)
|
||||
require.Equal(t, "webapp", c.Targets[1].Name)
|
||||
require.Equal(t, c.Targets[1].Name, "webapp")
|
||||
require.Equal(t, "webapp", *c.Targets[1].Target)
|
||||
}
|
||||
|
||||
@@ -156,22 +130,28 @@ services:
|
||||
os.Setenv("ZZZ_BAR", "zzz_foo")
|
||||
defer os.Unsetenv("ZZZ_BAR")
|
||||
|
||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, sliceToMap(os.Environ()))
|
||||
c, err := ParseCompose(dt)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "bar", c.Targets[0].Args["FOO"])
|
||||
require.Equal(t, "zzz_foo", c.Targets[0].Args["BAR"])
|
||||
require.Equal(t, "FOO", c.Targets[0].Args["BRB"])
|
||||
require.Equal(t, c.Targets[0].Args["FOO"], "bar")
|
||||
require.Equal(t, c.Targets[0].Args["BAR"], "zzz_foo")
|
||||
require.Equal(t, c.Targets[0].Args["BRB"], "FOO")
|
||||
}
|
||||
|
||||
func TestInconsistentComposeFile(t *testing.T) {
|
||||
func TestBogusCompose(t *testing.T) {
|
||||
var dt = []byte(`
|
||||
services:
|
||||
db:
|
||||
labels:
|
||||
- "foo"
|
||||
webapp:
|
||||
entrypoint: echo 1
|
||||
build:
|
||||
context: .
|
||||
target: webapp
|
||||
`)
|
||||
|
||||
_, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
_, err := ParseCompose(dt)
|
||||
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) {
|
||||
@@ -195,28 +175,10 @@ networks:
|
||||
gateway: 10.5.0.254
|
||||
`)
|
||||
|
||||
_, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
_, err := ParseCompose(dt)
|
||||
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([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{"foo", "bar"}, c.Targets[0].Tags)
|
||||
}
|
||||
|
||||
func TestDependsOnList(t *testing.T) {
|
||||
var dt = []byte(`
|
||||
version: "3.8"
|
||||
@@ -249,400 +211,6 @@ networks:
|
||||
name: test-net
|
||||
`)
|
||||
|
||||
_, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
_, err := ParseCompose(dt)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestComposeExt(t *testing.T) {
|
||||
var dt = []byte(`
|
||||
services:
|
||||
addon:
|
||||
image: ct-addon:bar
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Dockerfile
|
||||
cache_from:
|
||||
- user/app:cache
|
||||
cache_to:
|
||||
- user/app:cache
|
||||
tags:
|
||||
- ct-addon:baz
|
||||
args:
|
||||
CT_ECR: foo
|
||||
CT_TAG: bar
|
||||
x-bake:
|
||||
contexts:
|
||||
alpine: docker-image://alpine:3.13
|
||||
tags:
|
||||
- ct-addon:foo
|
||||
- ct-addon:alp
|
||||
platforms:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
cache-from:
|
||||
- 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=/local/secret
|
||||
- id=mysecret2,src=/local/secret2
|
||||
ssh: default
|
||||
platforms: linux/arm64
|
||||
output: type=docker
|
||||
no-cache: true
|
||||
`)
|
||||
|
||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(c.Targets))
|
||||
sort.Slice(c.Targets, func(i, j int) bool {
|
||||
return c.Targets[i].Name < c.Targets[j].Name
|
||||
})
|
||||
require.Equal(t, map[string]string{"CT_ECR": "foo", "CT_TAG": "bar"}, c.Targets[0].Args)
|
||||
require.Equal(t, []string{"ct-addon:baz", "ct-addon:foo", "ct-addon:alp"}, c.Targets[0].Tags)
|
||||
require.Equal(t, []string{"linux/amd64", "linux/arm64"}, c.Targets[0].Platforms)
|
||||
require.Equal(t, []string{"user/app:cache", "type=local,src=path/to/cache"}, c.Targets[0].CacheFrom)
|
||||
require.Equal(t, []string{"user/app:cache", "type=local,dest=path/to/cache"}, c.Targets[0].CacheTo)
|
||||
require.Equal(t, newBool(true), c.Targets[0].Pull)
|
||||
require.Equal(t, map[string]string{"alpine": "docker-image://alpine:3.13"}, c.Targets[0].Contexts)
|
||||
require.Equal(t, []string{"ct-fake-aws:bar"}, c.Targets[1].Tags)
|
||||
require.Equal(t, []string{"id=mysecret,src=/local/secret", "id=mysecret2,src=/local/secret2"}, c.Targets[1].Secrets)
|
||||
require.Equal(t, []string{"default"}, c.Targets[1].SSH)
|
||||
require.Equal(t, []string{"linux/arm64"}, c.Targets[1].Platforms)
|
||||
require.Equal(t, []string{"type=docker"}, c.Targets[1].Outputs)
|
||||
require.Equal(t, newBool(true), c.Targets[1].NoCache)
|
||||
}
|
||||
|
||||
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([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(c.Targets))
|
||||
require.Equal(t, []string{"ct-addon:foo", "ct-addon:baz"}, c.Targets[0].Tags)
|
||||
require.Equal(t, []string{"user/app:cache", "type=local,src=path/to/cache"}, c.Targets[0].CacheFrom)
|
||||
require.Equal(t, []string{"user/app:cache", "type=local,dest=path/to/cache"}, c.Targets[0].CacheTo)
|
||||
}
|
||||
|
||||
func TestEnv(t *testing.T) {
|
||||
envf, err := os.CreateTemp("", "env")
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(envf.Name())
|
||||
|
||||
_, err = envf.WriteString("FOO=bsdf -csdf\n")
|
||||
require.NoError(t, err)
|
||||
|
||||
var dt = []byte(`
|
||||
services:
|
||||
scratch:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
CT_ECR: foo
|
||||
FOO:
|
||||
NODE_ENV:
|
||||
environment:
|
||||
- NODE_ENV=test
|
||||
- AWS_ACCESS_KEY_ID=dummy
|
||||
- AWS_SECRET_ACCESS_KEY=dummy
|
||||
env_file:
|
||||
- ` + envf.Name() + `
|
||||
`)
|
||||
|
||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, map[string]string{"CT_ECR": "foo", "FOO": "bsdf -csdf", "NODE_ENV": "test"}, c.Targets[0].Args)
|
||||
}
|
||||
|
||||
func TestDotEnv(t *testing.T) {
|
||||
tmpdir := t.TempDir()
|
||||
|
||||
err := os.WriteFile(filepath.Join(tmpdir, ".env"), []byte("FOO=bar"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
var dt = []byte(`
|
||||
services:
|
||||
scratch:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
FOO:
|
||||
`)
|
||||
|
||||
chdir(t, tmpdir)
|
||||
c, err := ParseComposeFiles([]File{{
|
||||
Name: "docker-compose.yml",
|
||||
Data: dt,
|
||||
}})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, map[string]string{"FOO": "bar"}, c.Targets[0].Args)
|
||||
}
|
||||
|
||||
func TestPorts(t *testing.T) {
|
||||
var dt = []byte(`
|
||||
services:
|
||||
foo:
|
||||
build:
|
||||
context: .
|
||||
ports:
|
||||
- 3306:3306
|
||||
bar:
|
||||
build:
|
||||
context: .
|
||||
ports:
|
||||
- mode: ingress
|
||||
target: 3306
|
||||
published: "3306"
|
||||
protocol: tcp
|
||||
`)
|
||||
_, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func newBool(val bool) *bool {
|
||||
b := val
|
||||
return &b
|
||||
}
|
||||
|
||||
func TestServiceName(t *testing.T) {
|
||||
cases := []struct {
|
||||
svc string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
svc: "a",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
svc: "abc",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
svc: "a.b",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
svc: "_a",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
svc: "a_b",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
svc: "AbC",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
svc: "AbC-0123",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range cases {
|
||||
tt := tt
|
||||
t.Run(tt.svc, func(t *testing.T) {
|
||||
_, err := ParseCompose([]compose.ConfigFile{{Content: []byte(`
|
||||
services:
|
||||
` + tt.svc + `:
|
||||
build:
|
||||
context: .
|
||||
`)}}, nil)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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([]compose.ConfigFile{{Content: tt.dt}}, nil)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateComposeFile(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
fn string
|
||||
dt []byte
|
||||
isCompose bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "empty service",
|
||||
fn: "docker-compose.yml",
|
||||
dt: []byte(`
|
||||
services:
|
||||
foo:
|
||||
`),
|
||||
isCompose: true,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "build",
|
||||
fn: "docker-compose.yml",
|
||||
dt: []byte(`
|
||||
services:
|
||||
foo:
|
||||
build: .
|
||||
`),
|
||||
isCompose: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "image",
|
||||
fn: "docker-compose.yml",
|
||||
dt: []byte(`
|
||||
services:
|
||||
simple:
|
||||
image: nginx
|
||||
`),
|
||||
isCompose: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "unknown ext",
|
||||
fn: "docker-compose.foo",
|
||||
dt: []byte(`
|
||||
services:
|
||||
simple:
|
||||
image: nginx
|
||||
`),
|
||||
isCompose: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "hcl",
|
||||
fn: "docker-bake.hcl",
|
||||
dt: []byte(`
|
||||
target "default" {
|
||||
dockerfile = "test"
|
||||
}
|
||||
`),
|
||||
isCompose: false,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range cases {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
isCompose, err := validateComposeFile(tt.dt, tt.fn)
|
||||
assert.Equal(t, tt.isCompose, isCompose)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// chdir changes the current working directory to the named directory,
|
||||
// and then restore the original working directory at the end of the test.
|
||||
func chdir(t *testing.T, dir string) {
|
||||
olddir, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("chdir: %v", err)
|
||||
}
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
t.Fatalf("chdir %s: %v", dir, err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := os.Chdir(olddir); err != nil {
|
||||
t.Errorf("chdir to original working directory %s: %v", olddir, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package bake
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
hcl "github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclparse"
|
||||
"github.com/moby/buildkit/solver/errdefs"
|
||||
"github.com/moby/buildkit/solver/pb"
|
||||
|
||||
262
bake/hcl_test.go
262
bake/hcl_test.go
@@ -444,94 +444,6 @@ func TestHCLAttrs(t *testing.T) {
|
||||
// attr-multifile
|
||||
}
|
||||
|
||||
func TestHCLTargetAttrs(t *testing.T) {
|
||||
dt := []byte(`
|
||||
target "foo" {
|
||||
dockerfile = "xxx"
|
||||
context = target.bar.context
|
||||
target = target.foo.dockerfile
|
||||
}
|
||||
|
||||
target "bar" {
|
||||
dockerfile = target.foo.dockerfile
|
||||
context = "yyy"
|
||||
target = target.bar.context
|
||||
}
|
||||
`)
|
||||
|
||||
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 2, len(c.Targets))
|
||||
require.Equal(t, "foo", c.Targets[0].Name)
|
||||
require.Equal(t, "bar", c.Targets[1].Name)
|
||||
|
||||
require.Equal(t, "xxx", *c.Targets[0].Dockerfile)
|
||||
require.Equal(t, "yyy", *c.Targets[0].Context)
|
||||
require.Equal(t, "xxx", *c.Targets[0].Target)
|
||||
|
||||
require.Equal(t, "xxx", *c.Targets[1].Dockerfile)
|
||||
require.Equal(t, "yyy", *c.Targets[1].Context)
|
||||
require.Equal(t, "yyy", *c.Targets[1].Target)
|
||||
}
|
||||
|
||||
func TestHCLTargetGlobal(t *testing.T) {
|
||||
dt := []byte(`
|
||||
target "foo" {
|
||||
dockerfile = "x"
|
||||
}
|
||||
x = target.foo.dockerfile
|
||||
y = x
|
||||
target "bar" {
|
||||
dockerfile = y
|
||||
}
|
||||
`)
|
||||
|
||||
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 2, len(c.Targets))
|
||||
require.Equal(t, "foo", c.Targets[0].Name)
|
||||
require.Equal(t, "bar", c.Targets[1].Name)
|
||||
|
||||
require.Equal(t, "x", *c.Targets[0].Dockerfile)
|
||||
require.Equal(t, "x", *c.Targets[1].Dockerfile)
|
||||
}
|
||||
|
||||
func TestHCLTargetAttrName(t *testing.T) {
|
||||
dt := []byte(`
|
||||
target "foo" {
|
||||
dockerfile = target.foo.name
|
||||
}
|
||||
`)
|
||||
|
||||
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 1, len(c.Targets))
|
||||
require.Equal(t, "foo", c.Targets[0].Name)
|
||||
require.Equal(t, "foo", *c.Targets[0].Dockerfile)
|
||||
}
|
||||
|
||||
func TestHCLTargetAttrEmptyChain(t *testing.T) {
|
||||
dt := []byte(`
|
||||
target "foo" {
|
||||
# dockerfile = Dockerfile
|
||||
context = target.foo.dockerfile
|
||||
target = target.foo.context
|
||||
}
|
||||
`)
|
||||
|
||||
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 1, len(c.Targets))
|
||||
require.Equal(t, "foo", c.Targets[0].Name)
|
||||
require.Nil(t, c.Targets[0].Dockerfile)
|
||||
require.Nil(t, c.Targets[0].Context)
|
||||
require.Nil(t, c.Targets[0].Target)
|
||||
}
|
||||
|
||||
func TestHCLAttrsCustomType(t *testing.T) {
|
||||
dt := []byte(`
|
||||
platforms=["linux/arm64", "linux/amd64"]
|
||||
@@ -708,177 +620,3 @@ func TestHCLBuiltinVars(t *testing.T) {
|
||||
require.Equal(t, "foo", *c.Targets[0].Context)
|
||||
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)
|
||||
}
|
||||
|
||||
func TestVarUnsupportedType(t *testing.T) {
|
||||
dt := []byte(`
|
||||
variable "FOO" {
|
||||
default = []
|
||||
}
|
||||
target "default" {}`)
|
||||
|
||||
t.Setenv("FOO", "bar")
|
||||
_, err := ParseFile(dt, "docker-bake.hcl")
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
package hclparser
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
type filterBody struct {
|
||||
body hcl.Body
|
||||
schema *hcl.BodySchema
|
||||
exclude bool
|
||||
}
|
||||
|
||||
func FilterIncludeBody(body hcl.Body, schema *hcl.BodySchema) hcl.Body {
|
||||
return &filterBody{
|
||||
body: body,
|
||||
schema: schema,
|
||||
}
|
||||
}
|
||||
|
||||
func FilterExcludeBody(body hcl.Body, schema *hcl.BodySchema) hcl.Body {
|
||||
return &filterBody{
|
||||
body: body,
|
||||
schema: schema,
|
||||
exclude: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *filterBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
|
||||
if b.exclude {
|
||||
schema = subtractSchemas(schema, b.schema)
|
||||
} else {
|
||||
schema = intersectSchemas(schema, b.schema)
|
||||
}
|
||||
content, _, diag := b.body.PartialContent(schema)
|
||||
return content, diag
|
||||
}
|
||||
|
||||
func (b *filterBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
|
||||
if b.exclude {
|
||||
schema = subtractSchemas(schema, b.schema)
|
||||
} else {
|
||||
schema = intersectSchemas(schema, b.schema)
|
||||
}
|
||||
return b.body.PartialContent(schema)
|
||||
}
|
||||
|
||||
func (b *filterBody) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
|
||||
return b.body.JustAttributes()
|
||||
}
|
||||
|
||||
func (b *filterBody) MissingItemRange() hcl.Range {
|
||||
return b.body.MissingItemRange()
|
||||
}
|
||||
|
||||
func intersectSchemas(a, b *hcl.BodySchema) *hcl.BodySchema {
|
||||
result := &hcl.BodySchema{}
|
||||
for _, blockA := range a.Blocks {
|
||||
for _, blockB := range b.Blocks {
|
||||
if blockA.Type == blockB.Type {
|
||||
result.Blocks = append(result.Blocks, blockA)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, attrA := range a.Attributes {
|
||||
for _, attrB := range b.Attributes {
|
||||
if attrA.Name == attrB.Name {
|
||||
result.Attributes = append(result.Attributes, attrA)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func subtractSchemas(a, b *hcl.BodySchema) *hcl.BodySchema {
|
||||
result := &hcl.BodySchema{}
|
||||
for _, blockA := range a.Blocks {
|
||||
found := false
|
||||
for _, blockB := range b.Blocks {
|
||||
if blockA.Type == blockB.Type {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
result.Blocks = append(result.Blocks, blockA)
|
||||
}
|
||||
}
|
||||
for _, attrA := range a.Attributes {
|
||||
found := false
|
||||
for _, attrB := range b.Attributes {
|
||||
if attrA.Name == attrB.Name {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
result.Attributes = append(result.Attributes, attrA)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -87,7 +87,7 @@ func appendJSONFuncCalls(exp hcl.Expression, m map[string]struct{}) error {
|
||||
return nil
|
||||
}
|
||||
rng := src.FieldByName("SrcRange")
|
||||
if rng.IsZero() {
|
||||
if val.IsZero() {
|
||||
return nil
|
||||
}
|
||||
var stringVal struct {
|
||||
|
||||
@@ -13,13 +13,11 @@ import (
|
||||
"github.com/hashicorp/hcl/v2/gohcl"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/gocty"
|
||||
)
|
||||
|
||||
type Opt struct {
|
||||
LookupVar func(string) (string, bool)
|
||||
Vars map[string]string
|
||||
ValidateLabel func(string) error
|
||||
LookupVar func(string) (string, bool)
|
||||
Vars map[string]string
|
||||
}
|
||||
|
||||
type variable struct {
|
||||
@@ -49,17 +47,11 @@ type parser struct {
|
||||
attrs map[string]*hcl.Attribute
|
||||
funcs map[string]*functionDef
|
||||
|
||||
blocks map[string]map[string][]*hcl.Block
|
||||
blockValues map[*hcl.Block]reflect.Value
|
||||
blockTypes map[string]reflect.Type
|
||||
|
||||
ectx *hcl.EvalContext
|
||||
|
||||
progress map[string]struct{}
|
||||
progressF map[string]struct{}
|
||||
progressB map[*hcl.Block]map[string]struct{}
|
||||
doneF map[string]struct{}
|
||||
doneB map[*hcl.Block]map[string]struct{}
|
||||
}
|
||||
|
||||
func (p *parser) loadDeps(exp hcl.Expression, exclude map[string]struct{}) hcl.Diagnostics {
|
||||
@@ -86,69 +78,15 @@ func (p *parser) loadDeps(exp hcl.Expression, exclude map[string]struct{}) hcl.D
|
||||
if _, ok := exclude[v.RootName()]; ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := p.blockTypes[v.RootName()]; ok {
|
||||
blockType := v.RootName()
|
||||
|
||||
split := v.SimpleSplit().Rel
|
||||
if len(split) == 0 {
|
||||
return hcl.Diagnostics{
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid expression",
|
||||
Detail: fmt.Sprintf("cannot access %s as a variable", blockType),
|
||||
Subject: exp.Range().Ptr(),
|
||||
Context: exp.Range().Ptr(),
|
||||
},
|
||||
}
|
||||
}
|
||||
blockName, ok := split[0].(hcl.TraverseAttr)
|
||||
if !ok {
|
||||
return hcl.Diagnostics{
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid expression",
|
||||
Detail: fmt.Sprintf("cannot traverse %s without attribute", blockType),
|
||||
Subject: exp.Range().Ptr(),
|
||||
Context: exp.Range().Ptr(),
|
||||
},
|
||||
}
|
||||
}
|
||||
blocks := p.blocks[blockType][blockName.Name]
|
||||
if len(blocks) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var target *hcl.BodySchema
|
||||
if len(split) > 1 {
|
||||
if attr, ok := split[1].(hcl.TraverseAttr); ok {
|
||||
target = &hcl.BodySchema{
|
||||
Attributes: []hcl.AttributeSchema{{Name: attr.Name}},
|
||||
Blocks: []hcl.BlockHeaderSchema{{Type: attr.Name}},
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := p.resolveBlock(blocks[0], target); err != nil {
|
||||
return hcl.Diagnostics{
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid expression",
|
||||
Detail: err.Error(),
|
||||
Subject: v.SourceRange().Ptr(),
|
||||
Context: v.SourceRange().Ptr(),
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := p.resolveValue(v.RootName()); err != nil {
|
||||
return hcl.Diagnostics{
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid expression",
|
||||
Detail: err.Error(),
|
||||
Subject: v.SourceRange().Ptr(),
|
||||
Context: v.SourceRange().Ptr(),
|
||||
},
|
||||
}
|
||||
if err := p.resolveValue(v.RootName()); err != nil {
|
||||
return hcl.Diagnostics{
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid expression",
|
||||
Detail: err.Error(),
|
||||
Subject: v.SourceRange().Ptr(),
|
||||
Context: v.SourceRange().Ptr(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,8 +94,6 @@ func (p *parser) loadDeps(exp hcl.Expression, exclude map[string]struct{}) hcl.D
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveFunction forces evaluation of a function, storing the result into the
|
||||
// parser.
|
||||
func (p *parser) resolveFunction(name string) error {
|
||||
if _, ok := p.doneF[name]; ok {
|
||||
return nil
|
||||
@@ -174,13 +110,6 @@ func (p *parser) resolveFunction(name string) error {
|
||||
}
|
||||
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)
|
||||
if paramsDiags.HasErrors() {
|
||||
return paramsDiags
|
||||
@@ -233,8 +162,6 @@ func (p *parser) resolveFunction(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveValue forces evaluation of a named value, storing the result into the
|
||||
// parser.
|
||||
func (p *parser) resolveValue(name string) (err error) {
|
||||
if _, ok := p.ectx.Variables[name]; ok {
|
||||
return nil
|
||||
@@ -306,164 +233,13 @@ func (p *parser) resolveValue(name string) (err error) {
|
||||
return nil
|
||||
} else {
|
||||
// TODO: support lists with csv values
|
||||
return errors.Errorf("unsupported type %s for variable %s", vv.Type().FriendlyName(), name)
|
||||
return errors.Errorf("unsupported type %s for variable %s", v.Type(), name)
|
||||
}
|
||||
}
|
||||
v = &vv
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveBlock force evaluates a block, storing the result in the parser. If a
|
||||
// target schema is provided, only the attributes and blocks present in the
|
||||
// schema will be evaluated.
|
||||
func (p *parser) resolveBlock(block *hcl.Block, target *hcl.BodySchema) (err error) {
|
||||
name := block.Labels[0]
|
||||
if err := p.opt.ValidateLabel(name); err != nil {
|
||||
return hcl.Diagnostics{
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid name",
|
||||
Detail: err.Error(),
|
||||
Subject: &block.LabelRanges[0],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := p.doneB[block]; !ok {
|
||||
p.doneB[block] = map[string]struct{}{}
|
||||
}
|
||||
if _, ok := p.progressB[block]; !ok {
|
||||
p.progressB[block] = map[string]struct{}{}
|
||||
}
|
||||
|
||||
if target != nil {
|
||||
// filter out attributes and blocks that are already evaluated
|
||||
original := target
|
||||
target = &hcl.BodySchema{}
|
||||
for _, a := range original.Attributes {
|
||||
if _, ok := p.doneB[block][a.Name]; !ok {
|
||||
target.Attributes = append(target.Attributes, a)
|
||||
}
|
||||
}
|
||||
for _, b := range original.Blocks {
|
||||
if _, ok := p.doneB[block][b.Type]; !ok {
|
||||
target.Blocks = append(target.Blocks, b)
|
||||
}
|
||||
}
|
||||
if len(target.Attributes) == 0 && len(target.Blocks) == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if target != nil {
|
||||
// detect reference cycles
|
||||
for _, a := range target.Attributes {
|
||||
if _, ok := p.progressB[block][a.Name]; ok {
|
||||
return errors.Errorf("reference cycle not allowed for %s.%s.%s", block.Type, name, a.Name)
|
||||
}
|
||||
}
|
||||
for _, b := range target.Blocks {
|
||||
if _, ok := p.progressB[block][b.Type]; ok {
|
||||
return errors.Errorf("reference cycle not allowed for %s.%s.%s", block.Type, name, b.Type)
|
||||
}
|
||||
}
|
||||
for _, a := range target.Attributes {
|
||||
p.progressB[block][a.Name] = struct{}{}
|
||||
}
|
||||
for _, b := range target.Blocks {
|
||||
p.progressB[block][b.Type] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// create a filtered body that contains only the target properties
|
||||
body := func() hcl.Body {
|
||||
if target != nil {
|
||||
return FilterIncludeBody(block.Body, target)
|
||||
}
|
||||
|
||||
filter := &hcl.BodySchema{}
|
||||
for k := range p.doneB[block] {
|
||||
filter.Attributes = append(filter.Attributes, hcl.AttributeSchema{Name: k})
|
||||
filter.Blocks = append(filter.Blocks, hcl.BlockHeaderSchema{Type: k})
|
||||
}
|
||||
return FilterExcludeBody(block.Body, filter)
|
||||
}
|
||||
|
||||
// load dependencies from all targeted properties
|
||||
t, ok := p.blockTypes[block.Type]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
schema, _ := gohcl.ImpliedBodySchema(reflect.New(t).Interface())
|
||||
content, _, diag := body().PartialContent(schema)
|
||||
if diag.HasErrors() {
|
||||
return diag
|
||||
}
|
||||
for _, a := range content.Attributes {
|
||||
diag := p.loadDeps(a.Expr, nil)
|
||||
if diag.HasErrors() {
|
||||
return diag
|
||||
}
|
||||
}
|
||||
for _, b := range content.Blocks {
|
||||
err := p.resolveBlock(b, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// decode!
|
||||
var output reflect.Value
|
||||
if prev, ok := p.blockValues[block]; ok {
|
||||
output = prev
|
||||
} else {
|
||||
output = reflect.New(t)
|
||||
setLabel(output, block.Labels[0]) // early attach labels, so we can reference them
|
||||
}
|
||||
diag = gohcl.DecodeBody(body(), p.ectx, output.Interface())
|
||||
if diag.HasErrors() {
|
||||
return diag
|
||||
}
|
||||
p.blockValues[block] = output
|
||||
|
||||
// mark all targeted properties as done
|
||||
for _, a := range content.Attributes {
|
||||
p.doneB[block][a.Name] = struct{}{}
|
||||
}
|
||||
for _, b := range content.Blocks {
|
||||
p.doneB[block][b.Type] = struct{}{}
|
||||
}
|
||||
if target != nil {
|
||||
for _, a := range target.Attributes {
|
||||
p.doneB[block][a.Name] = struct{}{}
|
||||
}
|
||||
for _, b := range target.Blocks {
|
||||
p.doneB[block][b.Type] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// store the result into the evaluation context (so if can be referenced)
|
||||
outputType, err := gocty.ImpliedType(output.Interface())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outputValue, err := gocty.ToCtyValue(output.Interface(), outputType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var m map[string]cty.Value
|
||||
if m2, ok := p.ectx.Variables[block.Type]; ok {
|
||||
m = m2.AsValueMap()
|
||||
}
|
||||
if m == nil {
|
||||
m = map[string]cty.Value{}
|
||||
}
|
||||
m[name] = outputValue
|
||||
p.ectx.Variables[block.Type] = cty.MapVal(m)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
||||
reserved := map[string]struct{}{}
|
||||
schema, _ := gohcl.ImpliedBodySchema(val)
|
||||
@@ -479,7 +255,6 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
||||
if err := gohcl.DecodeBody(b, nil, &defs); err != nil {
|
||||
return err
|
||||
}
|
||||
defsSchema, _ := gohcl.ImpliedBodySchema(defs)
|
||||
|
||||
if opt.LookupVar == nil {
|
||||
opt.LookupVar = func(string) (string, bool) {
|
||||
@@ -487,12 +262,6 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
||||
}
|
||||
}
|
||||
|
||||
if opt.ValidateLabel == nil {
|
||||
opt.ValidateLabel = func(string) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
p := &parser{
|
||||
opt: opt,
|
||||
|
||||
@@ -500,16 +269,9 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
||||
attrs: map[string]*hcl.Attribute{},
|
||||
funcs: map[string]*functionDef{},
|
||||
|
||||
blocks: map[string]map[string][]*hcl.Block{},
|
||||
blockValues: map[*hcl.Block]reflect.Value{},
|
||||
blockTypes: map[string]reflect.Type{},
|
||||
|
||||
progress: map[string]struct{}{},
|
||||
progressF: map[string]struct{}{},
|
||||
progressB: map[*hcl.Block]map[string]struct{}{},
|
||||
|
||||
doneF: map[string]struct{}{},
|
||||
doneB: map[*hcl.Block]map[string]struct{}{},
|
||||
doneF: map[string]struct{}{},
|
||||
ectx: &hcl.EvalContext{
|
||||
Variables: map[string]cty.Value{},
|
||||
Functions: stdlibFunctions,
|
||||
@@ -531,20 +293,12 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
||||
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()
|
||||
if diags.HasErrors() {
|
||||
if d := removeAttributesDiags(diags, reserved, p.vars); len(d) > 0 {
|
||||
return d
|
||||
for _, d := range diags {
|
||||
if d.Detail != "Blocks are not allowed here." {
|
||||
return diags
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,15 +314,20 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
||||
_ = p.resolveValue(k)
|
||||
}
|
||||
|
||||
for _, a := range content.Attributes {
|
||||
return hcl.Diagnostics{
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid attribute",
|
||||
Detail: "global attributes currently not supported",
|
||||
Subject: &a.Range,
|
||||
Context: &a.Range,
|
||||
},
|
||||
for k := range p.attrs {
|
||||
if err := p.resolveValue(k); err != nil {
|
||||
if diags, ok := err.(hcl.Diagnostics); ok {
|
||||
return diags
|
||||
}
|
||||
return hcl.Diagnostics{
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid attribute",
|
||||
Detail: err.Error(),
|
||||
Subject: &p.attrs[k].Range,
|
||||
Context: &p.attrs[k].Range,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -595,32 +354,36 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
||||
if diags, ok := err.(hcl.Diagnostics); ok {
|
||||
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{
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid function",
|
||||
Detail: err.Error(),
|
||||
Subject: subject,
|
||||
Context: context,
|
||||
Subject: &p.funcs[k].Params.Range,
|
||||
Context: &p.funcs[k].Params.Range,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content, _, diags := b.PartialContent(schema)
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
|
||||
for _, a := range content.Attributes {
|
||||
return hcl.Diagnostics{
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid attribute",
|
||||
Detail: "global attributes currently not supported",
|
||||
Subject: &a.Range,
|
||||
Context: &a.Range,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
m := map[string]map[string][]*hcl.Block{}
|
||||
for _, b := range content.Blocks {
|
||||
if len(b.Labels) == 0 || len(b.Labels) > 1 {
|
||||
return hcl.Diagnostics{
|
||||
@@ -633,16 +396,19 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
||||
},
|
||||
}
|
||||
}
|
||||
bm, ok := p.blocks[b.Type]
|
||||
bm, ok := m[b.Type]
|
||||
if !ok {
|
||||
bm = map[string][]*hcl.Block{}
|
||||
p.blocks[b.Type] = bm
|
||||
m[b.Type] = bm
|
||||
}
|
||||
|
||||
lbl := b.Labels[0]
|
||||
bm[lbl] = append(bm[lbl], b)
|
||||
}
|
||||
|
||||
vt := reflect.ValueOf(val).Elem().Type()
|
||||
numFields := vt.NumField()
|
||||
|
||||
type value struct {
|
||||
reflect.Value
|
||||
idx int
|
||||
@@ -654,11 +420,9 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
||||
}
|
||||
types := map[string]field{}
|
||||
|
||||
vt := reflect.ValueOf(val).Elem().Type()
|
||||
for i := 0; i < vt.NumField(); i++ {
|
||||
for i := 0; i < numFields; i++ {
|
||||
tags := strings.Split(vt.Field(i).Tag.Get("hcl"), ",")
|
||||
|
||||
p.blockTypes[tags[0]] = vt.Field(i).Type.Elem().Elem()
|
||||
types[tags[0]] = field{
|
||||
idx: i,
|
||||
typ: vt.Field(i).Type,
|
||||
@@ -670,29 +434,18 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
||||
for _, b := range content.Blocks {
|
||||
v := reflect.ValueOf(val)
|
||||
|
||||
err := p.resolveBlock(b, nil)
|
||||
if err != nil {
|
||||
if diag, ok := err.(hcl.Diagnostics); ok {
|
||||
if diag.HasErrors() {
|
||||
diags = append(diags, diag...)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
return hcl.Diagnostics{
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid attribute",
|
||||
Detail: err.Error(),
|
||||
Subject: &b.LabelRanges[0],
|
||||
Context: &b.DefRange,
|
||||
},
|
||||
}
|
||||
}
|
||||
t, ok := types[b.Type]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
vv := p.blockValues[b]
|
||||
vv := reflect.New(t.typ.Elem().Elem())
|
||||
diag := gohcl.DecodeBody(b.Body, p.ectx, vv.Interface())
|
||||
if diag.HasErrors() {
|
||||
diags = append(diags, diag...)
|
||||
continue
|
||||
}
|
||||
|
||||
t := types[b.Type]
|
||||
lblIndex := setLabel(vv, b.Labels[0])
|
||||
|
||||
oldValue, exists := t.values[b.Labels[0]]
|
||||
@@ -706,6 +459,7 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if exists {
|
||||
if m := oldValue.Value.MethodByName("Merge"); m.IsValid() {
|
||||
@@ -726,23 +480,6 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
||||
return diags
|
||||
}
|
||||
|
||||
for k := range p.attrs {
|
||||
if err := p.resolveValue(k); err != nil {
|
||||
if diags, ok := err.(hcl.Diagnostics); ok {
|
||||
return diags
|
||||
}
|
||||
return hcl.Diagnostics{
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid attribute",
|
||||
Detail: err.Error(),
|
||||
Subject: &p.attrs[k].Range,
|
||||
Context: &p.attrs[k].Range,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -759,33 +496,3 @@ func setLabel(v reflect.Value, lbl string) int {
|
||||
}
|
||||
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
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-cty-funcs/cidr"
|
||||
"github.com/hashicorp/go-cty-funcs/crypto"
|
||||
"github.com/hashicorp/go-cty-funcs/encoding"
|
||||
"github.com/hashicorp/go-cty-funcs/uuid"
|
||||
"github.com/hashicorp/hcl/v2/ext/tryfunc"
|
||||
"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/stdlib"
|
||||
)
|
||||
@@ -99,7 +96,6 @@ var stdlibFunctions = map[string]function.Function{
|
||||
"substr": stdlib.SubstrFunc,
|
||||
"subtract": stdlib.SubtractFunc,
|
||||
"timeadd": stdlib.TimeAddFunc,
|
||||
"timestamp": timestampFunc,
|
||||
"title": stdlib.TitleFunc,
|
||||
"trim": stdlib.TrimFunc,
|
||||
"trimprefix": stdlib.TrimPrefixFunc,
|
||||
@@ -113,14 +109,3 @@ var stdlibFunctions = map[string]function.Function{
|
||||
"values": stdlib.ValuesFunc,
|
||||
"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
|
||||
},
|
||||
})
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/driver"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/moby/buildkit/client"
|
||||
@@ -20,11 +20,10 @@ type Input struct {
|
||||
URL string
|
||||
}
|
||||
|
||||
func ReadRemoteFiles(ctx context.Context, nodes []builder.Node, url string, names []string, pw progress.Writer) ([]File, *Input, error) {
|
||||
var filename string
|
||||
st, ok := detectGitContext(url)
|
||||
func ReadRemoteFiles(ctx context.Context, dis []build.DriverInfo, url string, names []string, pw progress.Writer) ([]File, *Input, error) {
|
||||
st, filename, ok := detectHTTPContext(url)
|
||||
if !ok {
|
||||
st, filename, ok = detectHTTPContext(url)
|
||||
st, ok = detectGitContext(url)
|
||||
if !ok {
|
||||
return nil, nil, errors.Errorf("not url context")
|
||||
}
|
||||
@@ -33,18 +32,18 @@ func ReadRemoteFiles(ctx context.Context, nodes []builder.Node, url string, name
|
||||
inp := &Input{State: st, URL: url}
|
||||
var files []File
|
||||
|
||||
var node *builder.Node
|
||||
for _, n := range nodes {
|
||||
if n.Err == nil {
|
||||
node = &n
|
||||
var di *build.DriverInfo
|
||||
for _, d := range dis {
|
||||
if d.Err == nil {
|
||||
di = &d
|
||||
continue
|
||||
}
|
||||
}
|
||||
if node == nil {
|
||||
if di == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
c, err := driver.Boot(ctx, ctx, node.Driver, pw)
|
||||
c, err := driver.Boot(ctx, ctx, di.Driver, pw)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
903
build/build.go
903
build/build.go
File diff suppressed because it is too large
Load Diff
109
build/git.go
109
build/git.go
@@ -1,109 +0,0 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/util/gitutil"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const DockerfileLabel = "com.docker.image.source.entrypoint"
|
||||
|
||||
func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath string) (res map[string]string) {
|
||||
res = make(map[string]string)
|
||||
if contextPath == "" {
|
||||
return
|
||||
}
|
||||
|
||||
setGitLabels := false
|
||||
if v, ok := os.LookupEnv("BUILDX_GIT_LABELS"); ok {
|
||||
if v == "full" { // backward compatibility with old "full" mode
|
||||
setGitLabels = true
|
||||
} else if v, _ := strconv.ParseBool(v); v {
|
||||
setGitLabels = v
|
||||
}
|
||||
}
|
||||
setGitInfo := true
|
||||
if v, ok := os.LookupEnv("BUILDX_GIT_INFO"); ok {
|
||||
if v, _ := strconv.ParseBool(v); v {
|
||||
setGitInfo = v
|
||||
}
|
||||
}
|
||||
|
||||
if !setGitLabels && !setGitInfo {
|
||||
return
|
||||
}
|
||||
|
||||
// figure out in which directory the git command needs to run in
|
||||
var wd string
|
||||
if filepath.IsAbs(contextPath) {
|
||||
wd = contextPath
|
||||
} else {
|
||||
cwd, _ := os.Getwd()
|
||||
wd, _ = filepath.Abs(filepath.Join(cwd, contextPath))
|
||||
}
|
||||
|
||||
gitc := gitutil.New(gitutil.WithContext(ctx), gitutil.WithWorkingDir(wd))
|
||||
if !gitc.IsInsideWorkTree() {
|
||||
logrus.Warnf("Unable to determine Git information")
|
||||
return
|
||||
}
|
||||
|
||||
var resRevision, resSource, resDockerfilePath string
|
||||
|
||||
if sha, err := gitc.FullCommit(); err == nil && sha != "" {
|
||||
resRevision = sha
|
||||
if gitc.IsDirty() {
|
||||
resRevision += "-dirty"
|
||||
}
|
||||
}
|
||||
|
||||
if rurl, err := gitc.RemoteURL(); err == nil && rurl != "" {
|
||||
resSource = rurl
|
||||
}
|
||||
|
||||
if setGitLabels {
|
||||
if root, err := gitc.RootDir(); err == nil && root != "" {
|
||||
if dockerfilePath == "" {
|
||||
dockerfilePath = filepath.Join(wd, "Dockerfile")
|
||||
}
|
||||
if !filepath.IsAbs(dockerfilePath) {
|
||||
cwd, _ := os.Getwd()
|
||||
dockerfilePath = filepath.Join(cwd, dockerfilePath)
|
||||
}
|
||||
dockerfilePath, _ = filepath.Rel(root, dockerfilePath)
|
||||
if !strings.HasPrefix(dockerfilePath, "..") {
|
||||
resDockerfilePath = dockerfilePath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if resSource != "" {
|
||||
if setGitLabels {
|
||||
res["label:"+specs.AnnotationSource] = resSource
|
||||
}
|
||||
if setGitInfo {
|
||||
res["vcs:source"] = resSource
|
||||
}
|
||||
}
|
||||
if resRevision != "" {
|
||||
if setGitLabels {
|
||||
res["label:"+specs.AnnotationRevision] = resRevision
|
||||
}
|
||||
if setGitInfo {
|
||||
res["vcs:revision"] = resRevision
|
||||
}
|
||||
}
|
||||
if resDockerfilePath != "" {
|
||||
if setGitLabels {
|
||||
res["label:"+DockerfileLabel] = resDockerfilePath
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/buildx/util/gitutil"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func setupTest(tb testing.TB) {
|
||||
gitutil.Mktmp(tb)
|
||||
gitutil.GitInit(tb)
|
||||
df := []byte("FROM alpine:latest\n")
|
||||
assert.NoError(tb, os.WriteFile("Dockerfile", df, 0644))
|
||||
gitutil.GitAdd(tb, "Dockerfile")
|
||||
gitutil.GitCommit(tb, "initial commit")
|
||||
}
|
||||
|
||||
func TestGetGitAttributesNoContext(t *testing.T) {
|
||||
setupTest(t)
|
||||
|
||||
gitattrs := getGitAttributes(context.Background(), "", "Dockerfile")
|
||||
assert.Empty(t, gitattrs)
|
||||
}
|
||||
|
||||
func TestGetGitAttributes(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
envGitLabels string
|
||||
envGitInfo string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "default",
|
||||
envGitLabels: "",
|
||||
envGitInfo: "",
|
||||
expected: []string{
|
||||
"vcs:revision",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "gitinfo",
|
||||
envGitLabels: "false",
|
||||
envGitInfo: "true",
|
||||
expected: []string{
|
||||
"vcs:revision",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "gitlabels",
|
||||
envGitLabels: "true",
|
||||
envGitInfo: "false",
|
||||
expected: []string{
|
||||
"label:" + DockerfileLabel,
|
||||
"label:" + specs.AnnotationRevision,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "both",
|
||||
envGitLabels: "true",
|
||||
envGitInfo: "",
|
||||
expected: []string{
|
||||
"label:" + DockerfileLabel,
|
||||
"label:" + specs.AnnotationRevision,
|
||||
"vcs:revision",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range cases {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
setupTest(t)
|
||||
if tt.envGitLabels != "" {
|
||||
t.Setenv("BUILDX_GIT_LABELS", tt.envGitLabels)
|
||||
}
|
||||
if tt.envGitInfo != "" {
|
||||
t.Setenv("BUILDX_GIT_INFO", tt.envGitInfo)
|
||||
}
|
||||
gitattrs := getGitAttributes(context.Background(), ".", "Dockerfile")
|
||||
for _, e := range tt.expected {
|
||||
assert.Contains(t, gitattrs, e)
|
||||
assert.NotEmpty(t, gitattrs[e])
|
||||
if e == "label:"+DockerfileLabel {
|
||||
assert.Equal(t, "Dockerfile", gitattrs[e])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGitAttributesWithRemote(t *testing.T) {
|
||||
setupTest(t)
|
||||
gitutil.GitSetRemote(t, "git@github.com:docker/buildx.git")
|
||||
|
||||
t.Setenv("BUILDX_GIT_LABELS", "true")
|
||||
gitattrs := getGitAttributes(context.Background(), ".", "Dockerfile")
|
||||
assert.Equal(t, 5, len(gitattrs))
|
||||
assert.Contains(t, gitattrs, "label:"+DockerfileLabel)
|
||||
assert.Equal(t, "Dockerfile", gitattrs["label:"+DockerfileLabel])
|
||||
assert.Contains(t, gitattrs, "label:"+specs.AnnotationRevision)
|
||||
assert.NotEmpty(t, gitattrs["label:"+specs.AnnotationRevision])
|
||||
assert.Contains(t, gitattrs, "label:"+specs.AnnotationSource)
|
||||
assert.Equal(t, "git@github.com:docker/buildx.git", gitattrs["label:"+specs.AnnotationSource])
|
||||
assert.Contains(t, gitattrs, "vcs:revision")
|
||||
assert.NotEmpty(t, gitattrs["vcs:revision"])
|
||||
assert.Contains(t, gitattrs, "vcs:source")
|
||||
assert.Equal(t, "git@github.com:docker/buildx.git", gitattrs["vcs:source"])
|
||||
}
|
||||
|
||||
func TestGetGitAttributesDirty(t *testing.T) {
|
||||
setupTest(t)
|
||||
|
||||
// make a change to test dirty flag
|
||||
df := []byte("FROM alpine:edge\n")
|
||||
assert.NoError(t, os.Mkdir("dir", 0755))
|
||||
assert.NoError(t, os.WriteFile(filepath.Join("dir", "Dockerfile"), df, 0644))
|
||||
|
||||
t.Setenv("BUILDX_GIT_LABELS", "true")
|
||||
gitattrs := getGitAttributes(context.Background(), ".", "Dockerfile")
|
||||
assert.Equal(t, 3, len(gitattrs))
|
||||
assert.Contains(t, gitattrs, "label:"+DockerfileLabel)
|
||||
assert.Equal(t, "Dockerfile", gitattrs["label:"+DockerfileLabel])
|
||||
assert.Contains(t, gitattrs, "label:"+specs.AnnotationRevision)
|
||||
assert.True(t, strings.HasSuffix(gitattrs["label:"+specs.AnnotationRevision], "-dirty"))
|
||||
assert.Contains(t, gitattrs, "vcs:revision")
|
||||
assert.True(t, strings.HasSuffix(gitattrs["vcs:revision"], "-dirty"))
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package build
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/buildx/driver"
|
||||
@@ -53,11 +53,11 @@ func createTempDockerfileFromURL(ctx context.Context, d driver.Driver, url strin
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dir, err := os.MkdirTemp("", "buildx")
|
||||
dir, err := ioutil.TempDir("", "buildx")
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
out = dir
|
||||
|
||||
@@ -7,18 +7,11 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// archiveHeaderSize is the number of bytes in an archive header
|
||||
archiveHeaderSize = 512
|
||||
// mobyHostGatewayName defines a special string which users can append to
|
||||
// --add-host to add an extra entry in /etc/hosts that maps
|
||||
// host.docker.internal to the host IP
|
||||
mobyHostGatewayName = "host-gateway"
|
||||
)
|
||||
// archiveHeaderSize is the number of bytes in an archive header
|
||||
const archiveHeaderSize = 512
|
||||
|
||||
func isLocalDir(c string) bool {
|
||||
st, err := os.Stat(c)
|
||||
@@ -45,35 +38,18 @@ func isArchive(header []byte) bool {
|
||||
}
|
||||
|
||||
// toBuildkitExtraHosts converts hosts from docker key:value format to buildkit's csv format
|
||||
func toBuildkitExtraHosts(inp []string, mobyDriver bool) (string, error) {
|
||||
func toBuildkitExtraHosts(inp []string) (string, error) {
|
||||
if len(inp) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
hosts := make([]string, 0, len(inp))
|
||||
for _, h := range inp {
|
||||
host, ip, ok := strings.Cut(h, ":")
|
||||
if !ok || host == "" || ip == "" {
|
||||
parts := strings.Split(h, ":")
|
||||
|
||||
if len(parts) != 2 || parts[0] == "" || net.ParseIP(parts[1]) == nil {
|
||||
return "", errors.Errorf("invalid host %s", h)
|
||||
}
|
||||
// Skip IP address validation for "host-gateway" string with moby driver
|
||||
if !mobyDriver || ip != mobyHostGatewayName {
|
||||
if net.ParseIP(ip) == nil {
|
||||
return "", errors.Errorf("invalid host %s", h)
|
||||
}
|
||||
}
|
||||
hosts = append(hosts, host+"="+ip)
|
||||
hosts = append(hosts, parts[0]+"="+parts[1])
|
||||
}
|
||||
return strings.Join(hosts, ","), nil
|
||||
}
|
||||
|
||||
// toBuildkitUlimits converts ulimits from docker type=soft:hard format to buildkit's csv format
|
||||
func toBuildkitUlimits(inp *opts.UlimitOpt) (string, error) {
|
||||
if inp == nil || len(inp.GetList()) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
ulimits := make([]string, 0, len(inp.GetList()))
|
||||
for _, ulimit := range inp.GetList() {
|
||||
ulimits = append(ulimits, ulimit.String())
|
||||
}
|
||||
return strings.Join(ulimits, ","), nil
|
||||
}
|
||||
|
||||
@@ -1,292 +0,0 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/buildx/driver"
|
||||
"github.com/docker/buildx/store"
|
||||
"github.com/docker/buildx/store/storeutil"
|
||||
"github.com/docker/buildx/util/dockerutil"
|
||||
"github.com/docker/buildx/util/imagetools"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// Builder represents an active builder object
|
||||
type Builder struct {
|
||||
*store.NodeGroup
|
||||
driverFactory driverFactory
|
||||
nodes []Node
|
||||
opts builderOpts
|
||||
err error
|
||||
}
|
||||
|
||||
type builderOpts struct {
|
||||
dockerCli command.Cli
|
||||
name string
|
||||
txn *store.Txn
|
||||
contextPathHash string
|
||||
validate bool
|
||||
}
|
||||
|
||||
// Option provides a variadic option for configuring the builder.
|
||||
type Option func(b *Builder)
|
||||
|
||||
// WithName sets builder name.
|
||||
func WithName(name string) Option {
|
||||
return func(b *Builder) {
|
||||
b.opts.name = name
|
||||
}
|
||||
}
|
||||
|
||||
// WithStore sets a store instance used at init.
|
||||
func WithStore(txn *store.Txn) Option {
|
||||
return func(b *Builder) {
|
||||
b.opts.txn = txn
|
||||
}
|
||||
}
|
||||
|
||||
// WithContextPathHash is used for determining pods in k8s driver instance.
|
||||
func WithContextPathHash(contextPathHash string) Option {
|
||||
return func(b *Builder) {
|
||||
b.opts.contextPathHash = contextPathHash
|
||||
}
|
||||
}
|
||||
|
||||
// WithSkippedValidation skips builder context validation.
|
||||
func WithSkippedValidation() Option {
|
||||
return func(b *Builder) {
|
||||
b.opts.validate = false
|
||||
}
|
||||
}
|
||||
|
||||
// New initializes a new builder client
|
||||
func New(dockerCli command.Cli, opts ...Option) (_ *Builder, err error) {
|
||||
b := &Builder{
|
||||
opts: builderOpts{
|
||||
dockerCli: dockerCli,
|
||||
validate: true,
|
||||
},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(b)
|
||||
}
|
||||
|
||||
if b.opts.txn == nil {
|
||||
// if store instance is nil we create a short-lived one using the
|
||||
// default store and ensure we release it on completion
|
||||
var release func()
|
||||
b.opts.txn, release, err = storeutil.GetStore(dockerCli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer release()
|
||||
}
|
||||
|
||||
if b.opts.name != "" {
|
||||
if b.NodeGroup, err = storeutil.GetNodeGroup(b.opts.txn, dockerCli, b.opts.name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if b.NodeGroup, err = storeutil.GetCurrentInstance(b.opts.txn, dockerCli); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if b.opts.validate {
|
||||
if err = b.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Validate validates builder context
|
||||
func (b *Builder) Validate() error {
|
||||
if b.NodeGroup.DockerContext {
|
||||
list, err := b.opts.dockerCli.ContextStore().List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentContext := b.opts.dockerCli.CurrentContext()
|
||||
for _, l := range list {
|
||||
if l.Name == b.Name && l.Name != currentContext {
|
||||
return errors.Errorf("use `docker --context=%s buildx` to switch to context %q", l.Name, l.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextName returns builder context name if available.
|
||||
func (b *Builder) ContextName() string {
|
||||
ctxbuilders, err := b.opts.dockerCli.ContextStore().List()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, cb := range ctxbuilders {
|
||||
if b.NodeGroup.Driver == "docker" && len(b.NodeGroup.Nodes) == 1 && b.NodeGroup.Nodes[0].Endpoint == cb.Name {
|
||||
return cb.Name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ImageOpt returns registry auth configuration
|
||||
func (b *Builder) ImageOpt() (imagetools.Opt, error) {
|
||||
return storeutil.GetImageConfig(b.opts.dockerCli, b.NodeGroup)
|
||||
}
|
||||
|
||||
// Boot bootstrap a builder
|
||||
func (b *Builder) Boot(ctx context.Context) (bool, error) {
|
||||
toBoot := make([]int, 0, len(b.nodes))
|
||||
for idx, d := range b.nodes {
|
||||
if d.Err != nil || d.Driver == nil || d.DriverInfo == nil {
|
||||
continue
|
||||
}
|
||||
if d.DriverInfo.Status != driver.Running {
|
||||
toBoot = append(toBoot, idx)
|
||||
}
|
||||
}
|
||||
if len(toBoot) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
printer, err := progress.NewPrinter(context.TODO(), os.Stderr, os.Stderr, progress.PrinterModeAuto)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
baseCtx := ctx
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
for _, idx := range toBoot {
|
||||
func(idx int) {
|
||||
eg.Go(func() error {
|
||||
pw := progress.WithPrefix(printer, b.NodeGroup.Nodes[idx].Name, len(toBoot) > 1)
|
||||
_, err := driver.Boot(ctx, baseCtx, b.nodes[idx].Driver, pw)
|
||||
if err != nil {
|
||||
b.nodes[idx].Err = err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}(idx)
|
||||
}
|
||||
|
||||
err = eg.Wait()
|
||||
err1 := printer.Wait()
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
|
||||
return true, err
|
||||
}
|
||||
|
||||
// Inactive checks if all nodes are inactive for this builder.
|
||||
func (b *Builder) Inactive() bool {
|
||||
for _, d := range b.nodes {
|
||||
if d.DriverInfo != nil && d.DriverInfo.Status == driver.Running {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Err returns error if any.
|
||||
func (b *Builder) Err() error {
|
||||
return b.err
|
||||
}
|
||||
|
||||
type driverFactory struct {
|
||||
driver.Factory
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// Factory returns the driver factory.
|
||||
func (b *Builder) Factory(ctx context.Context) (_ driver.Factory, err error) {
|
||||
b.driverFactory.once.Do(func() {
|
||||
if b.Driver != "" {
|
||||
b.driverFactory.Factory, err = driver.GetFactory(b.Driver, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// empty driver means nodegroup was implicitly created as a default
|
||||
// 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 := b.nodes[0].Endpoint
|
||||
var dockerapi *dockerutil.ClientAPI
|
||||
dockerapi, err = dockerutil.NewClientAPI(b.opts.dockerCli, b.nodes[0].Endpoint)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// check if endpoint is healthy is needed to determine the driver type.
|
||||
// if this fails then can't continue with driver selection.
|
||||
if _, err = dockerapi.Ping(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
b.driverFactory.Factory, err = driver.GetDefaultFactory(ctx, ep, dockerapi, false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
b.Driver = b.driverFactory.Factory.Name()
|
||||
}
|
||||
})
|
||||
return b.driverFactory.Factory, err
|
||||
}
|
||||
|
||||
// GetBuilders returns all builders
|
||||
func GetBuilders(dockerCli command.Cli, txn *store.Txn) ([]*Builder, error) {
|
||||
storeng, err := txn.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
builders := make([]*Builder, len(storeng))
|
||||
seen := make(map[string]struct{})
|
||||
for i, ng := range storeng {
|
||||
b, err := New(dockerCli,
|
||||
WithName(ng.Name),
|
||||
WithStore(txn),
|
||||
WithSkippedValidation(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
builders[i] = b
|
||||
seen[b.NodeGroup.Name] = struct{}{}
|
||||
}
|
||||
|
||||
contexts, err := dockerCli.ContextStore().List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.Slice(contexts, func(i, j int) bool {
|
||||
return contexts[i].Name < contexts[j].Name
|
||||
})
|
||||
|
||||
for _, c := range contexts {
|
||||
// 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 _, ok := seen[c.Name]; ok {
|
||||
continue
|
||||
}
|
||||
b, err := New(dockerCli,
|
||||
WithName(c.Name),
|
||||
WithStore(txn),
|
||||
WithSkippedValidation(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
builders = append(builders, b)
|
||||
}
|
||||
|
||||
return builders, nil
|
||||
}
|
||||
201
builder/node.go
201
builder/node.go
@@ -1,201 +0,0 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/buildx/driver"
|
||||
ctxkube "github.com/docker/buildx/driver/kubernetes/context"
|
||||
"github.com/docker/buildx/store"
|
||||
"github.com/docker/buildx/store/storeutil"
|
||||
"github.com/docker/buildx/util/dockerutil"
|
||||
"github.com/docker/buildx/util/imagetools"
|
||||
"github.com/docker/buildx/util/platformutil"
|
||||
"github.com/moby/buildkit/util/grpcerrors"
|
||||
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
type Node struct {
|
||||
store.Node
|
||||
Driver driver.Driver
|
||||
DriverInfo *driver.Info
|
||||
Platforms []ocispecs.Platform
|
||||
ImageOpt imagetools.Opt
|
||||
ProxyConfig map[string]string
|
||||
Version string
|
||||
Err error
|
||||
}
|
||||
|
||||
// Nodes returns nodes for this builder.
|
||||
func (b *Builder) Nodes() []Node {
|
||||
return b.nodes
|
||||
}
|
||||
|
||||
// LoadNodes loads and returns nodes for this builder.
|
||||
// TODO: this should be a method on a Node object and lazy load data for each driver.
|
||||
func (b *Builder) LoadNodes(ctx context.Context, withData bool) (_ []Node, err error) {
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
b.nodes = make([]Node, len(b.NodeGroup.Nodes))
|
||||
|
||||
defer func() {
|
||||
if b.err == nil && err != nil {
|
||||
b.err = err
|
||||
}
|
||||
}()
|
||||
|
||||
factory, err := b.Factory(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
imageopt, err := b.ImageOpt()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, n := range b.NodeGroup.Nodes {
|
||||
func(i int, n store.Node) {
|
||||
eg.Go(func() error {
|
||||
node := Node{
|
||||
Node: n,
|
||||
ProxyConfig: storeutil.GetProxyConfig(b.opts.dockerCli),
|
||||
}
|
||||
defer func() {
|
||||
b.nodes[i] = node
|
||||
}()
|
||||
|
||||
dockerapi, err := dockerutil.NewClientAPI(b.opts.dockerCli, n.Endpoint)
|
||||
if err != nil {
|
||||
node.Err = err
|
||||
return nil
|
||||
}
|
||||
|
||||
contextStore := b.opts.dockerCli.ContextStore()
|
||||
|
||||
var kcc driver.KubeClientConfig
|
||||
kcc, err = ctxkube.ConfigFromContext(n.Endpoint, contextStore)
|
||||
if err != nil {
|
||||
// err is returned if n.Endpoint is non-context name like "unix:///var/run/docker.sock".
|
||||
// try again with name="default".
|
||||
// FIXME(@AkihiroSuda): n should retain real context name.
|
||||
kcc, err = ctxkube.ConfigFromContext("default", contextStore)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
tryToUseKubeConfigInCluster := false
|
||||
if kcc == nil {
|
||||
tryToUseKubeConfigInCluster = true
|
||||
} else {
|
||||
if _, err := kcc.ClientConfig(); err != nil {
|
||||
tryToUseKubeConfigInCluster = true
|
||||
}
|
||||
}
|
||||
if tryToUseKubeConfigInCluster {
|
||||
kccInCluster := driver.KubeClientConfigInCluster{}
|
||||
if _, err := kccInCluster.ClientConfig(); err == nil {
|
||||
logrus.Debug("using kube config in cluster")
|
||||
kcc = kccInCluster
|
||||
}
|
||||
}
|
||||
|
||||
d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, factory, n.Endpoint, dockerapi, imageopt.Auth, kcc, n.Flags, n.Files, n.DriverOpts, n.Platforms, b.opts.contextPathHash)
|
||||
if err != nil {
|
||||
node.Err = err
|
||||
return nil
|
||||
}
|
||||
node.Driver = d
|
||||
node.ImageOpt = imageopt
|
||||
|
||||
if withData {
|
||||
if err := node.loadData(ctx); err != nil {
|
||||
node.Err = err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}(i, n)
|
||||
}
|
||||
|
||||
if err := eg.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: This should be done in the routine loading driver data
|
||||
if withData {
|
||||
kubernetesDriverCount := 0
|
||||
for _, d := range b.nodes {
|
||||
if d.DriverInfo != nil && len(d.DriverInfo.DynamicNodes) > 0 {
|
||||
kubernetesDriverCount++
|
||||
}
|
||||
}
|
||||
|
||||
isAllKubernetesDrivers := len(b.nodes) == kubernetesDriverCount
|
||||
if isAllKubernetesDrivers {
|
||||
var nodes []Node
|
||||
var dynamicNodes []store.Node
|
||||
for _, di := range b.nodes {
|
||||
// dynamic nodes are used in Kubernetes driver.
|
||||
// Kubernetes' pods are dynamically mapped to BuildKit Nodes.
|
||||
if di.DriverInfo != nil && len(di.DriverInfo.DynamicNodes) > 0 {
|
||||
for i := 0; i < len(di.DriverInfo.DynamicNodes); i++ {
|
||||
diClone := di
|
||||
if pl := di.DriverInfo.DynamicNodes[i].Platforms; len(pl) > 0 {
|
||||
diClone.Platforms = pl
|
||||
}
|
||||
nodes = append(nodes, di)
|
||||
}
|
||||
dynamicNodes = append(dynamicNodes, di.DriverInfo.DynamicNodes...)
|
||||
}
|
||||
}
|
||||
|
||||
// not append (remove the static nodes in the store)
|
||||
b.NodeGroup.Nodes = dynamicNodes
|
||||
b.nodes = nodes
|
||||
b.NodeGroup.Dynamic = true
|
||||
}
|
||||
}
|
||||
|
||||
return b.nodes, nil
|
||||
}
|
||||
|
||||
func (n *Node) loadData(ctx context.Context) error {
|
||||
if n.Driver == nil {
|
||||
return nil
|
||||
}
|
||||
info, err := n.Driver.Info(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.DriverInfo = info
|
||||
if n.DriverInfo.Status == driver.Running {
|
||||
driverClient, err := n.Driver.Client(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
workers, err := driverClient.ListWorkers(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "listing workers")
|
||||
}
|
||||
for _, w := range workers {
|
||||
n.Platforms = append(n.Platforms, w.Platforms...)
|
||||
}
|
||||
n.Platforms = platformutil.Dedupe(n.Platforms)
|
||||
inf, err := driverClient.Info(ctx)
|
||||
if err != nil {
|
||||
if st, ok := grpcerrors.AsGRPCStatus(err); ok && st.Code() == codes.Unimplemented {
|
||||
n.Version, err = n.Driver.Version(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getting version")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
n.Version = inf.BuildkitVersion.Version
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -15,8 +15,13 @@ import (
|
||||
cliflags "github.com/docker/cli/cli/flags"
|
||||
"github.com/moby/buildkit/solver/errdefs"
|
||||
"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/oidc"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
|
||||
@@ -24,67 +29,77 @@ import (
|
||||
_ "github.com/docker/buildx/driver/docker"
|
||||
_ "github.com/docker/buildx/driver/docker-container"
|
||||
_ "github.com/docker/buildx/driver/kubernetes"
|
||||
_ "github.com/docker/buildx/driver/remote"
|
||||
)
|
||||
|
||||
var experimental string
|
||||
|
||||
func init() {
|
||||
seed.WithTimeAndRand()
|
||||
stack.SetVersionInfo(version.Version, version.Revision)
|
||||
}
|
||||
|
||||
func runStandalone(cmd *command.DockerCli) error {
|
||||
if err := cmd.Initialize(cliflags.NewClientOptions()); err != nil {
|
||||
return err
|
||||
}
|
||||
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,
|
||||
})
|
||||
detect.ServiceName = "buildx"
|
||||
// do not log tracing errors to stdio
|
||||
otel.SetErrorHandler(skipErrors{})
|
||||
}
|
||||
|
||||
func main() {
|
||||
cmd, err := command.NewDockerCli()
|
||||
if os.Getenv("DOCKER_CLI_PLUGIN_ORIGINAL_CLI_COMMAND") == "" {
|
||||
if len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName {
|
||||
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 {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if plugin.RunningStandalone() {
|
||||
err = runStandalone(cmd)
|
||||
} else {
|
||||
err = runPlugin(cmd)
|
||||
}
|
||||
if err == nil {
|
||||
return
|
||||
p := commands.NewRootCmd("buildx", true, dockerCli)
|
||||
meta := manager.Metadata{
|
||||
SchemaVersion: "0.1.0",
|
||||
Vendor: "Docker Inc.",
|
||||
Version: version.Version,
|
||||
Experimental: experimental != "",
|
||||
}
|
||||
|
||||
if sterr, ok := err.(cli.StatusError); ok {
|
||||
if sterr.Status != "" {
|
||||
fmt.Fprintln(cmd.Err(), sterr.Status)
|
||||
if err := plugin.RunPlugin(dockerCli, p, meta); err != nil {
|
||||
if sterr, ok := err.(cli.StatusError); ok {
|
||||
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
|
||||
// have a non-zero exit status, so never exit with 0
|
||||
if sterr.StatusCode == 0 {
|
||||
os.Exit(1)
|
||||
for _, s := range errdefs.Sources(err) {
|
||||
s.Print(dockerCli.Err())
|
||||
}
|
||||
os.Exit(sterr.StatusCode)
|
||||
}
|
||||
|
||||
for _, s := range errdefs.Sources(err) {
|
||||
s.Print(cmd.Err())
|
||||
}
|
||||
if debug.IsEnabled() {
|
||||
fmt.Fprintf(cmd.Err(), "ERROR: %+v", stack.Formatter(err))
|
||||
} else {
|
||||
fmt.Fprintf(cmd.Err(), "ERROR: %v\n", err)
|
||||
}
|
||||
if debug.IsEnabled() {
|
||||
fmt.Fprintf(dockerCli.Err(), "error: %+v", stack.Formatter(err))
|
||||
} else {
|
||||
fmt.Fprintf(dockerCli.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) {}
|
||||
@@ -6,16 +6,12 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/docker/buildx/bake"
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/util/buildflags"
|
||||
"github.com/docker/buildx/util/confutil"
|
||||
"github.com/docker/buildx/util/dockerutil"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/buildx/util/tracing"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -23,8 +19,8 @@ import (
|
||||
|
||||
type bakeOptions struct {
|
||||
files []string
|
||||
overrides []string
|
||||
printOnly bool
|
||||
overrides []string
|
||||
commonOptions
|
||||
}
|
||||
|
||||
@@ -50,6 +46,7 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error
|
||||
if bake.IsRemoteURL(targets[0]) {
|
||||
cmdContext = targets[0]
|
||||
targets = targets[1:]
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,7 +61,7 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error
|
||||
if in.exportLoad {
|
||||
return errors.Errorf("push and load may not be set together at the moment")
|
||||
}
|
||||
overrides = append(overrides, "*.push=true")
|
||||
overrides = append(overrides, "*.output=type=registry")
|
||||
} else if in.exportLoad {
|
||||
overrides = append(overrides, "*.output=type=docker")
|
||||
}
|
||||
@@ -74,20 +71,11 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error
|
||||
if in.pull != nil {
|
||||
overrides = append(overrides, fmt.Sprintf("*.pull=%t", *in.pull))
|
||||
}
|
||||
if in.sbom != "" {
|
||||
overrides = append(overrides, fmt.Sprintf("*.attest=%s", buildflags.CanonicalizeAttest("sbom", in.sbom)))
|
||||
}
|
||||
if in.provenance != "" {
|
||||
overrides = append(overrides, fmt.Sprintf("*.attest=%s", buildflags.CanonicalizeAttest("provenance", in.provenance)))
|
||||
}
|
||||
contextPathHash, _ := os.Getwd()
|
||||
|
||||
ctx2, cancel := context.WithCancel(context.TODO())
|
||||
defer cancel()
|
||||
printer, err := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, in.progress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printer := progress.NewPrinter(ctx2, os.Stderr, in.progress)
|
||||
|
||||
defer func() {
|
||||
if printer != nil {
|
||||
@@ -98,30 +86,16 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error
|
||||
}
|
||||
}()
|
||||
|
||||
var nodes []builder.Node
|
||||
dis, err := getInstanceOrDefault(ctx, dockerCli, in.builder, contextPathHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var files []bake.File
|
||||
var inp *bake.Input
|
||||
|
||||
// instance only needed for reading remote bake files or building
|
||||
if url != "" || !in.printOnly {
|
||||
b, err := builder.New(dockerCli,
|
||||
builder.WithName(in.builder),
|
||||
builder.WithContextPathHash(contextPathHash),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = updateLastActivity(dockerCli, b.NodeGroup); err != nil {
|
||||
return errors.Wrapf(err, "failed to update builder last activity time")
|
||||
}
|
||||
nodes, err = b.LoadNodes(ctx, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if url != "" {
|
||||
files, inp, err = bake.ReadRemoteFiles(ctx, nodes, url, in.files, printer)
|
||||
files, inp, err = bake.ReadRemoteFiles(ctx, dis, url, in.files, printer)
|
||||
} else {
|
||||
files, err = bake.ReadLocalFiles(in.files)
|
||||
}
|
||||
@@ -129,30 +103,21 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error
|
||||
return err
|
||||
}
|
||||
|
||||
tgts, grps, err := bake.ReadTargets(ctx, files, targets, overrides, map[string]string{
|
||||
// don't forget to update documentation if you add a new
|
||||
// built-in variable: docs/manuals/bake/file-definition.md#built-in-variables
|
||||
"BAKE_CMD_CONTEXT": cmdContext,
|
||||
"BAKE_LOCAL_PLATFORM": platforms.DefaultString(),
|
||||
m, err := bake.ReadTargets(ctx, files, targets, overrides, map[string]string{
|
||||
"BAKE_CMD_CONTEXT": cmdContext,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// this function can update target context string from the input so call before printOnly check
|
||||
bo, err := bake.TargetsToBuildOpt(tgts, inp)
|
||||
bo, err := bake.TargetsToBuildOpt(m, inp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if in.printOnly {
|
||||
dt, err := json.MarshalIndent(struct {
|
||||
Group map[string]*bake.Group `json:"group,omitempty"`
|
||||
Target map[string]*bake.Target `json:"target"`
|
||||
}{
|
||||
grps,
|
||||
tgts,
|
||||
}, "", " ")
|
||||
dt, err := json.MarshalIndent(map[string]map[string]*bake.Target{"target": m}, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -165,17 +130,21 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error
|
||||
return nil
|
||||
}
|
||||
|
||||
resp, err := build.Build(ctx, nodes, bo, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), printer)
|
||||
resp, err := build.Build(ctx, dis, bo, dockerAPI(dockerCli), dockerCli.ConfigFile(), printer)
|
||||
if err != nil {
|
||||
return wrapBuildError(err, true)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(in.metadataFile) > 0 {
|
||||
dt := make(map[string]interface{})
|
||||
for t, r := range resp {
|
||||
dt[t] = decodeExporterResponse(r.ExporterResponse)
|
||||
if len(in.metadataFile) > 0 && resp != nil {
|
||||
mdata := map[string]map[string]string{}
|
||||
for k, r := range resp {
|
||||
mdata[k] = r.ExporterResponse
|
||||
}
|
||||
if err := writeMetadataFile(in.metadataFile, dt); err != nil {
|
||||
mdatab, err := json.MarshalIndent(mdata, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutils.AtomicWriteFile(in.metadataFile, mdatab, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -206,12 +175,10 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.StringArrayVarP(&options.files, "file", "f", []string{}, "Build definition file")
|
||||
flags.BoolVar(&options.exportLoad, "load", false, `Shorthand for "--set=*.output=type=docker"`)
|
||||
flags.BoolVar(&options.printOnly, "print", false, "Print the options without building")
|
||||
flags.BoolVar(&options.exportPush, "push", false, `Shorthand for "--set=*.output=type=registry"`)
|
||||
flags.StringVar(&options.sbom, "sbom", "", `Shorthand for "--set=*.attest=type=sbom"`)
|
||||
flags.StringVar(&options.provenance, "provenance", "", `Shorthand for "--set=*.attest=type=provenance"`)
|
||||
flags.StringArrayVar(&options.overrides, "set", nil, `Override target value (e.g., "targetpattern.key=value")`)
|
||||
flags.StringArrayVar(&options.overrides, "set", nil, "Override target value (eg: targetpattern.key=value)")
|
||||
flags.BoolVar(&options.exportPush, "push", false, "Shorthand for --set=*.output=type=registry")
|
||||
flags.BoolVar(&options.exportLoad, "load", false, "Shorthand for --set=*.output=type=docker")
|
||||
|
||||
commonBuildFlags(&options.commonOptions, flags)
|
||||
|
||||
|
||||
@@ -1,99 +1,94 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/console"
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/monitor"
|
||||
"github.com/docker/buildx/store"
|
||||
"github.com/docker/buildx/store/storeutil"
|
||||
"github.com/docker/buildx/util/buildflags"
|
||||
"github.com/docker/buildx/util/confutil"
|
||||
"github.com/docker/buildx/util/dockerutil"
|
||||
"github.com/docker/buildx/util/platformutil"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/buildx/util/tracing"
|
||||
"github.com/docker/cli-docs-tool/annotation"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/config"
|
||||
dockeropts "github.com/docker/cli/opts"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/session/auth/authprovider"
|
||||
"github.com/moby/buildkit/solver/errdefs"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/moby/buildkit/util/grpcerrors"
|
||||
"github.com/morikuni/aec"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
const defaultTargetName = "default"
|
||||
|
||||
type buildOptions struct {
|
||||
commonOptions
|
||||
contextPath string
|
||||
dockerfileName string
|
||||
printFunc string
|
||||
tags []string
|
||||
labels []string
|
||||
buildArgs []string
|
||||
|
||||
allow []string
|
||||
attests []string
|
||||
buildArgs []string
|
||||
cacheFrom []string
|
||||
cacheTo []string
|
||||
cgroupParent string
|
||||
contexts []string
|
||||
extraHosts []string
|
||||
imageIDFile string
|
||||
invoke string
|
||||
labels []string
|
||||
networkMode string
|
||||
noCacheFilter []string
|
||||
outputs []string
|
||||
platforms []string
|
||||
quiet bool
|
||||
secrets []string
|
||||
shmSize dockeropts.MemBytes
|
||||
ssh []string
|
||||
tags []string
|
||||
target string
|
||||
ulimits *dockeropts.UlimitOpt
|
||||
commonOptions
|
||||
cacheFrom []string
|
||||
cacheTo []string
|
||||
target string
|
||||
platforms []string
|
||||
secrets []string
|
||||
ssh []string
|
||||
outputs []string
|
||||
imageIDFile string
|
||||
extraHosts []string
|
||||
networkMode string
|
||||
|
||||
// unimplemented
|
||||
squash bool
|
||||
quiet bool
|
||||
|
||||
allow []string
|
||||
|
||||
// hidden
|
||||
// untrusted bool
|
||||
// ulimits *opts.UlimitOpt
|
||||
// memory opts.MemBytes
|
||||
// memorySwap opts.MemSwapBytes
|
||||
// shmSize opts.MemBytes
|
||||
// cpuShares int64
|
||||
// cpuPeriod int64
|
||||
// cpuQuota int64
|
||||
// cpuSetCpus string
|
||||
// cpuSetMems string
|
||||
// cgroupParent string
|
||||
// isolation string
|
||||
// compress bool
|
||||
// securityOpt []string
|
||||
}
|
||||
|
||||
type commonOptions struct {
|
||||
builder string
|
||||
metadataFile string
|
||||
noCache *bool
|
||||
progress string
|
||||
pull *bool
|
||||
|
||||
metadataFile string
|
||||
// golangci-lint#826
|
||||
// nolint:structcheck
|
||||
exportPush bool
|
||||
// nolint:structcheck
|
||||
exportLoad bool
|
||||
|
||||
sbom string
|
||||
provenance string
|
||||
}
|
||||
|
||||
func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
|
||||
if in.squash {
|
||||
return errors.Errorf("squash currently not implemented")
|
||||
}
|
||||
if in.quiet {
|
||||
logrus.Warnf("quiet currently not implemented")
|
||||
}
|
||||
|
||||
ctx := appcontext.Context()
|
||||
|
||||
ctx, end, err := tracing.TraceCurrentCommand(ctx, "build")
|
||||
@@ -113,46 +108,21 @@ func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
|
||||
pull = *in.pull
|
||||
}
|
||||
|
||||
if noCache && len(in.noCacheFilter) > 0 {
|
||||
return errors.Errorf("--no-cache and --no-cache-filter cannot currently be used together")
|
||||
}
|
||||
|
||||
if in.quiet && in.progress != progress.PrinterModeAuto && in.progress != progress.PrinterModeQuiet {
|
||||
return errors.Errorf("progress=%s and quiet cannot be used together", in.progress)
|
||||
} else if in.quiet {
|
||||
in.progress = "quiet"
|
||||
}
|
||||
|
||||
contexts, err := parseContextNames(in.contexts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printFunc, err := parsePrintFunc(in.printFunc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts := build.Options{
|
||||
Inputs: build.Inputs{
|
||||
ContextPath: in.contextPath,
|
||||
DockerfilePath: in.dockerfileName,
|
||||
InStream: os.Stdin,
|
||||
NamedContexts: contexts,
|
||||
},
|
||||
BuildArgs: listToMap(in.buildArgs, true),
|
||||
ExtraHosts: in.extraHosts,
|
||||
ImageIDFile: in.imageIDFile,
|
||||
Labels: listToMap(in.labels, false),
|
||||
NetworkMode: in.networkMode,
|
||||
NoCache: noCache,
|
||||
NoCacheFilter: in.noCacheFilter,
|
||||
Pull: pull,
|
||||
ShmSize: in.shmSize,
|
||||
Tags: in.tags,
|
||||
Target: in.target,
|
||||
Ulimits: in.ulimits,
|
||||
PrintFunc: printFunc,
|
||||
Tags: in.tags,
|
||||
Labels: listToMap(in.labels, false),
|
||||
BuildArgs: listToMap(in.buildArgs, true),
|
||||
Pull: pull,
|
||||
NoCache: noCache,
|
||||
Target: in.target,
|
||||
ImageIDFile: in.imageIDFile,
|
||||
ExtraHosts: in.extraHosts,
|
||||
NetworkMode: in.networkMode,
|
||||
}
|
||||
|
||||
platforms, err := platformutil.Parse(in.platforms)
|
||||
@@ -161,8 +131,7 @@ func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
|
||||
}
|
||||
opts.Platforms = platforms
|
||||
|
||||
dockerConfig := config.LoadDefaultConfigFile(os.Stderr)
|
||||
opts.Session = append(opts.Session, authprovider.NewDockerAuthProvider(dockerConfig))
|
||||
opts.Session = append(opts.Session, authprovider.NewDockerAuthProvider(os.Stderr))
|
||||
|
||||
secrets, err := buildflags.ParseSecretSpecs(in.secrets)
|
||||
if err != nil {
|
||||
@@ -218,19 +187,8 @@ func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
opts.Exports = outputs
|
||||
|
||||
inAttests := append([]string{}, in.attests...)
|
||||
if in.provenance != "" {
|
||||
inAttests = append(inAttests, buildflags.CanonicalizeAttest("provenance", in.provenance))
|
||||
}
|
||||
if in.sbom != "" {
|
||||
inAttests = append(inAttests, buildflags.CanonicalizeAttest("sbom", in.sbom))
|
||||
}
|
||||
opts.Attests, err = buildflags.ParseAttests(inAttests)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.Exports = outputs
|
||||
|
||||
cacheImports, err := buildflags.ParseCacheEntry(in.cacheFrom)
|
||||
if err != nil {
|
||||
@@ -256,198 +214,43 @@ func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
|
||||
contextPathHash = in.contextPath
|
||||
}
|
||||
|
||||
b, err := builder.New(dockerCli,
|
||||
builder.WithName(in.builder),
|
||||
builder.WithContextPathHash(contextPathHash),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = updateLastActivity(dockerCli, b.NodeGroup); err != nil {
|
||||
return errors.Wrapf(err, "failed to update builder last activity time")
|
||||
}
|
||||
nodes, err := b.LoadNodes(ctx, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imageID, res, err := buildTargets(ctx, dockerCli, nodes, map[string]build.Options{defaultTargetName: opts}, in.progress, in.metadataFile, in.invoke != "")
|
||||
err = wrapBuildError(err, false)
|
||||
if err != nil {
|
||||
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, nodes, map[string]build.Options{defaultTargetName: opts}, in.progress, in.metadataFile, true)
|
||||
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 {
|
||||
fmt.Println(imageID)
|
||||
}
|
||||
return nil
|
||||
return buildTargets(ctx, dockerCli, map[string]build.Options{defaultTargetName: opts}, in.progress, contextPathHash, in.builder, in.metadataFile)
|
||||
}
|
||||
|
||||
type nopCloser struct {
|
||||
io.WriteCloser
|
||||
}
|
||||
func buildTargets(ctx context.Context, dockerCli command.Cli, opts map[string]build.Options, progressMode, contextPathHash, instance string, metadataFile string) error {
|
||||
dis, err := getInstanceOrDefault(ctx, dockerCli, instance, contextPathHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
func (c nopCloser) Close() error { return nil }
|
||||
|
||||
func buildTargets(ctx context.Context, dockerCli command.Cli, nodes []builder.Node, opts map[string]build.Options, progressMode string, metadataFile string, allowNoOutput bool) (imageID string, res *build.ResultContext, err error) {
|
||||
ctx2, cancel := context.WithCancel(context.TODO())
|
||||
defer cancel()
|
||||
printer := progress.NewPrinter(ctx2, os.Stderr, progressMode)
|
||||
|
||||
printer, err := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, progressMode)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
var mu sync.Mutex
|
||||
var idx int
|
||||
resp, err := build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(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
|
||||
}
|
||||
}, allowNoOutput)
|
||||
resp, err := build.Build(ctx, dis, opts, dockerAPI(dockerCli), dockerCli.ConfigFile(), printer)
|
||||
err1 := printer.Wait()
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
if len(metadataFile) > 0 && resp != nil {
|
||||
if err := writeMetadataFile(metadataFile, decodeExporterResponse(resp[defaultTargetName].ExporterResponse)); err != nil {
|
||||
return "", nil, err
|
||||
mdatab, err := json.MarshalIndent(resp[defaultTargetName].ExporterResponse, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutils.AtomicWriteFile(metadataFile, mdatab, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
printWarnings(os.Stderr, printer.Warnings(), progressMode)
|
||||
|
||||
for k := range resp {
|
||||
if opts[k].PrintFunc != nil {
|
||||
if err := printResult(opts[k].PrintFunc, resp[k].ExporterResponse); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp[defaultTargetName].ExporterResponse["containerimage.digest"], res, err
|
||||
}
|
||||
|
||||
func parseInvokeConfig(invoke string) (cfg build.ContainerConfig, err error) {
|
||||
cfg.Tty = true
|
||||
if invoke == "default" {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
csvReader := csv.NewReader(strings.NewReader(invoke))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
if len(fields) == 1 && !strings.Contains(fields[0], "=") {
|
||||
cfg.Cmd = []string{fields[0]}
|
||||
return cfg, nil
|
||||
}
|
||||
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":
|
||||
cfg.Cmd = append(cfg.Cmd, value) // TODO: support JSON
|
||||
case "entrypoint":
|
||||
cfg.Entrypoint = append(cfg.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)
|
||||
}
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func printWarnings(w io.Writer, warnings []client.VertexWarning, mode string) {
|
||||
if len(warnings) == 0 || mode == progress.PrinterModeQuiet {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "\n ")
|
||||
sb := &bytes.Buffer{}
|
||||
if len(warnings) == 1 {
|
||||
fmt.Fprintf(sb, "1 warning found")
|
||||
} else {
|
||||
fmt.Fprintf(sb, "%d warnings found", len(warnings))
|
||||
}
|
||||
if logrus.GetLevel() < logrus.DebugLevel {
|
||||
fmt.Fprintf(sb, " (use --debug to expand)")
|
||||
}
|
||||
fmt.Fprintf(sb, ":\n")
|
||||
fmt.Fprint(w, aec.Apply(sb.String(), aec.YellowF))
|
||||
|
||||
for _, warn := range warnings {
|
||||
fmt.Fprintf(w, " - %s\n", warn.Short)
|
||||
if logrus.GetLevel() < logrus.DebugLevel {
|
||||
continue
|
||||
}
|
||||
for _, d := range warn.Detail {
|
||||
fmt.Fprintf(w, "%s\n", d)
|
||||
}
|
||||
if warn.URL != "" {
|
||||
fmt.Fprintf(w, "More info: %s\n", warn.URL)
|
||||
}
|
||||
if warn.SourceInfo != nil && warn.Range != nil {
|
||||
src := errdefs.Source{
|
||||
Info: warn.SourceInfo,
|
||||
Ranges: warn.Range,
|
||||
}
|
||||
src.Print(w)
|
||||
}
|
||||
fmt.Fprintf(w, "\n")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func newBuildOptions() buildOptions {
|
||||
ulimits := make(map[string]*units.Ulimit)
|
||||
return buildOptions{
|
||||
ulimits: dockeropts.NewUlimitOpt(&ulimits),
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
options := newBuildOptions()
|
||||
var options buildOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "build [OPTIONS] PATH | URL | -",
|
||||
@@ -457,152 +260,103 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.contextPath = args[0]
|
||||
options.builder = rootOpts.builder
|
||||
cmd.Flags().VisitAll(checkWarnedFlags)
|
||||
return runBuild(dockerCli, options)
|
||||
},
|
||||
}
|
||||
|
||||
var platformsDefault []string
|
||||
if v := os.Getenv("DOCKER_DEFAULT_PLATFORM"); v != "" {
|
||||
platformsDefault = []string{v}
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.StringSliceVar(&options.extraHosts, "add-host", []string{}, `Add a custom host-to-IP mapping (format: "host:ip")`)
|
||||
flags.SetAnnotation("add-host", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#add-entries-to-container-hosts-file---add-host"})
|
||||
|
||||
flags.StringSliceVar(&options.allow, "allow", []string{}, `Allow extra privileged entitlement (e.g., "network.host", "security.insecure")`)
|
||||
flags.BoolVar(&options.exportPush, "push", false, "Shorthand for --output=type=registry")
|
||||
flags.BoolVar(&options.exportLoad, "load", false, "Shorthand for --output=type=docker")
|
||||
|
||||
flags.StringArrayVarP(&options.tags, "tag", "t", []string{}, "Name and optionally a tag in the 'name:tag' format")
|
||||
flags.SetAnnotation("tag", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#tag-an-image--t"})
|
||||
flags.StringArrayVar(&options.buildArgs, "build-arg", []string{}, "Set build-time variables")
|
||||
flags.SetAnnotation("build-arg", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables---build-arg"})
|
||||
|
||||
flags.StringArrayVar(&options.cacheFrom, "cache-from", []string{}, `External cache sources (e.g., "user/app:cache", "type=local,src=path/to/dir")`)
|
||||
|
||||
flags.StringArrayVar(&options.cacheTo, "cache-to", []string{}, `Cache export destinations (e.g., "user/app:cache", "type=local,dest=path/to/dir")`)
|
||||
|
||||
flags.StringVar(&options.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container")
|
||||
flags.SetAnnotation("cgroup-parent", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#use-a-custom-parent-cgroup---cgroup-parent"})
|
||||
|
||||
flags.StringArrayVar(&options.contexts, "build-context", []string{}, "Additional build contexts (e.g., name=path)")
|
||||
|
||||
flags.StringVarP(&options.dockerfileName, "file", "f", "", `Name of the Dockerfile (default: "PATH/Dockerfile")`)
|
||||
flags.SetAnnotation("file", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#specify-a-dockerfile--f"})
|
||||
|
||||
flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file")
|
||||
flags.StringVarP(&options.dockerfileName, "file", "f", "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
|
||||
flags.SetAnnotation("file", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#specify-a-dockerfile--f"})
|
||||
|
||||
flags.StringArrayVar(&options.labels, "label", []string{}, "Set metadata for an image")
|
||||
|
||||
flags.BoolVar(&options.exportLoad, "load", false, `Shorthand for "--output=type=docker"`)
|
||||
flags.StringArrayVar(&options.cacheFrom, "cache-from", []string{}, "External cache sources (eg. user/app:cache, type=local,src=path/to/dir)")
|
||||
flags.StringArrayVar(&options.cacheTo, "cache-to", []string{}, "Cache export destinations (eg. user/app:cache, type=local,dest=path/to/dir)")
|
||||
|
||||
flags.StringVar(&options.networkMode, "network", "default", `Set the networking mode for the "RUN" instructions during build`)
|
||||
flags.StringVar(&options.target, "target", "", "Set the target build stage to build.")
|
||||
flags.SetAnnotation("target", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target"})
|
||||
|
||||
flags.StringArrayVar(&options.noCacheFilter, "no-cache-filter", []string{}, "Do not cache specified stages")
|
||||
|
||||
flags.StringArrayVarP(&options.outputs, "output", "o", []string{}, `Output destination (format: "type=local,dest=path")`)
|
||||
|
||||
flags.StringArrayVar(&options.platforms, "platform", platformsDefault, "Set target platform for build")
|
||||
|
||||
if isExperimental() {
|
||||
flags.StringVar(&options.printFunc, "print", "", "Print result of information request (e.g., outline, targets) [experimental]")
|
||||
}
|
||||
|
||||
flags.BoolVar(&options.exportPush, "push", false, `Shorthand for "--output=type=registry"`)
|
||||
flags.StringSliceVar(&options.allow, "allow", []string{}, "Allow extra privileged entitlement, e.g. network.host, security.insecure")
|
||||
|
||||
// not implemented
|
||||
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the build output and print image ID on success")
|
||||
|
||||
flags.StringArrayVar(&options.secrets, "secret", []string{}, `Secret to expose to the build (format: "id=mysecret[,src=/local/secret]")`)
|
||||
|
||||
flags.Var(&options.shmSize, "shm-size", `Size of "/dev/shm"`)
|
||||
|
||||
flags.StringArrayVar(&options.ssh, "ssh", []string{}, `SSH agent socket or keys to expose to the build (format: "default|<id>[=<socket>|<key>[,<key>]]")`)
|
||||
|
||||
flags.StringArrayVarP(&options.tags, "tag", "t", []string{}, `Name and optionally a tag (format: "name:tag")`)
|
||||
flags.SetAnnotation("tag", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#tag-an-image--t"})
|
||||
|
||||
flags.StringVar(&options.target, "target", "", "Set the target build stage to build")
|
||||
flags.SetAnnotation("target", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target"})
|
||||
|
||||
flags.Var(options.ulimits, "ulimit", "Ulimit options")
|
||||
|
||||
flags.StringArrayVar(&options.attests, "attest", []string{}, `Attestation parameters (format: "type=sbom,generator=image")`)
|
||||
flags.StringVar(&options.sbom, "sbom", "", `Shorthand for "--attest=type=sbom"`)
|
||||
flags.StringVar(&options.provenance, "provenance", "", `Shortand for "--attest=type=provenance"`)
|
||||
|
||||
if isExperimental() {
|
||||
flags.StringVar(&options.invoke, "invoke", "", "Invoke a command after the build [experimental]")
|
||||
}
|
||||
flags.StringVar(&options.networkMode, "network", "default", "Set the networking mode for the RUN instructions during build")
|
||||
flags.StringSliceVar(&options.extraHosts, "add-host", []string{}, "Add a custom host-to-IP mapping (host:ip)")
|
||||
flags.SetAnnotation("add-host", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#add-entries-to-container-hosts-file---add-host"})
|
||||
flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file")
|
||||
flags.BoolVar(&options.squash, "squash", false, "Squash newly built layers into a single new layer")
|
||||
flags.MarkHidden("quiet")
|
||||
flags.MarkHidden("squash")
|
||||
|
||||
// hidden flags
|
||||
var ignore string
|
||||
var ignoreSlice []string
|
||||
var ignoreBool bool
|
||||
var ignoreInt int64
|
||||
|
||||
flags.BoolVar(&ignoreBool, "compress", false, "Compress the build context using gzip")
|
||||
flags.MarkHidden("compress")
|
||||
|
||||
flags.StringVar(&ignore, "isolation", "", "Container isolation technology")
|
||||
flags.MarkHidden("isolation")
|
||||
flags.SetAnnotation("isolation", "flag-warn", []string{"isolation flag is deprecated with BuildKit."})
|
||||
|
||||
flags.StringVar(&ignore, "ulimit", "", "Ulimit options")
|
||||
flags.MarkHidden("ulimit")
|
||||
flags.StringSliceVar(&ignoreSlice, "security-opt", []string{}, "Security options")
|
||||
flags.MarkHidden("security-opt")
|
||||
flags.SetAnnotation("security-opt", "flag-warn", []string{`security-opt flag is deprecated. "RUN --security=insecure" should be used with BuildKit.`})
|
||||
|
||||
flags.BoolVar(&ignoreBool, "squash", false, "Squash newly built layers into a single new layer")
|
||||
flags.MarkHidden("squash")
|
||||
flags.SetAnnotation("squash", "flag-warn", []string{"experimental flag squash is removed with BuildKit. You should squash inside build using a multi-stage Dockerfile for efficiency."})
|
||||
|
||||
flags.BoolVar(&ignoreBool, "compress", false, "Compress the build context using gzip")
|
||||
flags.MarkHidden("compress")
|
||||
flags.StringVarP(&ignore, "memory", "m", "", "Memory limit")
|
||||
flags.MarkHidden("memory")
|
||||
|
||||
flags.StringVar(&ignore, "memory-swap", "", `Swap limit equal to memory plus swap: "-1" to enable unlimited swap`)
|
||||
flags.StringVar(&ignore, "memory-swap", "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
|
||||
flags.MarkHidden("memory-swap")
|
||||
|
||||
flags.StringVar(&ignore, "shm-size", "", "Size of /dev/shm")
|
||||
flags.MarkHidden("shm-size")
|
||||
flags.Int64VarP(&ignoreInt, "cpu-shares", "c", 0, "CPU shares (relative weight)")
|
||||
flags.MarkHidden("cpu-shares")
|
||||
|
||||
flags.Int64Var(&ignoreInt, "cpu-period", 0, "Limit the CPU CFS (Completely Fair Scheduler) period")
|
||||
flags.MarkHidden("cpu-period")
|
||||
|
||||
flags.Int64Var(&ignoreInt, "cpu-quota", 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
|
||||
flags.MarkHidden("cpu-quota")
|
||||
|
||||
flags.StringVar(&ignore, "cpuset-cpus", "", `CPUs in which to allow execution ("0-3", "0,1")`)
|
||||
flags.StringVar(&ignore, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
|
||||
flags.MarkHidden("cpuset-cpus")
|
||||
|
||||
flags.StringVar(&ignore, "cpuset-mems", "", `MEMs in which to allow execution ("0-3", "0,1")`)
|
||||
flags.StringVar(&ignore, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
|
||||
flags.MarkHidden("cpuset-mems")
|
||||
|
||||
flags.StringVar(&ignore, "cgroup-parent", "", "Optional parent cgroup for the container")
|
||||
flags.MarkHidden("cgroup-parent")
|
||||
flags.StringVar(&ignore, "isolation", "", "Container isolation technology")
|
||||
flags.MarkHidden("isolation")
|
||||
flags.BoolVar(&ignoreBool, "rm", true, "Remove intermediate containers after a successful build")
|
||||
flags.MarkHidden("rm")
|
||||
|
||||
flags.BoolVar(&ignoreBool, "force-rm", false, "Always remove intermediate containers")
|
||||
flags.MarkHidden("force-rm")
|
||||
|
||||
platformsDefault := []string{}
|
||||
if v := os.Getenv("DOCKER_DEFAULT_PLATFORM"); v != "" {
|
||||
platformsDefault = []string{v}
|
||||
}
|
||||
flags.StringArrayVar(&options.platforms, "platform", platformsDefault, "Set target platform for build")
|
||||
|
||||
flags.StringArrayVar(&options.secrets, "secret", []string{}, "Secret file to expose to the build: id=mysecret,src=/local/secret")
|
||||
|
||||
flags.StringArrayVar(&options.ssh, "ssh", []string{}, "SSH agent socket or keys to expose to the build (format: default|<id>[=<socket>|<key>[,<key>]])")
|
||||
|
||||
flags.StringArrayVarP(&options.outputs, "output", "o", []string{}, "Output destination (format: type=local,dest=path)")
|
||||
|
||||
commonBuildFlags(&options.commonOptions, flags)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func commonBuildFlags(options *commonOptions, flags *pflag.FlagSet) {
|
||||
options.noCache = flags.Bool("no-cache", false, "Do not use cache when building the image")
|
||||
flags.StringVar(&options.progress, "progress", "auto", `Set type of progress output ("auto", "plain", "tty"). Use plain to show container output`)
|
||||
options.pull = flags.Bool("pull", false, "Always attempt to pull all referenced images")
|
||||
flags.StringVar(&options.progress, "progress", "auto", "Set type of progress output (auto, plain, tty). Use plain to show container output")
|
||||
options.pull = flags.Bool("pull", false, "Always attempt to pull a newer version of the image")
|
||||
flags.StringVar(&options.metadataFile, "metadata-file", "", "Write build result metadata to the file")
|
||||
}
|
||||
|
||||
func checkWarnedFlags(f *pflag.Flag) {
|
||||
if !f.Changed {
|
||||
return
|
||||
}
|
||||
for t, m := range f.Annotations {
|
||||
switch t {
|
||||
case "flag-warn":
|
||||
logrus.Warn(m[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func listToMap(values []string, defaultEnv bool) map[string]string {
|
||||
result := make(map[string]string, len(values))
|
||||
for _, value := range values {
|
||||
@@ -622,125 +376,3 @@ func listToMap(values []string, defaultEnv bool) map[string]string {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func parseContextNames(values []string) (map[string]build.NamedContext, error) {
|
||||
if len(values) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
result := make(map[string]build.NamedContext, len(values))
|
||||
for _, value := range values {
|
||||
kv := strings.SplitN(value, "=", 2)
|
||||
if len(kv) != 2 {
|
||||
return nil, errors.Errorf("invalid context value: %s, expected key=value", value)
|
||||
}
|
||||
named, err := reference.ParseNormalizedNamed(kv[0])
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid context name %s", kv[0])
|
||||
}
|
||||
name := strings.TrimSuffix(reference.FamiliarString(named), ":latest")
|
||||
result[name] = build.NamedContext{Path: kv[1]}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func parsePrintFunc(str string) (*build.PrintFunc, error) {
|
||||
if str == "" {
|
||||
return nil, nil
|
||||
}
|
||||
csvReader := csv.NewReader(strings.NewReader(str))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f := &build.PrintFunc{}
|
||||
for _, field := range fields {
|
||||
parts := strings.SplitN(field, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
if parts[0] == "format" {
|
||||
f.Format = parts[1]
|
||||
} else {
|
||||
return nil, errors.Errorf("invalid print field: %s", field)
|
||||
}
|
||||
} else {
|
||||
if f.Name != "" {
|
||||
return nil, errors.Errorf("invalid print value: %s", str)
|
||||
}
|
||||
f.Name = field
|
||||
}
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func writeMetadataFile(filename string, dt interface{}) error {
|
||||
b, err := json.MarshalIndent(dt, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutils.AtomicWriteFile(filename, b, 0644)
|
||||
}
|
||||
|
||||
func decodeExporterResponse(exporterResponse map[string]string) map[string]interface{} {
|
||||
out := make(map[string]interface{})
|
||||
for k, v := range exporterResponse {
|
||||
dt, err := base64.StdEncoding.DecodeString(v)
|
||||
if err != nil {
|
||||
out[k] = v
|
||||
continue
|
||||
}
|
||||
var raw map[string]interface{}
|
||||
if err = json.Unmarshal(dt, &raw); err != nil || len(raw) == 0 {
|
||||
out[k] = v
|
||||
continue
|
||||
}
|
||||
out[k] = json.RawMessage(dt)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func wrapBuildError(err error, bake bool) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
st, ok := grpcerrors.AsGRPCStatus(err)
|
||||
if ok {
|
||||
if st.Code() == codes.Unimplemented && strings.Contains(st.Message(), "unsupported frontend capability moby.buildkit.frontend.contexts") {
|
||||
msg := "current frontend does not support --build-context."
|
||||
if bake {
|
||||
msg = "current frontend does not support defining additional contexts for targets."
|
||||
}
|
||||
msg += " Named contexts are supported since Dockerfile v1.4. Use #syntax directive in Dockerfile or update to latest BuildKit."
|
||||
return &wrapped{err, msg}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type wrapped struct {
|
||||
err error
|
||||
msg string
|
||||
}
|
||||
|
||||
func (w *wrapped) Error() string {
|
||||
return w.msg
|
||||
}
|
||||
|
||||
func (w *wrapped) Unwrap() error {
|
||||
return w.err
|
||||
}
|
||||
|
||||
func isExperimental() bool {
|
||||
if v, ok := os.LookupEnv("BUILDX_EXPERIMENTAL"); ok {
|
||||
vv, _ := strconv.ParseBool(v)
|
||||
return vv
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func updateLastActivity(dockerCli command.Cli, ng *store.NodeGroup) error {
|
||||
txn, release, err := storeutil.GetStore(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer release()
|
||||
return txn.UpdateLastActivity(ng)
|
||||
}
|
||||
|
||||
@@ -1,26 +1,16 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/driver"
|
||||
remoteutil "github.com/docker/buildx/driver/remote/util"
|
||||
"github.com/docker/buildx/store"
|
||||
"github.com/docker/buildx/store/storeutil"
|
||||
"github.com/docker/buildx/util/cobrautil"
|
||||
"github.com/docker/buildx/util/confutil"
|
||||
"github.com/docker/buildx/util/dockerutil"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
dopts "github.com/docker/cli/opts"
|
||||
"github.com/google/shlex"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/pkg/errors"
|
||||
@@ -39,7 +29,6 @@ type createOptions struct {
|
||||
flags string
|
||||
configFile string
|
||||
driverOpts []string
|
||||
bootstrap bool
|
||||
// upgrade bool // perform upgrade of the driver
|
||||
}
|
||||
|
||||
@@ -65,7 +54,23 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
txn, release, err := storeutil.GetStore(dockerCli)
|
||||
driverName := in.driver
|
||||
if driverName == "" {
|
||||
f, err := driver.GetDefaultFactory(ctx, dockerCli.Client(), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f == nil {
|
||||
return errors.Errorf("no valid drivers found")
|
||||
}
|
||||
driverName = f.Name()
|
||||
}
|
||||
|
||||
if driver.GetFactory(driverName, true) == nil {
|
||||
return errors.Errorf("failed to find driver %q", in.driver)
|
||||
}
|
||||
|
||||
txn, release, err := getStore(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -79,19 +84,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)
|
||||
if err != nil {
|
||||
if os.IsNotExist(errors.Cause(err)) {
|
||||
@@ -99,62 +91,29 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||
logrus.Warnf("failed to find %q for append, creating a new instance instead", in.name)
|
||||
}
|
||||
if in.actionLeave {
|
||||
return errors.Errorf("failed to find instance %q for leave", in.name)
|
||||
return errors.Errorf("failed to find instance %q for leave", name)
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
buildkitHost := os.Getenv("BUILDKIT_HOST")
|
||||
|
||||
driverName := in.driver
|
||||
if driverName == "" {
|
||||
if ng != nil {
|
||||
driverName = ng.Driver
|
||||
} else if len(args) == 0 && buildkitHost != "" {
|
||||
driverName = "remote"
|
||||
} else {
|
||||
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 ng != nil {
|
||||
if in.nodeName == "" && !in.actionAppend {
|
||||
return errors.Errorf("existing instance for %q but no append mode, specify --node to make changes for existing instances", name)
|
||||
return errors.Errorf("existing instance for %s but no append mode, specify --node to make changes for existing instances", name)
|
||||
}
|
||||
if driverName != ng.Driver {
|
||||
return errors.Errorf("existing instance for %q but has mismatched driver %q", name, ng.Driver)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := driver.GetFactory(driverName, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ngOriginal := ng
|
||||
if ngOriginal != nil {
|
||||
ngOriginal = ngOriginal.Copy()
|
||||
}
|
||||
|
||||
if ng == nil {
|
||||
ng = &store.NodeGroup{
|
||||
Name: name,
|
||||
Driver: driverName,
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
if ng.Driver == "" || in.driver != "" {
|
||||
ng.Driver = driverName
|
||||
}
|
||||
|
||||
var flags []string
|
||||
if in.flags != "" {
|
||||
flags, err = shlex.Split(in.flags)
|
||||
@@ -164,72 +123,44 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||
}
|
||||
|
||||
var ep string
|
||||
var setEp bool
|
||||
if in.actionLeave {
|
||||
if err := ng.Leave(in.nodeName); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
switch {
|
||||
case driverName == "kubernetes":
|
||||
if len(args) > 0 {
|
||||
logrus.Warnf("kubernetes driver does not support endpoint args %q", args[0])
|
||||
if len(args) > 0 {
|
||||
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 = getCurrentEndpoint(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if in.driver == "kubernetes" {
|
||||
// naming endpoint to make --append works
|
||||
ep = (&url.URL{
|
||||
Scheme: driverName,
|
||||
Scheme: in.driver,
|
||||
Path: "/" + in.name,
|
||||
RawQuery: (&url.Values{
|
||||
"deployment": {in.nodeName},
|
||||
"kubeconfig": {os.Getenv("KUBECONFIG")},
|
||||
}).Encode(),
|
||||
}).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 = dockerutil.GetCurrentEndpoint(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
setEp = false
|
||||
}
|
||||
|
||||
m, err := csvToMap(in.driverOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 {
|
||||
if err := ng.Update(in.nodeName, ep, in.platform, len(args) > 0, in.actionAppend, flags, in.configFile, m); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -238,41 +169,8 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := builder.New(dockerCli,
|
||||
builder.WithName(ng.Name),
|
||||
builder.WithStore(txn),
|
||||
builder.WithSkippedValidation(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
nodes, err := b.LoadNodes(timeoutCtx, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, node := range nodes {
|
||||
if err := node.Err; err != nil {
|
||||
err := errors.Errorf("failed to initialize builder %s (%s): %s", ng.Name, node.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 != "" {
|
||||
current, err := dockerutil.GetCurrentEndpoint(dockerCli)
|
||||
current, err := getCurrentEndpoint(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -281,12 +179,6 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if in.bootstrap {
|
||||
if _, err = b.Boot(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", ng.Name)
|
||||
return nil
|
||||
}
|
||||
@@ -294,12 +186,9 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||
func createCmd(dockerCli command.Cli) *cobra.Command {
|
||||
var options createOptions
|
||||
|
||||
var drivers bytes.Buffer
|
||||
for _, d := range driver.GetFactories(true) {
|
||||
if len(drivers.String()) > 0 {
|
||||
drivers.WriteString(", ")
|
||||
}
|
||||
drivers.WriteString(fmt.Sprintf(`"%s"`, d.Name()))
|
||||
var drivers []string
|
||||
for s := range driver.GetFactories() {
|
||||
drivers = append(drivers, s)
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@@ -314,28 +203,23 @@ func createCmd(dockerCli command.Cli) *cobra.Command {
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.StringVar(&options.name, "name", "", "Builder instance name")
|
||||
flags.StringVar(&options.driver, "driver", "", fmt.Sprintf("Driver to use (available: %s)", drivers.String()))
|
||||
flags.StringVar(&options.driver, "driver", "", fmt.Sprintf("Driver to use (available: %v)", drivers))
|
||||
flags.StringVar(&options.nodeName, "node", "", "Create/modify node with given name")
|
||||
flags.StringVar(&options.flags, "buildkitd-flags", "", "Flags for buildkitd daemon")
|
||||
flags.StringVar(&options.configFile, "config", "", "BuildKit config file")
|
||||
flags.StringArrayVar(&options.platform, "platform", []string{}, "Fixed platforms for current node")
|
||||
flags.StringArrayVar(&options.driverOpts, "driver-opt", []string{}, "Options for the driver")
|
||||
flags.BoolVar(&options.bootstrap, "bootstrap", false, "Boot builder after creation")
|
||||
|
||||
flags.BoolVar(&options.actionAppend, "append", false, "Append a node to builder instead of changing it")
|
||||
flags.BoolVar(&options.actionLeave, "leave", false, "Remove a node from builder instead of changing it")
|
||||
flags.BoolVar(&options.use, "use", false, "Set the current builder instance")
|
||||
|
||||
// hide builder persistent flag for this command
|
||||
cobrautil.HideInheritedFlags(cmd, "builder")
|
||||
_ = flags
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func csvToMap(in []string) (map[string]string, error) {
|
||||
if len(in) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
m := make(map[string]string, len(in))
|
||||
for _, s := range in {
|
||||
csvReader := csv.NewReader(strings.NewReader(s))
|
||||
@@ -353,27 +237,3 @@ func csvToMap(in []string) (map[string]string, error) {
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// validateEndpoint validates that endpoint is either a context or a docker host
|
||||
func validateEndpoint(dockerCli command.Cli, ep string) (string, error) {
|
||||
dem, err := dockerutil.GetDockerEndpoint(dockerCli, ep)
|
||||
if err == nil && dem != nil {
|
||||
if ep == "default" {
|
||||
return dem.Host, nil
|
||||
}
|
||||
return ep, nil
|
||||
}
|
||||
h, err := dopts.ParseHost(true, ep)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to parse endpoint %s", ep)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -4,18 +4,16 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tonistiigi/units"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
@@ -33,29 +31,25 @@ func runDiskUsage(dockerCli command.Cli, opts duOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := builder.New(dockerCli, builder.WithName(opts.builder))
|
||||
dis, err := getInstanceOrDefault(ctx, dockerCli, opts.builder, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nodes, err := b.LoadNodes(ctx, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, node := range nodes {
|
||||
if node.Err != nil {
|
||||
return node.Err
|
||||
for _, di := range dis {
|
||||
if di.Err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
out := make([][]*client.UsageInfo, len(nodes))
|
||||
out := make([][]*client.UsageInfo, len(dis))
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
for i, node := range nodes {
|
||||
func(i int, node builder.Node) {
|
||||
for i, di := range dis {
|
||||
func(i int, di build.DriverInfo) {
|
||||
eg.Go(func() error {
|
||||
if node.Driver != nil {
|
||||
c, err := node.Driver.Client(ctx)
|
||||
if di.Driver != nil {
|
||||
c, err := di.Driver.Client(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -68,7 +62,7 @@ func runDiskUsage(dockerCli command.Cli, opts duOptions) error {
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}(i, node)
|
||||
}(i, di)
|
||||
}
|
||||
|
||||
if err := eg.Wait(); err != nil {
|
||||
@@ -131,20 +125,20 @@ func printKV(w io.Writer, k string, v interface{}) {
|
||||
func printVerbose(tw *tabwriter.Writer, du []*client.UsageInfo) {
|
||||
for _, di := range du {
|
||||
printKV(tw, "ID", di.ID)
|
||||
if len(di.Parents) != 0 {
|
||||
printKV(tw, "Parent", strings.Join(di.Parents, ","))
|
||||
if di.Parent != "" {
|
||||
printKV(tw, "Parent", di.Parent)
|
||||
}
|
||||
printKV(tw, "Created at", di.CreatedAt)
|
||||
printKV(tw, "Mutable", di.Mutable)
|
||||
printKV(tw, "Reclaimable", !di.InUse)
|
||||
printKV(tw, "Shared", di.Shared)
|
||||
printKV(tw, "Size", units.HumanSize(float64(di.Size)))
|
||||
printKV(tw, "Size", fmt.Sprintf("%.2f", units.Bytes(di.Size)))
|
||||
if di.Description != "" {
|
||||
printKV(tw, "Description", di.Description)
|
||||
}
|
||||
printKV(tw, "Usage count", di.UsageCount)
|
||||
if di.LastUsedAt != nil {
|
||||
printKV(tw, "Last used", units.HumanDuration(time.Since(*di.LastUsedAt))+" ago")
|
||||
printKV(tw, "Last used", di.LastUsedAt)
|
||||
}
|
||||
if di.RecordType != "" {
|
||||
printKV(tw, "Type", di.RecordType)
|
||||
@@ -165,15 +159,11 @@ func printTableRow(tw *tabwriter.Writer, di *client.UsageInfo) {
|
||||
if di.Mutable {
|
||||
id += "*"
|
||||
}
|
||||
size := units.HumanSize(float64(di.Size))
|
||||
size := fmt.Sprintf("%.2f", units.Bytes(di.Size))
|
||||
if di.Shared {
|
||||
size += "*"
|
||||
}
|
||||
lastAccessed := ""
|
||||
if di.LastUsedAt != nil {
|
||||
lastAccessed = units.HumanDuration(time.Since(*di.LastUsedAt)) + " ago"
|
||||
}
|
||||
fmt.Fprintf(tw, "%-40s\t%-5v\t%-10s\t%s\n", id, !di.InUse, size, lastAccessed)
|
||||
fmt.Fprintf(tw, "%-71s\t%-11v\t%s\t\n", id, !di.InUse, size)
|
||||
}
|
||||
|
||||
func printSummary(tw *tabwriter.Writer, dus [][]*client.UsageInfo) {
|
||||
@@ -196,11 +186,11 @@ func printSummary(tw *tabwriter.Writer, dus [][]*client.UsageInfo) {
|
||||
}
|
||||
|
||||
if shared > 0 {
|
||||
fmt.Fprintf(tw, "Shared:\t%s\n", units.HumanSize(float64(shared)))
|
||||
fmt.Fprintf(tw, "Private:\t%s\n", units.HumanSize(float64(total-shared)))
|
||||
fmt.Fprintf(tw, "Shared:\t%.2f\n", units.Bytes(shared))
|
||||
fmt.Fprintf(tw, "Private:\t%.2f\n", units.Bytes(total-shared))
|
||||
}
|
||||
|
||||
fmt.Fprintf(tw, "Reclaimable:\t%s\n", units.HumanSize(float64(reclaimable)))
|
||||
fmt.Fprintf(tw, "Total:\t%s\n", units.HumanSize(float64(total)))
|
||||
fmt.Fprintf(tw, "Reclaimable:\t%.2f\n", units.Bytes(reclaimable))
|
||||
fmt.Fprintf(tw, "Total:\t%.2f\n", units.Bytes(total))
|
||||
tw.Flush()
|
||||
}
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/util/imagetools"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
@@ -21,12 +18,10 @@ import (
|
||||
)
|
||||
|
||||
type createOptions struct {
|
||||
builder string
|
||||
files []string
|
||||
tags []string
|
||||
dryrun bool
|
||||
actionAppend bool
|
||||
progress string
|
||||
}
|
||||
|
||||
func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||
@@ -40,7 +35,7 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||
|
||||
fileArgs := make([]string, len(in.files))
|
||||
for i, f := range in.files {
|
||||
dt, err := os.ReadFile(f)
|
||||
dt, err := ioutil.ReadFile(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -80,48 +75,35 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||
if len(repos) == 0 {
|
||||
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
|
||||
if len(repos) == 1 {
|
||||
for repo := range repos {
|
||||
defaultRepo = &repo
|
||||
}
|
||||
var repo string
|
||||
for r := range repos {
|
||||
repo = r
|
||||
}
|
||||
|
||||
for i, s := range srcs {
|
||||
if s.Ref == nil {
|
||||
if defaultRepo == nil {
|
||||
return errors.Errorf("multiple repositories specified, cannot infer repository for %q", args[i])
|
||||
}
|
||||
n, err := reference.ParseNormalizedNamed(*defaultRepo)
|
||||
if s.Ref == nil && s.Desc.MediaType == "" && s.Desc.Digest != "" {
|
||||
n, err := reference.ParseNormalizedNamed(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s.Desc.MediaType == "" && s.Desc.Digest != "" {
|
||||
r, err := reference.WithDigest(n, s.Desc.Digest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srcs[i].Ref = r
|
||||
sourceRefs = true
|
||||
} else {
|
||||
srcs[i].Ref = reference.TagNameOnly(n)
|
||||
r, err := reference.WithDigest(n, s.Desc.Digest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srcs[i].Ref = r
|
||||
sourceRefs = true
|
||||
}
|
||||
}
|
||||
|
||||
ctx := appcontext.Context()
|
||||
|
||||
b, err := builder.New(dockerCli, builder.WithName(in.builder))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imageopt, err := b.ImageOpt()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := imagetools.New(imageopt)
|
||||
r := imagetools.New(imagetools.Opt{
|
||||
Auth: dockerCli.ConfigFile(),
|
||||
})
|
||||
|
||||
if sourceRefs {
|
||||
eg, ctx2 := errgroup.WithContext(ctx)
|
||||
@@ -135,6 +117,7 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srcs[i].Ref = nil
|
||||
if srcs[i].Desc.Digest == "" {
|
||||
srcs[i].Desc = desc
|
||||
} else {
|
||||
@@ -153,7 +136,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 {
|
||||
return err
|
||||
}
|
||||
@@ -164,54 +152,27 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||
}
|
||||
|
||||
// new resolver cause need new auth
|
||||
r = imagetools.New(imageopt)
|
||||
|
||||
ctx2, cancel := context.WithCancel(context.TODO())
|
||||
defer cancel()
|
||||
printer, err := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, in.progress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
pw := progress.WithPrefix(printer, "internal", true)
|
||||
r = imagetools.New(imagetools.Opt{
|
||||
Auth: dockerCli.ConfigFile(),
|
||||
})
|
||||
|
||||
for _, t := range tags {
|
||||
t := t
|
||||
eg.Go(func() error {
|
||||
return progress.Wrap(fmt.Sprintf("pushing %s", t.String()), pw.Write, func(sub progress.SubLogger) error {
|
||||
eg2, _ := errgroup.WithContext(ctx)
|
||||
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)
|
||||
})
|
||||
})
|
||||
if err := r.Push(ctx, t, desc, dt); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(t.String())
|
||||
}
|
||||
|
||||
err = eg.Wait()
|
||||
err1 := printer.Wait()
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseSources(in []string) ([]*imagetools.Source, error) {
|
||||
out := make([]*imagetools.Source, len(in))
|
||||
type src struct {
|
||||
Desc ocispec.Descriptor
|
||||
Ref reference.Named
|
||||
}
|
||||
|
||||
func parseSources(in []string) ([]*src, error) {
|
||||
out := make([]*src, len(in))
|
||||
for i, in := range in {
|
||||
s, err := parseSource(in)
|
||||
if err != nil {
|
||||
@@ -234,11 +195,11 @@ func parseRefs(in []string) ([]reference.Named, error) {
|
||||
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
|
||||
dgst, err := digest.Parse(in)
|
||||
if err == nil {
|
||||
return &imagetools.Source{
|
||||
return &src{
|
||||
Desc: ocispec.Descriptor{
|
||||
Digest: dgst,
|
||||
},
|
||||
@@ -249,38 +210,39 @@ func parseSource(in string) (*imagetools.Source, error) {
|
||||
|
||||
ref, err := reference.ParseNormalizedNamed(in)
|
||||
if err == nil {
|
||||
return &imagetools.Source{
|
||||
return &src{
|
||||
Ref: ref,
|
||||
}, nil
|
||||
} else if !strings.HasPrefix(in, "{") {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var s imagetools.Source
|
||||
var s src
|
||||
if err := json.Unmarshal([]byte(in), &s.Desc); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func createCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command {
|
||||
func createCmd(dockerCli command.Cli) *cobra.Command {
|
||||
var options createOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "create [OPTIONS] [SOURCE] [SOURCE...]",
|
||||
Short: "Create a new image based on source images",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.builder = *opts.Builder
|
||||
return runCreate(dockerCli, options, args)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.StringArrayVarP(&options.files, "file", "f", []string{}, "Read source descriptor from file")
|
||||
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.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`)
|
||||
|
||||
_ = flags
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -1,65 +1,68 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/docker/buildx/builder"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/docker/buildx/util/imagetools"
|
||||
"github.com/docker/cli-docs-tool/annotation"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/pkg/errors"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type inspectOptions struct {
|
||||
builder string
|
||||
format string
|
||||
raw bool
|
||||
raw bool
|
||||
}
|
||||
|
||||
func runInspect(dockerCli command.Cli, in inspectOptions, name string) error {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
if in.format != "" && in.raw {
|
||||
return errors.Errorf("format and raw cannot be used together")
|
||||
}
|
||||
r := imagetools.New(imagetools.Opt{
|
||||
Auth: dockerCli.ConfigFile(),
|
||||
})
|
||||
|
||||
b, err := builder.New(dockerCli, builder.WithName(in.builder))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imageopt, err := b.ImageOpt()
|
||||
dt, desc, err := r.Get(ctx, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p, err := imagetools.NewPrinter(ctx, imageopt, name, in.format)
|
||||
if err != nil {
|
||||
return err
|
||||
if in.raw {
|
||||
fmt.Printf("%s", dt) // avoid newline to keep digest
|
||||
return nil
|
||||
}
|
||||
|
||||
return p.Print(in.raw, dockerCli.Out())
|
||||
switch desc.MediaType {
|
||||
// case images.MediaTypeDockerSchema2Manifest, specs.MediaTypeImageManifest:
|
||||
// TODO: handle distribution manifest and schema1
|
||||
case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
|
||||
return imagetools.PrintManifestList(dt, desc, name, os.Stdout)
|
||||
default:
|
||||
fmt.Printf("%s\n", dt)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func inspectCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
||||
func inspectCmd(dockerCli command.Cli) *cobra.Command {
|
||||
var options inspectOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "inspect [OPTIONS] NAME",
|
||||
Short: "Show details of an image in the registry",
|
||||
Short: "Show details of image in the registry",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.builder = *rootOpts.Builder
|
||||
return runInspect(dockerCli, options, args[0])
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.StringVar(&options.format, "format", "", "Format the output using the given Go template")
|
||||
flags.SetAnnotation("format", annotation.DefaultValue, []string{`"{{.Manifest}}"`})
|
||||
flags.BoolVar(&options.raw, "raw", false, "Show original JSON manifest")
|
||||
|
||||
flags.BoolVar(&options.raw, "raw", false, "Show original, unformatted JSON manifest")
|
||||
_ = flags
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -5,19 +5,15 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type RootOptions struct {
|
||||
Builder *string
|
||||
}
|
||||
|
||||
func RootCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command {
|
||||
func RootCmd(dockerCli command.Cli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "imagetools",
|
||||
Short: "Commands to work on images in registry",
|
||||
}
|
||||
|
||||
cmd.AddCommand(
|
||||
createCmd(dockerCli, opts),
|
||||
inspectCmd(dockerCli, opts),
|
||||
inspectCmd(dockerCli),
|
||||
createCmd(dockerCli),
|
||||
)
|
||||
|
||||
return cmd
|
||||
|
||||
@@ -8,12 +8,17 @@ import (
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/driver"
|
||||
"github.com/docker/buildx/store"
|
||||
"github.com/docker/buildx/util/platformutil"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type inspectOptions struct {
|
||||
@@ -21,74 +26,103 @@ type inspectOptions struct {
|
||||
builder string
|
||||
}
|
||||
|
||||
type dinfo struct {
|
||||
di *build.DriverInfo
|
||||
info *driver.Info
|
||||
platforms []specs.Platform
|
||||
err error
|
||||
}
|
||||
|
||||
type nginfo struct {
|
||||
ng *store.NodeGroup
|
||||
drivers []dinfo
|
||||
err error
|
||||
}
|
||||
|
||||
func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
b, err := builder.New(dockerCli,
|
||||
builder.WithName(in.builder),
|
||||
builder.WithSkippedValidation(),
|
||||
)
|
||||
txn, release, err := getStore(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer release()
|
||||
|
||||
var ng *store.NodeGroup
|
||||
|
||||
if in.builder != "" {
|
||||
ng, err = getNodeGroup(txn, dockerCli, in.builder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
ng, err = getCurrentInstance(txn, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if ng == nil {
|
||||
ng = &store.NodeGroup{
|
||||
Name: "default",
|
||||
Nodes: []store.Node{{
|
||||
Name: "default",
|
||||
Endpoint: "default",
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
ngi := &nginfo{ng: ng}
|
||||
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
nodes, err := b.LoadNodes(timeoutCtx, true)
|
||||
err = loadNodeGroupData(timeoutCtx, dockerCli, ngi)
|
||||
|
||||
var bootNgi *nginfo
|
||||
if in.bootstrap {
|
||||
var ok bool
|
||||
ok, err = b.Boot(ctx)
|
||||
ok, err = boot(ctx, ngi, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bootNgi = ngi
|
||||
if ok {
|
||||
nodes, err = b.LoadNodes(timeoutCtx, true)
|
||||
ngi = &nginfo{ng: ng}
|
||||
err = loadNodeGroupData(ctx, dockerCli, ngi)
|
||||
}
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||
fmt.Fprintf(w, "Name:\t%s\n", b.Name)
|
||||
fmt.Fprintf(w, "Driver:\t%s\n", b.Driver)
|
||||
if !b.NodeGroup.LastActivity.IsZero() {
|
||||
fmt.Fprintf(w, "Last Activity:\t%v\n", b.NodeGroup.LastActivity)
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "Name:\t%s\n", ngi.ng.Name)
|
||||
fmt.Fprintf(w, "Driver:\t%s\n", ngi.ng.Driver)
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
||||
} else if b.Err() != nil {
|
||||
fmt.Fprintf(w, "Error:\t%s\n", b.Err().Error())
|
||||
} else if ngi.err != nil {
|
||||
fmt.Fprintf(w, "Error:\t%s\n", ngi.err.Error())
|
||||
}
|
||||
if err == nil {
|
||||
fmt.Fprintln(w, "")
|
||||
fmt.Fprintln(w, "Nodes:")
|
||||
|
||||
for i, n := range nodes {
|
||||
for i, n := range ngi.ng.Nodes {
|
||||
if i != 0 {
|
||||
fmt.Fprintln(w, "")
|
||||
}
|
||||
fmt.Fprintf(w, "Name:\t%s\n", n.Name)
|
||||
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 := n.Err; err != nil {
|
||||
if err := ngi.drivers[i].di.Err; err != nil {
|
||||
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
||||
} else if err := ngi.drivers[i].err; err != nil {
|
||||
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
||||
} else if bootNgi != nil && len(bootNgi.drivers) > i && bootNgi.drivers[i].err != nil {
|
||||
fmt.Fprintf(w, "Error:\t%s\n", bootNgi.drivers[i].err.Error())
|
||||
} else {
|
||||
fmt.Fprintf(w, "Status:\t%s\n", nodes[i].DriverInfo.Status)
|
||||
fmt.Fprintf(w, "Status:\t%s\n", ngi.drivers[i].info.Status)
|
||||
if len(n.Flags) > 0 {
|
||||
fmt.Fprintf(w, "Flags:\t%s\n", strings.Join(n.Flags, " "))
|
||||
}
|
||||
if nodes[i].Version != "" {
|
||||
fmt.Fprintf(w, "Buildkit:\t%s\n", nodes[i].Version)
|
||||
}
|
||||
fmt.Fprintf(w, "Platforms:\t%s\n", strings.Join(platformutil.FormatInGroups(n.Node.Platforms, n.Platforms), ", "))
|
||||
fmt.Fprintf(w, "Platforms:\t%s\n", strings.Join(platformutil.FormatInGroups(n.Platforms, ngi.drivers[i].platforms), ", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,7 +149,50 @@ func inspectCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.BoolVar(&options.bootstrap, "bootstrap", false, "Ensure builder has booted before inspecting")
|
||||
|
||||
_ = flags
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func boot(ctx context.Context, ngi *nginfo, dockerCli command.Cli) (bool, error) {
|
||||
toBoot := make([]int, 0, len(ngi.drivers))
|
||||
for i, d := range ngi.drivers {
|
||||
if d.err != nil || d.di.Err != nil || d.di.Driver == nil || d.info == nil {
|
||||
continue
|
||||
}
|
||||
if d.info.Status != driver.Running {
|
||||
toBoot = append(toBoot, i)
|
||||
}
|
||||
}
|
||||
if len(toBoot) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
printer := progress.NewPrinter(context.TODO(), os.Stderr, "auto")
|
||||
|
||||
baseCtx := ctx
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
for _, idx := range toBoot {
|
||||
func(idx int) {
|
||||
eg.Go(func() error {
|
||||
pw := progress.WithPrefix(printer, ngi.ng.Nodes[idx].Name, len(toBoot) > 1)
|
||||
_, err := driver.Boot(ctx, baseCtx, ngi.drivers[idx].di.Driver, pw)
|
||||
if err != nil {
|
||||
ngi.drivers[idx].err = err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}(idx)
|
||||
}
|
||||
|
||||
err := eg.Wait()
|
||||
err1 := printer.Wait()
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
|
||||
return true, err
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package commands
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/docker/buildx/util/cobrautil"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/config"
|
||||
@@ -49,8 +48,5 @@ func installCmd(dockerCli command.Cli) *cobra.Command {
|
||||
Hidden: true,
|
||||
}
|
||||
|
||||
// hide builder persistent flag for this command
|
||||
cobrautil.HideInheritedFlags(cmd, "builder")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
136
commands/ls.go
136
commands/ls.go
@@ -4,13 +4,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/store/storeutil"
|
||||
"github.com/docker/buildx/util/cobrautil"
|
||||
"github.com/docker/buildx/store"
|
||||
"github.com/docker/buildx/util/platformutil"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
@@ -25,30 +24,51 @@ type lsOptions struct {
|
||||
func runLs(dockerCli command.Cli, in lsOptions) error {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
txn, release, err := storeutil.GetStore(dockerCli)
|
||||
txn, release, err := getStore(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer release()
|
||||
|
||||
current, err := storeutil.GetCurrentInstance(txn, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
builders, err := builder.GetBuilders(dockerCli, txn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
eg, _ := errgroup.WithContext(timeoutCtx)
|
||||
ll, err := txn.List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
builders := make([]*nginfo, len(ll))
|
||||
for i, ng := range ll {
|
||||
builders[i] = &nginfo{ng: ng}
|
||||
}
|
||||
|
||||
list, err := dockerCli.ContextStore().List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctxbuilders := make([]*nginfo, len(list))
|
||||
for i, l := range list {
|
||||
ctxbuilders[i] = &nginfo{ng: &store.NodeGroup{
|
||||
Name: l.Name,
|
||||
Nodes: []store.Node{{
|
||||
Name: l.Name,
|
||||
Endpoint: l.Name,
|
||||
}},
|
||||
}}
|
||||
}
|
||||
|
||||
builders = append(builders, ctxbuilders...)
|
||||
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
|
||||
for _, b := range builders {
|
||||
func(b *builder.Builder) {
|
||||
func(b *nginfo) {
|
||||
eg.Go(func() error {
|
||||
_, _ = b.LoadNodes(timeoutCtx, true)
|
||||
err = loadNodeGroupData(ctx, dockerCli, b)
|
||||
if b.err == nil && err != nil {
|
||||
b.err = err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}(b)
|
||||
@@ -58,62 +78,61 @@ func runLs(dockerCli command.Cli, in lsOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(dockerCli.Out(), 0, 0, 1, ' ', 0)
|
||||
fmt.Fprintf(w, "NAME/NODE\tDRIVER/ENDPOINT\tSTATUS\tBUILDKIT\tPLATFORMS\n")
|
||||
currentName := "default"
|
||||
current, err := getCurrentInstance(txn, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if current != nil {
|
||||
currentName = current.Name
|
||||
if current.Name == "default" {
|
||||
currentName = current.Nodes[0].Endpoint
|
||||
}
|
||||
}
|
||||
|
||||
printErr := false
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||
fmt.Fprintf(w, "NAME/NODE\tDRIVER/ENDPOINT\tSTATUS\tPLATFORMS\n")
|
||||
|
||||
currentSet := false
|
||||
for _, b := range builders {
|
||||
if current.Name == b.Name {
|
||||
b.Name += " *"
|
||||
}
|
||||
if ok := printBuilder(w, b); !ok {
|
||||
printErr = true
|
||||
if !currentSet && b.ng.Name == currentName {
|
||||
b.ng.Name += " *"
|
||||
currentSet = true
|
||||
}
|
||||
printngi(w, b)
|
||||
}
|
||||
|
||||
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.Name, strings.TrimSpace(b.Err().Error()))
|
||||
} else {
|
||||
for _, d := range b.Nodes() {
|
||||
if d.Err != nil {
|
||||
_, _ = fmt.Fprintf(dockerCli.Err(), "Failed to get status for %s (%s): %s\n", b.Name, d.Name, strings.TrimSpace(d.Err.Error()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printBuilder(w io.Writer, b *builder.Builder) (ok bool) {
|
||||
ok = true
|
||||
func printngi(w io.Writer, ngi *nginfo) {
|
||||
var err string
|
||||
if b.Err() != nil {
|
||||
ok = false
|
||||
err = "error"
|
||||
if ngi.err != nil {
|
||||
err = ngi.err.Error()
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t\t\n", b.Name, b.Driver, err)
|
||||
if b.Err() == nil {
|
||||
for _, n := range b.Nodes() {
|
||||
var status string
|
||||
if n.DriverInfo != nil {
|
||||
status = n.DriverInfo.Status.String()
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t\n", ngi.ng.Name, ngi.ng.Driver, err)
|
||||
if ngi.err == nil {
|
||||
for idx, n := range ngi.ng.Nodes {
|
||||
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()
|
||||
}
|
||||
if n.Err != nil {
|
||||
ok = false
|
||||
fmt.Fprintf(w, " %s\t%s\t%s\t\t\n", n.Name, n.Endpoint, "error")
|
||||
var status string
|
||||
if d.info != nil {
|
||||
status = d.info.Status.String()
|
||||
}
|
||||
if err != "" {
|
||||
fmt.Fprintf(w, " %s\t%s\t%s\n", n.Name, n.Endpoint, err)
|
||||
} else {
|
||||
fmt.Fprintf(w, " %s\t%s\t%s\t%s\t%s\n", n.Name, n.Endpoint, status, n.Version, strings.Join(platformutil.FormatInGroups(n.Node.Platforms, n.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 {
|
||||
@@ -128,8 +147,5 @@ func lsCmd(dockerCli command.Cli) *cobra.Command {
|
||||
},
|
||||
}
|
||||
|
||||
// hide builder persistent flag for this command
|
||||
cobrautil.HideInheritedFlags(cmd, "builder")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/moby/buildkit/frontend/subrequests"
|
||||
"github.com/moby/buildkit/frontend/subrequests/outline"
|
||||
"github.com/moby/buildkit/frontend/subrequests/targets"
|
||||
)
|
||||
|
||||
func printResult(f *build.PrintFunc, res map[string]string) error {
|
||||
switch f.Name {
|
||||
case "outline":
|
||||
return printValue(outline.PrintOutline, outline.SubrequestsOutlineDefinition.Version, f.Format, res)
|
||||
case "targets":
|
||||
return printValue(targets.PrintTargets, targets.SubrequestsTargetsDefinition.Version, f.Format, res)
|
||||
case "subrequests.describe":
|
||||
return printValue(subrequests.PrintDescribe, subrequests.SubrequestsDescribeDefinition.Version, f.Format, res)
|
||||
default:
|
||||
if dt, ok := res["result.txt"]; ok {
|
||||
fmt.Print(dt)
|
||||
} else {
|
||||
log.Printf("%s %+v", f, res)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type printFunc func([]byte, io.Writer) error
|
||||
|
||||
func printValue(printer printFunc, version string, format string, res map[string]string) error {
|
||||
if format == "json" {
|
||||
fmt.Fprintln(os.Stdout, res["result.json"])
|
||||
return nil
|
||||
}
|
||||
|
||||
if res["version"] != "" && versions.LessThan(version, res["version"]) && res["result.txt"] != "" {
|
||||
// structure is too new and we don't know how to print it
|
||||
fmt.Fprint(os.Stdout, res["result.txt"])
|
||||
return nil
|
||||
}
|
||||
return printer([]byte(res["result.json"]), os.Stdout)
|
||||
}
|
||||
@@ -7,16 +7,16 @@ import (
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tonistiigi/units"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
@@ -54,18 +54,14 @@ func runPrune(dockerCli command.Cli, opts pruneOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
b, err := builder.New(dockerCli, builder.WithName(opts.builder))
|
||||
dis, err := getInstanceOrDefault(ctx, dockerCli, opts.builder, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nodes, err := b.LoadNodes(ctx, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, node := range nodes {
|
||||
if node.Err != nil {
|
||||
return node.Err
|
||||
for _, di := range dis {
|
||||
if di.Err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,11 +90,11 @@ func runPrune(dockerCli command.Cli, opts pruneOptions) error {
|
||||
}()
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
for _, node := range nodes {
|
||||
func(node builder.Node) {
|
||||
for _, di := range dis {
|
||||
func(di build.DriverInfo) {
|
||||
eg.Go(func() error {
|
||||
if node.Driver != nil {
|
||||
c, err := node.Driver.Client(ctx)
|
||||
if di.Driver != nil {
|
||||
c, err := di.Driver.Client(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -113,7 +109,7 @@ func runPrune(dockerCli command.Cli, opts pruneOptions) error {
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}(node)
|
||||
}(di)
|
||||
}
|
||||
|
||||
if err := eg.Wait(); err != nil {
|
||||
@@ -123,7 +119,7 @@ func runPrune(dockerCli command.Cli, opts pruneOptions) error {
|
||||
<-printed
|
||||
|
||||
tw = tabwriter.NewWriter(os.Stdout, 1, 8, 1, '\t', 0)
|
||||
fmt.Fprintf(tw, "Total:\t%s\n", units.HumanSize(float64(total)))
|
||||
fmt.Fprintf(tw, "Total:\t%.2f\n", units.Bytes(total))
|
||||
tw.Flush()
|
||||
return nil
|
||||
}
|
||||
@@ -142,8 +138,8 @@ func pruneCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&options.all, "all", "a", false, "Include internal/frontend images")
|
||||
flags.Var(&options.filter, "filter", `Provide filter values (e.g., "until=24h")`)
|
||||
flags.BoolVarP(&options.all, "all", "a", false, "Remove all unused images, not just dangling ones")
|
||||
flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'until=24h')")
|
||||
flags.Var(&options.keepStorage, "keep-storage", "Amount of disk space to keep for cache")
|
||||
flags.BoolVar(&options.verbose, "verbose", false, "Provide a more verbose output")
|
||||
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
|
||||
@@ -159,9 +155,9 @@ func toBuildkitPruneInfo(f filters.Args) (*client.PruneInfo, error) {
|
||||
if len(untilValues) > 0 && len(unusedForValues) > 0 {
|
||||
return nil, errors.Errorf("conflicting filters %q and %q", "until", "unused-for")
|
||||
}
|
||||
untilKey := "until"
|
||||
filterKey := "until"
|
||||
if len(unusedForValues) > 0 {
|
||||
untilKey = "unused-for"
|
||||
filterKey = "unused-for"
|
||||
}
|
||||
untilValues = append(untilValues, unusedForValues...)
|
||||
|
||||
@@ -172,27 +168,23 @@ func toBuildkitPruneInfo(f filters.Args) (*client.PruneInfo, error) {
|
||||
var err error
|
||||
until, err = time.ParseDuration(untilValues[0])
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "%q filter expects a duration (e.g., '24h')", untilKey)
|
||||
return nil, errors.Wrapf(err, "%q filter expects a duration (e.g., '24h')", filterKey)
|
||||
}
|
||||
default:
|
||||
return nil, errors.Errorf("filters expect only one value")
|
||||
}
|
||||
|
||||
filters := make([]string, 0, f.Len())
|
||||
for _, filterKey := range f.Keys() {
|
||||
if filterKey == untilKey {
|
||||
continue
|
||||
}
|
||||
|
||||
values := f.Get(filterKey)
|
||||
bkFilter := make([]string, 0, f.Len())
|
||||
for _, field := range f.Keys() {
|
||||
values := f.Get(field)
|
||||
switch len(values) {
|
||||
case 0:
|
||||
filters = append(filters, filterKey)
|
||||
bkFilter = append(bkFilter, field)
|
||||
case 1:
|
||||
if filterKey == "id" {
|
||||
filters = append(filters, filterKey+"~="+values[0])
|
||||
if field == "id" {
|
||||
bkFilter = append(bkFilter, field+"~="+values[0])
|
||||
} else {
|
||||
filters = append(filters, filterKey+"=="+values[0])
|
||||
bkFilter = append(bkFilter, field+"=="+values[0])
|
||||
}
|
||||
default:
|
||||
return nil, errors.Errorf("filters expect only one value")
|
||||
@@ -200,6 +192,6 @@ func toBuildkitPruneInfo(f filters.Args) (*client.PruneInfo, error) {
|
||||
}
|
||||
return &client.PruneInfo{
|
||||
KeepDuration: until,
|
||||
Filter: []string{strings.Join(filters, ",")},
|
||||
Filter: []string{strings.Join(bkFilter, ",")},
|
||||
}, nil
|
||||
}
|
||||
|
||||
143
commands/rm.go
143
commands/rm.go
@@ -2,76 +2,52 @@ package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/store"
|
||||
"github.com/docker/buildx/store/storeutil"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type rmOptions struct {
|
||||
builder string
|
||||
keepState bool
|
||||
keepDaemon bool
|
||||
allInactive bool
|
||||
force bool
|
||||
builder string
|
||||
keepState bool
|
||||
}
|
||||
|
||||
const (
|
||||
rmInactiveWarning = `WARNING! This will remove all builders that are not in running state. Are you sure you want to continue?`
|
||||
)
|
||||
|
||||
func runRm(dockerCli command.Cli, in rmOptions) error {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
if in.allInactive && !in.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), rmInactiveWarning) {
|
||||
return nil
|
||||
}
|
||||
|
||||
txn, release, err := storeutil.GetStore(dockerCli)
|
||||
txn, release, err := getStore(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer release()
|
||||
|
||||
if in.allInactive {
|
||||
return rmAllInactive(ctx, txn, dockerCli, in)
|
||||
}
|
||||
|
||||
b, err := builder.New(dockerCli,
|
||||
builder.WithName(in.builder),
|
||||
builder.WithStore(txn),
|
||||
builder.WithSkippedValidation(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nodes, err := b.LoadNodes(ctx, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cb := b.ContextName(); cb != "" {
|
||||
return errors.Errorf("context builder cannot be removed, run `docker context rm %s` to remove this context", cb)
|
||||
}
|
||||
|
||||
err1 := rm(ctx, nodes, in)
|
||||
if err := txn.Remove(b.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err1 != nil {
|
||||
if in.builder != "" {
|
||||
ng, err := getNodeGroup(txn, dockerCli, in.builder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err1 := rm(ctx, dockerCli, ng, in.keepState)
|
||||
if err := txn.Remove(ng.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
return err1
|
||||
}
|
||||
|
||||
ng, err := getCurrentInstance(txn, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ng != nil {
|
||||
err1 := rm(ctx, dockerCli, ng, in.keepState)
|
||||
if err := txn.Remove(ng.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
return err1
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", b.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -85,9 +61,6 @@ func rmCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.builder = rootOpts.builder
|
||||
if len(args) > 0 {
|
||||
if options.allInactive {
|
||||
return errors.New("cannot specify builder name when --all-inactive is set")
|
||||
}
|
||||
options.builder = args[0]
|
||||
}
|
||||
return runRm(dockerCli, options)
|
||||
@@ -96,69 +69,27 @@ func rmCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVar(&options.keepState, "keep-state", false, "Keep BuildKit state")
|
||||
flags.BoolVar(&options.keepDaemon, "keep-daemon", false, "Keep the buildkitd daemon running")
|
||||
flags.BoolVar(&options.allInactive, "all-inactive", false, "Remove all inactive builders")
|
||||
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func rm(ctx context.Context, nodes []builder.Node, in rmOptions) (err error) {
|
||||
for _, node := range nodes {
|
||||
if node.Driver == nil {
|
||||
continue
|
||||
}
|
||||
// Do not stop the buildkitd daemon when --keep-daemon is provided
|
||||
if !in.keepDaemon {
|
||||
if err := node.Driver.Stop(ctx, true); err != nil {
|
||||
func rm(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, keepState bool) error {
|
||||
dis, err := driversForNodeGroup(ctx, dockerCli, ng, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, di := range dis {
|
||||
if di.Driver != nil {
|
||||
if err := di.Driver.Stop(ctx, true); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := di.Driver.Rm(ctx, true, !keepState); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := node.Driver.Rm(ctx, true, !in.keepState, !in.keepDaemon); err != nil {
|
||||
return err
|
||||
}
|
||||
if node.Err != nil {
|
||||
err = node.Err
|
||||
if di.Err != nil {
|
||||
err = di.Err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func rmAllInactive(ctx context.Context, txn *store.Txn, dockerCli command.Cli, in rmOptions) error {
|
||||
builders, err := builder.GetBuilders(dockerCli, txn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
eg, _ := errgroup.WithContext(timeoutCtx)
|
||||
for _, b := range builders {
|
||||
func(b *builder.Builder) {
|
||||
eg.Go(func() error {
|
||||
nodes, err := b.LoadNodes(timeoutCtx, true)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cannot load %s", b.Name)
|
||||
}
|
||||
if cb := b.ContextName(); cb != "" {
|
||||
return errors.Errorf("context builder cannot be removed, run `docker context rm %s` to remove this context", cb)
|
||||
}
|
||||
if b.Dynamic {
|
||||
return nil
|
||||
}
|
||||
if b.Inactive() {
|
||||
rmerr := rm(ctx, nodes, in)
|
||||
if err := txn.Remove(b.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", b.Name)
|
||||
return rmerr
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}(b)
|
||||
}
|
||||
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
@@ -4,60 +4,23 @@ import (
|
||||
"os"
|
||||
|
||||
imagetoolscmd "github.com/docker/buildx/commands/imagetools"
|
||||
"github.com/docker/buildx/util/logutil"
|
||||
"github.com/docker/cli-docs-tool/annotation"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli-plugins/plugin"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func NewRootCmd(name string, isPlugin bool, dockerCli command.Cli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Short: "Docker Buildx",
|
||||
Long: `Extended build capabilities with BuildKit`,
|
||||
Short: "Build with BuildKit",
|
||||
Use: name,
|
||||
Annotations: map[string]string{
|
||||
annotation.CodeDelimiter: `"`,
|
||||
},
|
||||
}
|
||||
if isPlugin {
|
||||
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||
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.AddHook(logutil.NewFilter([]logrus.Level{
|
||||
logrus.DebugLevel,
|
||||
},
|
||||
"serving grpc connection",
|
||||
"stopping session",
|
||||
"using default config store",
|
||||
))
|
||||
|
||||
// filter out useless commandConn.CloseWrite warning message that can occur
|
||||
// when listing builder instances with "buildx ls" for those that are
|
||||
// unreachable: "commandConn.CloseWrite: commandconn: failed to wait: signal: killed"
|
||||
// https://github.com/docker/cli/blob/3fb4fb83dfb5db0c0753a8316f21aea54dab32c5/cli/connhelper/commandconn/commandconn.go#L203-L214
|
||||
logrus.AddHook(logutil.NewFilter([]logrus.Level{
|
||||
logrus.WarnLevel,
|
||||
},
|
||||
"commandConn.CloseWrite:",
|
||||
"commandConn.CloseRead:",
|
||||
))
|
||||
|
||||
addCommands(cmd, dockerCli)
|
||||
return cmd
|
||||
}
|
||||
@@ -84,7 +47,7 @@ func addCommands(cmd *cobra.Command, dockerCli command.Cli) {
|
||||
versionCmd(dockerCli),
|
||||
pruneCmd(dockerCli, opts),
|
||||
duCmd(dockerCli, opts),
|
||||
imagetoolscmd.RootCmd(dockerCli, imagetoolscmd.RootOptions{Builder: &opts.builder}),
|
||||
imagetoolscmd.RootCmd(dockerCli),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package commands
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/store"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
@@ -17,19 +17,32 @@ type stopOptions struct {
|
||||
func runStop(dockerCli command.Cli, in stopOptions) error {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
b, err := builder.New(dockerCli,
|
||||
builder.WithName(in.builder),
|
||||
builder.WithSkippedValidation(),
|
||||
)
|
||||
txn, release, err := getStore(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nodes, err := b.LoadNodes(ctx, false)
|
||||
if err != nil {
|
||||
return err
|
||||
defer release()
|
||||
|
||||
if in.builder != "" {
|
||||
ng, err := getNodeGroup(txn, dockerCli, in.builder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := stop(ctx, dockerCli, ng); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return stop(ctx, nodes)
|
||||
ng, err := getCurrentInstance(txn, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ng != nil {
|
||||
return stop(ctx, dockerCli, ng)
|
||||
}
|
||||
|
||||
return stopCurrent(ctx, dockerCli)
|
||||
}
|
||||
|
||||
func stopCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
@@ -48,18 +61,46 @@ func stopCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
// flags.StringArrayVarP(&options.outputs, "output", "o", []string{}, "Output destination (format: type=local,dest=path)")
|
||||
|
||||
_ = flags
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func stop(ctx context.Context, nodes []builder.Node) (err error) {
|
||||
for _, node := range nodes {
|
||||
if node.Driver != nil {
|
||||
if err := node.Driver.Stop(ctx, true); err != nil {
|
||||
func stop(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup) error {
|
||||
dis, err := driversForNodeGroup(ctx, dockerCli, ng, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, di := range dis {
|
||||
if di.Driver != nil {
|
||||
if err := di.Driver.Stop(ctx, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if node.Err != nil {
|
||||
err = node.Err
|
||||
if di.Err != nil {
|
||||
err = di.Err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func stopCurrent(ctx context.Context, dockerCli command.Cli) error {
|
||||
dis, err := getDefaultDrivers(ctx, dockerCli, false, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, di := range dis {
|
||||
if di.Driver != nil {
|
||||
if err := di.Driver.Stop(ctx, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if di.Err != nil {
|
||||
err = di.Err
|
||||
}
|
||||
}
|
||||
return err
|
||||
|
||||
@@ -3,7 +3,6 @@ package commands
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/docker/buildx/util/cobrautil"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/config"
|
||||
@@ -55,8 +54,5 @@ func uninstallCmd(dockerCli command.Cli) *cobra.Command {
|
||||
Hidden: true,
|
||||
}
|
||||
|
||||
// hide builder persistent flag for this command
|
||||
cobrautil.HideInheritedFlags(cmd, "builder")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ package commands
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/docker/buildx/store/storeutil"
|
||||
"github.com/docker/buildx/util/dockerutil"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/pkg/errors"
|
||||
@@ -18,7 +16,7 @@ type useOptions struct {
|
||||
}
|
||||
|
||||
func runUse(dockerCli command.Cli, in useOptions) error {
|
||||
txn, release, err := storeutil.GetStore(dockerCli)
|
||||
txn, release, err := getStore(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -30,7 +28,7 @@ func runUse(dockerCli command.Cli, in useOptions) error {
|
||||
return errors.Errorf("run `docker context use default` to switch to default context")
|
||||
}
|
||||
if in.builder == "default" || in.builder == dockerCli.CurrentContext() {
|
||||
ep, err := dockerutil.GetCurrentEndpoint(dockerCli)
|
||||
ep, err := getCurrentEndpoint(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -53,7 +51,7 @@ func runUse(dockerCli command.Cli, in useOptions) error {
|
||||
return errors.Wrapf(err, "failed to find instance %q", in.builder)
|
||||
}
|
||||
|
||||
ep, err := dockerutil.GetCurrentEndpoint(dockerCli)
|
||||
ep, err := getCurrentEndpoint(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -81,8 +79,11 @@ func useCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.BoolVar(&options.isGlobal, "global", false, "Builder persists context changes")
|
||||
flags.BoolVar(&options.isDefault, "default", false, "Set builder as default for current context")
|
||||
|
||||
_ = flags
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
477
commands/util.go
Normal file
477
commands/util.go
Normal file
@@ -0,0 +1,477 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/driver"
|
||||
"github.com/docker/buildx/store"
|
||||
"github.com/docker/buildx/util/platformutil"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/context/docker"
|
||||
"github.com/docker/cli/cli/context/kubernetes"
|
||||
ctxstore "github.com/docker/cli/cli/context/store"
|
||||
dopts "github.com/docker/cli/opts"
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
// getStore returns current builder instance store
|
||||
func getStore(dockerCli command.Cli) (*store.Txn, func(), error) {
|
||||
s, err := store.New(getConfigStorePath(dockerCli))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return s.Txn()
|
||||
}
|
||||
|
||||
// getConfigStorePath will look for correct configuration store path;
|
||||
// if `$BUILDX_CONFIG` is set - use it, otherwise use parent directory
|
||||
// of Docker config file (i.e. `${DOCKER_CONFIG}/buildx`)
|
||||
func getConfigStorePath(dockerCli command.Cli) string {
|
||||
if buildxConfig := os.Getenv("BUILDX_CONFIG"); buildxConfig != "" {
|
||||
logrus.Debugf("using config store %q based in \"$BUILDX_CONFIG\" environment variable", buildxConfig)
|
||||
return buildxConfig
|
||||
}
|
||||
|
||||
buildxConfig := filepath.Join(filepath.Dir(dockerCli.ConfigFile().Filename), "buildx")
|
||||
logrus.Debugf("using default config store %q", buildxConfig)
|
||||
return buildxConfig
|
||||
}
|
||||
|
||||
// getCurrentEndpoint returns the current default endpoint value
|
||||
func getCurrentEndpoint(dockerCli command.Cli) (string, error) {
|
||||
name := dockerCli.CurrentContext()
|
||||
if name != "default" {
|
||||
return name, nil
|
||||
}
|
||||
de, err := getDockerEndpoint(dockerCli, name)
|
||||
if err != nil {
|
||||
return "", errors.Errorf("docker endpoint for %q not found", name)
|
||||
}
|
||||
return de, nil
|
||||
}
|
||||
|
||||
// getDockerEndpoint returns docker endpoint string for given context
|
||||
func getDockerEndpoint(dockerCli command.Cli, name string) (string, error) {
|
||||
list, err := dockerCli.ContextStore().List()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, l := range list {
|
||||
if l.Name == name {
|
||||
ep, ok := l.Endpoints["docker"]
|
||||
if !ok {
|
||||
return "", errors.Errorf("context %q does not have a Docker endpoint", name)
|
||||
}
|
||||
typed, ok := ep.(docker.EndpointMeta)
|
||||
if !ok {
|
||||
return "", errors.Errorf("endpoint %q is not of type EndpointMeta, %T", ep, ep)
|
||||
}
|
||||
return typed.Host, nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// validateEndpoint validates that endpoint is either a context or a docker host
|
||||
func validateEndpoint(dockerCli command.Cli, ep string) (string, error) {
|
||||
de, err := getDockerEndpoint(dockerCli, ep)
|
||||
if err == nil && de != "" {
|
||||
if ep == "default" {
|
||||
return de, nil
|
||||
}
|
||||
return ep, nil
|
||||
}
|
||||
h, err := dopts.ParseHost(true, ep)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to parse endpoint %s", ep)
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// getCurrentInstance finds the current builder instance
|
||||
func getCurrentInstance(txn *store.Txn, dockerCli command.Cli) (*store.NodeGroup, error) {
|
||||
ep, err := getCurrentEndpoint(dockerCli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ng, err := txn.Current(ep)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ng == nil {
|
||||
ng, _ = getNodeGroup(txn, dockerCli, dockerCli.CurrentContext())
|
||||
}
|
||||
|
||||
return ng, nil
|
||||
}
|
||||
|
||||
// getNodeGroup returns nodegroup based on the name
|
||||
func getNodeGroup(txn *store.Txn, dockerCli command.Cli, name string) (*store.NodeGroup, error) {
|
||||
ng, err := txn.NodeGroupByName(name)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(errors.Cause(err)) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if ng != nil {
|
||||
return ng, nil
|
||||
}
|
||||
|
||||
if name == "default" {
|
||||
name = dockerCli.CurrentContext()
|
||||
}
|
||||
|
||||
list, err := dockerCli.ContextStore().List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, l := range list {
|
||||
if l.Name == name {
|
||||
return &store.NodeGroup{
|
||||
Name: "default",
|
||||
Nodes: []store.Node{
|
||||
{
|
||||
Name: "default",
|
||||
Endpoint: name,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.Errorf("no builder %q found", name)
|
||||
}
|
||||
|
||||
// driversForNodeGroup returns drivers for a nodegroup instance
|
||||
func driversForNodeGroup(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, contextPathHash string) ([]build.DriverInfo, error) {
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
|
||||
dis := make([]build.DriverInfo, len(ng.Nodes))
|
||||
|
||||
var f driver.Factory
|
||||
if ng.Driver != "" {
|
||||
f = driver.GetFactory(ng.Driver, true)
|
||||
if f == nil {
|
||||
return nil, errors.Errorf("failed to find driver %q", f)
|
||||
}
|
||||
} else {
|
||||
dockerapi, err := clientForEndpoint(dockerCli, ng.Nodes[0].Endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err = driver.GetDefaultFactory(ctx, dockerapi, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ng.Driver = f.Name()
|
||||
}
|
||||
|
||||
for i, n := range ng.Nodes {
|
||||
func(i int, n store.Node) {
|
||||
eg.Go(func() error {
|
||||
di := build.DriverInfo{
|
||||
Name: n.Name,
|
||||
Platform: n.Platforms,
|
||||
}
|
||||
defer func() {
|
||||
dis[i] = di
|
||||
}()
|
||||
dockerapi, err := clientForEndpoint(dockerCli, n.Endpoint)
|
||||
if err != nil {
|
||||
di.Err = err
|
||||
return nil
|
||||
}
|
||||
// TODO: replace the following line with dockerclient.WithAPIVersionNegotiation option in clientForEndpoint
|
||||
dockerapi.NegotiateAPIVersion(ctx)
|
||||
|
||||
contextStore := dockerCli.ContextStore()
|
||||
|
||||
var kcc driver.KubeClientConfig
|
||||
kcc, err = configFromContext(n.Endpoint, contextStore)
|
||||
if err != nil {
|
||||
// err is returned if n.Endpoint is non-context name like "unix:///var/run/docker.sock".
|
||||
// try again with name="default".
|
||||
// FIXME: n should retain real context name.
|
||||
kcc, err = configFromContext("default", contextStore)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
tryToUseKubeConfigInCluster := false
|
||||
if kcc == nil {
|
||||
tryToUseKubeConfigInCluster = true
|
||||
} else {
|
||||
if _, err := kcc.ClientConfig(); err != nil {
|
||||
tryToUseKubeConfigInCluster = true
|
||||
}
|
||||
}
|
||||
if tryToUseKubeConfigInCluster {
|
||||
kccInCluster := driver.KubeClientConfigInCluster{}
|
||||
if _, err := kccInCluster.ClientConfig(); err == nil {
|
||||
logrus.Debug("using kube config in cluster")
|
||||
kcc = kccInCluster
|
||||
}
|
||||
}
|
||||
|
||||
d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, f, dockerapi, dockerCli.ConfigFile(), kcc, n.Flags, n.ConfigFile, n.DriverOpts, n.Platforms, contextPathHash)
|
||||
if err != nil {
|
||||
di.Err = err
|
||||
return nil
|
||||
}
|
||||
di.Driver = d
|
||||
return nil
|
||||
})
|
||||
}(i, n)
|
||||
}
|
||||
|
||||
if err := eg.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dis, nil
|
||||
}
|
||||
|
||||
func configFromContext(endpointName string, s ctxstore.Reader) (clientcmd.ClientConfig, error) {
|
||||
if strings.HasPrefix(endpointName, "kubernetes://") {
|
||||
u, _ := url.Parse(endpointName)
|
||||
|
||||
if kubeconfig := u.Query().Get("kubeconfig"); kubeconfig != "" {
|
||||
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
|
||||
&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig},
|
||||
&clientcmd.ConfigOverrides{},
|
||||
)
|
||||
return clientConfig, nil
|
||||
}
|
||||
}
|
||||
return kubernetes.ConfigFromContext(endpointName, s)
|
||||
}
|
||||
|
||||
// clientForEndpoint returns a docker client for an endpoint
|
||||
func clientForEndpoint(dockerCli command.Cli, name string) (dockerclient.APIClient, error) {
|
||||
list, err := dockerCli.ContextStore().List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, l := range list {
|
||||
if l.Name == name {
|
||||
dep, ok := l.Endpoints["docker"]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("context %q does not have a Docker endpoint", name)
|
||||
}
|
||||
epm, ok := dep.(docker.EndpointMeta)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("endpoint %q is not of type EndpointMeta, %T", dep, dep)
|
||||
}
|
||||
ep, err := docker.WithTLSData(dockerCli.ContextStore(), name, epm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientOpts, err := ep.ClientOpts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dockerclient.NewClientWithOpts(clientOpts...)
|
||||
}
|
||||
}
|
||||
|
||||
ep := docker.Endpoint{
|
||||
EndpointMeta: docker.EndpointMeta{
|
||||
Host: name,
|
||||
},
|
||||
}
|
||||
|
||||
clientOpts, err := ep.ClientOpts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dockerclient.NewClientWithOpts(clientOpts...)
|
||||
}
|
||||
|
||||
func getInstanceOrDefault(ctx context.Context, dockerCli command.Cli, instance, contextPathHash string) ([]build.DriverInfo, error) {
|
||||
var defaultOnly bool
|
||||
|
||||
if instance == "default" && instance != dockerCli.CurrentContext() {
|
||||
return nil, errors.Errorf("use `docker --context=default buildx` to switch to default context")
|
||||
}
|
||||
if instance == "default" || instance == dockerCli.CurrentContext() {
|
||||
instance = ""
|
||||
defaultOnly = true
|
||||
}
|
||||
list, err := dockerCli.ContextStore().List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, l := range list {
|
||||
if l.Name == instance {
|
||||
return nil, errors.Errorf("use `docker --context=%s buildx` to switch to context %s", instance, instance)
|
||||
}
|
||||
}
|
||||
|
||||
if instance != "" {
|
||||
return getInstanceByName(ctx, dockerCli, instance, contextPathHash)
|
||||
}
|
||||
return getDefaultDrivers(ctx, dockerCli, defaultOnly, contextPathHash)
|
||||
}
|
||||
|
||||
func getInstanceByName(ctx context.Context, dockerCli command.Cli, instance, contextPathHash string) ([]build.DriverInfo, error) {
|
||||
txn, release, err := getStore(dockerCli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer release()
|
||||
|
||||
ng, err := txn.NodeGroupByName(instance)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return driversForNodeGroup(ctx, dockerCli, ng, contextPathHash)
|
||||
}
|
||||
|
||||
// getDefaultDrivers returns drivers based on current cli config
|
||||
func getDefaultDrivers(ctx context.Context, dockerCli command.Cli, defaultOnly bool, contextPathHash string) ([]build.DriverInfo, error) {
|
||||
txn, release, err := getStore(dockerCli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer release()
|
||||
|
||||
if !defaultOnly {
|
||||
ng, err := getCurrentInstance(txn, dockerCli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ng != nil {
|
||||
return driversForNodeGroup(ctx, dockerCli, ng, contextPathHash)
|
||||
}
|
||||
}
|
||||
|
||||
d, err := driver.GetDriver(ctx, "buildx_buildkit_default", nil, dockerCli.Client(), dockerCli.ConfigFile(), nil, nil, "", nil, nil, contextPathHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []build.DriverInfo{
|
||||
{
|
||||
Name: "default",
|
||||
Driver: d,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func loadInfoData(ctx context.Context, d *dinfo) error {
|
||||
if d.di.Driver == nil {
|
||||
return nil
|
||||
}
|
||||
info, err := d.di.Driver.Info(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.info = info
|
||||
if info.Status == driver.Running {
|
||||
c, err := d.di.Driver.Client(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
workers, err := c.ListWorkers(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "listing workers")
|
||||
}
|
||||
for _, w := range workers {
|
||||
for _, p := range w.Platforms {
|
||||
d.platforms = append(d.platforms, p)
|
||||
}
|
||||
}
|
||||
d.platforms = platformutil.Dedupe(d.platforms)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadNodeGroupData(ctx context.Context, dockerCli command.Cli, ngi *nginfo) error {
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
|
||||
dis, err := driversForNodeGroup(ctx, dockerCli, ngi.ng, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ngi.drivers = make([]dinfo, len(dis))
|
||||
for i, di := range dis {
|
||||
d := di
|
||||
ngi.drivers[i].di = &d
|
||||
func(d *dinfo) {
|
||||
eg.Go(func() error {
|
||||
if err := loadInfoData(ctx, d); err != nil {
|
||||
d.err = err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}(&ngi.drivers[i])
|
||||
}
|
||||
|
||||
if eg.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kubernetesDriverCount := 0
|
||||
|
||||
for _, di := range ngi.drivers {
|
||||
if di.info != nil && len(di.info.DynamicNodes) > 0 {
|
||||
kubernetesDriverCount++
|
||||
}
|
||||
}
|
||||
|
||||
isAllKubernetesDrivers := len(ngi.drivers) == kubernetesDriverCount
|
||||
|
||||
if isAllKubernetesDrivers {
|
||||
var drivers []dinfo
|
||||
var dynamicNodes []store.Node
|
||||
|
||||
for _, di := range ngi.drivers {
|
||||
// dynamic nodes are used in Kubernetes driver.
|
||||
// Kubernetes pods are dynamically mapped to BuildKit Nodes.
|
||||
if di.info != nil && len(di.info.DynamicNodes) > 0 {
|
||||
for i := 0; i < len(di.info.DynamicNodes); i++ {
|
||||
// all []dinfo share *build.DriverInfo and *driver.Info
|
||||
diClone := di
|
||||
if pl := di.info.DynamicNodes[i].Platforms; len(pl) > 0 {
|
||||
diClone.platforms = pl
|
||||
}
|
||||
drivers = append(drivers, di)
|
||||
}
|
||||
dynamicNodes = append(dynamicNodes, di.info.DynamicNodes...)
|
||||
}
|
||||
}
|
||||
|
||||
// not append (remove the static nodes in the store)
|
||||
ngi.ng.Nodes = dynamicNodes
|
||||
ngi.drivers = drivers
|
||||
ngi.ng.Dynamic = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func dockerAPI(dockerCli command.Cli) *api {
|
||||
return &api{dockerCli: dockerCli}
|
||||
}
|
||||
|
||||
type api struct {
|
||||
dockerCli command.Cli
|
||||
}
|
||||
|
||||
func (a *api) DockerAPI(name string) (dockerclient.APIClient, error) {
|
||||
if name == "" {
|
||||
name = a.dockerCli.CurrentContext()
|
||||
}
|
||||
return clientForEndpoint(a.dockerCli, name)
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package commands
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/buildx/util/cobrautil"
|
||||
"github.com/docker/buildx/version"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
@@ -24,9 +23,5 @@ func versionCmd(dockerCli command.Cli) *cobra.Command {
|
||||
return runVersion(dockerCli)
|
||||
},
|
||||
}
|
||||
|
||||
// hide builder persistent flag for this command
|
||||
cobrautil.HideInheritedFlags(cmd, "builder")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
143
docker-bake.hcl
143
docker-bake.hcl
@@ -1,143 +0,0 @@
|
||||
variable "GO_VERSION" {
|
||||
default = "1.19"
|
||||
}
|
||||
variable "DOCS_FORMATS" {
|
||||
default = "md"
|
||||
}
|
||||
variable "DESTDIR" {
|
||||
default = "./bin"
|
||||
}
|
||||
|
||||
# Special target: https://github.com/docker/metadata-action#bake-definition
|
||||
target "meta-helper" {
|
||||
tags = ["docker/buildx-bin:local"]
|
||||
}
|
||||
|
||||
target "_common" {
|
||||
args = {
|
||||
GO_VERSION = GO_VERSION
|
||||
BUILDKIT_CONTEXT_KEEP_GIT_DIR = 1
|
||||
BUILDX_EXPERIMENTAL = 1
|
||||
}
|
||||
}
|
||||
|
||||
group "default" {
|
||||
targets = ["binaries"]
|
||||
}
|
||||
|
||||
group "validate" {
|
||||
targets = ["lint", "validate-vendor", "validate-docs"]
|
||||
}
|
||||
|
||||
target "lint" {
|
||||
inherits = ["_common"]
|
||||
dockerfile = "./hack/dockerfiles/lint.Dockerfile"
|
||||
output = ["type=cacheonly"]
|
||||
}
|
||||
|
||||
target "validate-vendor" {
|
||||
inherits = ["_common"]
|
||||
dockerfile = "./hack/dockerfiles/vendor.Dockerfile"
|
||||
target = "validate"
|
||||
output = ["type=cacheonly"]
|
||||
}
|
||||
|
||||
target "validate-docs" {
|
||||
inherits = ["_common"]
|
||||
args = {
|
||||
FORMATS = DOCS_FORMATS
|
||||
}
|
||||
dockerfile = "./hack/dockerfiles/docs.Dockerfile"
|
||||
target = "validate"
|
||||
output = ["type=cacheonly"]
|
||||
}
|
||||
|
||||
target "validate-authors" {
|
||||
inherits = ["_common"]
|
||||
dockerfile = "./hack/dockerfiles/authors.Dockerfile"
|
||||
target = "validate"
|
||||
output = ["type=cacheonly"]
|
||||
}
|
||||
|
||||
target "update-vendor" {
|
||||
inherits = ["_common"]
|
||||
dockerfile = "./hack/dockerfiles/vendor.Dockerfile"
|
||||
target = "update"
|
||||
output = ["."]
|
||||
}
|
||||
|
||||
target "update-docs" {
|
||||
inherits = ["_common"]
|
||||
args = {
|
||||
FORMATS = DOCS_FORMATS
|
||||
}
|
||||
dockerfile = "./hack/dockerfiles/docs.Dockerfile"
|
||||
target = "update"
|
||||
output = ["./docs/reference"]
|
||||
}
|
||||
|
||||
target "update-authors" {
|
||||
inherits = ["_common"]
|
||||
dockerfile = "./hack/dockerfiles/authors.Dockerfile"
|
||||
target = "update"
|
||||
output = ["."]
|
||||
}
|
||||
|
||||
target "mod-outdated" {
|
||||
inherits = ["_common"]
|
||||
dockerfile = "./hack/dockerfiles/vendor.Dockerfile"
|
||||
target = "outdated"
|
||||
no-cache-filter = ["outdated"]
|
||||
output = ["type=cacheonly"]
|
||||
}
|
||||
|
||||
target "test" {
|
||||
inherits = ["_common"]
|
||||
target = "test-coverage"
|
||||
output = ["${DESTDIR}/coverage"]
|
||||
}
|
||||
|
||||
target "binaries" {
|
||||
inherits = ["_common"]
|
||||
target = "binaries"
|
||||
output = ["${DESTDIR}/build"]
|
||||
platforms = ["local"]
|
||||
}
|
||||
|
||||
target "binaries-cross" {
|
||||
inherits = ["binaries"]
|
||||
platforms = [
|
||||
"darwin/amd64",
|
||||
"darwin/arm64",
|
||||
"linux/amd64",
|
||||
"linux/arm/v6",
|
||||
"linux/arm/v7",
|
||||
"linux/arm64",
|
||||
"linux/ppc64le",
|
||||
"linux/riscv64",
|
||||
"linux/s390x",
|
||||
"windows/amd64",
|
||||
"windows/arm64"
|
||||
]
|
||||
}
|
||||
|
||||
target "release" {
|
||||
inherits = ["binaries-cross"]
|
||||
target = "release"
|
||||
output = ["${DESTDIR}/release"]
|
||||
}
|
||||
|
||||
target "image" {
|
||||
inherits = ["meta-helper", "binaries"]
|
||||
output = ["type=image"]
|
||||
}
|
||||
|
||||
target "image-cross" {
|
||||
inherits = ["meta-helper", "binaries-cross"]
|
||||
output = ["type=image"]
|
||||
}
|
||||
|
||||
target "image-local" {
|
||||
inherits = ["image"]
|
||||
output = ["type=docker"]
|
||||
}
|
||||
198
docs/docsgen/generate.go
Normal file
198
docs/docsgen/generate.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/commands"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
const descriptionSourcePath = "docs/reference/"
|
||||
|
||||
func generateDocs(opts *options) error {
|
||||
dockerCLI, err := command.NewDockerCli()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "docker [OPTIONS] COMMAND [ARG...]",
|
||||
Short: "The base command for the Docker CLI.",
|
||||
}
|
||||
cmd.AddCommand(commands.NewRootCmd("buildx", true, dockerCLI))
|
||||
return genCmd(cmd, opts.target)
|
||||
}
|
||||
|
||||
func getMDFilename(cmd *cobra.Command) string {
|
||||
name := cmd.CommandPath()
|
||||
if i := strings.Index(name, " "); i >= 0 {
|
||||
name = name[i+1:]
|
||||
}
|
||||
return strings.ReplaceAll(name, " ", "_") + ".md"
|
||||
}
|
||||
|
||||
func genCmd(cmd *cobra.Command, dir string) error {
|
||||
for _, c := range cmd.Commands() {
|
||||
if err := genCmd(c, dir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !cmd.HasParent() {
|
||||
return nil
|
||||
}
|
||||
|
||||
mdFile := getMDFilename(cmd)
|
||||
fullPath := filepath.Join(dir, mdFile)
|
||||
|
||||
content, err := ioutil.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return errors.Wrapf(err, "%s does not exist", mdFile)
|
||||
}
|
||||
}
|
||||
|
||||
cs := string(content)
|
||||
|
||||
markerStart := "<!---MARKER_GEN_START-->"
|
||||
markerEnd := "<!---MARKER_GEN_END-->"
|
||||
|
||||
start := strings.Index(cs, markerStart)
|
||||
end := strings.Index(cs, markerEnd)
|
||||
|
||||
if start == -1 {
|
||||
return errors.Errorf("no start marker in %s", mdFile)
|
||||
}
|
||||
if end == -1 {
|
||||
return errors.Errorf("no end marker in %s", mdFile)
|
||||
}
|
||||
|
||||
out, err := cmdOutput(cmd, cs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cont := cs[:start] + markerStart + "\n" + out + "\n" + cs[end:]
|
||||
|
||||
fi, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(fullPath, []byte(cont), fi.Mode()); err != nil {
|
||||
return errors.Wrapf(err, "failed to write %s", fullPath)
|
||||
}
|
||||
log.Printf("updated %s", fullPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeLink(txt, link string, f *pflag.Flag, isAnchor bool) string {
|
||||
link = "#" + link
|
||||
annotations, ok := f.Annotations["docs.external.url"]
|
||||
if ok && len(annotations) > 0 {
|
||||
link = annotations[0]
|
||||
} else {
|
||||
if !isAnchor {
|
||||
return txt
|
||||
}
|
||||
}
|
||||
|
||||
return "[" + txt + "](" + link + ")"
|
||||
}
|
||||
|
||||
func cmdOutput(cmd *cobra.Command, old string) (string, error) {
|
||||
b := &strings.Builder{}
|
||||
|
||||
desc := cmd.Short
|
||||
if cmd.Long != "" {
|
||||
desc = cmd.Long
|
||||
}
|
||||
if desc != "" {
|
||||
fmt.Fprintf(b, "%s\n\n", desc)
|
||||
}
|
||||
|
||||
if len(cmd.Aliases) != 0 {
|
||||
fmt.Fprintf(b, "### Aliases\n\n`%s`", cmd.Name())
|
||||
for _, a := range cmd.Aliases {
|
||||
fmt.Fprintf(b, ", `%s`", a)
|
||||
}
|
||||
fmt.Fprint(b, "\n\n")
|
||||
}
|
||||
|
||||
if len(cmd.Commands()) != 0 {
|
||||
fmt.Fprint(b, "### Subcommands\n\n")
|
||||
fmt.Fprint(b, "| Name | Description |\n")
|
||||
fmt.Fprint(b, "| --- | --- |\n")
|
||||
for _, c := range cmd.Commands() {
|
||||
fmt.Fprintf(b, "| [`%s`](%s) | %s |\n", c.Name(), getMDFilename(c), c.Short)
|
||||
}
|
||||
fmt.Fprint(b, "\n\n")
|
||||
}
|
||||
|
||||
hasFlags := cmd.Flags().HasAvailableFlags()
|
||||
|
||||
cmd.Flags().AddFlagSet(cmd.InheritedFlags())
|
||||
|
||||
if hasFlags {
|
||||
fmt.Fprint(b, "### Options\n\n")
|
||||
fmt.Fprint(b, "| Name | Description |\n")
|
||||
fmt.Fprint(b, "| --- | --- |\n")
|
||||
|
||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
if f.Hidden {
|
||||
return
|
||||
}
|
||||
isLink := strings.Contains(old, "<a name=\""+f.Name+"\"></a>")
|
||||
fmt.Fprint(b, "| ")
|
||||
if f.Shorthand != "" {
|
||||
name := "`-" + f.Shorthand + "`"
|
||||
name = makeLink(name, f.Name, f, isLink)
|
||||
fmt.Fprintf(b, "%s, ", name)
|
||||
}
|
||||
name := "`--" + f.Name
|
||||
if f.Value.Type() != "bool" {
|
||||
name += " " + f.Value.Type()
|
||||
}
|
||||
name += "`"
|
||||
name = makeLink(name, f.Name, f, isLink)
|
||||
fmt.Fprintf(b, "%s | %s |\n", name, f.Usage)
|
||||
})
|
||||
fmt.Fprintln(b, "")
|
||||
}
|
||||
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
type options struct {
|
||||
target string
|
||||
}
|
||||
|
||||
func parseArgs() (*options, error) {
|
||||
opts := &options{}
|
||||
flags := pflag.NewFlagSet(os.Args[0], pflag.ContinueOnError)
|
||||
flags.StringVar(&opts.target, "target", descriptionSourcePath, "Docs directory")
|
||||
err := flags.Parse(os.Args[1:])
|
||||
return opts, err
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
log.Printf("error: %+v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run() error {
|
||||
opts, err := parseArgs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := generateDocs(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/docker/buildx/commands"
|
||||
clidocstool "github.com/docker/cli-docs-tool"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
// import drivers otherwise factories are empty
|
||||
// for --driver output flag usage
|
||||
_ "github.com/docker/buildx/driver/docker"
|
||||
_ "github.com/docker/buildx/driver/docker-container"
|
||||
_ "github.com/docker/buildx/driver/kubernetes"
|
||||
_ "github.com/docker/buildx/driver/remote"
|
||||
)
|
||||
|
||||
const defaultSourcePath = "docs/reference/"
|
||||
|
||||
type options struct {
|
||||
source string
|
||||
formats []string
|
||||
}
|
||||
|
||||
func gen(opts *options) error {
|
||||
log.SetFlags(0)
|
||||
|
||||
dockerCLI, err := command.NewDockerCli()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "docker [OPTIONS] COMMAND [ARG...]",
|
||||
Short: "The base command for the Docker CLI.",
|
||||
DisableAutoGenTag: true,
|
||||
}
|
||||
|
||||
cmd.AddCommand(commands.NewRootCmd("buildx", true, dockerCLI))
|
||||
|
||||
c, err := clidocstool.New(clidocstool.Options{
|
||||
Root: cmd,
|
||||
SourceDir: opts.source,
|
||||
Plugin: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, format := range opts.formats {
|
||||
switch format {
|
||||
case "md":
|
||||
if err = c.GenMarkdownTree(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
case "yaml":
|
||||
if err = c.GenYamlTree(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.Errorf("unknown format %q", format)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func run() error {
|
||||
opts := &options{}
|
||||
flags := pflag.NewFlagSet(os.Args[0], pflag.ContinueOnError)
|
||||
flags.StringVar(&opts.source, "source", defaultSourcePath, "Docs source folder")
|
||||
flags.StringSliceVar(&opts.formats, "formats", []string{}, "Format (md, yaml)")
|
||||
if err := flags.Parse(os.Args[1:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(opts.formats) == 0 {
|
||||
return errors.New("Docs format required")
|
||||
}
|
||||
return gen(opts)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
log.Printf("ERROR: %+v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
# CI/CD
|
||||
|
||||
## GitHub Actions
|
||||
|
||||
Docker provides a [GitHub Action that will build and push your image](https://github.com/docker/build-push-action/#about)
|
||||
using Buildx. Here is a simple workflow:
|
||||
|
||||
```yaml
|
||||
name: ci
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
push: true
|
||||
tags: user/app:latest
|
||||
```
|
||||
|
||||
In this example we are also using 3 other actions:
|
||||
|
||||
* [`setup-buildx`](https://github.com/docker/setup-buildx-action) action will create and boot a builder using by
|
||||
default the `docker-container` [builder driver](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver).
|
||||
This is **not required but recommended** using it to be able to build multi-platform images, export cache, etc.
|
||||
* [`setup-qemu`](https://github.com/docker/setup-qemu-action) action can be useful if you want
|
||||
to add emulation support with QEMU to be able to build against more platforms.
|
||||
* [`login`](https://github.com/docker/login-action) action will take care to log
|
||||
in against a Docker registry.
|
||||
@@ -1,23 +0,0 @@
|
||||
# CNI networking
|
||||
|
||||
It can be useful to use a bridge network for your builder if for example you
|
||||
encounter a network port contention during multiple builds. If you're using
|
||||
the BuildKit image, CNI is not yet available in it, but you can create
|
||||
[a custom BuildKit image with CNI support](https://github.com/moby/buildkit/blob/master/docs/cni-networking.md).
|
||||
|
||||
Now build this image:
|
||||
|
||||
```console
|
||||
$ docker buildx build --tag buildkit-cni:local --load .
|
||||
```
|
||||
|
||||
Then [create a `docker-container` builder](https://docs.docker.com/engine/reference/commandline/buildx_create/) that
|
||||
will use this image:
|
||||
|
||||
```console
|
||||
$ docker buildx create --use \
|
||||
--name mybuilder \
|
||||
--driver docker-container \
|
||||
--driver-opt "image=buildkit-cni:local" \
|
||||
--buildkitd-flags "--oci-worker-net=cni"
|
||||
```
|
||||
@@ -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).
|
||||
@@ -1,34 +0,0 @@
|
||||
# Using a custom network
|
||||
|
||||
[Create a network](https://docs.docker.com/engine/reference/commandline/network_create/)
|
||||
named `foonet`:
|
||||
|
||||
```console
|
||||
$ docker network create foonet
|
||||
```
|
||||
|
||||
[Create a `docker-container` builder](https://docs.docker.com/engine/reference/commandline/buildx_create/)
|
||||
named `mybuilder` that will use this network:
|
||||
|
||||
```console
|
||||
$ docker buildx create --use \
|
||||
--name mybuilder \
|
||||
--driver docker-container \
|
||||
--driver-opt "network=foonet"
|
||||
```
|
||||
|
||||
Boot and [inspect `mybuilder`](https://docs.docker.com/engine/reference/commandline/buildx_inspect/):
|
||||
|
||||
```console
|
||||
$ docker buildx inspect --bootstrap
|
||||
```
|
||||
|
||||
[Inspect the builder container](https://docs.docker.com/engine/reference/commandline/inspect/)
|
||||
and see what network is being used:
|
||||
|
||||
{% raw %}
|
||||
```console
|
||||
$ docker inspect buildx_buildkit_mybuilder0 --format={{.NetworkSettings.Networks}}
|
||||
map[foonet:0xc00018c0c0]
|
||||
```
|
||||
{% endraw %}
|
||||
@@ -1,63 +0,0 @@
|
||||
# Using a custom registry configuration
|
||||
|
||||
If you [create a `docker-container` or `kubernetes` builder](https://docs.docker.com/engine/reference/commandline/buildx_create/) and
|
||||
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
|
||||
configuration will be updated to reflect that.
|
||||
|
||||
Take the following `buildkitd.toml` configuration that will be used for
|
||||
pushing an image to this registry using self-signed certificates:
|
||||
|
||||
```toml
|
||||
# /etc/buildkitd.toml
|
||||
debug = true
|
||||
[registry."myregistry.com"]
|
||||
ca=["/etc/certs/myregistry.pem"]
|
||||
[[registry."myregistry.com".keypair]]
|
||||
key="/etc/certs/myregistry_key.pem"
|
||||
cert="/etc/certs/myregistry_cert.pem"
|
||||
```
|
||||
|
||||
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/)
|
||||
that will use this BuildKit configuration:
|
||||
|
||||
```console
|
||||
$ docker buildx create --use \
|
||||
--name mybuilder \
|
||||
--driver docker-container \
|
||||
--config /etc/buildkitd.toml
|
||||
```
|
||||
|
||||
Inspecting the builder container, you can see that buildkitd configuration
|
||||
has changed:
|
||||
|
||||
```console
|
||||
$ docker exec -it buildx_buildkit_mybuilder0 cat /etc/buildkit/buildkitd.toml
|
||||
```
|
||||
```toml
|
||||
debug = true
|
||||
|
||||
[registry]
|
||||
|
||||
[registry."myregistry.com"]
|
||||
ca = ["/etc/buildkit/certs/myregistry.com/myregistry.pem"]
|
||||
|
||||
[[registry."myregistry.com".keypair]]
|
||||
cert = "/etc/buildkit/certs/myregistry.com/myregistry_cert.pem"
|
||||
key = "/etc/buildkit/certs/myregistry.com/myregistry_key.pem"
|
||||
```
|
||||
|
||||
And certificates copied inside the container:
|
||||
|
||||
```console
|
||||
$ docker exec -it buildx_buildkit_mybuilder0 ls /etc/buildkit/certs/myregistry.com/
|
||||
myregistry.pem myregistry_cert.pem myregistry_key.pem
|
||||
```
|
||||
|
||||
Now you should be able to push to the registry with this builder:
|
||||
|
||||
```console
|
||||
$ docker buildx build --push --tag myregistry.com/myimage:latest .
|
||||
```
|
||||
@@ -1,31 +0,0 @@
|
||||
# OpenTelemetry support
|
||||
|
||||
To capture the trace to [Jaeger](https://github.com/jaegertracing/jaeger), set
|
||||
`JAEGER_TRACE` environment variable to the collection address using a `driver-opt`.
|
||||
|
||||
First create a Jaeger container:
|
||||
|
||||
```console
|
||||
$ 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/)
|
||||
that will use the Jaeger instance via the `JAEGER_TRACE` env var:
|
||||
|
||||
```console
|
||||
$ docker buildx create --use \
|
||||
--name mybuilder \
|
||||
--driver docker-container \
|
||||
--driver-opt "network=host" \
|
||||
--driver-opt "env.JAEGER_TRACE=localhost:6831"
|
||||
```
|
||||
|
||||
Boot and [inspect `mybuilder`](https://docs.docker.com/engine/reference/commandline/buildx_inspect/):
|
||||
|
||||
```console
|
||||
$ docker buildx inspect --bootstrap
|
||||
```
|
||||
|
||||
Buildx commands should be traced at `http://127.0.0.1:16686/`:
|
||||
|
||||

|
||||
@@ -1,62 +0,0 @@
|
||||
# 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)
|
||||
while creating a builder with the [`--config` flags](https://docs.docker.com/engine/reference/commandline/buildx_create/#config).
|
||||
|
||||
```toml
|
||||
# /etc/buildkitd.toml
|
||||
debug = true
|
||||
[registry."docker.io"]
|
||||
mirrors = ["mirror.gcr.io"]
|
||||
```
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> `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/)
|
||||
that will use this BuildKit configuration:
|
||||
|
||||
```console
|
||||
$ docker buildx create --use \
|
||||
--name mybuilder \
|
||||
--driver docker-container \
|
||||
--config /etc/buildkitd.toml
|
||||
```
|
||||
|
||||
Boot and [inspect `mybuilder`](https://docs.docker.com/engine/reference/commandline/buildx_inspect/):
|
||||
|
||||
```console
|
||||
$ docker buildx inspect --bootstrap
|
||||
```
|
||||
|
||||
Build an image:
|
||||
|
||||
```console
|
||||
$ docker buildx build --load . -f-<<EOF
|
||||
FROM alpine
|
||||
RUN echo "hello world"
|
||||
EOF
|
||||
```
|
||||
|
||||
Now let's check the BuildKit logs in the builder container:
|
||||
|
||||
```console
|
||||
$ docker logs buildx_buildkit_mybuilder0
|
||||
```
|
||||
```text
|
||||
...
|
||||
time="2022-02-06T17:47:48Z" level=debug msg="do request" request.header.accept="application/vnd.docker.container.image.v1+json, */*" request.header.user-agent=containerd/1.5.8+unknown request.method=GET spanID=9460e5b6e64cec91 traceID=b162d3040ddf86d6614e79c66a01a577
|
||||
time="2022-02-06T17:47:48Z" level=debug msg="fetch response received" response.header.accept-ranges=bytes response.header.age=1356 response.header.alt-svc="h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000,h3-Q050=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000,quic=\":443\"; ma=2592000; v=\"46,43\"" response.header.cache-control="public, max-age=3600" response.header.content-length=1469 response.header.content-type=application/octet-stream response.header.date="Sun, 06 Feb 2022 17:25:17 GMT" response.header.etag="\"774380abda8f4eae9a149e5d5d3efc83\"" response.header.expires="Sun, 06 Feb 2022 18:25:17 GMT" response.header.last-modified="Wed, 24 Nov 2021 21:07:57 GMT" response.header.server=UploadServer response.header.x-goog-generation=1637788077652182 response.header.x-goog-hash="crc32c=V3DSrg==" response.header.x-goog-hash.1="md5=d0OAq9qPTq6aFJ5dXT78gw==" response.header.x-goog-metageneration=1 response.header.x-goog-storage-class=STANDARD response.header.x-goog-stored-content-encoding=identity response.header.x-goog-stored-content-length=1469 response.header.x-guploader-uploadid=ADPycduqQipVAXc3tzXmTzKQ2gTT6CV736B2J628smtD1iDytEyiYCgvvdD8zz9BT1J1sASUq9pW_ctUyC4B-v2jvhIxnZTlKg response.status="200 OK" spanID=9460e5b6e64cec91 traceID=b162d3040ddf86d6614e79c66a01a577
|
||||
time="2022-02-06T17:47:48Z" level=debug msg="fetch response received" response.header.accept-ranges=bytes response.header.age=760 response.header.alt-svc="h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000,h3-Q050=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000,quic=\":443\"; ma=2592000; v=\"46,43\"" response.header.cache-control="public, max-age=3600" response.header.content-length=1471 response.header.content-type=application/octet-stream response.header.date="Sun, 06 Feb 2022 17:35:13 GMT" response.header.etag="\"35d688bd15327daafcdb4d4395e616a8\"" response.header.expires="Sun, 06 Feb 2022 18:35:13 GMT" response.header.last-modified="Wed, 24 Nov 2021 21:07:12 GMT" response.header.server=UploadServer response.header.x-goog-generation=1637788032100793 response.header.x-goog-hash="crc32c=aWgRjA==" response.header.x-goog-hash.1="md5=NdaIvRUyfar8201DleYWqA==" response.header.x-goog-metageneration=1 response.header.x-goog-storage-class=STANDARD response.header.x-goog-stored-content-encoding=identity response.header.x-goog-stored-content-length=1471 response.header.x-guploader-uploadid=ADPycdtR-gJYwC7yHquIkJWFFG8FovDySvtmRnZBqlO3yVDanBXh_VqKYt400yhuf0XbQ3ZMB9IZV2vlcyHezn_Pu3a1SMMtiw response.status="200 OK" spanID=9460e5b6e64cec91 traceID=b162d3040ddf86d6614e79c66a01a577
|
||||
time="2022-02-06T17:47:48Z" level=debug msg=fetch spanID=9460e5b6e64cec91 traceID=b162d3040ddf86d6614e79c66a01a577
|
||||
time="2022-02-06T17:47:48Z" level=debug msg=fetch spanID=9460e5b6e64cec91 traceID=b162d3040ddf86d6614e79c66a01a577
|
||||
time="2022-02-06T17:47:48Z" level=debug msg=fetch spanID=9460e5b6e64cec91 traceID=b162d3040ddf86d6614e79c66a01a577
|
||||
time="2022-02-06T17:47:48Z" level=debug msg=fetch spanID=9460e5b6e64cec91 traceID=b162d3040ddf86d6614e79c66a01a577
|
||||
time="2022-02-06T17:47:48Z" level=debug msg="do request" request.header.accept="application/vnd.docker.image.rootfs.diff.tar.gzip, */*" request.header.user-agent=containerd/1.5.8+unknown request.method=GET spanID=9460e5b6e64cec91 traceID=b162d3040ddf86d6614e79c66a01a577
|
||||
time="2022-02-06T17:47:48Z" level=debug msg="fetch response received" response.header.accept-ranges=bytes response.header.age=1356 response.header.alt-svc="h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000,h3-Q050=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000,quic=\":443\"; ma=2592000; v=\"46,43\"" response.header.cache-control="public, max-age=3600" response.header.content-length=2818413 response.header.content-type=application/octet-stream response.header.date="Sun, 06 Feb 2022 17:25:17 GMT" response.header.etag="\"1d55e7be5a77c4a908ad11bc33ebea1c\"" response.header.expires="Sun, 06 Feb 2022 18:25:17 GMT" response.header.last-modified="Wed, 24 Nov 2021 21:07:06 GMT" response.header.server=UploadServer response.header.x-goog-generation=1637788026431708 response.header.x-goog-hash="crc32c=ZojF+g==" response.header.x-goog-hash.1="md5=HVXnvlp3xKkIrRG8M+vqHA==" response.header.x-goog-metageneration=1 response.header.x-goog-storage-class=STANDARD response.header.x-goog-stored-content-encoding=identity response.header.x-goog-stored-content-length=2818413 response.header.x-guploader-uploadid=ADPycdsebqxiTBJqZ0bv9zBigjFxgQydD2ESZSkKchpE0ILlN9Ibko3C5r4fJTJ4UR9ddp-UBd-2v_4eRpZ8Yo2llW_j4k8WhQ response.status="200 OK" spanID=9460e5b6e64cec91 traceID=b162d3040ddf86d6614e79c66a01a577
|
||||
...
|
||||
```
|
||||
|
||||
As you can see, requests come from the GCR registry mirror (`response.header.x-goog*`).
|
||||
@@ -1,33 +0,0 @@
|
||||
# Resource limiting
|
||||
|
||||
## Max parallelism
|
||||
|
||||
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)
|
||||
while creating a builder with the [`--config` flags](https://docs.docker.com/engine/reference/commandline/buildx_create/#config).
|
||||
|
||||
```toml
|
||||
# /etc/buildkitd.toml
|
||||
[worker.oci]
|
||||
max-parallelism = 4
|
||||
```
|
||||
|
||||
Now you can [create a `docker-container` builder](https://docs.docker.com/engine/reference/commandline/buildx_create/)
|
||||
that will use this BuildKit configuration to limit parallelism.
|
||||
|
||||
```console
|
||||
$ docker buildx create --use \
|
||||
--name mybuilder \
|
||||
--driver docker-container \
|
||||
--config /etc/buildkitd.toml
|
||||
```
|
||||
|
||||
## Limit on TCP connections
|
||||
|
||||
We are also now limiting TCP connections to **4 per registry** with an additional
|
||||
connection not used for layer pulls and pushes. This limitation will be able to
|
||||
manage TCP connection per host to avoid your build being stuck while pulling
|
||||
images. The additional connection is used for metadata requests
|
||||
(image config retrieval) to enhance the overall build time.
|
||||
|
||||
More info: [moby/buildkit#2259](https://github.com/moby/buildkit/pull/2259)
|
||||
@@ -1,14 +0,0 @@
|
||||
# Buildx manuals 📚
|
||||
|
||||
This directory contains a bunch of useful docs for how to use Buildx features.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> The markdown files in this directory (excluding this README) are reused
|
||||
> downstream by the
|
||||
> [Docker documentation repository](https://github.com/docker/docs).
|
||||
>
|
||||
> If you wish to contribute to these docs, be sure to first review the
|
||||
> [documentation contribution guidelines](https://docs.docker.com/contribute/overview/).
|
||||
>
|
||||
> Thank you!
|
||||
@@ -1,3 +0,0 @@
|
||||
# Defining additional build contexts and linking targets
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/customize/bake/build-contexts)
|
||||
@@ -1,3 +0,0 @@
|
||||
# Building from Compose file
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/customize/bake/compose-file)
|
||||
@@ -1,3 +0,0 @@
|
||||
# Configuring builds
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/customize/bake/configuring-build)
|
||||
@@ -1,3 +0,0 @@
|
||||
# Bake file definition
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/customize/bake/file-definition)
|
||||
@@ -1,3 +0,0 @@
|
||||
# User defined HCL functions
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/customize/bake/hcl-funcs)
|
||||
@@ -1,3 +0,0 @@
|
||||
# High-level build options with Bake
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/customize/bake)
|
||||
3
docs/manuals/cache/backends/azblob.md
vendored
3
docs/manuals/cache/backends/azblob.md
vendored
@@ -1,3 +0,0 @@
|
||||
# Azure Blob Storage cache storage
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/building/cache/backends/azblob)
|
||||
3
docs/manuals/cache/backends/gha.md
vendored
3
docs/manuals/cache/backends/gha.md
vendored
@@ -1,3 +0,0 @@
|
||||
# GitHub Actions cache storage
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/building/cache/backends/gha)
|
||||
3
docs/manuals/cache/backends/index.md
vendored
3
docs/manuals/cache/backends/index.md
vendored
@@ -1,3 +0,0 @@
|
||||
# Cache storage backends
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/building/cache/backends)
|
||||
3
docs/manuals/cache/backends/inline.md
vendored
3
docs/manuals/cache/backends/inline.md
vendored
@@ -1,3 +0,0 @@
|
||||
# Inline cache storage
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/building/cache/backends/inline)
|
||||
3
docs/manuals/cache/backends/local.md
vendored
3
docs/manuals/cache/backends/local.md
vendored
@@ -1,3 +0,0 @@
|
||||
# Local cache storage
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/building/cache/backends/local)
|
||||
3
docs/manuals/cache/backends/registry.md
vendored
3
docs/manuals/cache/backends/registry.md
vendored
@@ -1,3 +0,0 @@
|
||||
# Registry cache storage
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/building/cache/backends/registry)
|
||||
3
docs/manuals/cache/backends/s3.md
vendored
3
docs/manuals/cache/backends/s3.md
vendored
@@ -1,3 +0,0 @@
|
||||
# Amazon S3 cache storage
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/building/cache/backends/s3)
|
||||
@@ -1,3 +0,0 @@
|
||||
# Docker container driver
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/building/drivers/docker-container)
|
||||
@@ -1,3 +0,0 @@
|
||||
# Docker driver
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/building/drivers/docker)
|
||||
@@ -1,3 +0,0 @@
|
||||
# Buildx drivers overview
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/building/drivers)
|
||||
@@ -1,3 +0,0 @@
|
||||
# Kubernetes driver
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/building/drivers/kubernetes)
|
||||
@@ -1,3 +0,0 @@
|
||||
# Remote driver
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/building/drivers/remote)
|
||||
@@ -1,3 +0,0 @@
|
||||
# Image and registry exporters
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/building/exporters/image-registry)
|
||||
@@ -1,3 +0,0 @@
|
||||
# Exporters overview
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/building/exporters)
|
||||
@@ -1,3 +0,0 @@
|
||||
# Local and tar exporters
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/building/exporters/local-tar)
|
||||
@@ -1,3 +0,0 @@
|
||||
# OCI and Docker exporters
|
||||
|
||||
Moved to [docs.docker.com](https://docs.docker.com/build/building/exporters/oci-docker)
|
||||
@@ -5,7 +5,7 @@ docker buildx [OPTIONS] COMMAND
|
||||
```
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Extended build capabilities with BuildKit
|
||||
Build with BuildKit
|
||||
|
||||
### Subcommands
|
||||
|
||||
@@ -27,17 +27,5 @@ Extended build capabilities with BuildKit
|
||||
| [`version`](buildx_version.md) | Show buildx version information |
|
||||
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Examples
|
||||
|
||||
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||
|
||||
You can also use the `BUILDX_BUILDER` environment variable.
|
||||
|
||||
@@ -9,24 +9,22 @@ Build from a file
|
||||
|
||||
### Aliases
|
||||
|
||||
`docker buildx bake`, `docker buildx f`
|
||||
`bake`, `f`
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||
| [`-f`](#file), [`--file`](#file) | `stringArray` | | Build definition file |
|
||||
| `--load` | | | Shorthand for `--set=*.output=type=docker` |
|
||||
| `--metadata-file` | `string` | | Write build result metadata to the file |
|
||||
| [`--no-cache`](#no-cache) | | | Do not use cache when building the image |
|
||||
| [`--print`](#print) | | | Print the options without building |
|
||||
| [`--progress`](#progress) | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output |
|
||||
| `--provenance` | `string` | | Shorthand for `--set=*.attest=type=provenance` |
|
||||
| [`--pull`](#pull) | | | Always attempt to pull all referenced images |
|
||||
| `--push` | | | Shorthand for `--set=*.output=type=registry` |
|
||||
| `--sbom` | `string` | | Shorthand for `--set=*.attest=type=sbom` |
|
||||
| [`--set`](#set) | `stringArray` | | Override target value (e.g., `targetpattern.key=value`) |
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| `--builder string` | Override the configured builder instance |
|
||||
| [`-f`](#file), [`--file stringArray`](#file) | Build definition file |
|
||||
| `--load` | Shorthand for --set=*.output=type=docker |
|
||||
| `--metadata-file string` | Write build result metadata to the file |
|
||||
| [`--no-cache`](#no-cache) | Do not use cache when building the image |
|
||||
| [`--print`](#print) | Print the options without building |
|
||||
| [`--progress string`](#progress) | Set type of progress output (auto, plain, tty). Use plain to show container output |
|
||||
| [`--pull`](#pull) | Always attempt to pull a newer version of the image |
|
||||
| `--push` | Shorthand for --set=*.output=type=registry |
|
||||
| [`--set stringArray`](#set) | Override target value (eg: targetpattern.key=value) |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
@@ -36,59 +34,60 @@ Build from a file
|
||||
Bake is a high-level build command. Each specified target will run in parallel
|
||||
as part of the build.
|
||||
|
||||
Read [High-level build options with Bake](https://docs.docker.com/build/customize/bake/)
|
||||
guide for introduction to writing bake files.
|
||||
Read [High-level build options](https://github.com/docker/buildx#high-level-build-options) for introduction.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> `buildx bake` command may receive backwards incompatible features in the future
|
||||
> if needed. We are looking for feedback on improving the command and extending
|
||||
> the functionality further.
|
||||
Please note that `buildx bake` command may receive backwards incompatible features in the future if needed. We are looking for feedback on improving the command and extending the functionality further.
|
||||
|
||||
## Examples
|
||||
|
||||
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||
|
||||
Same as [`buildx --builder`](buildx.md#builder).
|
||||
|
||||
### <a name="file"></a> Specify a build definition file (-f, --file)
|
||||
|
||||
Use the `-f` / `--file` option to specify the build definition file to use.
|
||||
The file can be an HCL, JSON or Compose file. If multiple files are specified
|
||||
By default, `buildx bake` looks for build definition files in the current 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.
|
||||
|
||||
You can pass the names of the targets to build, to build only specific target(s).
|
||||
The following example builds the `db` and `webapp-release` targets that are
|
||||
defined in the `docker-bake.dev.hcl` file:
|
||||
|
||||
```hcl
|
||||
# docker-bake.dev.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"]
|
||||
}
|
||||
```
|
||||
The following example uses a Docker Compose file named `docker-compose.dev.yaml`
|
||||
as build definition file, and builds all targets in the file:
|
||||
|
||||
```console
|
||||
$ docker buildx bake -f docker-bake.dev.hcl db webapp-release
|
||||
$ docker buildx bake -f docker-compose.dev.yaml
|
||||
|
||||
[+] 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
|
||||
...
|
||||
```
|
||||
|
||||
See our [file definition](https://docs.docker.com/build/customize/bake/file-definition/)
|
||||
guide for more details.
|
||||
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
|
||||
...
|
||||
```
|
||||
|
||||
### <a name="no-cache"></a> Do not use cache when building the image (--no-cache)
|
||||
|
||||
@@ -96,34 +95,47 @@ Same as `build --no-cache`. Do not use cache when building the image.
|
||||
|
||||
### <a name="print"></a> Print the options without building (--print)
|
||||
|
||||
Prints the resulting options of the targets desired to be built, in a JSON
|
||||
format, without starting a build.
|
||||
Prints the resulting options of the targets desired to be built, in a JSON format,
|
||||
without starting a build.
|
||||
|
||||
```console
|
||||
$ docker buildx bake -f docker-bake.hcl --print db
|
||||
{
|
||||
"group": {
|
||||
"default": {
|
||||
"targets": [
|
||||
"db"
|
||||
]
|
||||
}
|
||||
},
|
||||
"target": {
|
||||
"db": {
|
||||
"context": "./",
|
||||
"dockerfile": "Dockerfile",
|
||||
"tags": [
|
||||
"docker.io/tiborvass/db"
|
||||
]
|
||||
}
|
||||
}
|
||||
"target": {
|
||||
"db": {
|
||||
"context": "./",
|
||||
"dockerfile": "Dockerfile",
|
||||
"tags": [
|
||||
"docker.io/tiborvass/db"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### <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)
|
||||
|
||||
@@ -135,8 +147,10 @@ Same as `build --pull`.
|
||||
--set targetpattern.key[.subkey]=value
|
||||
```
|
||||
|
||||
Override target configurations from command line. The pattern matching syntax
|
||||
is defined in https://golang.org/pkg/path/#Match.
|
||||
Override target configurations from command line. The pattern matching syntax is
|
||||
defined in https://golang.org/pkg/path/#Match.
|
||||
|
||||
**Examples**
|
||||
|
||||
```console
|
||||
$ docker buildx bake --set target.args.mybuildarg=value
|
||||
@@ -147,20 +161,216 @@ $ docker buildx bake --set foo*.no-cache # bypass caching only for
|
||||
```
|
||||
|
||||
Complete list of overridable fields:
|
||||
args, cache-from, cache-to, context, dockerfile, labels, no-cache, output, platform,
|
||||
pull, secrets, ssh, tags, target
|
||||
|
||||
* `args`
|
||||
* `cache-from`
|
||||
* `cache-to`
|
||||
* `context`
|
||||
* `dockerfile`
|
||||
* `labels`
|
||||
* `no-cache`
|
||||
* `no-cache-filter`
|
||||
* `output`
|
||||
* `platform`
|
||||
* `pull`
|
||||
* `push`
|
||||
* `secrets`
|
||||
* `ssh`
|
||||
* `tags`
|
||||
* `target`
|
||||
### File definition
|
||||
|
||||
In addition to compose files, bake supports a JSON and an equivalent HCL file
|
||||
format for defining build groups and targets.
|
||||
|
||||
A target reflects a single docker build invocation with the same options that
|
||||
you would specify for `docker build`. A group is a grouping of targets.
|
||||
|
||||
Multiple files can include the same target and final build options will be
|
||||
determined by merging them together.
|
||||
|
||||
In the case of compose files, each service corresponds to a target.
|
||||
|
||||
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.
|
||||
|
||||
Note: Design of bake command is work in progress, the user experience may change
|
||||
based on feedback.
|
||||
|
||||
|
||||
**Example HCL definition**
|
||||
|
||||
```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`, `dockerfile`, `inherits`, `labels`,
|
||||
`no-cache`, `output`, `platform`, `pull`, `secrets`, `ssh`, `tags`, `target`
|
||||
|
||||
### 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.
|
||||
|
||||
|
||||
Example of using interpolation to tag an image with the git sha:
|
||||
|
||||
```console
|
||||
$ cat <<'EOF' > docker-bake.hcl
|
||||
variable "TAG" {
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
group "default" {
|
||||
targets = ["webapp"]
|
||||
}
|
||||
|
||||
target "webapp" {
|
||||
tags = ["docker.io/username/webapp:${TAG}"]
|
||||
}
|
||||
EOF
|
||||
|
||||
$ docker buildx bake --print webapp
|
||||
{
|
||||
"target": {
|
||||
"webapp": {
|
||||
"context": ".",
|
||||
"dockerfile": "Dockerfile",
|
||||
"tags": [
|
||||
"docker.io/username/webapp:latest"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ TAG=$(git rev-parse --short HEAD) docker buildx bake --print webapp
|
||||
{
|
||||
"target": {
|
||||
"webapp": {
|
||||
"context": ".",
|
||||
"dockerfile": "Dockerfile",
|
||||
"tags": [
|
||||
"docker.io/username/webapp:985e9e9"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Example of using the `add` function:
|
||||
|
||||
```console
|
||||
$ cat <<'EOF' > docker-bake.hcl
|
||||
variable "TAG" {
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
group "default" {
|
||||
targets = ["webapp"]
|
||||
}
|
||||
|
||||
target "webapp" {
|
||||
args = {
|
||||
buildno = "${add(123, 1)}"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
$ docker buildx bake --print webapp
|
||||
{
|
||||
"target": {
|
||||
"webapp": {
|
||||
"context": ".",
|
||||
"dockerfile": "Dockerfile",
|
||||
"args": {
|
||||
"buildno": "124"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example of defining an `increment` function:
|
||||
|
||||
```console
|
||||
$ cat <<'EOF' > docker-bake.hcl
|
||||
function "increment" {
|
||||
params = [number]
|
||||
result = number + 1
|
||||
}
|
||||
|
||||
group "default" {
|
||||
targets = ["webapp"]
|
||||
}
|
||||
|
||||
target "webapp" {
|
||||
args = {
|
||||
buildno = "${increment(123)}"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
$ docker buildx bake --print webapp
|
||||
{
|
||||
"target": {
|
||||
"webapp": {
|
||||
"context": ".",
|
||||
"dockerfile": "Dockerfile",
|
||||
"args": {
|
||||
"buildno": "124"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example of only adding tags if a variable is not empty using an `notequal`
|
||||
function:
|
||||
|
||||
```console
|
||||
$ cat <<'EOF' > docker-bake.hcl
|
||||
variable "TAG" {default="" }
|
||||
|
||||
group "default" {
|
||||
targets = [
|
||||
"webapp",
|
||||
]
|
||||
}
|
||||
|
||||
target "webapp" {
|
||||
context="."
|
||||
dockerfile="Dockerfile"
|
||||
tags = [
|
||||
"my-image:latest",
|
||||
notequal("",TAG) ? "my-image:${TAG}": "",
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
$ docker buildx bake --print webapp
|
||||
{
|
||||
"target": {
|
||||
"webapp": {
|
||||
"context": ".",
|
||||
"dockerfile": "Dockerfile",
|
||||
"tags": [
|
||||
"my-image:latest"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -9,52 +9,38 @@ Start a build
|
||||
|
||||
### Aliases
|
||||
|
||||
`docker buildx build`, `docker buildx b`
|
||||
`build`, `b`
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [`--add-host`](https://docs.docker.com/engine/reference/commandline/build/#add-entries-to-container-hosts-file---add-host) | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) |
|
||||
| [`--allow`](#allow) | `stringSlice` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`) |
|
||||
| `--attest` | `stringArray` | | Attestation parameters (format: `type=sbom,generator=image`) |
|
||||
| [`--build-arg`](#build-arg) | `stringArray` | | Set build-time variables |
|
||||
| [`--build-context`](#build-context) | `stringArray` | | Additional build contexts (e.g., name=path) |
|
||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||
| [`--cache-from`](#cache-from) | `stringArray` | | External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`) |
|
||||
| [`--cache-to`](#cache-to) | `stringArray` | | Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`) |
|
||||
| [`--cgroup-parent`](https://docs.docker.com/engine/reference/commandline/build/#use-a-custom-parent-cgroup---cgroup-parent) | `string` | | Optional parent cgroup for the container |
|
||||
| [`-f`](https://docs.docker.com/engine/reference/commandline/build/#specify-a-dockerfile--f), [`--file`](https://docs.docker.com/engine/reference/commandline/build/#specify-a-dockerfile--f) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
|
||||
| `--iidfile` | `string` | | Write the image ID to the file |
|
||||
| `--invoke` | `string` | | Invoke a command after the build [experimental] |
|
||||
| `--label` | `stringArray` | | Set metadata for an image |
|
||||
| [`--load`](#load) | | | Shorthand for `--output=type=docker` |
|
||||
| [`--metadata-file`](#metadata-file) | `string` | | Write build result metadata to the file |
|
||||
| `--network` | `string` | `default` | Set the networking mode for the `RUN` instructions during build |
|
||||
| `--no-cache` | | | Do not use cache when building the image |
|
||||
| `--no-cache-filter` | `stringArray` | | Do not cache specified stages |
|
||||
| [`-o`](#output), [`--output`](#output) | `stringArray` | | Output destination (format: `type=local,dest=path`) |
|
||||
| [`--platform`](#platform) | `stringArray` | | Set target platform for build |
|
||||
| `--print` | `string` | | Print result of information request (e.g., outline, targets) [experimental] |
|
||||
| [`--progress`](#progress) | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output |
|
||||
| `--provenance` | `string` | | Shortand for `--attest=type=provenance` |
|
||||
| `--pull` | | | Always attempt to pull all referenced images |
|
||||
| [`--push`](#push) | | | Shorthand for `--output=type=registry` |
|
||||
| `-q`, `--quiet` | | | Suppress the build output and print image ID on success |
|
||||
| `--sbom` | `string` | | Shorthand for `--attest=type=sbom` |
|
||||
| [`--secret`](#secret) | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
||||
| [`--shm-size`](#shm-size) | `bytes` | `0` | Size of `/dev/shm` |
|
||||
| [`--ssh`](#ssh) | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|<id>[=<socket>\|<key>[,<key>]]`) |
|
||||
| [`-t`](https://docs.docker.com/engine/reference/commandline/build/#tag-an-image--t), [`--tag`](https://docs.docker.com/engine/reference/commandline/build/#tag-an-image--t) | `stringArray` | | Name and optionally a tag (format: `name:tag`) |
|
||||
| [`--target`](https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target) | `string` | | Set the target build stage to build |
|
||||
| [`--ulimit`](#ulimit) | `ulimit` | | Ulimit options |
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| [`--add-host stringSlice`](https://docs.docker.com/engine/reference/commandline/build/#add-entries-to-container-hosts-file---add-host) | Add a custom host-to-IP mapping (host:ip) |
|
||||
| [`--allow stringSlice`](#allow) | Allow extra privileged entitlement, e.g. network.host, security.insecure |
|
||||
| [`--build-arg stringArray`](https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables---build-arg) | Set build-time variables |
|
||||
| `--builder string` | Override the configured builder instance |
|
||||
| [`--cache-from stringArray`](#cache-from) | External cache sources (eg. user/app:cache, type=local,src=path/to/dir) |
|
||||
| [`--cache-to stringArray`](#cache-to) | Cache export destinations (eg. user/app:cache, type=local,dest=path/to/dir) |
|
||||
| [`-f`](https://docs.docker.com/engine/reference/commandline/build/#specify-a-dockerfile--f), [`--file string`](https://docs.docker.com/engine/reference/commandline/build/#specify-a-dockerfile--f) | Name of the Dockerfile (Default is 'PATH/Dockerfile') |
|
||||
| `--iidfile string` | Write the image ID to the file |
|
||||
| `--label stringArray` | Set metadata for an image |
|
||||
| [`--load`](#load) | Shorthand for --output=type=docker |
|
||||
| `--metadata-file string` | Write build result metadata to the file |
|
||||
| `--network string` | Set the networking mode for the RUN instructions during build |
|
||||
| `--no-cache` | Do not use cache when building the image |
|
||||
| [`-o`](#output), [`--output stringArray`](#output) | Output destination (format: type=local,dest=path) |
|
||||
| [`--platform stringArray`](#platform) | Set target platform for build |
|
||||
| [`--progress string`](#progress) | Set type of progress output (auto, plain, tty). Use plain to show container output |
|
||||
| `--pull` | Always attempt to pull a newer version of the image |
|
||||
| [`--push`](#push) | Shorthand for --output=type=registry |
|
||||
| `--secret stringArray` | Secret file to expose to the build: id=mysecret,src=/local/secret |
|
||||
| `--ssh stringArray` | SSH agent socket or keys to expose to the build (format: default|<id>[=<socket>|<key>[,<key>]]) |
|
||||
| [`-t`](https://docs.docker.com/engine/reference/commandline/build/#tag-an-image--t), [`--tag stringArray`](https://docs.docker.com/engine/reference/commandline/build/#tag-an-image--t) | Name and optionally a tag in the 'name:tag' format |
|
||||
| [`--target string`](https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target) | Set the target build stage to build. |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
Flags marked with `[experimental]` need to be explicitly enabled by setting the
|
||||
`BUILDX_EXPERIMENTAL=1` environment variable.
|
||||
|
||||
## Description
|
||||
|
||||
The `buildx build` command starts a build using BuildKit. This command is similar
|
||||
@@ -62,236 +48,76 @@ to the UI of `docker build` command and takes the same flags and arguments.
|
||||
|
||||
For documentation on most of these flags, refer to the [`docker build`
|
||||
documentation](https://docs.docker.com/engine/reference/commandline/build/). In
|
||||
here we'll document a subset of the new flags.
|
||||
here we’ll document a subset of the new flags.
|
||||
|
||||
## Examples
|
||||
|
||||
### <a name="allow"></a> Allow extra privileged entitlement (--allow)
|
||||
### <a name="platform"></a> Set the target platforms for the build (--platform)
|
||||
|
||||
```
|
||||
--allow=ENTITLEMENT
|
||||
--platform=value[,value]
|
||||
```
|
||||
|
||||
Allow extra privileged entitlement. List of entitlements:
|
||||
Set the target platform for the build. All `FROM` commands inside the Dockerfile
|
||||
without their own `--platform` flag will pull base images for this platform and
|
||||
this value will also be the platform of the resulting image. The default value
|
||||
will be the current platform of the buildkit daemon.
|
||||
|
||||
- `network.host` - Allows executions with host networking.
|
||||
- `security.insecure` - Allows executions without sandbox. See
|
||||
[related Dockerfile extensions](https://docs.docker.com/engine/reference/builder/#run---securitysandbox).
|
||||
When using `docker-container` driver with `buildx`, this flag can accept multiple
|
||||
values as an input separated by a comma. With multiple values the result will be
|
||||
built for all of the specified platforms and joined together into a single manifest
|
||||
list.
|
||||
|
||||
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))
|
||||
If the `Dockerfile` needs to invoke the `RUN` command, the builder needs runtime
|
||||
support for the specified platform. In a clean setup, you can only execute `RUN`
|
||||
commands for your system architecture.
|
||||
If your kernel supports [`binfmt_misc`](https://en.wikipedia.org/wiki/Binfmt_misc)
|
||||
launchers for secondary architectures, buildx will pick them up automatically.
|
||||
Docker desktop releases come with `binfmt_misc` automatically configured for `arm64`
|
||||
and `arm` architectures. You can see what runtime platforms your current builder
|
||||
instance supports by running `docker buildx inspect --bootstrap`.
|
||||
|
||||
Inside a `Dockerfile`, you can access the current platform value through
|
||||
`TARGETPLATFORM` build argument. Please refer to the [`docker build`
|
||||
documentation](https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope)
|
||||
for the full description of automatic platform argument variants .
|
||||
|
||||
The formatting for the platform specifier is defined in the [containerd source
|
||||
code](https://github.com/containerd/containerd/blob/v1.4.3/platforms/platforms.go#L63).
|
||||
|
||||
**Examples**
|
||||
|
||||
```console
|
||||
$ docker buildx create --use --name insecure-builder --buildkitd-flags '--allow-insecure-entitlement security.insecure'
|
||||
$ docker buildx build --allow security.insecure .
|
||||
$ docker buildx build --platform=linux/arm64 .
|
||||
$ docker buildx build --platform=linux/amd64,linux/arm64,linux/arm/v7 .
|
||||
$ docker buildx build --platform=darwin .
|
||||
```
|
||||
|
||||
### <a name="build-arg"></a> Set build-time variables (--build-arg)
|
||||
### <a name="progress"></a> Set type of progress output (--progress)
|
||||
|
||||
Same as [`docker build` command](https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables---build-arg).
|
||||
```
|
||||
--progress=VALUE
|
||||
```
|
||||
|
||||
There are also useful built-in build args like:
|
||||
Set type of progress output (auto, plain, tty). Use plain to show container
|
||||
output (default "auto").
|
||||
|
||||
* `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_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
|
||||
> 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 build --build-arg BUILDKIT_MULTI_PLATFORM=1 .
|
||||
```
|
||||
$ docker buildx build --load --progress=plain .
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> More built-in build args can be found in [Dockerfile reference docs](https://docs.docker.com/engine/reference/builder/#buildkit-built-in-build-args).
|
||||
#1 [internal] load build definition from Dockerfile
|
||||
#1 transferring dockerfile: 227B 0.0s done
|
||||
#1 DONE 0.1s
|
||||
|
||||
### <a name="build-context"></a> Additional build contexts (--build-context)
|
||||
|
||||
```
|
||||
--build-context=name=VALUE
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Replace `alpine:latest` with a pinned one:
|
||||
|
||||
```console
|
||||
$ docker buildx build --build-context alpine=docker-image://alpine@sha256:0123456789 .
|
||||
```
|
||||
|
||||
Expose a secondary local source directory:
|
||||
|
||||
```console
|
||||
$ docker buildx build --build-context project=path/to/project/source .
|
||||
# docker buildx build --build-context project=https://github.com/myuser/project.git .
|
||||
```
|
||||
|
||||
```dockerfile
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM alpine
|
||||
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
|
||||
# syntax=docker/dockerfile:1
|
||||
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)
|
||||
|
||||
Same as [`buildx --builder`](buildx.md#builder).
|
||||
|
||||
### <a name="cache-from"></a> Use an external cache source for a build (--cache-from)
|
||||
|
||||
```
|
||||
--cache-from=[NAME|type=TYPE[,KEY=VALUE]]
|
||||
```
|
||||
|
||||
Use an external cache source for a build. Supported types are `registry`,
|
||||
`local`, `gha` and `s3`.
|
||||
|
||||
- [`registry` source](https://github.com/moby/buildkit#registry-push-image-and-cache-separately)
|
||||
can import cache from a cache manifest or (special) image configuration on the
|
||||
registry.
|
||||
- [`local` source](https://github.com/moby/buildkit#local-directory-1) can
|
||||
import cache from local files previously exported with `--cache-to`.
|
||||
- [`gha` source](https://github.com/moby/buildkit#github-actions-cache-experimental)
|
||||
can import cache from a previously exported cache with `--cache-to` in your
|
||||
GitHub repository
|
||||
- [`s3` source](https://github.com/moby/buildkit#s3-cache-experimental)
|
||||
can import cache from a previously exported cache with `--cache-to` in your
|
||||
S3 bucket
|
||||
|
||||
If no type is specified, `registry` exporter is used with a specified reference.
|
||||
|
||||
`docker` driver currently only supports importing build cache from the registry.
|
||||
|
||||
```console
|
||||
$ docker buildx build --cache-from=user/app:cache .
|
||||
$ docker buildx build --cache-from=user/app .
|
||||
$ docker buildx build --cache-from=type=registry,ref=user/app .
|
||||
$ docker buildx build --cache-from=type=local,src=path/to/cache .
|
||||
$ docker buildx build --cache-from=type=gha .
|
||||
$ docker buildx build --cache-from=type=s3,region=eu-west-1,bucket=mybucket .
|
||||
```
|
||||
|
||||
More info about cache exporters and available attributes: https://github.com/moby/buildkit#export-cache
|
||||
|
||||
### <a name="cache-to"></a> Export build cache to an external cache destination (--cache-to)
|
||||
|
||||
```
|
||||
--cache-to=[NAME|type=TYPE[,KEY=VALUE]]
|
||||
```
|
||||
|
||||
Export build cache to an external cache destination. Supported types are
|
||||
`registry`, `local`, `inline`, `gha` and `s3`.
|
||||
|
||||
- [`registry` type](https://github.com/moby/buildkit#registry-push-image-and-cache-separately) exports build cache to a cache manifest in the registry.
|
||||
- [`local` type](https://github.com/moby/buildkit#local-directory-1) exports
|
||||
cache to a local directory on the client.
|
||||
- [`inline` type](https://github.com/moby/buildkit#inline-push-image-and-cache-together)
|
||||
writes the cache metadata into the image configuration.
|
||||
- [`gha` type](https://github.com/moby/buildkit#github-actions-cache-experimental)
|
||||
exports cache through the [GitHub Actions Cache service API](https://github.com/tonistiigi/go-actions-cache/blob/master/api.md#authentication).
|
||||
- [`s3` type](https://github.com/moby/buildkit#s3-cache-experimental) exports
|
||||
cache to a S3 bucket.
|
||||
|
||||
`docker` driver currently only supports exporting inline cache metadata to image
|
||||
configuration. Alternatively, `--build-arg BUILDKIT_INLINE_CACHE=1` can be used
|
||||
to trigger inline cache exporter.
|
||||
|
||||
Attribute key:
|
||||
|
||||
- `mode` - Specifies how many layers are exported with the cache. `min` on only
|
||||
exports layers already in the final build stage, `max` exports layers for
|
||||
all stages. Metadata is always exported for the whole build.
|
||||
|
||||
```console
|
||||
$ docker buildx build --cache-to=user/app:cache .
|
||||
$ docker buildx build --cache-to=type=inline .
|
||||
$ docker buildx build --cache-to=type=registry,ref=user/app .
|
||||
$ docker buildx build --cache-to=type=local,dest=path/to/cache .
|
||||
$ docker buildx build --cache-to=type=gha .
|
||||
$ docker buildx build --cache-to=type=s3,region=eu-west-1,bucket=mybucket .
|
||||
```
|
||||
|
||||
More info about cache exporters and available attributes: https://github.com/moby/buildkit#export-cache
|
||||
|
||||
### <a name="load"></a> Load the single-platform build result to `docker images` (--load)
|
||||
|
||||
Shorthand for [`--output=type=docker`](#docker). Will automatically load the
|
||||
single-platform build result to `docker images`.
|
||||
|
||||
### <a name="metadata-file"></a> Write build result metadata to the file (--metadata-file)
|
||||
|
||||
To output build metadata such as the image digest, pass the `--metadata-file` flag.
|
||||
The metadata will be written as a JSON object to the specified file. The
|
||||
directory of the specified file must already exist and be writable.
|
||||
|
||||
```console
|
||||
$ docker buildx build --load --metadata-file metadata.json .
|
||||
$ cat metadata.json
|
||||
```
|
||||
```json
|
||||
{
|
||||
"containerimage.buildinfo": {
|
||||
"frontend": "dockerfile.v0",
|
||||
"attrs": {
|
||||
"context": "https://github.com/crazy-max/buildkit-buildsources-test.git#master",
|
||||
"filename": "Dockerfile",
|
||||
"source": "docker/dockerfile:master"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"type": "docker-image",
|
||||
"ref": "docker.io/docker/buildx-bin:0.6.1@sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0",
|
||||
"pin": "sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0"
|
||||
},
|
||||
{
|
||||
"type": "docker-image",
|
||||
"ref": "docker.io/library/alpine:3.13",
|
||||
"pin": "sha256:026f721af4cf2843e07bba648e158fb35ecc876d822130633cc49f707f0fc88c"
|
||||
}
|
||||
]
|
||||
},
|
||||
"containerimage.config.digest": "sha256:2937f66a9722f7f4a2df583de2f8cb97fc9196059a410e7f00072fc918930e66",
|
||||
"containerimage.descriptor": {
|
||||
"annotations": {
|
||||
"config.digest": "sha256:2937f66a9722f7f4a2df583de2f8cb97fc9196059a410e7f00072fc918930e66",
|
||||
"org.opencontainers.image.created": "2022-02-08T21:28:03Z"
|
||||
},
|
||||
"digest": "sha256:19ffeab6f8bc9293ac2c3fdf94ebe28396254c993aea0b5a542cfb02e0883fa3",
|
||||
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||
"size": 506
|
||||
},
|
||||
"containerimage.digest": "sha256:19ffeab6f8bc9293ac2c3fdf94ebe28396254c993aea0b5a542cfb02e0883fa3"
|
||||
}
|
||||
#2 [internal] load .dockerignore
|
||||
#2 transferring context: 129B 0.0s done
|
||||
#2 DONE 0.0s
|
||||
...
|
||||
```
|
||||
|
||||
### <a name="output"></a> Set the export action for the build result (-o, --output)
|
||||
@@ -312,6 +138,8 @@ If just the path is specified as a value, `buildx` will use the local exporter
|
||||
with this path as the destination. If the value is "-", `buildx` will use `tar`
|
||||
exporter and write to `stdout`.
|
||||
|
||||
**Examples**
|
||||
|
||||
```console
|
||||
$ docker buildx build -o . .
|
||||
$ docker buildx build -o outdir .
|
||||
@@ -365,7 +193,7 @@ The most common usecase for multi-platform images is to directly push to a regis
|
||||
Attribute keys:
|
||||
|
||||
- `dest` - destination path where tarball will be written. If not specified the
|
||||
tar will be loaded automatically to the current docker instance.
|
||||
tar will be loaded automatically to the current docker instance.
|
||||
- `context` - name for the docker context where to import the result
|
||||
|
||||
#### `image`
|
||||
@@ -383,180 +211,89 @@ Attribute keys:
|
||||
|
||||
The `registry` exporter is a shortcut for `type=image,push=true`.
|
||||
|
||||
### <a name="platform"></a> Set the target platforms for the build (--platform)
|
||||
|
||||
```
|
||||
--platform=value[,value]
|
||||
```
|
||||
|
||||
Set the target platform for the build. All `FROM` commands inside the Dockerfile
|
||||
without their own `--platform` flag will pull base images for this platform and
|
||||
this value will also be the platform of the resulting image. The default value
|
||||
will be the current platform of the buildkit daemon.
|
||||
|
||||
When using `docker-container` driver with `buildx`, this flag can accept multiple
|
||||
values as an input separated by a comma. With multiple values the result will be
|
||||
built for all of the specified platforms and joined together into a single manifest
|
||||
list.
|
||||
|
||||
If the `Dockerfile` needs to invoke the `RUN` command, the builder needs runtime
|
||||
support for the specified platform. In a clean setup, you can only execute `RUN`
|
||||
commands for your system architecture.
|
||||
If your kernel supports [`binfmt_misc`](https://en.wikipedia.org/wiki/Binfmt_misc)
|
||||
launchers for secondary architectures, buildx will pick them up automatically.
|
||||
Docker desktop releases come with `binfmt_misc` automatically configured for `arm64`
|
||||
and `arm` architectures. You can see what runtime platforms your current builder
|
||||
instance supports by running `docker buildx inspect --bootstrap`.
|
||||
|
||||
Inside a `Dockerfile`, you can access the current platform value through
|
||||
`TARGETPLATFORM` build argument. Please refer to the [`docker build`
|
||||
documentation](https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope)
|
||||
for the full description of automatic platform argument variants .
|
||||
|
||||
The formatting for the platform specifier is defined in the [containerd source
|
||||
code](https://github.com/containerd/containerd/blob/v1.4.3/platforms/platforms.go#L63).
|
||||
|
||||
```console
|
||||
$ docker buildx build --platform=linux/arm64 .
|
||||
$ docker buildx build --platform=linux/amd64,linux/arm64,linux/arm/v7 .
|
||||
$ docker buildx build --platform=darwin .
|
||||
```
|
||||
|
||||
### <a name="progress"></a> Set type of progress output (--progress)
|
||||
|
||||
```
|
||||
--progress=VALUE
|
||||
```
|
||||
|
||||
Set type of progress output (auto, plain, tty). Use plain to show container
|
||||
output (default "auto").
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> 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 build --load --progress=plain .
|
||||
|
||||
#1 [internal] load build definition from Dockerfile
|
||||
#1 transferring dockerfile: 227B 0.0s done
|
||||
#1 DONE 0.1s
|
||||
|
||||
#2 [internal] load .dockerignore
|
||||
#2 transferring context: 129B 0.0s done
|
||||
#2 DONE 0.0s
|
||||
...
|
||||
```
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> Check also our [Color output controls guide](https://github.com/docker/buildx/blob/master/docs/guides/color-output.md)
|
||||
> 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)
|
||||
|
||||
Shorthand for [`--output=type=registry`](#registry). Will automatically push the
|
||||
build result to registry.
|
||||
|
||||
### <a name="secret"></a> Secret to expose to the build (--secret)
|
||||
### <a name="load"></a> Load the single-platform build result to `docker images` (--load)
|
||||
|
||||
Shorthand for [`--output=type=docker`](#docker). Will automatically load the
|
||||
single-platform build result to `docker images`.
|
||||
|
||||
### <a name="cache-from"></a> Use an external cache source for a build (--cache-from)
|
||||
|
||||
```
|
||||
--secret=[type=TYPE[,KEY=VALUE]
|
||||
--cache-from=[NAME|type=TYPE[,KEY=VALUE]]
|
||||
```
|
||||
|
||||
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).
|
||||
Use an external cache source for a build. Supported types are `registry` and `local`.
|
||||
The `registry` source can import cache from a cache manifest or (special) image
|
||||
configuration on the registry. The `local` source can import cache from local
|
||||
files previously exported with `--cache-to`.
|
||||
|
||||
If `type` is unset it will be detected. Supported types are:
|
||||
If no type is specified, `registry` exporter is used with a specified reference.
|
||||
|
||||
#### `file`
|
||||
`docker` driver currently only supports importing build cache from the registry.
|
||||
|
||||
Attribute keys:
|
||||
|
||||
- `id` - ID of the secret. Defaults to basename of the `src` path.
|
||||
- `src`, `source` - Secret filename. `id` used if unset.
|
||||
|
||||
```dockerfile
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM python:3
|
||||
RUN pip install awscli
|
||||
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \
|
||||
aws s3 cp s3://... ...
|
||||
```
|
||||
**Examples**
|
||||
|
||||
```console
|
||||
$ docker buildx build --secret id=aws,src=$HOME/.aws/credentials .
|
||||
$ docker buildx build --cache-from=user/app:cache .
|
||||
$ docker buildx build --cache-from=user/app .
|
||||
$ docker buildx build --cache-from=type=registry,ref=user/app .
|
||||
$ docker buildx build --cache-from=type=local,src=path/to/cache .
|
||||
```
|
||||
|
||||
#### `env`
|
||||
### <a name="cache-to"></a> Export build cache to an external cache destination (--cache-to)
|
||||
|
||||
Attribute keys:
|
||||
|
||||
- `id` - ID of the secret. Defaults to `env` name.
|
||||
- `env` - Secret environment variable. `id` used if unset, otherwise will look for `src`, `source` if `id` unset.
|
||||
|
||||
```dockerfile
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM node:alpine
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=secret,id=SECRET_TOKEN \
|
||||
SECRET_TOKEN=$(cat /run/secrets/SECRET_TOKEN) yarn run test
|
||||
```
|
||||
--cache-to=[NAME|type=TYPE[,KEY=VALUE]]
|
||||
```
|
||||
|
||||
Export build cache to an external cache destination. Supported types are `registry`,
|
||||
`local` and `inline`. Registry exports build cache to a cache manifest in the
|
||||
registry, local exports cache to a local directory on the client and inline writes
|
||||
the cache metadata into the image configuration.
|
||||
|
||||
`docker` driver currently only supports exporting inline cache metadata to image
|
||||
configuration. Alternatively, `--build-arg BUILDKIT_INLINE_CACHE=1` can be used
|
||||
to trigger inline cache exporter.
|
||||
|
||||
Attribute key:
|
||||
|
||||
- `mode` - Specifies how many layers are exported with the cache. “min” on only
|
||||
exports layers already in the final build stage, “max” exports layers for
|
||||
all stages. Metadata is always exported for the whole build.
|
||||
|
||||
**Examples**
|
||||
|
||||
```console
|
||||
$ SECRET_TOKEN=token docker buildx build --secret id=SECRET_TOKEN .
|
||||
$ docker buildx build --cache-to=user/app:cache .
|
||||
$ docker buildx build --cache-to=type=inline .
|
||||
$ docker buildx build --cache-to=type=registry,ref=user/app .
|
||||
$ docker buildx build --cache-to=type=local,dest=path/to/cache .
|
||||
```
|
||||
|
||||
### <a name="shm-size"></a> Size of /dev/shm (--shm-size)
|
||||
|
||||
The format is `<number><unit>`. `number` must be greater than `0`. Unit is
|
||||
optional and can be `b` (bytes), `k` (kilobytes), `m` (megabytes), or `g`
|
||||
(gigabytes). If you omit the unit, the system uses bytes.
|
||||
|
||||
### <a name="ssh"></a> SSH agent socket or keys to expose to the build (--ssh)
|
||||
### <a name="allow"></a> Allow extra privileged entitlement (--allow)
|
||||
|
||||
```
|
||||
--ssh=default|<id>[=<socket>|<key>[,<key>]]
|
||||
--allow=ENTITLEMENT
|
||||
```
|
||||
|
||||
This can be useful when some commands in your Dockerfile need specific SSH
|
||||
authentication (e.g., cloning a private repository).
|
||||
Allow extra privileged entitlement. List of entitlements:
|
||||
|
||||
`--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).
|
||||
- `network.host` - Allows executions with host networking.
|
||||
- `security.insecure` - Allows executions without sandbox. See
|
||||
[related Dockerfile extensions](https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md#run---securityinsecuresandbox).
|
||||
|
||||
Example to access Gitlab using an SSH agent socket:
|
||||
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-flags))
|
||||
|
||||
```dockerfile
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM alpine
|
||||
RUN apk add --no-cache openssh-client
|
||||
RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan gitlab.com >> ~/.ssh/known_hosts
|
||||
RUN --mount=type=ssh ssh -q -T git@gitlab.com 2>&1 | tee /hello
|
||||
# "Welcome to GitLab, @GITLAB_USERNAME_ASSOCIATED_WITH_SSHKEY" should be printed here
|
||||
# with the type of build progress is defined as `plain`.
|
||||
```
|
||||
**Examples**
|
||||
|
||||
```console
|
||||
$ eval $(ssh-agent)
|
||||
$ ssh-add ~/.ssh/id_rsa
|
||||
(Input your passphrase here)
|
||||
$ docker buildx build --ssh default=$SSH_AUTH_SOCK .
|
||||
$ docker buildx create --use --name insecure-builder --buildkitd-flags '--allow-insecure-entitlement security.insecure'
|
||||
$ docker buildx build --allow security.insecure .
|
||||
```
|
||||
|
||||
### <a name="ulimit"></a> Set ulimits (--ulimit)
|
||||
|
||||
`--ulimit` is specified with a soft and hard limit as such:
|
||||
`<type>=<soft limit>[:<hard limit>]`, for example:
|
||||
|
||||
```console
|
||||
$ docker buildx build --ulimit nofile=1024:1024 .
|
||||
```
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> If you do not provide a `hard limit`, the `soft limit` is used
|
||||
> for both values. If no `ulimits` are set, they are inherited from
|
||||
> the default `ulimits` set on the daemon.
|
||||
|
||||
@@ -9,19 +9,19 @@ Create a new builder instance
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [`--append`](#append) | | | Append a node to builder instead of changing it |
|
||||
| `--bootstrap` | | | Boot builder after creation |
|
||||
| [`--buildkitd-flags`](#buildkitd-flags) | `string` | | Flags for buildkitd daemon |
|
||||
| [`--config`](#config) | `string` | | BuildKit config file |
|
||||
| [`--driver`](#driver) | `string` | | Driver to use (available: `docker-container`, `kubernetes`, `remote`) |
|
||||
| [`--driver-opt`](#driver-opt) | `stringArray` | | Options for the driver |
|
||||
| [`--leave`](#leave) | | | Remove a node from builder instead of changing it |
|
||||
| [`--name`](#name) | `string` | | Builder instance name |
|
||||
| [`--node`](#node) | `string` | | Create/modify node with given name |
|
||||
| [`--platform`](#platform) | `stringArray` | | Fixed platforms for current node |
|
||||
| [`--use`](#use) | | | Set the current builder instance |
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| [`--append`](#append) | Append a node to builder instead of changing it |
|
||||
| `--builder string` | Override the configured builder instance |
|
||||
| [`--buildkitd-flags string`](#buildkitd-flags) | Flags for buildkitd daemon |
|
||||
| [`--config string`](#config) | BuildKit config file |
|
||||
| [`--driver string`](#driver) | Driver to use (available: []) |
|
||||
| [`--driver-opt stringArray`](#driver-opt) | Options for the driver |
|
||||
| [`--leave`](#leave) | Remove a node from builder instead of changing it |
|
||||
| [`--name string`](#name) | Builder instance name |
|
||||
| [`--node string`](#node) | Create/modify node with given name |
|
||||
| [`--platform stringArray`](#platform) | Fixed platforms for current node |
|
||||
| [`--use`](#use) | Set the current builder instance |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
@@ -47,6 +47,8 @@ The `--append` flag changes the action of the command to append a new node to an
|
||||
existing builder specified by `--name`. Buildx will choose an appropriate node
|
||||
for a build based on the platforms it supports.
|
||||
|
||||
**Examples**
|
||||
|
||||
```console
|
||||
$ docker buildx create mycontext1
|
||||
eager_beaver
|
||||
@@ -62,9 +64,11 @@ eager_beaver
|
||||
```
|
||||
|
||||
Adds flags when starting the buildkitd daemon. They take precedence over the
|
||||
configuration file specified by [`--config`](#config). See `buildkitd --help`
|
||||
configuration file specified by [`--config`](#--config-file). See `buildkitd --help`
|
||||
for the available flags.
|
||||
|
||||
**Example**
|
||||
|
||||
```
|
||||
--buildkitd-flags '--debug --debugaddr 0.0.0.0:6666'
|
||||
```
|
||||
@@ -76,19 +80,9 @@ for the available flags.
|
||||
```
|
||||
|
||||
Specifies the configuration file for the buildkitd daemon to use. The configuration
|
||||
can be overridden by [`--buildkitd-flags`](#buildkitd-flags).
|
||||
can be overridden by [`--buildkitd-flags`](#--buildkitd-flags-flags).
|
||||
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
|
||||
certificates for registries in the `buildkitd.toml` configuration, the files
|
||||
will be copied into the container under `/etc/buildkit/certs` and configuration
|
||||
will be updated to reflect that.
|
||||
|
||||
### <a name="driver"></a> Set the builder driver to use (--driver)
|
||||
|
||||
```
|
||||
@@ -98,40 +92,17 @@ will be updated to reflect that.
|
||||
Sets the builder driver to be used. There are two available drivers, each have
|
||||
their own specificities.
|
||||
|
||||
#### `docker` driver
|
||||
- `docker` - Uses the builder that is built into the docker daemon. With this
|
||||
driver, the [`--load`](buildx_build.md#--load) flag is implied by default on
|
||||
`buildx build`. However, building multi-platform images or exporting cache is
|
||||
not currently supported.
|
||||
- `docker-container` - Uses a buildkit container that will be spawned via docker.
|
||||
With this driver, both building multi-platform images and exporting cache are
|
||||
supported. However, images built will not automatically appear in `docker images`
|
||||
(see [`build --load`](buildx_build.md#--load)).
|
||||
- `kubernetes` - Uses a kubernetes pods. With this driver, you can spin up pods
|
||||
with defined buildkit container image to build your images.
|
||||
|
||||
Uses the builder that is built into the docker daemon. With this driver,
|
||||
the [`--load`](buildx_build.md#load) flag is implied by default on
|
||||
`buildx build`. However, building multi-platform images or exporting cache is
|
||||
not currently supported.
|
||||
|
||||
#### `docker-container` driver
|
||||
|
||||
Uses a BuildKit container that will be spawned via docker. With this driver,
|
||||
both building multi-platform images and exporting cache are supported.
|
||||
|
||||
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.
|
||||
|
||||
#### `kubernetes` driver
|
||||
|
||||
Uses a kubernetes pods. With this driver, you can spin up pods with defined
|
||||
BuildKit container image to build your images.
|
||||
|
||||
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.
|
||||
|
||||
#### `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)
|
||||
|
||||
@@ -139,43 +110,28 @@ to achieve that.
|
||||
--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
|
||||
`tolerations` options, ensure that quotes are escaped correctly for your shell.
|
||||
- `docker` - No driver options
|
||||
- `docker-container`
|
||||
- `image=IMAGE` - Sets the container image to be used for running buildkit.
|
||||
- `network=NETMODE` - Sets the network mode for running the buildkit container.
|
||||
- Example:
|
||||
|
||||
#### `docker` driver
|
||||
|
||||
No driver options.
|
||||
|
||||
#### `docker-container` driver
|
||||
|
||||
- `image=IMAGE` - Sets the container image to be used for running buildkit.
|
||||
- `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`.
|
||||
|
||||
#### `kubernetes` driver
|
||||
|
||||
- `image=IMAGE` - Sets the container image to be used for running buildkit.
|
||||
- `namespace=NS` - Sets the Kubernetes namespace. Defaults to the current namespace.
|
||||
- `replicas=N` - Sets the number of `Pod` replicas. Defaults to 1.
|
||||
- `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).
|
||||
```console
|
||||
--driver docker-container --driver-opt image=moby/buildkit:master,network=host
|
||||
```
|
||||
- `kubernetes`
|
||||
- `image=IMAGE` - Sets the container image to be used for running buildkit.
|
||||
- `namespace=NS` - Sets the Kubernetes namespace. Defaults to the current namespace.
|
||||
- `replicas=N` - Sets the number of `Pod` replicas. Defaults to 1.
|
||||
- `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`
|
||||
- `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.
|
||||
- `loadbalance=(sticky|random)` - Load-balancing strategy. If set to "sticky", the pod is chosen using the hash of the context path. Defaults to "sticky"
|
||||
|
||||
### <a name="leave"></a> Remove a node from a builder (--leave)
|
||||
|
||||
@@ -183,6 +139,8 @@ The `--leave` flag changes the action of the command to remove a node from a
|
||||
builder. The builder needs to be specified with `--name` and node that is removed
|
||||
is set with `--node`.
|
||||
|
||||
**Examples**
|
||||
|
||||
```console
|
||||
$ docker buildx create --name mybuilder --node mybuilder0 --leave
|
||||
```
|
||||
@@ -206,7 +164,7 @@ The `--node` flag specifies the name of the node to be created or modified. If
|
||||
none is specified, it is the name of the builder it belongs to, with an index
|
||||
number suffix.
|
||||
|
||||
### <a name="platform"></a> Set the platforms supported by the node (--platform)
|
||||
### <a name="platform"></a> Set the platforms supported by the node
|
||||
|
||||
```
|
||||
--platform PLATFORMS
|
||||
@@ -218,12 +176,14 @@ will also automatically detect the platforms it supports, but manual values take
|
||||
priority over the detected ones and can be used when multiple nodes support
|
||||
building for the same platform.
|
||||
|
||||
**Examples**
|
||||
|
||||
```console
|
||||
$ docker buildx create --platform linux/amd64
|
||||
$ docker buildx create --platform linux/arm64,linux/arm/v8
|
||||
```
|
||||
|
||||
### <a name="use"></a> Automatically switch to the newly created builder (--use)
|
||||
### <a name="use"></a> Automatically switch to the newly created builder
|
||||
|
||||
The `--use` flag automatically switches the current builder to the newly created
|
||||
one. Equivalent to running `docker buildx use $(docker buildx create ...)`.
|
||||
|
||||
@@ -9,17 +9,11 @@ Disk usage
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||
| `--filter` | `filter` | | Provide filter values |
|
||||
| `--verbose` | | | Provide a more verbose output |
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| `--builder string` | Override the configured builder instance |
|
||||
| `--filter filter` | Provide filter values |
|
||||
| `--verbose` | Provide a more verbose output |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Examples
|
||||
|
||||
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||
|
||||
Same as [`buildx --builder`](buildx.md#builder).
|
||||
<!---MARKER_GEN_END-->
|
||||
@@ -12,15 +12,9 @@ Commands to work on images in registry
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| [`create`](buildx_imagetools_create.md) | Create a new image based on source images |
|
||||
| [`inspect`](buildx_imagetools_inspect.md) | Show details of an image in the registry |
|
||||
| [`inspect`](buildx_imagetools_inspect.md) | Show details of image in the registry |
|
||||
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -28,9 +22,3 @@ Commands to work on images in registry
|
||||
|
||||
Imagetools contains commands for working with manifest lists in the registry.
|
||||
These commands are useful for inspecting multi-platform build results.
|
||||
|
||||
## Examples
|
||||
|
||||
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||
|
||||
Same as [`buildx --builder`](buildx.md#builder).
|
||||
|
||||
@@ -9,20 +9,22 @@ Create a new image based on source images
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [`--append`](#append) | | | Append to existing manifest |
|
||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||
| [`--dry-run`](#dry-run) | | | Show final image instead of pushing |
|
||||
| [`-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 |
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| [`--append`](#append) | Append to existing manifest |
|
||||
| `--builder string` | Override the configured builder instance |
|
||||
| [`--dry-run`](#dry-run) | Show final image instead of pushing |
|
||||
| [`-f`](#file), [`--file stringArray`](#file) | Read source descriptor from file |
|
||||
| [`-t`](#tag), [`--tag stringArray`](#tag) | Set reference for new image |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Description
|
||||
|
||||
Imagetools contains commands for working with manifest lists in the registry.
|
||||
These commands are useful for inspecting multi-platform build results.
|
||||
|
||||
Create a new manifest list based on source manifests. The source manifests can
|
||||
be manifest lists or single platform distribution manifests and must already
|
||||
exist in the registry where the new manifest is created. If only one source is
|
||||
@@ -35,10 +37,6 @@ specified, create performs a carbon copy.
|
||||
Use the `--append` flag to append the new sources to an existing manifest list
|
||||
in the destination.
|
||||
|
||||
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||
|
||||
Same as [`buildx --builder`](buildx.md#builder).
|
||||
|
||||
### <a name="dry-run"></a> Show final image instead of pushing (--dry-run)
|
||||
|
||||
Use the `--dry-run` flag to not push the image, just show it.
|
||||
@@ -55,15 +53,16 @@ or a JSON of OCI descriptor object.
|
||||
In order to define annotations or additional platform properties like `os.version` and
|
||||
`os.features` you need to add them in the OCI descriptor object encoded in JSON.
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools inspect --raw alpine | jq '.manifests[0] | .platform."os.version"="10.1"' > descr.json
|
||||
$ docker buildx imagetools create -f descr.json myuser/image
|
||||
```
|
||||
docker buildx imagetools inspect --raw alpine | jq '.manifests[0] | .platform."os.version"="10.1"' > descr.json
|
||||
docker buildx imagetools create -f descr.json myuser/image
|
||||
```
|
||||
|
||||
The descriptor in the file is merged with existing descriptor in the registry if it exists.
|
||||
|
||||
The supported fields for the descriptor are defined in [OCI spec](https://github.com/opencontainers/image-spec/blob/master/descriptor.md#properties) .
|
||||
|
||||
|
||||
### <a name="tag"></a> Set reference for new image (-t, --tag)
|
||||
|
||||
```
|
||||
@@ -72,7 +71,10 @@ The supported fields for the descriptor are defined in [OCI spec](https://github
|
||||
|
||||
Use the `-t` or `--tag` flag to set the name of the image to be created.
|
||||
|
||||
**Examples**
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools create --dry-run alpine@sha256:5c40b3c27b9f13c873fefb2139765c56ce97fd50230f1f2d5c91e55dec171907 sha256:c4ba6347b0e4258ce6a6de2401619316f982b7bcc529f73d2a410d0097730204
|
||||
|
||||
$ docker buildx imagetools create -t tonistiigi/myapp -f image1 -f image2
|
||||
```
|
||||
|
||||
@@ -5,628 +5,43 @@ docker buildx imagetools inspect [OPTIONS] NAME
|
||||
```
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Show details of an image in the registry
|
||||
Show details of image in the registry
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||
| [`--format`](#format) | `string` | `{{.Manifest}}` | Format the output using the given Go template |
|
||||
| [`--raw`](#raw) | | | Show original, unformatted JSON manifest |
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| `--builder string` | Override the configured builder instance |
|
||||
| [`--raw`](#raw) | Show original JSON manifest |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Description
|
||||
|
||||
Show details of an image in the registry.
|
||||
Show details of image in the registry.
|
||||
|
||||
Example:
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools inspect alpine
|
||||
|
||||
Name: docker.io/library/alpine:latest
|
||||
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
|
||||
Digest: sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300
|
||||
Digest: sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913
|
||||
|
||||
Manifests:
|
||||
Name: docker.io/library/alpine:latest@sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3
|
||||
Name: docker.io/library/alpine:latest@sha256:5c40b3c27b9f13c873fefb2139765c56ce97fd50230f1f2d5c91e55dec171907
|
||||
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||
Platform: linux/amd64
|
||||
|
||||
Name: docker.io/library/alpine:latest@sha256:e047bc2af17934d38c5a7fa9f46d443f1de3a7675546402592ef805cfa929f9d
|
||||
Name: docker.io/library/alpine:latest@sha256:c4ba6347b0e4258ce6a6de2401619316f982b7bcc529f73d2a410d0097730204
|
||||
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||
Platform: linux/arm/v6
|
||||
|
||||
Name: docker.io/library/alpine:latest@sha256:8483ecd016885d8dba70426fda133c30466f661bb041490d525658f1aac73822
|
||||
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||
Platform: linux/arm/v7
|
||||
|
||||
Name: docker.io/library/alpine:latest@sha256:c74f1b1166784193ea6c8f9440263b9be6cae07dfe35e32a5df7a31358ac2060
|
||||
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||
Platform: linux/arm64/v8
|
||||
|
||||
Name: docker.io/library/alpine:latest@sha256:2689e157117d2da668ad4699549e55eba1ceb79cb7862368b30919f0488213f4
|
||||
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||
Platform: linux/386
|
||||
|
||||
Name: docker.io/library/alpine:latest@sha256:2042a492bcdd847a01cd7f119cd48caa180da696ed2aedd085001a78664407d6
|
||||
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||
Platform: linux/ppc64le
|
||||
|
||||
Name: docker.io/library/alpine:latest@sha256:49e322ab6690e73a4909f787bcbdb873631264ff4a108cddfd9f9c249ba1d58e
|
||||
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||
Platform: linux/s390x
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||
|
||||
Same as [`buildx --builder`](buildx.md#builder).
|
||||
|
||||
### <a name="format"></a> Format the output (--format)
|
||||
|
||||
Format the output using the given Go template. Defaults to `{{.Manifest}}` if
|
||||
unset. Following fields are available:
|
||||
|
||||
* `.Name`: provides the reference of the image
|
||||
* `.Manifest`: provides the manifest or manifest list
|
||||
* `.Image`: provides the image config
|
||||
* `.BuildInfo`: provides [build info from image config](https://github.com/moby/buildkit/blob/master/docs/build-repro.md#image-config)
|
||||
|
||||
#### `.Name`
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools inspect alpine --format "{{.Name}}"
|
||||
Name: docker.io/library/alpine:latest
|
||||
```
|
||||
|
||||
#### `.Manifest`
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools inspect crazymax/loop --format "{{.Manifest}}"
|
||||
Name: docker.io/crazymax/loop:latest
|
||||
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||
Digest: sha256:08602e7340970e92bde5e0a2e887c1fde4d9ae753d1e05efb4c8ef3b609f97f1
|
||||
```
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools inspect moby/buildkit:master --format "{{.Manifest}}"
|
||||
Name: docker.io/moby/buildkit:master
|
||||
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
|
||||
Digest: sha256:3183f7ce54d1efb44c34b84f428ae10aaf141e553c6b52a7ff44cc7083a05a66
|
||||
|
||||
Manifests:
|
||||
Name: docker.io/moby/buildkit:master@sha256:667d28c9fb33820ce686887a717a148e89fa77f9097f9352996bbcce99d352b1
|
||||
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||
Platform: linux/amd64
|
||||
|
||||
Name: docker.io/moby/buildkit:master@sha256:71789527b64ab3d7b3de01d364b449cd7f7a3da758218fbf73b9c9aae05a6775
|
||||
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||
Platform: linux/arm/v7
|
||||
|
||||
Name: docker.io/moby/buildkit:master@sha256:fb64667e1ce6ab0d05478f3a8402af07b27737598dcf9a510fb1d792b13a66be
|
||||
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||
Platform: linux/arm64
|
||||
|
||||
Name: docker.io/moby/buildkit:master@sha256:1c3ddf95a0788e23f72f25800c05abc4458946685e2b66788c3d978cde6da92b
|
||||
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||
Platform: linux/s390x
|
||||
|
||||
Name: docker.io/moby/buildkit:master@sha256:05bcde6d460a284e5bc88026cd070277e8380355de3126cbc8fe8a452708c6b1
|
||||
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||
Platform: linux/ppc64le
|
||||
|
||||
Name: docker.io/moby/buildkit:master@sha256:c04c57765304ab84f4f9807fff3e11605c3a60e16435c734b02c723680f6bd6e
|
||||
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||
Platform: linux/riscv64
|
||||
```
|
||||
|
||||
#### `.BuildInfo`
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools inspect crazymax/buildx:buildinfo --format "{{.BuildInfo}}"
|
||||
Name: docker.io/crazymax/buildx:buildinfo
|
||||
Frontend: dockerfile.v0
|
||||
Attrs:
|
||||
filename: Dockerfile
|
||||
source: docker/dockerfile-upstream:master-labs
|
||||
build-arg:bar: foo
|
||||
build-arg:foo: bar
|
||||
Sources:
|
||||
Type: docker-image
|
||||
Ref: docker.io/docker/buildx-bin:0.6.1@sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0
|
||||
Pin: sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0
|
||||
|
||||
Type: docker-image
|
||||
Ref: docker.io/library/alpine:3.13
|
||||
Pin: sha256:026f721af4cf2843e07bba648e158fb35ecc876d822130633cc49f707f0fc88c
|
||||
|
||||
Type: docker-image
|
||||
Ref: docker.io/moby/buildkit:v0.9.0
|
||||
Pin: sha256:8dc668e7f66db1c044aadbed306020743516a94848793e0f81f94a087ee78cab
|
||||
|
||||
Type: docker-image
|
||||
Ref: docker.io/tonistiigi/xx@sha256:21a61be4744f6531cb5f33b0e6f40ede41fa3a1b8c82d5946178f80cc84bfc04
|
||||
Pin: sha256:21a61be4744f6531cb5f33b0e6f40ede41fa3a1b8c82d5946178f80cc84bfc04
|
||||
|
||||
Type: http
|
||||
Ref: https://raw.githubusercontent.com/moby/moby/master/README.md
|
||||
Pin: sha256:419455202b0ef97e480d7f8199b26a721a417818bc0e2d106975f74323f25e6c
|
||||
```
|
||||
|
||||
#### JSON output
|
||||
|
||||
A `json` go template func is also available if you want to render fields as
|
||||
JSON bytes:
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools inspect crazymax/loop --format "{{json .Manifest}}"
|
||||
```
|
||||
```json
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"digest": "sha256:08602e7340970e92bde5e0a2e887c1fde4d9ae753d1e05efb4c8ef3b609f97f1",
|
||||
"size": 949
|
||||
}
|
||||
```
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools inspect moby/buildkit:master --format "{{json .Manifest}}"
|
||||
```
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
|
||||
"digest": "sha256:79d97f205e2799d99a3a8ae2a1ef17acb331e11784262c3faada847dc6972c52",
|
||||
"size": 2010,
|
||||
"manifests": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"digest": "sha256:bd1e78f06de26610fadf4eb9d04b1a45a545799d6342701726e952cc0c11c912",
|
||||
"size": 1158,
|
||||
"platform": {
|
||||
"architecture": "amd64",
|
||||
"os": "linux"
|
||||
}
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"digest": "sha256:d37dcced63ec0965824fca644f0ac9efad8569434ec15b4c83adfcb3dcfc743b",
|
||||
"size": 1158,
|
||||
"platform": {
|
||||
"architecture": "arm",
|
||||
"os": "linux",
|
||||
"variant": "v7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"digest": "sha256:ce142eb2255e6af46f2809e159fd03081697c7605a3de03b9cbe9a52ddb244bf",
|
||||
"size": 1158,
|
||||
"platform": {
|
||||
"architecture": "arm64",
|
||||
"os": "linux"
|
||||
}
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"digest": "sha256:f59bfb5062fff76ce464bfa4e25ebaaaac887d6818238e119d68613c456d360c",
|
||||
"size": 1158,
|
||||
"platform": {
|
||||
"architecture": "s390x",
|
||||
"os": "linux"
|
||||
}
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"digest": "sha256:cc96426e0c50a78105d5637d31356db5dd6ec594f21b24276e534a32da09645c",
|
||||
"size": 1159,
|
||||
"platform": {
|
||||
"architecture": "ppc64le",
|
||||
"os": "linux"
|
||||
}
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"digest": "sha256:39f9c1e2878e6c333acb23187d6b205ce82ed934c60da326cb2c698192631478",
|
||||
"size": 1158,
|
||||
"platform": {
|
||||
"architecture": "riscv64",
|
||||
"os": "linux"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools inspect crazymax/buildx:buildinfo --format "{{json .BuildInfo}}"
|
||||
```
|
||||
```json
|
||||
{
|
||||
"frontend": "dockerfile.v0",
|
||||
"attrs": {
|
||||
"build-arg:bar": "foo",
|
||||
"build-arg:foo": "bar",
|
||||
"filename": "Dockerfile",
|
||||
"source": "crazymax/dockerfile:buildattrs"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"type": "docker-image",
|
||||
"ref": "docker.io/docker/buildx-bin:0.6.1@sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0",
|
||||
"pin": "sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0"
|
||||
},
|
||||
{
|
||||
"type": "docker-image",
|
||||
"ref": "docker.io/library/alpine:3.13@sha256:026f721af4cf2843e07bba648e158fb35ecc876d822130633cc49f707f0fc88c",
|
||||
"pin": "sha256:026f721af4cf2843e07bba648e158fb35ecc876d822130633cc49f707f0fc88c"
|
||||
},
|
||||
{
|
||||
"type": "docker-image",
|
||||
"ref": "docker.io/moby/buildkit:v0.9.0@sha256:8dc668e7f66db1c044aadbed306020743516a94848793e0f81f94a087ee78cab",
|
||||
"pin": "sha256:8dc668e7f66db1c044aadbed306020743516a94848793e0f81f94a087ee78cab"
|
||||
},
|
||||
{
|
||||
"type": "docker-image",
|
||||
"ref": "docker.io/tonistiigi/xx@sha256:21a61be4744f6531cb5f33b0e6f40ede41fa3a1b8c82d5946178f80cc84bfc04",
|
||||
"pin": "sha256:21a61be4744f6531cb5f33b0e6f40ede41fa3a1b8c82d5946178f80cc84bfc04"
|
||||
},
|
||||
{
|
||||
"type": "http",
|
||||
"ref": "https://raw.githubusercontent.com/moby/moby/master/README.md",
|
||||
"pin": "sha256:419455202b0ef97e480d7f8199b26a721a417818bc0e2d106975f74323f25e6c"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools inspect crazymax/buildx:buildinfo --format "{{json .}}"
|
||||
```
|
||||
```json
|
||||
{
|
||||
"name": "crazymax/buildx:buildinfo",
|
||||
"manifest": {
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"digest": "sha256:899d2c7acbc124d406820857bb51d9089717bbe4e22b97eb4bc5789e99f09f83",
|
||||
"size": 2628
|
||||
},
|
||||
"image": {
|
||||
"created": "2022-02-24T12:27:43.627154558Z",
|
||||
"architecture": "amd64",
|
||||
"os": "linux",
|
||||
"config": {
|
||||
"Env": [
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"DOCKER_TLS_CERTDIR=/certs",
|
||||
"DOCKER_CLI_EXPERIMENTAL=enabled"
|
||||
],
|
||||
"Entrypoint": [
|
||||
"docker-entrypoint.sh"
|
||||
],
|
||||
"Cmd": [
|
||||
"sh"
|
||||
]
|
||||
},
|
||||
"rootfs": {
|
||||
"type": "layers",
|
||||
"diff_ids": [
|
||||
"sha256:7fcb75871b2101082203959c83514ac8a9f4ecfee77a0fe9aa73bbe56afdf1b4",
|
||||
"sha256:d3c0b963ff5684160641f936d6a4aa14efc8ff27b6edac255c07f2d03ff92e82",
|
||||
"sha256:3f8d78f13fa9b1f35d3bc3f1351d03a027c38018c37baca73f93eecdea17f244",
|
||||
"sha256:8e6eb1137b182ae0c3f5d40ca46341fda2eaeeeb5fa516a9a2bf96171238e2e0",
|
||||
"sha256:fde4c869a56b54dd76d7352ddaa813fd96202bda30b9dceb2c2f2ad22fa2e6ce",
|
||||
"sha256:52025823edb284321af7846419899234b3c66219bf06061692b709875ed0760f",
|
||||
"sha256:50adb5982dbf6126c7cf279ac3181d1e39fc9116b610b947a3dadae6f7e7c5bc",
|
||||
"sha256:9801c319e1c66c5d295e78b2d3e80547e73c7e3c63a4b71e97c8ca357224af24",
|
||||
"sha256:dfbfac44d5d228c49b42194c8a2f470abd6916d072f612a6fb14318e94fde8ae",
|
||||
"sha256:3dfb74e19dedf61568b917c19b0fd3ee4580870027ca0b6054baf239855d1322",
|
||||
"sha256:b182e707c23e4f19be73f9022a99d2d1ca7bf1ca8f280d40e4d1c10a6f51550e"
|
||||
]
|
||||
},
|
||||
"history": [
|
||||
{
|
||||
"created": "2021-11-12T17:19:58.698676655Z",
|
||||
"created_by": "/bin/sh -c #(nop) ADD file:5a707b9d6cb5fff532e4c2141bc35707593f21da5528c9e71ae2ddb6ba4a4eb6 in / "
|
||||
},
|
||||
{
|
||||
"created": "2021-11-12T17:19:58.948920855Z",
|
||||
"created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-02-24T12:27:38.285594601Z",
|
||||
"created_by": "RUN /bin/sh -c apk --update --no-cache add bash ca-certificates openssh-client \u0026\u0026 rm -rf /tmp/* /var/cache/apk/* # buildkit",
|
||||
"comment": "buildkit.dockerfile.v0"
|
||||
},
|
||||
{
|
||||
"created": "2022-02-24T12:27:41.061874167Z",
|
||||
"created_by": "COPY /opt/docker/ /usr/local/bin/ # buildkit",
|
||||
"comment": "buildkit.dockerfile.v0"
|
||||
},
|
||||
{
|
||||
"created": "2022-02-24T12:27:41.174098947Z",
|
||||
"created_by": "COPY /usr/bin/buildctl /usr/local/bin/buildctl # buildkit",
|
||||
"comment": "buildkit.dockerfile.v0"
|
||||
},
|
||||
{
|
||||
"created": "2022-02-24T12:27:41.320343683Z",
|
||||
"created_by": "COPY /usr/bin/buildkit* /usr/local/bin/ # buildkit",
|
||||
"comment": "buildkit.dockerfile.v0"
|
||||
},
|
||||
{
|
||||
"created": "2022-02-24T12:27:41.447149933Z",
|
||||
"created_by": "COPY /buildx /usr/libexec/docker/cli-plugins/docker-buildx # buildkit",
|
||||
"comment": "buildkit.dockerfile.v0"
|
||||
},
|
||||
{
|
||||
"created": "2022-02-24T12:27:43.057722191Z",
|
||||
"created_by": "COPY /opt/docker-compose /usr/libexec/docker/cli-plugins/docker-compose # buildkit",
|
||||
"comment": "buildkit.dockerfile.v0"
|
||||
},
|
||||
{
|
||||
"created": "2022-02-24T12:27:43.145224134Z",
|
||||
"created_by": "ADD https://raw.githubusercontent.com/moby/moby/master/README.md / # buildkit",
|
||||
"comment": "buildkit.dockerfile.v0"
|
||||
},
|
||||
{
|
||||
"created": "2022-02-24T12:27:43.422212427Z",
|
||||
"created_by": "ENV DOCKER_TLS_CERTDIR=/certs",
|
||||
"comment": "buildkit.dockerfile.v0",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-02-24T12:27:43.422212427Z",
|
||||
"created_by": "ENV DOCKER_CLI_EXPERIMENTAL=enabled",
|
||||
"comment": "buildkit.dockerfile.v0",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-02-24T12:27:43.422212427Z",
|
||||
"created_by": "RUN /bin/sh -c docker --version \u0026\u0026 buildkitd --version \u0026\u0026 buildctl --version \u0026\u0026 docker buildx version \u0026\u0026 docker compose version \u0026\u0026 mkdir /certs /certs/client \u0026\u0026 chmod 1777 /certs /certs/client # buildkit",
|
||||
"comment": "buildkit.dockerfile.v0"
|
||||
},
|
||||
{
|
||||
"created": "2022-02-24T12:27:43.514320155Z",
|
||||
"created_by": "COPY rootfs/modprobe.sh /usr/local/bin/modprobe # buildkit",
|
||||
"comment": "buildkit.dockerfile.v0"
|
||||
},
|
||||
{
|
||||
"created": "2022-02-24T12:27:43.627154558Z",
|
||||
"created_by": "COPY rootfs/docker-entrypoint.sh /usr/local/bin/ # buildkit",
|
||||
"comment": "buildkit.dockerfile.v0"
|
||||
},
|
||||
{
|
||||
"created": "2022-02-24T12:27:43.627154558Z",
|
||||
"created_by": "ENTRYPOINT [\"docker-entrypoint.sh\"]",
|
||||
"comment": "buildkit.dockerfile.v0",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-02-24T12:27:43.627154558Z",
|
||||
"created_by": "CMD [\"sh\"]",
|
||||
"comment": "buildkit.dockerfile.v0",
|
||||
"empty_layer": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"buildinfo": {
|
||||
"frontend": "dockerfile.v0",
|
||||
"attrs": {
|
||||
"build-arg:bar": "foo",
|
||||
"build-arg:foo": "bar",
|
||||
"filename": "Dockerfile",
|
||||
"source": "docker/dockerfile-upstream:master-labs"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"type": "docker-image",
|
||||
"ref": "docker.io/docker/buildx-bin:0.6.1@sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0",
|
||||
"pin": "sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0"
|
||||
},
|
||||
{
|
||||
"type": "docker-image",
|
||||
"ref": "docker.io/library/alpine:3.13",
|
||||
"pin": "sha256:026f721af4cf2843e07bba648e158fb35ecc876d822130633cc49f707f0fc88c"
|
||||
},
|
||||
{
|
||||
"type": "docker-image",
|
||||
"ref": "docker.io/moby/buildkit:v0.9.0",
|
||||
"pin": "sha256:8dc668e7f66db1c044aadbed306020743516a94848793e0f81f94a087ee78cab"
|
||||
},
|
||||
{
|
||||
"type": "docker-image",
|
||||
"ref": "docker.io/tonistiigi/xx@sha256:21a61be4744f6531cb5f33b0e6f40ede41fa3a1b8c82d5946178f80cc84bfc04",
|
||||
"pin": "sha256:21a61be4744f6531cb5f33b0e6f40ede41fa3a1b8c82d5946178f80cc84bfc04"
|
||||
},
|
||||
{
|
||||
"type": "http",
|
||||
"ref": "https://raw.githubusercontent.com/moby/moby/master/README.md",
|
||||
"pin": "sha256:419455202b0ef97e480d7f8199b26a721a417818bc0e2d106975f74323f25e6c"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Multi-platform
|
||||
|
||||
Multi-platform images are supported for `.Image` and `.BuildInfo` fields. If
|
||||
you want to pick up a specific platform, you can specify it using the `index`
|
||||
go template function:
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools inspect --format '{{json (index .Image "linux/s390x")}}' moby/buildkit:master
|
||||
```
|
||||
```json
|
||||
{
|
||||
"created": "2022-02-25T17:13:27.89891722Z",
|
||||
"architecture": "s390x",
|
||||
"os": "linux",
|
||||
"config": {
|
||||
"Env": [
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
],
|
||||
"Entrypoint": [
|
||||
"buildkitd"
|
||||
],
|
||||
"Volumes": {
|
||||
"/var/lib/buildkit": {}
|
||||
}
|
||||
},
|
||||
"rootfs": {
|
||||
"type": "layers",
|
||||
"diff_ids": [
|
||||
"sha256:41048e32d0684349141cf05f629c5fc3c5915d1f3426b66dbb8953a540e01e1e",
|
||||
"sha256:2651209b9208fff6c053bc3c17353cb07874e50f1a9bc96d6afd03aef63de76a",
|
||||
"sha256:6741ed7e73039d853fa8902246a4c7e8bf9dd09652fd1b08251bc5f9e8876a7f",
|
||||
"sha256:92ac046adeeb65c86ae3f0b458dee04ad4a462e417661c04d77642c66494f69b"
|
||||
]
|
||||
},
|
||||
"history": [
|
||||
{
|
||||
"created": "2021-11-24T20:41:23.709681315Z",
|
||||
"created_by": "/bin/sh -c #(nop) ADD file:cd24c711a2ef431b3ff94f9a02bfc42f159bc60de1d0eceecafea4e8af02441d in / "
|
||||
},
|
||||
{
|
||||
"created": "2021-11-24T20:41:23.94211262Z",
|
||||
"created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-01-26T18:15:21.449825391Z",
|
||||
"created_by": "RUN /bin/sh -c apk add --no-cache fuse3 git openssh pigz xz \u0026\u0026 ln -s fusermount3 /usr/bin/fusermount # buildkit",
|
||||
"comment": "buildkit.dockerfile.v0"
|
||||
},
|
||||
{
|
||||
"created": "2022-02-24T00:34:00.924540012Z",
|
||||
"created_by": "COPY examples/buildctl-daemonless/buildctl-daemonless.sh /usr/bin/ # buildkit",
|
||||
"comment": "buildkit.dockerfile.v0"
|
||||
},
|
||||
{
|
||||
"created": "2022-02-25T17:13:27.89891722Z",
|
||||
"created_by": "VOLUME [/var/lib/buildkit]",
|
||||
"comment": "buildkit.dockerfile.v0",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-02-25T17:13:27.89891722Z",
|
||||
"created_by": "COPY / /usr/bin/ # buildkit",
|
||||
"comment": "buildkit.dockerfile.v0"
|
||||
},
|
||||
{
|
||||
"created": "2022-02-25T17:13:27.89891722Z",
|
||||
"created_by": "ENTRYPOINT [\"buildkitd\"]",
|
||||
"comment": "buildkit.dockerfile.v0",
|
||||
"empty_layer": true
|
||||
}
|
||||
]
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
### <a name="raw"></a> Show original, unformatted JSON manifest (--raw)
|
||||
|
||||
Use the `--raw` option to print the unformatted JSON manifest bytes.
|
||||
|
||||
> `jq` is used here to get a better rendering of the output result.
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools inspect --raw crazymax/loop | jq
|
||||
```
|
||||
```json
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"schemaVersion": 2,
|
||||
"config": {
|
||||
"mediaType": "application/vnd.docker.container.image.v1+json",
|
||||
"digest": "sha256:7ace7d324e79b360b2db8b820d83081863d96d22e734cdf297a8e7fd83f6ceb3",
|
||||
"size": 2298
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:5843afab387455b37944e709ee8c78d7520df80f8d01cf7f861aae63beeddb6b",
|
||||
"size": 2811478
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:726d3732a87e1c430d67e8969de6b222a889d45e045ebae1a008a37ba38f3b1f",
|
||||
"size": 1776812
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:5d7cf9b33148a8f220c84f27dd2cfae46aca019a3ea3fbf7274f6d6dbfae8f3b",
|
||||
"size": 382855
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools inspect --raw moby/buildkit:master | jq
|
||||
```
|
||||
```json
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
|
||||
"schemaVersion": 2,
|
||||
"manifests": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"digest": "sha256:667d28c9fb33820ce686887a717a148e89fa77f9097f9352996bbcce99d352b1",
|
||||
"size": 1158,
|
||||
"platform": {
|
||||
"architecture": "amd64",
|
||||
"os": "linux"
|
||||
}
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"digest": "sha256:71789527b64ab3d7b3de01d364b449cd7f7a3da758218fbf73b9c9aae05a6775",
|
||||
"size": 1158,
|
||||
"platform": {
|
||||
"architecture": "arm",
|
||||
"os": "linux",
|
||||
"variant": "v7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"digest": "sha256:fb64667e1ce6ab0d05478f3a8402af07b27737598dcf9a510fb1d792b13a66be",
|
||||
"size": 1158,
|
||||
"platform": {
|
||||
"architecture": "arm64",
|
||||
"os": "linux"
|
||||
}
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"digest": "sha256:1c3ddf95a0788e23f72f25800c05abc4458946685e2b66788c3d978cde6da92b",
|
||||
"size": 1158,
|
||||
"platform": {
|
||||
"architecture": "s390x",
|
||||
"os": "linux"
|
||||
}
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"digest": "sha256:05bcde6d460a284e5bc88026cd070277e8380355de3126cbc8fe8a452708c6b1",
|
||||
"size": 1159,
|
||||
"platform": {
|
||||
"architecture": "ppc64le",
|
||||
"os": "linux"
|
||||
}
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"digest": "sha256:c04c57765304ab84f4f9807fff3e11605c3a60e16435c734b02c723680f6bd6e",
|
||||
"size": 1158,
|
||||
"platform": {
|
||||
"architecture": "riscv64",
|
||||
"os": "linux"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
Use the `--raw` option to print the original JSON bytes instead of the formatted
|
||||
output.
|
||||
|
||||
@@ -9,10 +9,10 @@ Inspect current builder instance
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [`--bootstrap`](#bootstrap) | | | Ensure builder has booted before inspecting |
|
||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| [`--bootstrap`](#bootstrap) | Ensure builder has booted before inspecting |
|
||||
| `--builder string` | Override the configured builder instance |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
@@ -23,19 +23,6 @@ Shows information about the current or specified builder.
|
||||
|
||||
## Examples
|
||||
|
||||
### <a name="bootstrap"></a> Ensure that the builder is running before inspecting (--bootstrap)
|
||||
|
||||
Use the `--bootstrap` option to ensure that the builder is running before
|
||||
inspecting it. If the driver is `docker-container`, then `--bootstrap` starts
|
||||
the buildkit container and waits until it is operational. Bootstrapping is
|
||||
automatically done during build, and therefore not necessary. The same BuildKit
|
||||
container is used during the lifetime of the associated builder node (as
|
||||
displayed in `buildx ls`).
|
||||
|
||||
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||
|
||||
Same as [`buildx --builder`](buildx.md#builder).
|
||||
|
||||
### Get information about a builder instance
|
||||
|
||||
By default, `inspect` shows information about the current builder. Specify the
|
||||
@@ -43,10 +30,6 @@ name of the builder to inspect to get information about that builder.
|
||||
The following example shows information about a builder instance named
|
||||
`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
|
||||
$ docker buildx inspect elated_tesla
|
||||
|
||||
@@ -57,12 +40,19 @@ Nodes:
|
||||
Name: elated_tesla0
|
||||
Endpoint: unix:///var/run/docker.sock
|
||||
Status: running
|
||||
Buildkit: v0.10.3
|
||||
Platforms: linux/amd64
|
||||
|
||||
Name: elated_tesla1
|
||||
Endpoint: ssh://ubuntu@1.2.3.4
|
||||
Status: running
|
||||
Buildkit: v0.10.3
|
||||
Platforms: linux/arm64*, linux/arm/v7, linux/arm/v6
|
||||
Platforms: linux/arm64, linux/arm/v7, linux/arm/v6
|
||||
```
|
||||
|
||||
### <a name="bootstrap"></a> Ensure that the builder is running before inspecting (--bootstrap)
|
||||
|
||||
Use the `--bootstrap` option to ensure that the builder is running before
|
||||
inspecting it. If the driver is `docker-container`, then `--bootstrap` starts
|
||||
the buildkit container and waits until it is operational. Bootstrapping is
|
||||
automatically done during build, and therefore not necessary. The same BuildKit
|
||||
container is used during the lifetime of the associated builder node (as
|
||||
displayed in `buildx ls`).
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user