mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-08-29 23:19:10 +08:00
Compare commits
339 Commits
v0.9.0-rc1
...
v0.10.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86bdced776 | ||
|
|
edb535f263 | ||
|
|
f16694cc5d | ||
|
|
e7db0ce587 | ||
|
|
c513d34049 | ||
|
|
d455c07331 | ||
|
|
5ac3b4c4b6 | ||
|
|
b1440b07f2 | ||
|
|
a3286a0ab1 | ||
|
|
b79345c63e | ||
|
|
23eb3c3ccd | ||
|
|
79e156beb1 | ||
|
|
c960d16da5 | ||
|
|
b5b9de69d9 | ||
|
|
45863c4f16 | ||
|
|
f2feea8bed | ||
|
|
a73d07ff7a | ||
|
|
0fad89c3b9 | ||
|
|
661af29d46 | ||
|
|
02cf539a08 | ||
|
|
cc87bd104e | ||
|
|
582cc04be6 | ||
|
|
ae278ce450 | ||
|
|
b66988c824 | ||
|
|
00ed17df6d | ||
|
|
cfb71fab97 | ||
|
|
f62342768b | ||
|
|
7776652a4d | ||
|
|
5a4f80f3ce | ||
|
|
b5ea79e277 | ||
|
|
481796f84f | ||
|
|
0090d49e57 | ||
|
|
389ac0c3d1 | ||
|
|
2bb8ce2f57 | ||
|
|
65cea456fd | ||
|
|
f7bd5b99da | ||
|
|
8c14407fa2 | ||
|
|
5245a2b3ff | ||
|
|
44d99d4573 | ||
|
|
14942a266e | ||
|
|
123febf107 | ||
|
|
3f5f7c5228 | ||
|
|
6d935625a6 | ||
|
|
e640dc6041 | ||
|
|
08244b12b5 | ||
|
|
78d8b926db | ||
|
|
19291d900e | ||
|
|
ed9b4a7169 | ||
|
|
033d5629c0 | ||
|
|
7cd5add568 | ||
|
|
2a000096fa | ||
|
|
b7781447d7 | ||
|
|
f6ba0a23f8 | ||
|
|
bf4b95fc3a | ||
|
|
467586dc8d | ||
|
|
8764628976 | ||
|
|
583fe71740 | ||
|
|
9fb3ff1a27 | ||
|
|
9d4f38c5fa | ||
|
|
793082f543 | ||
|
|
fe6f697205 | ||
|
|
fd3fb752d3 | ||
|
|
7fcea64eb4 | ||
|
|
05e0ce4953 | ||
|
|
f8d9d1e776 | ||
|
|
8a7a221a7f | ||
|
|
e4db8d2a21 | ||
|
|
7394853ddf | ||
|
|
a8be6b576b | ||
|
|
8b960ededd | ||
|
|
4735a71fbd | ||
|
|
37fce8cc06 | ||
|
|
82476ab039 | ||
|
|
88852e2330 | ||
|
|
6369c50614 | ||
|
|
a22d0a35a4 | ||
|
|
c93c02df85 | ||
|
|
e584c6e1a7 | ||
|
|
64e4c19971 | ||
|
|
551b8f6785 | ||
|
|
fbbe1c1b91 | ||
|
|
1a85745bf1 | ||
|
|
0d1fea8134 | ||
|
|
19417e76e7 | ||
|
|
53d88a79ef | ||
|
|
4c21b7e680 | ||
|
|
a8f689c223 | ||
|
|
ba8e3f9bc5 | ||
|
|
477200d1f9 | ||
|
|
662738a7e5 | ||
|
|
f992b77535 | ||
|
|
21b2f135b5 | ||
|
|
71e6be5d99 | ||
|
|
df8e7d0a9a | ||
|
|
64422a48d9 | ||
|
|
04f9c62772 | ||
|
|
2185d07f05 | ||
|
|
a49d28e00e | ||
|
|
629128c497 | ||
|
|
b741d26eb5 | ||
|
|
cf8fa4a404 | ||
|
|
fe76a1b179 | ||
|
|
df4957307f | ||
|
|
e21f56e801 | ||
|
|
e51b55e03c | ||
|
|
296b8249cb | ||
|
|
7c6b840199 | ||
|
|
2a6ff4cbfc | ||
|
|
6ad5e2fcf3 | ||
|
|
37811320ef | ||
|
|
99ac7f5f9e | ||
|
|
96aca741a2 | ||
|
|
12ec931237 | ||
|
|
0e293a4ec9 | ||
|
|
163712a23b | ||
|
|
5f4d463780 | ||
|
|
abc8121aa8 | ||
|
|
8c47277141 | ||
|
|
36b5cd18e8 | ||
|
|
1e72e32ec3 | ||
|
|
8e5e5a563d | ||
|
|
98049e7eda | ||
|
|
25aa893bad | ||
|
|
b270a20274 | ||
|
|
f0262dd10e | ||
|
|
f8b673eccd | ||
|
|
0c0c9a0030 | ||
|
|
d1f79317cf | ||
|
|
fa58522242 | ||
|
|
aa6fd3d888 | ||
|
|
ebdd8834a9 | ||
|
|
fe8d5627e0 | ||
|
|
b242e3280b | ||
|
|
cc01caaecb | ||
|
|
e7b5ee7518 | ||
|
|
63073b65c0 | ||
|
|
47cf72b8ba | ||
|
|
af24d72dd8 | ||
|
|
f451b455c4 | ||
|
|
16f4dfafb1 | ||
|
|
5b4e8b9d71 | ||
|
|
b06eaffeeb | ||
|
|
3d55540db1 | ||
|
|
3c2b9aab96 | ||
|
|
49d46e71de | ||
|
|
6c5168e1ec | ||
|
|
e91d5326fe | ||
|
|
48b573e835 | ||
|
|
4788eb24ab | ||
|
|
3ed2783f34 | ||
|
|
c0e8a41a6f | ||
|
|
23b217af24 | ||
|
|
3dab19f933 | ||
|
|
05efb6291f | ||
|
|
eba49fdefd | ||
|
|
29f2c49374 | ||
|
|
2245371696 | ||
|
|
74631d5808 | ||
|
|
9264b0ca09 | ||
|
|
a96fb92939 | ||
|
|
ae59e1f72e | ||
|
|
47167a4e6f | ||
|
|
23cabd67fb | ||
|
|
e66410b932 | ||
|
|
c3bba05770 | ||
|
|
69b91f2760 | ||
|
|
e6b09580b4 | ||
|
|
36e663edda | ||
|
|
60e2029e70 | ||
|
|
5e1db43e34 | ||
|
|
6e9b743296 | ||
|
|
ef9710d8e2 | ||
|
|
468b3b9c8c | ||
|
|
0d8c853917 | ||
|
|
df3b868fe7 | ||
|
|
3f6a5ab6ba | ||
|
|
aa1f4389b1 | ||
|
|
246cd2aee9 | ||
|
|
0b6f8149d1 | ||
|
|
4dda2ad58b | ||
|
|
15bb14fcf9 | ||
|
|
b68114375f | ||
|
|
83a09b3cf2 | ||
|
|
3690cb12e6 | ||
|
|
b4de4826c4 | ||
|
|
b06df637c7 | ||
|
|
9bb9ae43f9 | ||
|
|
35e7172b89 | ||
|
|
abebf4d955 | ||
|
|
1c826d253b | ||
|
|
d1b454232d | ||
|
|
be3b41acc6 | ||
|
|
2a3e51ebfe | ||
|
|
1382fda1c9 | ||
|
|
c658096c17 | ||
|
|
6097919958 | ||
|
|
330bdde0a3 | ||
|
|
a55404fa2e | ||
|
|
c8c7c9f376 | ||
|
|
df34c1ce45 | ||
|
|
da1d66c938 | ||
|
|
d32926a7e5 | ||
|
|
7f008a7d1e | ||
|
|
eab3f704f5 | ||
|
|
a50e89c38e | ||
|
|
85723a138f | ||
|
|
9c69ba6f6f | ||
|
|
e84ed65525 | ||
|
|
4060abd3aa | ||
|
|
c924a0428d | ||
|
|
33ef1b3a30 | ||
|
|
a6caf4b948 | ||
|
|
cc7e11da99 | ||
|
|
a4c3efe783 | ||
|
|
4e22846e95 | ||
|
|
ddbd0cd095 | ||
|
|
255a3ec82c | ||
|
|
167c77baec | ||
|
|
ca2718366e | ||
|
|
58d3a643b9 | ||
|
|
718b8085fa | ||
|
|
64930d7440 | ||
|
|
4d2f948869 | ||
|
|
19c224cbe1 | ||
|
|
efd1581c01 | ||
|
|
ac85f590ba | ||
|
|
b0d3162875 | ||
|
|
4715a7e9e1 | ||
|
|
c5aec243c9 | ||
|
|
c76f3d3dba | ||
|
|
7add6e48b6 | ||
|
|
1267e0c076 | ||
|
|
361c093a35 | ||
|
|
9ad39a29f7 | ||
|
|
f5a1d8bff9 | ||
|
|
8c86afbd57 | ||
|
|
4d6e36df99 | ||
|
|
f51884e893 | ||
|
|
4afd9ecf16 | ||
|
|
ed3b311de4 | ||
|
|
d030fcc076 | ||
|
|
398da1f916 | ||
|
|
3a5741f534 | ||
|
|
c53b0b8a12 | ||
|
|
8fd34669ed | ||
|
|
be7e91899b | ||
|
|
74a822568e | ||
|
|
105c214d15 | ||
|
|
2b6a51ed34 | ||
|
|
e98c252490 | ||
|
|
17f5d6309f | ||
|
|
6a46ea04ab | ||
|
|
7bd97f6717 | ||
|
|
2a9c98ae40 | ||
|
|
1adf80c613 | ||
|
|
f823d3c73c | ||
|
|
91f0ed3fc3 | ||
|
|
04b56c7331 | ||
|
|
3c1a20097f | ||
|
|
966c4d4e14 | ||
|
|
6b8289d68e | ||
|
|
294421db9c | ||
|
|
9fdf991c27 | ||
|
|
77b33260f8 | ||
|
|
33e5f47c6c | ||
|
|
25ceb90678 | ||
|
|
27e29055cb | ||
|
|
810ce31f4b | ||
|
|
e3c91c9d29 | ||
|
|
2f47838ea1 | ||
|
|
0566e62995 | ||
|
|
aeac42be47 | ||
|
|
aa21ff7efd | ||
|
|
57d22a7bd1 | ||
|
|
6804bcbf12 | ||
|
|
6d34cc0b60 | ||
|
|
1bb375fe5c | ||
|
|
ed00243a0c | ||
|
|
1223e759a4 | ||
|
|
4fd3ec1a50 | ||
|
|
7f9cad1e4e | ||
|
|
437b8b140f | ||
|
|
8f0d9bd71f | ||
|
|
1378c616d6 | ||
|
|
3b5dfb3fb4 | ||
|
|
9c22be5d9c | ||
|
|
42dea89247 | ||
|
|
982a332679 | ||
|
|
441853f189 | ||
|
|
611329fc7f | ||
|
|
f3c135e583 | ||
|
|
7f84582b37 | ||
|
|
297526c49d | ||
|
|
d01d394a2b | ||
|
|
17d4369866 | ||
|
|
fb5e1393a4 | ||
|
|
18dbde9ed6 | ||
|
|
2a13491919 | ||
|
|
3509a1a7ff | ||
|
|
da1f4b8496 | ||
|
|
5b2e1d3ce4 | ||
|
|
7d8a6bc1d7 | ||
|
|
a378f8095e | ||
|
|
005bc009e8 | ||
|
|
3bc7d4bec6 | ||
|
|
96c1b05238 | ||
|
|
98f9f806f3 | ||
|
|
c834ba1389 | ||
|
|
cab437adef | ||
|
|
eefa8188e1 | ||
|
|
1d8db8a738 | ||
|
|
75ddc5b811 | ||
|
|
17dc0e1108 | ||
|
|
64ac6c9621 | ||
|
|
a7753ea781 | ||
|
|
12a6eb5b22 | ||
|
|
74b21258b6 | ||
|
|
2f9d46ce27 | ||
|
|
7b660c4e30 | ||
|
|
406799eb1c | ||
|
|
ef0cbf20f4 | ||
|
|
7f572eb044 | ||
|
|
0defb614a4 | ||
|
|
18023d7f32 | ||
|
|
4983b98005 | ||
|
|
8675e02cea | ||
|
|
45fc3bf842 | ||
|
|
cf809aec47 | ||
|
|
cceb1acca8 | ||
|
|
e620c40a14 | ||
|
|
e1590bf68b | ||
|
|
bad07943b5 | ||
|
|
603595559f | ||
|
|
febcc25d1a | ||
|
|
e3c0e34b33 | ||
|
|
3f5974b7f9 | ||
|
|
7ab3dc080b | ||
|
|
0883beac30 | ||
|
|
f9102a3295 |
@@ -1,3 +1 @@
|
|||||||
bin/
|
bin/
|
||||||
cross-out/
|
|
||||||
release-out/
|
|
||||||
|
|||||||
171
.github/workflows/build.yml
vendored
171
.github/workflows/build.yml
vendored
@@ -16,14 +16,108 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
- 'v[0-9]*'
|
- 'v[0-9]*'
|
||||||
|
paths-ignore:
|
||||||
|
- 'README.md'
|
||||||
|
- 'docs/**'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
BUILDX_VERSION: "latest"
|
||||||
|
BUILDKIT_IMAGE: "moby/buildkit:latest"
|
||||||
REPO_SLUG: "docker/buildx-bin"
|
REPO_SLUG: "docker/buildx-bin"
|
||||||
RELEASE_OUT: "./release-out"
|
DESTDIR: "./bin"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
with:
|
||||||
|
version: ${{ env.BUILDX_VERSION }}
|
||||||
|
driver-opts: image=${{ env.BUILDKIT_IMAGE }}
|
||||||
|
buildkitd-flags: --debug
|
||||||
|
-
|
||||||
|
name: Test
|
||||||
|
uses: docker/bake-action@v2
|
||||||
|
with:
|
||||||
|
targets: test
|
||||||
|
set: |
|
||||||
|
*.cache-from=type=gha,scope=test
|
||||||
|
*.cache-to=type=gha,scope=test
|
||||||
|
-
|
||||||
|
name: Upload coverage
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
with:
|
||||||
|
directory: ${{ env.DESTDIR }}/coverage
|
||||||
|
|
||||||
|
prepare:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
outputs:
|
||||||
|
matrix: ${{ steps.platforms.outputs.matrix }}
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
-
|
||||||
|
name: Create matrix
|
||||||
|
id: platforms
|
||||||
|
run: |
|
||||||
|
echo "matrix=$(docker buildx bake binaries-cross --print | jq -cr '.target."binaries-cross".platforms')" >>${GITHUB_OUTPUT}
|
||||||
|
-
|
||||||
|
name: Show matrix
|
||||||
|
run: |
|
||||||
|
echo ${{ steps.platforms.outputs.matrix }}
|
||||||
|
|
||||||
|
binaries:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs:
|
||||||
|
- prepare
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
platform: ${{ fromJson(needs.prepare.outputs.matrix) }}
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Prepare
|
||||||
|
run: |
|
||||||
|
platform=${{ matrix.platform }}
|
||||||
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
|
-
|
||||||
|
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: ${{ env.BUILDX_VERSION }}
|
||||||
|
driver-opts: image=${{ env.BUILDKIT_IMAGE }}
|
||||||
|
buildkitd-flags: --debug
|
||||||
|
-
|
||||||
|
name: Build
|
||||||
|
run: |
|
||||||
|
make release
|
||||||
|
env:
|
||||||
|
PLATFORMS: ${{ matrix.platform }}
|
||||||
|
CACHE_FROM: type=gha,scope=binaries-${{ env.PLATFORM_PAIR }}
|
||||||
|
CACHE_TO: type=gha,scope=binaries-${{ env.PLATFORM_PAIR }},mode=max
|
||||||
|
-
|
||||||
|
name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: buildx
|
||||||
|
path: ${{ env.DESTDIR }}/*
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
bin-image:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
@@ -35,33 +129,9 @@ jobs:
|
|||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: ${{ env.BUILDX_VERSION }}
|
||||||
-
|
driver-opts: image=${{ env.BUILDKIT_IMAGE }}
|
||||||
name: Test
|
buildkitd-flags: --debug
|
||||||
run: |
|
|
||||||
make test
|
|
||||||
-
|
|
||||||
name: Send to Codecov
|
|
||||||
uses: codecov/codecov-action@v3
|
|
||||||
with:
|
|
||||||
file: ./coverage/coverage.txt
|
|
||||||
-
|
|
||||||
name: Expose GitHub Runtime
|
|
||||||
uses: crazy-max/ghaction-github-runtime@906832f62b7baa936e3fbef72b029308af505ee7
|
|
||||||
-
|
|
||||||
name: Build binaries
|
|
||||||
run: |
|
|
||||||
make release
|
|
||||||
env:
|
|
||||||
CACHE_FROM: type=gha,scope=release
|
|
||||||
CACHE_TO: type=gha,scope=release
|
|
||||||
-
|
|
||||||
name: Upload artifacts
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: buildx
|
|
||||||
path: ${{ env.RELEASE_OUT }}/*
|
|
||||||
if-no-files-found: error
|
|
||||||
-
|
-
|
||||||
name: Docker meta
|
name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
@@ -90,18 +160,49 @@ jobs:
|
|||||||
${{ steps.meta.outputs.bake-file }}
|
${{ steps.meta.outputs.bake-file }}
|
||||||
targets: image-cross
|
targets: image-cross
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
set: |
|
||||||
|
*.cache-from=type=gha,scope=bin-image
|
||||||
|
*.cache-to=type=gha,scope=bin-image,mode=max
|
||||||
|
*.attest=type=sbom
|
||||||
|
*.attest=type=provenance,mode=max,builder-id=https://github.com/${{ env.GITHUB_REPOSITORY }}/actions/runs/${{ env.GITHUB_RUN_ID }}
|
||||||
|
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
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 -- {} +
|
||||||
-
|
-
|
||||||
name: GitHub Release
|
name: GitHub Release
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5
|
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
files: ${{ env.RELEASE_OUT }}/*
|
files: ${{ env.DESTDIR }}/*
|
||||||
|
|
||||||
buildkit-edge:
|
buildkit-edge:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
@@ -114,7 +215,7 @@ jobs:
|
|||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: ${{ env.BUILDX_VERSION }}
|
||||||
driver-opts: image=moby/buildkit:master
|
driver-opts: image=moby/buildkit:master
|
||||||
buildkitd-flags: --debug
|
buildkitd-flags: --debug
|
||||||
-
|
-
|
||||||
@@ -122,4 +223,4 @@ jobs:
|
|||||||
name: Build
|
name: Build
|
||||||
uses: docker/bake-action@v2
|
uses: docker/bake-action@v2
|
||||||
with:
|
with:
|
||||||
targets: binaries-cross
|
targets: binaries
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
name: docs
|
name: docs-release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [ published ]
|
types:
|
||||||
|
- released
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
open-pr:
|
open-pr:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
|
if: "!github.event.release.prerelease"
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout docs repo
|
name: Checkout docs repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
|
token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
|
||||||
repository: docker/docker.github.io
|
repository: docker/docs
|
||||||
ref: master
|
ref: main
|
||||||
-
|
-
|
||||||
name: Prepare
|
name: Prepare
|
||||||
run: |
|
run: |
|
||||||
@@ -42,7 +44,7 @@ jobs:
|
|||||||
git add -A .
|
git add -A .
|
||||||
-
|
-
|
||||||
name: Create PR on docs repo
|
name: Create PR on docs repo
|
||||||
uses: peter-evans/create-pull-request@923ad837f191474af6b1721408744feb989a4c27 # v4.0.4
|
uses: peter-evans/create-pull-request@2b011faafdcbc9ceb11414d64d0573f37c774b04
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
|
token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
|
||||||
push-to-fork: docker-tools-robot/docker.github.io
|
push-to-fork: docker-tools-robot/docker.github.io
|
||||||
61
.github/workflows/docs-upstream.yml
vendored
Normal file
61
.github/workflows/docs-upstream.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# 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-22.04
|
||||||
|
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
|
||||||
82
.github/workflows/e2e.yml
vendored
82
.github/workflows/e2e.yml
vendored
@@ -14,12 +14,17 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
- 'v[0-9]*'
|
- 'v[0-9]*'
|
||||||
|
paths-ignore:
|
||||||
|
- 'README.md'
|
||||||
|
- 'docs/**'
|
||||||
|
|
||||||
|
env:
|
||||||
|
DESTDIR: "./bin"
|
||||||
|
K3S_VERSION: "v1.21.2-k3s1"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
env:
|
|
||||||
BIN_OUT: ./bin
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@@ -40,13 +45,13 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Rename binary
|
name: Rename binary
|
||||||
run: |
|
run: |
|
||||||
mv ${{ env.BIN_OUT }}/buildx ${{ env.BIN_OUT }}/docker-buildx
|
mv ${{ env.DESTDIR }}/build/buildx ${{ env.DESTDIR }}/build/docker-buildx
|
||||||
-
|
-
|
||||||
name: Upload artifacts
|
name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: binary
|
name: binary
|
||||||
path: ${{ env.BIN_OUT }}
|
path: ${{ env.DESTDIR }}/build
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
@@ -129,20 +134,67 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Install k3s
|
name: Install k3s
|
||||||
if: matrix.driver == 'kubernetes'
|
if: matrix.driver == 'kubernetes'
|
||||||
uses: debianmaster/actions-k3s@b9cf3f599fd118699a3c8a0d18a2f2bda6cf4ce4
|
uses: actions/github-script@v6
|
||||||
id: k3s
|
|
||||||
with:
|
with:
|
||||||
version: v1.21.2-k3s1
|
script: |
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
let wait = function(milliseconds) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (typeof(milliseconds) !== 'number') {
|
||||||
|
throw new Error('milleseconds not a number');
|
||||||
|
}
|
||||||
|
setTimeout(() => resolve("done!"), milliseconds)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const kubeconfig="/tmp/buildkit-k3s/kubeconfig.yaml";
|
||||||
|
core.info(`storing kubeconfig in ${kubeconfig}`);
|
||||||
|
|
||||||
|
await exec.exec('docker', ["run", "-d",
|
||||||
|
"--privileged",
|
||||||
|
"--name=buildkit-k3s",
|
||||||
|
"-e", "K3S_KUBECONFIG_OUTPUT="+kubeconfig,
|
||||||
|
"-e", "K3S_KUBECONFIG_MODE=666",
|
||||||
|
"-v", "/tmp/buildkit-k3s:/tmp/buildkit-k3s",
|
||||||
|
"-p", "6443:6443",
|
||||||
|
"-p", "80:80",
|
||||||
|
"-p", "443:443",
|
||||||
|
"-p", "8080:8080",
|
||||||
|
"rancher/k3s:${{ env.K3S_VERSION }}", "server"
|
||||||
|
]);
|
||||||
|
await wait(10000);
|
||||||
|
|
||||||
|
core.exportVariable('KUBECONFIG', kubeconfig);
|
||||||
|
|
||||||
|
let nodeName;
|
||||||
|
for (let count = 1; count <= 5; count++) {
|
||||||
|
try {
|
||||||
|
const nodeNameOutput = await exec.getExecOutput("kubectl get nodes --no-headers -oname");
|
||||||
|
nodeName = nodeNameOutput.stdout
|
||||||
|
} catch (error) {
|
||||||
|
core.info(`Unable to resolve node name (${error.message}). Attempt ${count} of 5.`)
|
||||||
|
} finally {
|
||||||
|
if (nodeName) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await wait(5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!nodeName) {
|
||||||
|
throw new Error(`Unable to resolve node name after 5 attempts.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await exec.exec(`kubectl wait --for=condition=Ready ${nodeName}`);
|
||||||
|
} catch (error) {
|
||||||
|
core.setFailed(error.message);
|
||||||
|
}
|
||||||
-
|
-
|
||||||
name: Config k3s
|
name: Print KUBECONFIG
|
||||||
if: matrix.driver == 'kubernetes'
|
if: matrix.driver == 'kubernetes'
|
||||||
run: |
|
run: |
|
||||||
(set -x ; cat ${{ steps.k3s.outputs.kubeconfig }})
|
yq ${{ env.KUBECONFIG }}
|
||||||
-
|
|
||||||
name: Check k3s nodes
|
|
||||||
if: matrix.driver == 'kubernetes'
|
|
||||||
run: |
|
|
||||||
kubectl get nodes
|
|
||||||
-
|
-
|
||||||
name: Launch remote buildkitd
|
name: Launch remote buildkitd
|
||||||
if: matrix.driver == 'remote'
|
if: matrix.driver == 'remote'
|
||||||
|
|||||||
22
.github/workflows/validate.yml
vendored
22
.github/workflows/validate.yml
vendored
@@ -19,7 +19,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
validate:
|
validate:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -40,23 +40,3 @@ jobs:
|
|||||||
name: Run
|
name: Run
|
||||||
run: |
|
run: |
|
||||||
make ${{ matrix.target }}
|
make ${{ matrix.target }}
|
||||||
|
|
||||||
validate-docs-yaml:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- validate
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
-
|
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
-
|
|
||||||
name: Run
|
|
||||||
run: |
|
|
||||||
make docs
|
|
||||||
env:
|
|
||||||
FORMATS: yaml
|
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,4 +1 @@
|
|||||||
bin
|
/bin
|
||||||
coverage
|
|
||||||
cross-out
|
|
||||||
release-out
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ linters:
|
|||||||
- revive
|
- revive
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- typecheck
|
- typecheck
|
||||||
- structcheck
|
- nolintlint
|
||||||
disable-all: true
|
disable-all: true
|
||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
|
|||||||
37
Dockerfile
37
Dockerfile
@@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:1.4
|
# syntax=docker/dockerfile-upstream:1.5.0
|
||||||
|
|
||||||
ARG GO_VERSION=1.18
|
ARG GO_VERSION=1.19
|
||||||
ARG XX_VERSION=1.1.2
|
ARG XX_VERSION=1.1.2
|
||||||
ARG DOCKERD_VERSION=20.10.14
|
ARG DOCKERD_VERSION=20.10.14
|
||||||
|
|
||||||
@@ -19,20 +19,24 @@ ENV CGO_ENABLED=0
|
|||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
FROM gobase AS buildx-version
|
FROM gobase AS buildx-version
|
||||||
RUN --mount=target=. \
|
RUN --mount=type=bind,target=. <<EOT
|
||||||
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); \
|
set -e
|
||||||
echo "-X ${PKG}/version.Version=${VERSION} -X ${PKG}/version.Revision=${REVISION} -X ${PKG}/version.Package=${PKG}" | tee /tmp/.ldflags; \
|
mkdir /buildx-version
|
||||||
echo -n "${VERSION}" | tee /tmp/.version;
|
echo -n "$(./hack/git-meta version)" | tee /buildx-version/version
|
||||||
|
echo -n "$(./hack/git-meta revision)" | tee /buildx-version/revision
|
||||||
|
EOT
|
||||||
|
|
||||||
FROM gobase AS buildx-build
|
FROM gobase AS buildx-build
|
||||||
ARG LDFLAGS="-w -s"
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
RUN --mount=type=bind,target=. \
|
RUN --mount=type=bind,target=. \
|
||||||
--mount=type=cache,target=/root/.cache \
|
--mount=type=cache,target=/root/.cache \
|
||||||
--mount=type=cache,target=/go/pkg/mod \
|
--mount=type=cache,target=/go/pkg/mod \
|
||||||
--mount=type=bind,source=/tmp/.ldflags,target=/tmp/.ldflags,from=buildx-version \
|
--mount=type=bind,from=buildx-version,source=/buildx-version,target=/buildx-version <<EOT
|
||||||
set -x; xx-go build -ldflags "$(cat /tmp/.ldflags) ${LDFLAGS}" -o /usr/bin/buildx ./cmd/buildx && \
|
set -e
|
||||||
xx-verify --static /usr/bin/buildx
|
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
|
||||||
|
|
||||||
FROM gobase AS test
|
FROM gobase AS test
|
||||||
RUN --mount=type=bind,target=. \
|
RUN --mount=type=bind,target=. \
|
||||||
@@ -45,23 +49,28 @@ FROM scratch AS test-coverage
|
|||||||
COPY --from=test /tmp/coverage.txt /coverage.txt
|
COPY --from=test /tmp/coverage.txt /coverage.txt
|
||||||
|
|
||||||
FROM scratch AS binaries-unix
|
FROM scratch AS binaries-unix
|
||||||
COPY --link --from=buildx-build /usr/bin/buildx /
|
COPY --link --from=buildx-build /usr/bin/docker-buildx /buildx
|
||||||
|
|
||||||
FROM binaries-unix AS binaries-darwin
|
FROM binaries-unix AS binaries-darwin
|
||||||
FROM binaries-unix AS binaries-linux
|
FROM binaries-unix AS binaries-linux
|
||||||
|
|
||||||
FROM scratch AS binaries-windows
|
FROM scratch AS binaries-windows
|
||||||
COPY --link --from=buildx-build /usr/bin/buildx /buildx.exe
|
COPY --link --from=buildx-build /usr/bin/docker-buildx /buildx.exe
|
||||||
|
|
||||||
FROM binaries-$TARGETOS AS binaries
|
FROM binaries-$TARGETOS AS binaries
|
||||||
|
# enable scanning for this stage
|
||||||
|
ARG BUILDKIT_SBOM_SCAN_STAGE=true
|
||||||
|
|
||||||
# Release
|
# Release
|
||||||
FROM --platform=$BUILDPLATFORM alpine AS releaser
|
FROM --platform=$BUILDPLATFORM alpine AS releaser
|
||||||
WORKDIR /work
|
WORKDIR /work
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
RUN --mount=from=binaries \
|
RUN --mount=from=binaries \
|
||||||
--mount=type=bind,source=/tmp/.version,target=/tmp/.version,from=buildx-version \
|
--mount=type=bind,from=buildx-version,source=/buildx-version,target=/buildx-version <<EOT
|
||||||
mkdir -p /out && cp buildx* "/out/buildx-$(cat /tmp/.version).$(echo $TARGETPLATFORM | sed 's/\//-/g')$(ls buildx* | sed -e 's/^buildx//')"
|
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
|
||||||
|
|
||||||
FROM scratch AS release
|
FROM scratch AS release
|
||||||
COPY --from=releaser /out/ /
|
COPY --from=releaser /out/ /
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ made through a pull request.
|
|||||||
people = [
|
people = [
|
||||||
"akihirosuda",
|
"akihirosuda",
|
||||||
"crazy-max",
|
"crazy-max",
|
||||||
|
"jedevc",
|
||||||
"tiborvass",
|
"tiborvass",
|
||||||
"tonistiigi",
|
"tonistiigi",
|
||||||
]
|
]
|
||||||
@@ -188,6 +189,11 @@ made through a pull request.
|
|||||||
Email = "contact@crazymax.dev"
|
Email = "contact@crazymax.dev"
|
||||||
GitHub = "crazy-max"
|
GitHub = "crazy-max"
|
||||||
|
|
||||||
|
[people.jedevc]
|
||||||
|
Name = "Justin Chadwell"
|
||||||
|
Email = "me@jedevc.com"
|
||||||
|
GitHub = "jedevc"
|
||||||
|
|
||||||
[people.thajeztah]
|
[people.thajeztah]
|
||||||
Name = "Sebastiaan van Stijn"
|
Name = "Sebastiaan van Stijn"
|
||||||
Email = "github@gone.nl"
|
Email = "github@gone.nl"
|
||||||
|
|||||||
32
Makefile
32
Makefile
@@ -4,59 +4,77 @@ else ifneq (, $(shell docker buildx version))
|
|||||||
export BUILDX_CMD = docker buildx
|
export BUILDX_CMD = docker buildx
|
||||||
else ifneq (, $(shell which buildx))
|
else ifneq (, $(shell which buildx))
|
||||||
export BUILDX_CMD = $(which buildx)
|
export BUILDX_CMD = $(which buildx)
|
||||||
else
|
|
||||||
$(error "Buildx is required: https://github.com/docker/buildx#installing")
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
export BIN_OUT = ./bin
|
export BUILDX_CMD ?= docker buildx
|
||||||
export RELEASE_OUT = ./release-out
|
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: binaries
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
./hack/build
|
||||||
|
|
||||||
|
.PHONY: shell
|
||||||
shell:
|
shell:
|
||||||
./hack/shell
|
./hack/shell
|
||||||
|
|
||||||
|
.PHONY: binaries
|
||||||
binaries:
|
binaries:
|
||||||
$(BUILDX_CMD) bake binaries
|
$(BUILDX_CMD) bake binaries
|
||||||
|
|
||||||
|
.PHONY: binaries-cross
|
||||||
binaries-cross:
|
binaries-cross:
|
||||||
$(BUILDX_CMD) bake binaries-cross
|
$(BUILDX_CMD) bake binaries-cross
|
||||||
|
|
||||||
|
.PHONY: install
|
||||||
install: binaries
|
install: binaries
|
||||||
mkdir -p ~/.docker/cli-plugins
|
mkdir -p ~/.docker/cli-plugins
|
||||||
install bin/buildx ~/.docker/cli-plugins/docker-buildx
|
install bin/build/buildx ~/.docker/cli-plugins/docker-buildx
|
||||||
|
|
||||||
|
.PHONY: release
|
||||||
release:
|
release:
|
||||||
./hack/release
|
./hack/release
|
||||||
|
|
||||||
|
.PHONY: validate-all
|
||||||
validate-all: lint test validate-vendor validate-docs
|
validate-all: lint test validate-vendor validate-docs
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
lint:
|
lint:
|
||||||
$(BUILDX_CMD) bake lint
|
$(BUILDX_CMD) bake lint
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
$(BUILDX_CMD) bake test
|
$(BUILDX_CMD) bake test
|
||||||
|
|
||||||
|
.PHONY: validate-vendor
|
||||||
validate-vendor:
|
validate-vendor:
|
||||||
$(BUILDX_CMD) bake validate-vendor
|
$(BUILDX_CMD) bake validate-vendor
|
||||||
|
|
||||||
|
.PHONY: validate-docs
|
||||||
validate-docs:
|
validate-docs:
|
||||||
$(BUILDX_CMD) bake validate-docs
|
$(BUILDX_CMD) bake validate-docs
|
||||||
|
|
||||||
|
.PHONY: validate-authors
|
||||||
validate-authors:
|
validate-authors:
|
||||||
$(BUILDX_CMD) bake validate-authors
|
$(BUILDX_CMD) bake validate-authors
|
||||||
|
|
||||||
|
.PHONY: test-driver
|
||||||
test-driver:
|
test-driver:
|
||||||
./hack/test-driver
|
./hack/test-driver
|
||||||
|
|
||||||
|
.PHONY: vendor
|
||||||
vendor:
|
vendor:
|
||||||
./hack/update-vendor
|
./hack/update-vendor
|
||||||
|
|
||||||
|
.PHONY: docs
|
||||||
docs:
|
docs:
|
||||||
./hack/update-docs
|
./hack/update-docs
|
||||||
|
|
||||||
|
.PHONY: authors
|
||||||
authors:
|
authors:
|
||||||
$(BUILDX_CMD) bake update-authors
|
$(BUILDX_CMD) bake update-authors
|
||||||
|
|
||||||
|
.PHONY: mod-outdated
|
||||||
mod-outdated:
|
mod-outdated:
|
||||||
$(BUILDX_CMD) bake mod-outdated
|
$(BUILDX_CMD) bake mod-outdated
|
||||||
|
|
||||||
.PHONY: shell binaries binaries-cross install release validate-all lint validate-vendor validate-docs validate-authors vendor docs authors
|
|
||||||
|
|||||||
27
README.md
27
README.md
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[](https://github.com/docker/buildx/releases/latest)
|
[](https://github.com/docker/buildx/releases/latest)
|
||||||
[](https://pkg.go.dev/github.com/docker/buildx)
|
[](https://pkg.go.dev/github.com/docker/buildx)
|
||||||
[](https://github.com/docker/buildx/actions?query=workflow%3Abuild)
|
[](https://github.com/docker/buildx/actions?query=workflow%3Abuild)
|
||||||
[](https://goreportcard.com/report/github.com/docker/buildx)
|
[](https://goreportcard.com/report/github.com/docker/buildx)
|
||||||
[](https://codecov.io/gh/docker/buildx)
|
[](https://codecov.io/gh/docker/buildx)
|
||||||
|
|
||||||
@@ -32,15 +32,18 @@ Key features:
|
|||||||
- [Building with buildx](#building-with-buildx)
|
- [Building with buildx](#building-with-buildx)
|
||||||
- [Working with builder instances](#working-with-builder-instances)
|
- [Working with builder instances](#working-with-builder-instances)
|
||||||
- [Building multi-platform images](#building-multi-platform-images)
|
- [Building multi-platform images](#building-multi-platform-images)
|
||||||
|
- [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)
|
- [Guides](docs/guides)
|
||||||
- [High-level build options with Bake](docs/guides/bake/index.md)
|
|
||||||
- [CI/CD](docs/guides/cicd.md)
|
- [CI/CD](docs/guides/cicd.md)
|
||||||
- [CNI networking](docs/guides/cni-networking.md)
|
- [CNI networking](docs/guides/cni-networking.md)
|
||||||
- [Using a custom network](docs/guides/custom-network.md)
|
- [Using a custom network](docs/guides/custom-network.md)
|
||||||
- [Using a custom registry configuration](docs/guides/custom-registry-config.md)
|
- [Using a custom registry configuration](docs/guides/custom-registry-config.md)
|
||||||
- [OpenTelemetry support](docs/guides/opentelemetry.md)
|
- [OpenTelemetry support](docs/guides/opentelemetry.md)
|
||||||
- [Registry mirror](docs/guides/registry-mirror.md)
|
- [Registry mirror](docs/guides/registry-mirror.md)
|
||||||
- [Drivers](docs/guides/drivers/index.md)
|
|
||||||
- [Resource limiting](docs/guides/resource-limiting.md)
|
- [Resource limiting](docs/guides/resource-limiting.md)
|
||||||
- [Reference](docs/reference/buildx.md)
|
- [Reference](docs/reference/buildx.md)
|
||||||
- [`buildx bake`](docs/reference/buildx_bake.md)
|
- [`buildx bake`](docs/reference/buildx_bake.md)
|
||||||
@@ -123,7 +126,8 @@ On Windows:
|
|||||||
Here is how to install and use Buildx inside a Dockerfile through the
|
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:
|
[`docker/buildx-bin`](https://hub.docker.com/r/docker/buildx-bin) image:
|
||||||
|
|
||||||
```Dockerfile
|
```dockerfile
|
||||||
|
# syntax=docker/dockerfile:1
|
||||||
FROM docker
|
FROM docker
|
||||||
COPY --from=docker/buildx-bin /buildx /usr/libexec/docker/cli-plugins/docker-buildx
|
COPY --from=docker/buildx-bin /buildx /usr/libexec/docker/cli-plugins/docker-buildx
|
||||||
RUN docker buildx version
|
RUN docker buildx version
|
||||||
@@ -143,7 +147,7 @@ To remove this alias, run [`docker buildx uninstall`](docs/reference/buildx_unin
|
|||||||
# Buildx 0.6+
|
# Buildx 0.6+
|
||||||
$ docker buildx bake "https://github.com/docker/buildx.git"
|
$ docker buildx bake "https://github.com/docker/buildx.git"
|
||||||
$ mkdir -p ~/.docker/cli-plugins
|
$ mkdir -p ~/.docker/cli-plugins
|
||||||
$ mv ./bin/buildx ~/.docker/cli-plugins/docker-buildx
|
$ mv ./bin/build/buildx ~/.docker/cli-plugins/docker-buildx
|
||||||
|
|
||||||
# Docker 19.03+
|
# Docker 19.03+
|
||||||
$ DOCKER_BUILDKIT=1 docker build --platform=local -o . "https://github.com/docker/buildx.git"
|
$ DOCKER_BUILDKIT=1 docker build --platform=local -o . "https://github.com/docker/buildx.git"
|
||||||
@@ -190,12 +194,12 @@ through various "drivers". Each driver defines how and where a build should
|
|||||||
run, and have different feature sets.
|
run, and have different feature sets.
|
||||||
|
|
||||||
We currently support the following drivers:
|
We currently support the following drivers:
|
||||||
- The `docker` driver ([guide](docs/guides/drivers/docker.md), [reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver))
|
- 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/guides/drivers/docker-container.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/guides/drivers/kubernetes.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/guides/drivers/remote.md))
|
- The `remote` driver ([guide](docs/manuals/drivers/remote.md))
|
||||||
|
|
||||||
For more information on drivers, see the [drivers guide](docs/guides/drivers/index.md).
|
For more information on drivers, see the [drivers guide](docs/manuals/drivers/index.md).
|
||||||
|
|
||||||
## Working with builder instances
|
## Working with builder instances
|
||||||
|
|
||||||
@@ -298,6 +302,7 @@ inside your Dockerfile and can be leveraged by the processes running as part
|
|||||||
of your build.
|
of your build.
|
||||||
|
|
||||||
```dockerfile
|
```dockerfile
|
||||||
|
# syntax=docker/dockerfile:1
|
||||||
FROM --platform=$BUILDPLATFORM golang:alpine AS build
|
FROM --platform=$BUILDPLATFORM golang:alpine AS build
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
ARG BUILDPLATFORM
|
ARG BUILDPLATFORM
|
||||||
@@ -311,7 +316,7 @@ cross-compilation helpers for more advanced use-cases.
|
|||||||
|
|
||||||
## High-level build options
|
## High-level build options
|
||||||
|
|
||||||
See [`docs/guides/bake/index.md`](docs/guides/bake/index.md) for more details.
|
See [`docs/manuals/bake/index.md`](docs/manuals/bake/index.md) for more details.
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
|
|||||||
283
bake/bake.go
283
bake/bake.go
@@ -84,7 +84,7 @@ func ReadLocalFiles(names []string) ([]File, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadTargets(ctx context.Context, files []File, targets, overrides []string, defaults map[string]string) (map[string]*Target, []*Group, error) {
|
func ReadTargets(ctx context.Context, files []File, targets, overrides []string, defaults map[string]string) (map[string]*Target, map[string]*Group, error) {
|
||||||
c, err := ParseFiles(files, defaults)
|
c, err := ParseFiles(files, defaults)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -99,42 +99,39 @@ func ReadTargets(ctx context.Context, files []File, targets, overrides []string,
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
m := map[string]*Target{}
|
m := map[string]*Target{}
|
||||||
for _, n := range targets {
|
n := map[string]*Group{}
|
||||||
for _, n := range c.ResolveGroup(n) {
|
for _, target := range targets {
|
||||||
t, err := c.ResolveTarget(n, o)
|
ts, gs := c.ResolveGroup(target)
|
||||||
|
for _, tname := range ts {
|
||||||
|
t, err := c.ResolveTarget(tname, o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if t != nil {
|
if t != nil {
|
||||||
m[n] = t
|
m[tname] = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, gname := range gs {
|
||||||
|
for _, group := range c.Groups {
|
||||||
|
if group.Name == gname {
|
||||||
|
n[gname] = group
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var g []*Group
|
for _, target := range targets {
|
||||||
if len(targets) == 0 || (len(targets) == 1 && targets[0] == "default") {
|
if target == "default" {
|
||||||
for _, group := range c.Groups {
|
|
||||||
if group.Name != "default" {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
g = []*Group{{Targets: group.Targets}}
|
if _, ok := n["default"]; !ok {
|
||||||
|
n["default"] = &Group{Name: "default"}
|
||||||
}
|
}
|
||||||
} else {
|
n["default"].Targets = append(n["default"].Targets, target)
|
||||||
var gt []string
|
|
||||||
for _, target := range targets {
|
|
||||||
isGroup := false
|
|
||||||
for _, group := range c.Groups {
|
|
||||||
if target == group.Name {
|
|
||||||
gt = append(gt, group.Targets...)
|
|
||||||
isGroup = true
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
if g, ok := n["default"]; ok {
|
||||||
if !isGroup {
|
g.Targets = dedupSlice(g.Targets)
|
||||||
gt = append(gt, target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g = []*Group{{Targets: dedupString(gt)}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, t := range m {
|
for name, t := range m {
|
||||||
@@ -143,10 +140,23 @@ func ReadTargets(ctx context.Context, files []File, targets, overrides []string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return m, g, nil
|
// Propagate SOURCE_DATE_EPOCH from the client env.
|
||||||
|
// The logic is purposely duplicated from `build/build`.go for keeping this visible in `bake --print`.
|
||||||
|
if v := os.Getenv("SOURCE_DATE_EPOCH"); v != "" {
|
||||||
|
for _, f := range m {
|
||||||
|
if f.Args == nil {
|
||||||
|
f.Args = make(map[string]*string)
|
||||||
|
}
|
||||||
|
if _, ok := f.Args["SOURCE_DATE_EPOCH"]; !ok {
|
||||||
|
f.Args["SOURCE_DATE_EPOCH"] = &v
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func dedupString(s []string) []string {
|
return m, n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dedupSlice(s []string) []string {
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
@@ -161,21 +171,54 @@ func dedupString(s []string) []string {
|
|||||||
return res
|
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
|
||||||
|
}
|
||||||
|
|
||||||
func ParseFiles(files []File, defaults map[string]string) (_ *Config, err error) {
|
func ParseFiles(files []File, defaults map[string]string) (_ *Config, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
err = formatHCLError(err, files)
|
err = formatHCLError(err, files)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var c Config
|
var c Config
|
||||||
var fs []*hcl.File
|
var composeFiles []File
|
||||||
|
var hclFiles []*hcl.File
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
cfg, isCompose, composeErr := ParseComposeFile(f.Data, f.Name)
|
isCompose, composeErr := validateComposeFile(f.Data, f.Name)
|
||||||
if isCompose {
|
if isCompose {
|
||||||
if composeErr != nil {
|
if composeErr != nil {
|
||||||
return nil, composeErr
|
return nil, composeErr
|
||||||
}
|
}
|
||||||
c = mergeConfig(c, *cfg)
|
composeFiles = append(composeFiles, f)
|
||||||
c = dedupeConfig(c)
|
|
||||||
}
|
}
|
||||||
if !isCompose {
|
if !isCompose {
|
||||||
hf, isHCL, err := ParseHCLFile(f.Data, f.Name)
|
hf, isHCL, err := ParseHCLFile(f.Data, f.Name)
|
||||||
@@ -183,7 +226,7 @@ func ParseFiles(files []File, defaults map[string]string) (_ *Config, err error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
fs = append(fs, hf)
|
hclFiles = append(hclFiles, hf)
|
||||||
} else if composeErr != nil {
|
} else if composeErr != nil {
|
||||||
return nil, fmt.Errorf("failed to parse %s: parsing yaml: %v, parsing hcl: %w", f.Name, composeErr, err)
|
return nil, fmt.Errorf("failed to parse %s: parsing yaml: %v, parsing hcl: %w", f.Name, composeErr, err)
|
||||||
} else {
|
} else {
|
||||||
@@ -192,8 +235,17 @@ func ParseFiles(files []File, defaults map[string]string) (_ *Config, err error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(fs) > 0 {
|
if len(composeFiles) > 0 {
|
||||||
if err := hclparser.Parse(hcl.MergeFiles(fs), hclparser.Opt{
|
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,
|
LookupVar: os.LookupEnv,
|
||||||
Vars: defaults,
|
Vars: defaults,
|
||||||
ValidateLabel: validateTargetName,
|
ValidateLabel: validateTargetName,
|
||||||
@@ -201,18 +253,25 @@ func ParseFiles(files []File, defaults map[string]string) (_ *Config, err error)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &c, nil
|
return &c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dedupeConfig(c Config) Config {
|
func dedupeConfig(c Config) Config {
|
||||||
c2 := c
|
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))
|
c2.Targets = make([]*Target, 0, len(c2.Targets))
|
||||||
m := map[string]*Target{}
|
mt := map[string]*Target{}
|
||||||
for _, t := range c.Targets {
|
for _, t := range c.Targets {
|
||||||
if t2, ok := m[t.Name]; ok {
|
if t2, ok := mt[t.Name]; ok {
|
||||||
t2.Merge(t)
|
t2.Merge(t)
|
||||||
} else {
|
} else {
|
||||||
m[t.Name] = t
|
mt[t.Name] = t
|
||||||
c2.Targets = append(c2.Targets, t)
|
c2.Targets = append(c2.Targets, t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,22 +282,9 @@ func ParseFile(dt []byte, fn string) (*Config, error) {
|
|||||||
return ParseFiles([]File{{Data: dt, Name: fn}}, nil)
|
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 {
|
type Config struct {
|
||||||
Groups []*Group `json:"group" hcl:"group,block"`
|
Groups []*Group `json:"group" hcl:"group,block" cty:"group"`
|
||||||
Targets []*Target `json:"target" hcl:"target,block"`
|
Targets []*Target `json:"target" hcl:"target,block" cty:"target"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeConfig(c1, c2 Config) Config {
|
func mergeConfig(c1, c2 Config) Config {
|
||||||
@@ -384,7 +430,7 @@ func (c Config) newOverrides(v []string) (map[string]map[string]Override, error)
|
|||||||
o := t[kk[1]]
|
o := t[kk[1]]
|
||||||
|
|
||||||
switch keys[1] {
|
switch keys[1] {
|
||||||
case "output", "cache-to", "cache-from", "tags", "platform", "secrets", "ssh":
|
case "output", "cache-to", "cache-from", "tags", "platform", "secrets", "ssh", "attest":
|
||||||
if len(parts) == 2 {
|
if len(parts) == 2 {
|
||||||
o.ArrValue = append(o.ArrValue, parts[1])
|
o.ArrValue = append(o.ArrValue, parts[1])
|
||||||
}
|
}
|
||||||
@@ -417,13 +463,19 @@ func (c Config) newOverrides(v []string) (map[string]map[string]Override, error)
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Config) ResolveGroup(name string) []string {
|
func (c Config) ResolveGroup(name string) ([]string, []string) {
|
||||||
return dedupString(c.group(name, map[string][]string{}))
|
targets, groups := c.group(name, map[string]visit{})
|
||||||
|
return dedupSlice(targets), dedupSlice(groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Config) group(name string, visited map[string][]string) []string {
|
type visit struct {
|
||||||
if _, ok := visited[name]; ok {
|
target []string
|
||||||
return visited[name]
|
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
|
||||||
}
|
}
|
||||||
var g *Group
|
var g *Group
|
||||||
for _, group := range c.Groups {
|
for _, group := range c.Groups {
|
||||||
@@ -433,20 +485,24 @@ func (c Config) group(name string, visited map[string][]string) []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if g == nil {
|
if g == nil {
|
||||||
return []string{name}
|
return []string{name}, nil
|
||||||
}
|
}
|
||||||
visited[name] = []string{}
|
visited[name] = visit{}
|
||||||
targets := make([]string, 0, len(g.Targets))
|
targets := make([]string, 0, len(g.Targets))
|
||||||
|
groups := []string{name}
|
||||||
for _, t := range g.Targets {
|
for _, t := range g.Targets {
|
||||||
tgroup := c.group(t, visited)
|
ttarget, tgroup := c.group(t, visited)
|
||||||
if len(tgroup) > 0 {
|
if len(ttarget) > 0 {
|
||||||
targets = append(targets, tgroup...)
|
targets = append(targets, ttarget...)
|
||||||
} else {
|
} else {
|
||||||
targets = append(targets, t)
|
targets = append(targets, t)
|
||||||
}
|
}
|
||||||
|
if len(tgroup) > 0 {
|
||||||
|
groups = append(groups, tgroup...)
|
||||||
}
|
}
|
||||||
visited[name] = targets
|
}
|
||||||
return targets
|
visited[name] = visit{target: targets, group: groups}
|
||||||
|
return targets, groups
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Config) ResolveTarget(name string, overrides map[string]map[string]Override) (*Target, error) {
|
func (c Config) ResolveTarget(name string, overrides map[string]map[string]Override) (*Target, error) {
|
||||||
@@ -504,42 +560,44 @@ func (c Config) target(name string, visited map[string]*Target, overrides map[st
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Group struct {
|
type Group struct {
|
||||||
Name string `json:"-" hcl:"name,label"`
|
Name string `json:"-" hcl:"name,label" cty:"name"`
|
||||||
Targets []string `json:"targets" hcl:"targets"`
|
Targets []string `json:"targets" hcl:"targets" cty:"targets"`
|
||||||
// Target // TODO?
|
// Target // TODO?
|
||||||
}
|
}
|
||||||
|
|
||||||
type Target struct {
|
type Target struct {
|
||||||
Name string `json:"-" hcl:"name,label"`
|
Name string `json:"-" hcl:"name,label" cty:"name"`
|
||||||
|
|
||||||
// Inherits is the only field that cannot be overridden with --set
|
// Inherits is the only field that cannot be overridden with --set
|
||||||
Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional"`
|
Attest []string `json:"attest,omitempty" hcl:"attest,optional" cty:"attest"`
|
||||||
|
Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional" cty:"inherits"`
|
||||||
|
|
||||||
Context *string `json:"context,omitempty" hcl:"context,optional"`
|
Context *string `json:"context,omitempty" hcl:"context,optional" cty:"context"`
|
||||||
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional"`
|
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional" cty:"contexts"`
|
||||||
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional"`
|
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional" cty:"dockerfile"`
|
||||||
DockerfileInline *string `json:"dockerfile-inline,omitempty" hcl:"dockerfile-inline,optional"`
|
DockerfileInline *string `json:"dockerfile-inline,omitempty" hcl:"dockerfile-inline,optional" cty:"dockerfile-inline"`
|
||||||
Args map[string]string `json:"args,omitempty" hcl:"args,optional"`
|
Args map[string]*string `json:"args,omitempty" hcl:"args,optional" cty:"args"`
|
||||||
Labels map[string]string `json:"labels,omitempty" hcl:"labels,optional"`
|
Labels map[string]*string `json:"labels,omitempty" hcl:"labels,optional" cty:"labels"`
|
||||||
Tags []string `json:"tags,omitempty" hcl:"tags,optional"`
|
Tags []string `json:"tags,omitempty" hcl:"tags,optional" cty:"tags"`
|
||||||
CacheFrom []string `json:"cache-from,omitempty" hcl:"cache-from,optional"`
|
CacheFrom []string `json:"cache-from,omitempty" hcl:"cache-from,optional" cty:"cache-from"`
|
||||||
CacheTo []string `json:"cache-to,omitempty" hcl:"cache-to,optional"`
|
CacheTo []string `json:"cache-to,omitempty" hcl:"cache-to,optional" cty:"cache-to"`
|
||||||
Target *string `json:"target,omitempty" hcl:"target,optional"`
|
Target *string `json:"target,omitempty" hcl:"target,optional" cty:"target"`
|
||||||
Secrets []string `json:"secret,omitempty" hcl:"secret,optional"`
|
Secrets []string `json:"secret,omitempty" hcl:"secret,optional" cty:"secret"`
|
||||||
SSH []string `json:"ssh,omitempty" hcl:"ssh,optional"`
|
SSH []string `json:"ssh,omitempty" hcl:"ssh,optional" cty:"ssh"`
|
||||||
Platforms []string `json:"platforms,omitempty" hcl:"platforms,optional"`
|
Platforms []string `json:"platforms,omitempty" hcl:"platforms,optional" cty:"platforms"`
|
||||||
Outputs []string `json:"output,omitempty" hcl:"output,optional"`
|
Outputs []string `json:"output,omitempty" hcl:"output,optional" cty:"output"`
|
||||||
Pull *bool `json:"pull,omitempty" hcl:"pull,optional"`
|
Pull *bool `json:"pull,omitempty" hcl:"pull,optional" cty:"pull"`
|
||||||
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional"`
|
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional" cty:"no-cache"`
|
||||||
NetworkMode *string `json:"-" hcl:"-"`
|
NetworkMode *string `json:"-" hcl:"-" cty:"-"`
|
||||||
NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional"`
|
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/guides/bake/file-definition.md.
|
// IMPORTANT: if you add more fields here, do not forget to update newOverrides and docs/bake-reference.md.
|
||||||
|
|
||||||
// linked is a private field to mark a target used as a linked one
|
// linked is a private field to mark a target used as a linked one
|
||||||
linked bool
|
linked bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Target) normalize() {
|
func (t *Target) normalize() {
|
||||||
|
t.Attest = removeDupes(t.Attest)
|
||||||
t.Tags = removeDupes(t.Tags)
|
t.Tags = removeDupes(t.Tags)
|
||||||
t.Secrets = removeDupes(t.Secrets)
|
t.Secrets = removeDupes(t.Secrets)
|
||||||
t.SSH = removeDupes(t.SSH)
|
t.SSH = removeDupes(t.SSH)
|
||||||
@@ -570,8 +628,11 @@ func (t *Target) Merge(t2 *Target) {
|
|||||||
t.DockerfileInline = t2.DockerfileInline
|
t.DockerfileInline = t2.DockerfileInline
|
||||||
}
|
}
|
||||||
for k, v := range t2.Args {
|
for k, v := range t2.Args {
|
||||||
|
if v == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if t.Args == nil {
|
if t.Args == nil {
|
||||||
t.Args = map[string]string{}
|
t.Args = map[string]*string{}
|
||||||
}
|
}
|
||||||
t.Args[k] = v
|
t.Args[k] = v
|
||||||
}
|
}
|
||||||
@@ -582,8 +643,11 @@ func (t *Target) Merge(t2 *Target) {
|
|||||||
t.Contexts[k] = v
|
t.Contexts[k] = v
|
||||||
}
|
}
|
||||||
for k, v := range t2.Labels {
|
for k, v := range t2.Labels {
|
||||||
|
if v == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if t.Labels == nil {
|
if t.Labels == nil {
|
||||||
t.Labels = map[string]string{}
|
t.Labels = map[string]*string{}
|
||||||
}
|
}
|
||||||
t.Labels[k] = v
|
t.Labels[k] = v
|
||||||
}
|
}
|
||||||
@@ -593,6 +657,9 @@ func (t *Target) Merge(t2 *Target) {
|
|||||||
if t2.Target != nil {
|
if t2.Target != nil {
|
||||||
t.Target = t2.Target
|
t.Target = t2.Target
|
||||||
}
|
}
|
||||||
|
if t2.Attest != nil { // merge
|
||||||
|
t.Attest = append(t.Attest, t2.Attest...)
|
||||||
|
}
|
||||||
if t2.Secrets != nil { // merge
|
if t2.Secrets != nil { // merge
|
||||||
t.Secrets = append(t.Secrets, t2.Secrets...)
|
t.Secrets = append(t.Secrets, t2.Secrets...)
|
||||||
}
|
}
|
||||||
@@ -640,9 +707,9 @@ func (t *Target) AddOverrides(overrides map[string]Override) error {
|
|||||||
return errors.Errorf("args require name")
|
return errors.Errorf("args require name")
|
||||||
}
|
}
|
||||||
if t.Args == nil {
|
if t.Args == nil {
|
||||||
t.Args = map[string]string{}
|
t.Args = map[string]*string{}
|
||||||
}
|
}
|
||||||
t.Args[keys[1]] = value
|
t.Args[keys[1]] = &value
|
||||||
case "contexts":
|
case "contexts":
|
||||||
if len(keys) != 2 {
|
if len(keys) != 2 {
|
||||||
return errors.Errorf("contexts require name")
|
return errors.Errorf("contexts require name")
|
||||||
@@ -656,9 +723,9 @@ func (t *Target) AddOverrides(overrides map[string]Override) error {
|
|||||||
return errors.Errorf("labels require name")
|
return errors.Errorf("labels require name")
|
||||||
}
|
}
|
||||||
if t.Labels == nil {
|
if t.Labels == nil {
|
||||||
t.Labels = map[string]string{}
|
t.Labels = map[string]*string{}
|
||||||
}
|
}
|
||||||
t.Labels[keys[1]] = value
|
t.Labels[keys[1]] = &value
|
||||||
case "tags":
|
case "tags":
|
||||||
t.Tags = o.ArrValue
|
t.Tags = o.ArrValue
|
||||||
case "cache-from":
|
case "cache-from":
|
||||||
@@ -675,6 +742,8 @@ func (t *Target) AddOverrides(overrides map[string]Override) error {
|
|||||||
t.Platforms = o.ArrValue
|
t.Platforms = o.ArrValue
|
||||||
case "output":
|
case "output":
|
||||||
t.Outputs = o.ArrValue
|
t.Outputs = o.ArrValue
|
||||||
|
case "attest":
|
||||||
|
t.Attest = append(t.Attest, o.ArrValue...)
|
||||||
case "no-cache":
|
case "no-cache":
|
||||||
noCache, err := strconv.ParseBool(value)
|
noCache, err := strconv.ParseBool(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -832,6 +901,22 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
|||||||
dockerfilePath = path.Join(contextPath, dockerfilePath)
|
dockerfilePath = path.Join(contextPath, dockerfilePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args := map[string]string{}
|
||||||
|
for k, v := range t.Args {
|
||||||
|
if v == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
args[k] = *v
|
||||||
|
}
|
||||||
|
|
||||||
|
labels := map[string]string{}
|
||||||
|
for k, v := range t.Labels {
|
||||||
|
if v == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
labels[k] = *v
|
||||||
|
}
|
||||||
|
|
||||||
noCache := false
|
noCache := false
|
||||||
if t.NoCache != nil {
|
if t.NoCache != nil {
|
||||||
noCache = *t.NoCache
|
noCache = *t.NoCache
|
||||||
@@ -872,8 +957,8 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
|||||||
bo := &build.Options{
|
bo := &build.Options{
|
||||||
Inputs: bi,
|
Inputs: bi,
|
||||||
Tags: t.Tags,
|
Tags: t.Tags,
|
||||||
BuildArgs: t.Args,
|
BuildArgs: args,
|
||||||
Labels: t.Labels,
|
Labels: labels,
|
||||||
NoCache: noCache,
|
NoCache: noCache,
|
||||||
NoCacheFilter: t.NoCacheFilter,
|
NoCacheFilter: t.NoCacheFilter,
|
||||||
Pull: pull,
|
Pull: pull,
|
||||||
@@ -928,6 +1013,12 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
|||||||
}
|
}
|
||||||
bo.Exports = outputs
|
bo.Exports = outputs
|
||||||
|
|
||||||
|
attests, err := buildflags.ParseAttests(t.Attest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bo.Attests = attests
|
||||||
|
|
||||||
return bo, nil
|
return bo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package bake
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -41,12 +41,12 @@ target "webapp" {
|
|||||||
|
|
||||||
require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile)
|
require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile)
|
||||||
require.Equal(t, ".", *m["webapp"].Context)
|
require.Equal(t, ".", *m["webapp"].Context)
|
||||||
require.Equal(t, "webDEP", m["webapp"].Args["VAR_INHERITED"])
|
require.Equal(t, ptrstr("webDEP"), m["webapp"].Args["VAR_INHERITED"])
|
||||||
require.Equal(t, true, *m["webapp"].NoCache)
|
require.Equal(t, true, *m["webapp"].NoCache)
|
||||||
require.Nil(t, m["webapp"].Pull)
|
require.Nil(t, m["webapp"].Pull)
|
||||||
|
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
require.Equal(t, []string{"webapp"}, g[0].Targets)
|
require.Equal(t, []string{"webapp"}, g["default"].Targets)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("InvalidTargetOverrides", func(t *testing.T) {
|
t.Run("InvalidTargetOverrides", func(t *testing.T) {
|
||||||
@@ -57,8 +57,7 @@ target "webapp" {
|
|||||||
|
|
||||||
t.Run("ArgsOverrides", func(t *testing.T) {
|
t.Run("ArgsOverrides", func(t *testing.T) {
|
||||||
t.Run("leaf", func(t *testing.T) {
|
t.Run("leaf", func(t *testing.T) {
|
||||||
os.Setenv("VAR_FROMENV"+t.Name(), "fromEnv")
|
t.Setenv("VAR_FROMENV"+t.Name(), "fromEnv")
|
||||||
defer os.Unsetenv("VAR_FROM_ENV" + t.Name())
|
|
||||||
|
|
||||||
m, g, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{
|
m, g, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{
|
||||||
"webapp.args.VAR_UNSET",
|
"webapp.args.VAR_UNSET",
|
||||||
@@ -79,15 +78,15 @@ target "webapp" {
|
|||||||
_, isSet = m["webapp"].Args["VAR_EMPTY"]
|
_, isSet = m["webapp"].Args["VAR_EMPTY"]
|
||||||
require.True(t, isSet, m["webapp"].Args["VAR_EMPTY"])
|
require.True(t, isSet, m["webapp"].Args["VAR_EMPTY"])
|
||||||
|
|
||||||
require.Equal(t, m["webapp"].Args["VAR_SET"], "bananas")
|
require.Equal(t, ptrstr("bananas"), m["webapp"].Args["VAR_SET"])
|
||||||
|
|
||||||
require.Equal(t, m["webapp"].Args["VAR_FROMENV"+t.Name()], "fromEnv")
|
require.Equal(t, ptrstr("fromEnv"), m["webapp"].Args["VAR_FROMENV"+t.Name()])
|
||||||
|
|
||||||
require.Equal(t, m["webapp"].Args["VAR_BOTH"], "webapp")
|
require.Equal(t, ptrstr("webapp"), m["webapp"].Args["VAR_BOTH"])
|
||||||
require.Equal(t, m["webapp"].Args["VAR_INHERITED"], "override")
|
require.Equal(t, ptrstr("override"), m["webapp"].Args["VAR_INHERITED"])
|
||||||
|
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
require.Equal(t, []string{"webapp"}, g[0].Targets)
|
require.Equal(t, []string{"webapp"}, g["default"].Targets)
|
||||||
})
|
})
|
||||||
|
|
||||||
// building leaf but overriding parent fields
|
// building leaf but overriding parent fields
|
||||||
@@ -98,10 +97,10 @@ target "webapp" {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, m["webapp"].Args["VAR_INHERITED"], "override")
|
require.Equal(t, ptrstr("override"), m["webapp"].Args["VAR_INHERITED"])
|
||||||
require.Equal(t, m["webapp"].Args["VAR_BOTH"], "webapp")
|
require.Equal(t, ptrstr("webapp"), m["webapp"].Args["VAR_BOTH"])
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
require.Equal(t, []string{"webapp"}, g[0].Targets)
|
require.Equal(t, []string{"webapp"}, g["default"].Targets)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -113,7 +112,7 @@ target "webapp" {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "foo", *m["webapp"].Context)
|
require.Equal(t, "foo", *m["webapp"].Context)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
require.Equal(t, []string{"webapp"}, g[0].Targets)
|
require.Equal(t, []string{"webapp"}, g["default"].Targets)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("NoCacheOverride", func(t *testing.T) {
|
t.Run("NoCacheOverride", func(t *testing.T) {
|
||||||
@@ -121,7 +120,7 @@ target "webapp" {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, false, *m["webapp"].NoCache)
|
require.Equal(t, false, *m["webapp"].NoCache)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
require.Equal(t, []string{"webapp"}, g[0].Targets)
|
require.Equal(t, []string{"webapp"}, g["default"].Targets)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("PullOverride", func(t *testing.T) {
|
t.Run("PullOverride", func(t *testing.T) {
|
||||||
@@ -129,28 +128,28 @@ target "webapp" {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, false, *m["webapp"].Pull)
|
require.Equal(t, false, *m["webapp"].Pull)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
require.Equal(t, []string{"webapp"}, g[0].Targets)
|
require.Equal(t, []string{"webapp"}, g["default"].Targets)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("PatternOverride", func(t *testing.T) {
|
t.Run("PatternOverride", func(t *testing.T) {
|
||||||
// same check for two cases
|
// same check for two cases
|
||||||
multiTargetCheck := func(t *testing.T, m map[string]*Target, g []*Group, err error) {
|
multiTargetCheck := func(t *testing.T, m map[string]*Target, g map[string]*Group, err error) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, len(m))
|
require.Equal(t, 2, len(m))
|
||||||
require.Equal(t, "foo", *m["webapp"].Dockerfile)
|
require.Equal(t, "foo", *m["webapp"].Dockerfile)
|
||||||
require.Equal(t, "webDEP", m["webapp"].Args["VAR_INHERITED"])
|
require.Equal(t, ptrstr("webDEP"), m["webapp"].Args["VAR_INHERITED"])
|
||||||
require.Equal(t, "foo", *m["webDEP"].Dockerfile)
|
require.Equal(t, "foo", *m["webDEP"].Dockerfile)
|
||||||
require.Equal(t, "webDEP", m["webDEP"].Args["VAR_INHERITED"])
|
require.Equal(t, ptrstr("webDEP"), m["webDEP"].Args["VAR_INHERITED"])
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
sort.Strings(g[0].Targets)
|
sort.Strings(g["default"].Targets)
|
||||||
require.Equal(t, []string{"webDEP", "webapp"}, g[0].Targets)
|
require.Equal(t, []string{"webDEP", "webapp"}, g["default"].Targets)
|
||||||
}
|
}
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
targets []string
|
targets []string
|
||||||
overrides []string
|
overrides []string
|
||||||
check func(*testing.T, map[string]*Target, []*Group, error)
|
check func(*testing.T, map[string]*Target, map[string]*Group, error)
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "multi target single pattern",
|
name: "multi target single pattern",
|
||||||
@@ -168,20 +167,20 @@ target "webapp" {
|
|||||||
name: "single target",
|
name: "single target",
|
||||||
targets: []string{"webapp"},
|
targets: []string{"webapp"},
|
||||||
overrides: []string{"web*.dockerfile=foo"},
|
overrides: []string{"web*.dockerfile=foo"},
|
||||||
check: func(t *testing.T, m map[string]*Target, g []*Group, err error) {
|
check: func(t *testing.T, m map[string]*Target, g map[string]*Group, err error) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(m))
|
require.Equal(t, 1, len(m))
|
||||||
require.Equal(t, "foo", *m["webapp"].Dockerfile)
|
require.Equal(t, "foo", *m["webapp"].Dockerfile)
|
||||||
require.Equal(t, "webDEP", m["webapp"].Args["VAR_INHERITED"])
|
require.Equal(t, ptrstr("webDEP"), m["webapp"].Args["VAR_INHERITED"])
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
require.Equal(t, []string{"webapp"}, g[0].Targets)
|
require.Equal(t, []string{"webapp"}, g["default"].Targets)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "nomatch",
|
name: "nomatch",
|
||||||
targets: []string{"webapp"},
|
targets: []string{"webapp"},
|
||||||
overrides: []string{"nomatch*.dockerfile=foo"},
|
overrides: []string{"nomatch*.dockerfile=foo"},
|
||||||
check: func(t *testing.T, m map[string]*Target, g []*Group, err error) {
|
check: func(t *testing.T, m map[string]*Target, g map[string]*Group, err error) {
|
||||||
// NOTE: I am unsure whether failing to match should always error out
|
// NOTE: I am unsure whether failing to match should always error out
|
||||||
// instead of simply skipping that override.
|
// instead of simply skipping that override.
|
||||||
// Let's enforce the error and we can relax it later if users complain.
|
// Let's enforce the error and we can relax it later if users complain.
|
||||||
@@ -299,12 +298,12 @@ services:
|
|||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile)
|
require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile)
|
||||||
require.Equal(t, ".", *m["webapp"].Context)
|
require.Equal(t, ".", *m["webapp"].Context)
|
||||||
require.Equal(t, "1", m["webapp"].Args["buildno"])
|
require.Equal(t, ptrstr("1"), m["webapp"].Args["buildno"])
|
||||||
require.Equal(t, "12", m["webapp"].Args["buildno2"])
|
require.Equal(t, ptrstr("12"), m["webapp"].Args["buildno2"])
|
||||||
|
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
sort.Strings(g[0].Targets)
|
sort.Strings(g["default"].Targets)
|
||||||
require.Equal(t, []string{"db", "newservice", "webapp"}, g[0].Targets)
|
require.Equal(t, []string{"db", "newservice", "webapp"}, g["default"].Targets)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadTargetsWithDotCompose(t *testing.T) {
|
func TestReadTargetsWithDotCompose(t *testing.T) {
|
||||||
@@ -343,7 +342,7 @@ services:
|
|||||||
_, ok := m["web_app"]
|
_, ok := m["web_app"]
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
require.Equal(t, "Dockerfile.webapp", *m["web_app"].Dockerfile)
|
require.Equal(t, "Dockerfile.webapp", *m["web_app"].Dockerfile)
|
||||||
require.Equal(t, "1", m["web_app"].Args["buildno"])
|
require.Equal(t, ptrstr("1"), m["web_app"].Args["buildno"])
|
||||||
|
|
||||||
m, _, err = ReadTargets(ctx, []File{fp2}, []string{"web_app"}, nil, nil)
|
m, _, err = ReadTargets(ctx, []File{fp2}, []string{"web_app"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -351,7 +350,7 @@ services:
|
|||||||
_, ok = m["web_app"]
|
_, ok = m["web_app"]
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
require.Equal(t, "Dockerfile", *m["web_app"].Dockerfile)
|
require.Equal(t, "Dockerfile", *m["web_app"].Dockerfile)
|
||||||
require.Equal(t, "12", m["web_app"].Args["buildno2"])
|
require.Equal(t, ptrstr("12"), m["web_app"].Args["buildno2"])
|
||||||
|
|
||||||
m, g, err := ReadTargets(ctx, []File{fp, fp2}, []string{"default"}, nil, nil)
|
m, g, err := ReadTargets(ctx, []File{fp, fp2}, []string{"default"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -360,12 +359,12 @@ services:
|
|||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
require.Equal(t, "Dockerfile.webapp", *m["web_app"].Dockerfile)
|
require.Equal(t, "Dockerfile.webapp", *m["web_app"].Dockerfile)
|
||||||
require.Equal(t, ".", *m["web_app"].Context)
|
require.Equal(t, ".", *m["web_app"].Context)
|
||||||
require.Equal(t, "1", m["web_app"].Args["buildno"])
|
require.Equal(t, ptrstr("1"), m["web_app"].Args["buildno"])
|
||||||
require.Equal(t, "12", m["web_app"].Args["buildno2"])
|
require.Equal(t, ptrstr("12"), m["web_app"].Args["buildno2"])
|
||||||
|
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
sort.Strings(g[0].Targets)
|
sort.Strings(g["default"].Targets)
|
||||||
require.Equal(t, []string{"web_app"}, g[0].Targets)
|
require.Equal(t, []string{"web_app"}, g["default"].Targets)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHCLCwdPrefix(t *testing.T) {
|
func TestHCLCwdPrefix(t *testing.T) {
|
||||||
@@ -392,7 +391,7 @@ func TestHCLCwdPrefix(t *testing.T) {
|
|||||||
require.Equal(t, "foo", *m["app"].Context)
|
require.Equal(t, "foo", *m["app"].Context)
|
||||||
|
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
require.Equal(t, []string{"app"}, g[0].Targets)
|
require.Equal(t, []string{"app"}, g["default"].Targets)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOverrideMerge(t *testing.T) {
|
func TestOverrideMerge(t *testing.T) {
|
||||||
@@ -530,7 +529,8 @@ func TestReadEmptyTargets(t *testing.T) {
|
|||||||
Name: "docker-compose.yml",
|
Name: "docker-compose.yml",
|
||||||
Data: []byte(`
|
Data: []byte(`
|
||||||
services:
|
services:
|
||||||
app2: {}
|
app2:
|
||||||
|
build: {}
|
||||||
`),
|
`),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,7 +695,7 @@ target "image" {
|
|||||||
m, g, err := ReadTargets(ctx, []File{f}, []string{"image"}, nil, nil)
|
m, g, err := ReadTargets(ctx, []File{f}, []string{"image"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
require.Equal(t, []string{"image"}, g[0].Targets)
|
require.Equal(t, []string{"image"}, g["default"].Targets)
|
||||||
require.Equal(t, 1, len(m))
|
require.Equal(t, 1, len(m))
|
||||||
require.Equal(t, "test", *m["image"].Dockerfile)
|
require.Equal(t, "test", *m["image"].Dockerfile)
|
||||||
}
|
}
|
||||||
@@ -716,8 +716,9 @@ target "image" {
|
|||||||
|
|
||||||
m, g, err := ReadTargets(ctx, []File{f}, []string{"foo"}, nil, nil)
|
m, g, err := ReadTargets(ctx, []File{f}, []string{"foo"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 2, len(g))
|
||||||
require.Equal(t, []string{"image"}, g[0].Targets)
|
require.Equal(t, []string{"foo"}, g["default"].Targets)
|
||||||
|
require.Equal(t, []string{"image"}, g["foo"].Targets)
|
||||||
require.Equal(t, 1, len(m))
|
require.Equal(t, 1, len(m))
|
||||||
require.Equal(t, "test", *m["image"].Dockerfile)
|
require.Equal(t, "test", *m["image"].Dockerfile)
|
||||||
}
|
}
|
||||||
@@ -741,15 +742,17 @@ target "image" {
|
|||||||
|
|
||||||
m, g, err := ReadTargets(ctx, []File{f}, []string{"foo"}, nil, nil)
|
m, g, err := ReadTargets(ctx, []File{f}, []string{"foo"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 2, len(g))
|
||||||
require.Equal(t, []string{"image"}, g[0].Targets)
|
require.Equal(t, []string{"foo"}, g["default"].Targets)
|
||||||
|
require.Equal(t, []string{"image"}, g["foo"].Targets)
|
||||||
require.Equal(t, 1, len(m))
|
require.Equal(t, 1, len(m))
|
||||||
require.Equal(t, "test", *m["image"].Dockerfile)
|
require.Equal(t, "test", *m["image"].Dockerfile)
|
||||||
|
|
||||||
m, g, err = ReadTargets(ctx, []File{f}, []string{"foo", "foo"}, nil, nil)
|
m, g, err = ReadTargets(ctx, []File{f}, []string{"foo", "foo"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 2, len(g))
|
||||||
require.Equal(t, []string{"image"}, g[0].Targets)
|
require.Equal(t, []string{"foo"}, g["default"].Targets)
|
||||||
|
require.Equal(t, []string{"image"}, g["foo"].Targets)
|
||||||
require.Equal(t, 1, len(m))
|
require.Equal(t, 1, len(m))
|
||||||
require.Equal(t, "test", *m["image"].Dockerfile)
|
require.Equal(t, "test", *m["image"].Dockerfile)
|
||||||
}
|
}
|
||||||
@@ -828,7 +831,7 @@ services:
|
|||||||
m, g, err := ReadTargets(ctx, []File{fhcl}, []string{"default"}, nil, nil)
|
m, g, err := ReadTargets(ctx, []File{fhcl}, []string{"default"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
require.Equal(t, []string{"image"}, g[0].Targets)
|
require.Equal(t, []string{"image"}, g["default"].Targets)
|
||||||
require.Equal(t, 1, len(m))
|
require.Equal(t, 1, len(m))
|
||||||
require.Equal(t, 1, len(m["image"].Outputs))
|
require.Equal(t, 1, len(m["image"].Outputs))
|
||||||
require.Equal(t, "type=docker", m["image"].Outputs[0])
|
require.Equal(t, "type=docker", m["image"].Outputs[0])
|
||||||
@@ -836,7 +839,7 @@ services:
|
|||||||
m, g, err = ReadTargets(ctx, []File{fhcl}, []string{"image-release"}, nil, nil)
|
m, g, err = ReadTargets(ctx, []File{fhcl}, []string{"image-release"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
require.Equal(t, []string{"image-release"}, g[0].Targets)
|
require.Equal(t, []string{"image-release"}, g["default"].Targets)
|
||||||
require.Equal(t, 1, len(m))
|
require.Equal(t, 1, len(m))
|
||||||
require.Equal(t, 1, len(m["image-release"].Outputs))
|
require.Equal(t, 1, len(m["image-release"].Outputs))
|
||||||
require.Equal(t, "type=image,push=true", m["image-release"].Outputs[0])
|
require.Equal(t, "type=image,push=true", m["image-release"].Outputs[0])
|
||||||
@@ -844,7 +847,7 @@ services:
|
|||||||
m, g, err = ReadTargets(ctx, []File{fhcl}, []string{"image", "image-release"}, nil, nil)
|
m, g, err = ReadTargets(ctx, []File{fhcl}, []string{"image", "image-release"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
require.Equal(t, []string{"image", "image-release"}, g[0].Targets)
|
require.Equal(t, []string{"image", "image-release"}, g["default"].Targets)
|
||||||
require.Equal(t, 2, len(m))
|
require.Equal(t, 2, len(m))
|
||||||
require.Equal(t, ".", *m["image"].Context)
|
require.Equal(t, ".", *m["image"].Context)
|
||||||
require.Equal(t, 1, len(m["image-release"].Outputs))
|
require.Equal(t, 1, len(m["image-release"].Outputs))
|
||||||
@@ -853,22 +856,22 @@ services:
|
|||||||
m, g, err = ReadTargets(ctx, []File{fyml, fhcl}, []string{"default"}, nil, nil)
|
m, g, err = ReadTargets(ctx, []File{fyml, fhcl}, []string{"default"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
require.Equal(t, []string{"image"}, g[0].Targets)
|
require.Equal(t, []string{"image"}, g["default"].Targets)
|
||||||
require.Equal(t, 1, len(m))
|
require.Equal(t, 1, len(m))
|
||||||
require.Equal(t, ".", *m["image"].Context)
|
require.Equal(t, ".", *m["image"].Context)
|
||||||
|
|
||||||
m, g, err = ReadTargets(ctx, []File{fjson}, []string{"default"}, nil, nil)
|
m, g, err = ReadTargets(ctx, []File{fjson}, []string{"default"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
require.Equal(t, []string{"image"}, g[0].Targets)
|
require.Equal(t, []string{"image"}, g["default"].Targets)
|
||||||
require.Equal(t, 1, len(m))
|
require.Equal(t, 1, len(m))
|
||||||
require.Equal(t, ".", *m["image"].Context)
|
require.Equal(t, ".", *m["image"].Context)
|
||||||
|
|
||||||
m, g, err = ReadTargets(ctx, []File{fyml}, []string{"default"}, nil, nil)
|
m, g, err = ReadTargets(ctx, []File{fyml}, []string{"default"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
sort.Strings(g[0].Targets)
|
sort.Strings(g["default"].Targets)
|
||||||
require.Equal(t, []string{"addon", "aws"}, g[0].Targets)
|
require.Equal(t, []string{"addon", "aws"}, g["default"].Targets)
|
||||||
require.Equal(t, 2, len(m))
|
require.Equal(t, 2, len(m))
|
||||||
require.Equal(t, "./Dockerfile", *m["addon"].Dockerfile)
|
require.Equal(t, "./Dockerfile", *m["addon"].Dockerfile)
|
||||||
require.Equal(t, "./aws.Dockerfile", *m["aws"].Dockerfile)
|
require.Equal(t, "./aws.Dockerfile", *m["aws"].Dockerfile)
|
||||||
@@ -876,8 +879,8 @@ services:
|
|||||||
m, g, err = ReadTargets(ctx, []File{fyml, fhcl}, []string{"addon", "aws"}, nil, nil)
|
m, g, err = ReadTargets(ctx, []File{fyml, fhcl}, []string{"addon", "aws"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
sort.Strings(g[0].Targets)
|
sort.Strings(g["default"].Targets)
|
||||||
require.Equal(t, []string{"addon", "aws"}, g[0].Targets)
|
require.Equal(t, []string{"addon", "aws"}, g["default"].Targets)
|
||||||
require.Equal(t, 2, len(m))
|
require.Equal(t, 2, len(m))
|
||||||
require.Equal(t, "./Dockerfile", *m["addon"].Dockerfile)
|
require.Equal(t, "./Dockerfile", *m["addon"].Dockerfile)
|
||||||
require.Equal(t, "./aws.Dockerfile", *m["aws"].Dockerfile)
|
require.Equal(t, "./aws.Dockerfile", *m["aws"].Dockerfile)
|
||||||
@@ -885,8 +888,8 @@ services:
|
|||||||
m, g, err = ReadTargets(ctx, []File{fyml, fhcl}, []string{"addon", "aws", "image"}, nil, nil)
|
m, g, err = ReadTargets(ctx, []File{fyml, fhcl}, []string{"addon", "aws", "image"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
sort.Strings(g[0].Targets)
|
sort.Strings(g["default"].Targets)
|
||||||
require.Equal(t, []string{"addon", "aws", "image"}, g[0].Targets)
|
require.Equal(t, []string{"addon", "aws", "image"}, g["default"].Targets)
|
||||||
require.Equal(t, 3, len(m))
|
require.Equal(t, 3, len(m))
|
||||||
require.Equal(t, ".", *m["image"].Context)
|
require.Equal(t, ".", *m["image"].Context)
|
||||||
require.Equal(t, "./Dockerfile", *m["addon"].Dockerfile)
|
require.Equal(t, "./Dockerfile", *m["addon"].Dockerfile)
|
||||||
@@ -912,15 +915,17 @@ target "image" {
|
|||||||
|
|
||||||
m, g, err := ReadTargets(ctx, []File{f}, []string{"foo"}, nil, nil)
|
m, g, err := ReadTargets(ctx, []File{f}, []string{"foo"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 2, len(g))
|
||||||
require.Equal(t, []string{"foo"}, g[0].Targets)
|
require.Equal(t, []string{"foo"}, g["default"].Targets)
|
||||||
|
require.Equal(t, []string{"foo"}, g["foo"].Targets)
|
||||||
require.Equal(t, 1, len(m))
|
require.Equal(t, 1, len(m))
|
||||||
require.Equal(t, "bar", *m["foo"].Dockerfile)
|
require.Equal(t, "bar", *m["foo"].Dockerfile)
|
||||||
|
|
||||||
m, g, err = ReadTargets(ctx, []File{f}, []string{"foo", "foo"}, nil, nil)
|
m, g, err = ReadTargets(ctx, []File{f}, []string{"foo", "foo"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 2, len(g))
|
||||||
require.Equal(t, []string{"foo"}, g[0].Targets)
|
require.Equal(t, []string{"foo"}, g["default"].Targets)
|
||||||
|
require.Equal(t, []string{"foo"}, g["foo"].Targets)
|
||||||
require.Equal(t, 1, len(m))
|
require.Equal(t, 1, len(m))
|
||||||
require.Equal(t, "bar", *m["foo"].Dockerfile)
|
require.Equal(t, "bar", *m["foo"].Dockerfile)
|
||||||
}
|
}
|
||||||
@@ -944,16 +949,18 @@ target "image" {
|
|||||||
|
|
||||||
m, g, err := ReadTargets(ctx, []File{f}, []string{"foo"}, nil, nil)
|
m, g, err := ReadTargets(ctx, []File{f}, []string{"foo"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 2, len(g))
|
||||||
require.Equal(t, []string{"foo", "image"}, g[0].Targets)
|
require.Equal(t, []string{"foo"}, g["default"].Targets)
|
||||||
|
require.Equal(t, []string{"foo", "image"}, g["foo"].Targets)
|
||||||
require.Equal(t, 2, len(m))
|
require.Equal(t, 2, len(m))
|
||||||
require.Equal(t, "bar", *m["foo"].Dockerfile)
|
require.Equal(t, "bar", *m["foo"].Dockerfile)
|
||||||
require.Equal(t, "type=docker", m["image"].Outputs[0])
|
require.Equal(t, "type=docker", m["image"].Outputs[0])
|
||||||
|
|
||||||
m, g, err = ReadTargets(ctx, []File{f}, []string{"foo", "image"}, nil, nil)
|
m, g, err = ReadTargets(ctx, []File{f}, []string{"foo", "image"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 2, len(g))
|
||||||
require.Equal(t, []string{"foo", "image"}, g[0].Targets)
|
require.Equal(t, []string{"foo", "image"}, g["default"].Targets)
|
||||||
|
require.Equal(t, []string{"foo", "image"}, g["foo"].Targets)
|
||||||
require.Equal(t, 2, len(m))
|
require.Equal(t, 2, len(m))
|
||||||
require.Equal(t, "bar", *m["foo"].Dockerfile)
|
require.Equal(t, "bar", *m["foo"].Dockerfile)
|
||||||
require.Equal(t, "type=docker", m["image"].Outputs[0])
|
require.Equal(t, "type=docker", m["image"].Outputs[0])
|
||||||
@@ -990,22 +997,22 @@ target "d" {
|
|||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
overrides []string
|
overrides []string
|
||||||
want map[string]string
|
want map[string]*string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "nested simple",
|
name: "nested simple",
|
||||||
overrides: nil,
|
overrides: nil,
|
||||||
want: map[string]string{"bar": "234", "baz": "890", "foo": "123"},
|
want: map[string]*string{"bar": ptrstr("234"), "baz": ptrstr("890"), "foo": ptrstr("123")},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "nested with overrides first",
|
name: "nested with overrides first",
|
||||||
overrides: []string{"a.args.foo=321", "b.args.bar=432"},
|
overrides: []string{"a.args.foo=321", "b.args.bar=432"},
|
||||||
want: map[string]string{"bar": "234", "baz": "890", "foo": "321"},
|
want: map[string]*string{"bar": ptrstr("234"), "baz": ptrstr("890"), "foo": ptrstr("321")},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "nested with overrides last",
|
name: "nested with overrides last",
|
||||||
overrides: []string{"a.args.foo=321", "c.args.bar=432"},
|
overrides: []string{"a.args.foo=321", "c.args.bar=432"},
|
||||||
want: map[string]string{"bar": "432", "baz": "890", "foo": "321"},
|
want: map[string]*string{"bar": ptrstr("432"), "baz": ptrstr("890"), "foo": ptrstr("321")},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range cases {
|
for _, tt := range cases {
|
||||||
@@ -1014,7 +1021,7 @@ target "d" {
|
|||||||
m, g, err := ReadTargets(ctx, []File{f}, []string{"d"}, tt.overrides, nil)
|
m, g, err := ReadTargets(ctx, []File{f}, []string{"d"}, tt.overrides, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
require.Equal(t, []string{"d"}, g[0].Targets)
|
require.Equal(t, []string{"d"}, g["default"].Targets)
|
||||||
require.Equal(t, 1, len(m))
|
require.Equal(t, 1, len(m))
|
||||||
require.Equal(t, tt.want, m["d"].Args)
|
require.Equal(t, tt.want, m["d"].Args)
|
||||||
})
|
})
|
||||||
@@ -1058,26 +1065,26 @@ group "default" {
|
|||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
overrides []string
|
overrides []string
|
||||||
wantch1 map[string]string
|
wantch1 map[string]*string
|
||||||
wantch2 map[string]string
|
wantch2 map[string]*string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "nested simple",
|
name: "nested simple",
|
||||||
overrides: nil,
|
overrides: nil,
|
||||||
wantch1: map[string]string{"BAR": "fuu", "FOO": "bar"},
|
wantch1: map[string]*string{"BAR": ptrstr("fuu"), "FOO": ptrstr("bar")},
|
||||||
wantch2: map[string]string{"BAR": "fuu", "FOO": "bar", "FOO2": "bar2"},
|
wantch2: map[string]*string{"BAR": ptrstr("fuu"), "FOO": ptrstr("bar"), "FOO2": ptrstr("bar2")},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "nested with overrides first",
|
name: "nested with overrides first",
|
||||||
overrides: []string{"grandparent.args.BAR=fii", "child1.args.FOO=baaar"},
|
overrides: []string{"grandparent.args.BAR=fii", "child1.args.FOO=baaar"},
|
||||||
wantch1: map[string]string{"BAR": "fii", "FOO": "baaar"},
|
wantch1: map[string]*string{"BAR": ptrstr("fii"), "FOO": ptrstr("baaar")},
|
||||||
wantch2: map[string]string{"BAR": "fii", "FOO": "bar", "FOO2": "bar2"},
|
wantch2: map[string]*string{"BAR": ptrstr("fii"), "FOO": ptrstr("bar"), "FOO2": ptrstr("bar2")},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "nested with overrides last",
|
name: "nested with overrides last",
|
||||||
overrides: []string{"grandparent.args.BAR=fii", "child2.args.FOO=baaar"},
|
overrides: []string{"grandparent.args.BAR=fii", "child2.args.FOO=baaar"},
|
||||||
wantch1: map[string]string{"BAR": "fii", "FOO": "bar"},
|
wantch1: map[string]*string{"BAR": ptrstr("fii"), "FOO": ptrstr("bar")},
|
||||||
wantch2: map[string]string{"BAR": "fii", "FOO": "baaar", "FOO2": "bar2"},
|
wantch2: map[string]*string{"BAR": ptrstr("fii"), "FOO": ptrstr("baaar"), "FOO2": ptrstr("bar2")},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range cases {
|
for _, tt := range cases {
|
||||||
@@ -1086,7 +1093,7 @@ group "default" {
|
|||||||
m, g, err := ReadTargets(ctx, []File{f}, []string{"default"}, tt.overrides, nil)
|
m, g, err := ReadTargets(ctx, []File{f}, []string{"default"}, tt.overrides, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
require.Equal(t, []string{"child1", "child2"}, g[0].Targets)
|
require.Equal(t, []string{"child1", "child2"}, g["default"].Targets)
|
||||||
require.Equal(t, 2, len(m))
|
require.Equal(t, 2, len(m))
|
||||||
require.Equal(t, tt.wantch1, m["child1"].Args)
|
require.Equal(t, tt.wantch1, m["child1"].Args)
|
||||||
require.Equal(t, []string{"type=docker"}, m["child1"].Outputs)
|
require.Equal(t, []string{"type=docker"}, m["child1"].Outputs)
|
||||||
@@ -1183,46 +1190,171 @@ target "f" {
|
|||||||
}`)}
|
}`)}
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
names []string
|
||||||
targets []string
|
targets []string
|
||||||
ntargets int
|
groups []string
|
||||||
|
count int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "a",
|
names: []string{"a"},
|
||||||
targets: []string{"b", "c"},
|
targets: []string{"a"},
|
||||||
ntargets: 1,
|
groups: []string{"default", "a", "b", "c"},
|
||||||
|
count: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "b",
|
names: []string{"b"},
|
||||||
targets: []string{"d"},
|
|
||||||
ntargets: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "c",
|
|
||||||
targets: []string{"b"},
|
targets: []string{"b"},
|
||||||
ntargets: 1,
|
groups: []string{"default", "b"},
|
||||||
|
count: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "d",
|
names: []string{"c"},
|
||||||
|
targets: []string{"c"},
|
||||||
|
groups: []string{"default", "b", "c"},
|
||||||
|
count: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: []string{"d"},
|
||||||
targets: []string{"d"},
|
targets: []string{"d"},
|
||||||
ntargets: 1,
|
groups: []string{"default"},
|
||||||
|
count: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "e",
|
names: []string{"e"},
|
||||||
targets: []string{"a", "f"},
|
targets: []string{"e"},
|
||||||
ntargets: 2,
|
groups: []string{"default", "a", "b", "c", "e"},
|
||||||
|
count: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: []string{"a", "e"},
|
||||||
|
targets: []string{"a", "e"},
|
||||||
|
groups: []string{"default", "a", "b", "c", "e"},
|
||||||
|
count: 2,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range cases {
|
for _, tt := range cases {
|
||||||
tt := tt
|
tt := tt
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(strings.Join(tt.names, "+"), func(t *testing.T) {
|
||||||
m, g, err := ReadTargets(ctx, []File{f}, []string{tt.name}, nil, nil)
|
m, g, err := ReadTargets(ctx, []File{f}, tt.names, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(g))
|
|
||||||
require.Equal(t, tt.targets, g[0].Targets)
|
var gnames []string
|
||||||
require.Equal(t, tt.ntargets, len(m))
|
for _, g := range g {
|
||||||
|
gnames = append(gnames, g.Name)
|
||||||
|
}
|
||||||
|
sort.Strings(gnames)
|
||||||
|
sort.Strings(tt.groups)
|
||||||
|
require.Equal(t, tt.groups, gnames)
|
||||||
|
|
||||||
|
sort.Strings(g["default"].Targets)
|
||||||
|
sort.Strings(tt.targets)
|
||||||
|
require.Equal(t, tt.targets, g["default"].Targets)
|
||||||
|
|
||||||
|
require.Equal(t, tt.count, len(m))
|
||||||
require.Equal(t, ".", *m["d"].Context)
|
require.Equal(t, ".", *m["d"].Context)
|
||||||
require.Equal(t, "./testdockerfile", *m["d"].Dockerfile)
|
require.Equal(t, "./testdockerfile", *m["d"].Dockerfile)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnknownExt(t *testing.T) {
|
||||||
|
dt := []byte(`
|
||||||
|
target "app" {
|
||||||
|
context = "dir"
|
||||||
|
args = {
|
||||||
|
v1 = "foo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
dt2 := []byte(`
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
dockerfile: Dockerfile-alternate
|
||||||
|
args:
|
||||||
|
v2: "bar"
|
||||||
|
`)
|
||||||
|
|
||||||
|
c, err := ParseFiles([]File{
|
||||||
|
{Data: dt, Name: "c1.foo"},
|
||||||
|
{Data: dt2, Name: "c2.bar"},
|
||||||
|
}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, "app", c.Targets[0].Name)
|
||||||
|
require.Equal(t, ptrstr("foo"), c.Targets[0].Args["v1"])
|
||||||
|
require.Equal(t, ptrstr("bar"), c.Targets[0].Args["v2"])
|
||||||
|
require.Equal(t, "dir", *c.Targets[0].Context)
|
||||||
|
require.Equal(t, "Dockerfile-alternate", *c.Targets[0].Dockerfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHCLNullVars(t *testing.T) {
|
||||||
|
fp := File{
|
||||||
|
Name: "docker-bake.hcl",
|
||||||
|
Data: []byte(
|
||||||
|
`variable "FOO" {
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
variable "BAR" {
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
target "default" {
|
||||||
|
args = {
|
||||||
|
foo = FOO
|
||||||
|
bar = "baz"
|
||||||
|
}
|
||||||
|
labels = {
|
||||||
|
"com.docker.app.bar" = BAR
|
||||||
|
"com.docker.app.baz" = "foo"
|
||||||
|
}
|
||||||
|
}`),
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.TODO()
|
||||||
|
m, _, err := ReadTargets(ctx, []File{fp}, []string{"default"}, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(m))
|
||||||
|
_, ok := m["default"]
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
_, err = TargetsToBuildOpt(m, &Input{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, map[string]*string{"bar": ptrstr("baz")}, m["default"].Args)
|
||||||
|
require.Equal(t, map[string]*string{"com.docker.app.baz": ptrstr("foo")}, m["default"].Labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONNullVars(t *testing.T) {
|
||||||
|
fp := File{
|
||||||
|
Name: "docker-bake.json",
|
||||||
|
Data: []byte(
|
||||||
|
`{
|
||||||
|
"variable": {
|
||||||
|
"FOO": {
|
||||||
|
"default": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"default": {
|
||||||
|
"args": {
|
||||||
|
"foo": "${FOO}",
|
||||||
|
"bar": "baz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`),
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.TODO()
|
||||||
|
m, _, err := ReadTargets(ctx, []File{fp}, []string{"default"}, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(m))
|
||||||
|
_, ok := m["default"]
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
_, err = TargetsToBuildOpt(m, &Input{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, map[string]*string{"bar": ptrstr("baz")}, m["default"].Args)
|
||||||
|
}
|
||||||
|
|||||||
197
bake/compose.go
197
bake/compose.go
@@ -1,51 +1,40 @@
|
|||||||
package bake
|
package bake
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/compose-spec/compose-go/dotenv"
|
||||||
"github.com/compose-spec/compose-go/loader"
|
"github.com/compose-spec/compose-go/loader"
|
||||||
compose "github.com/compose-spec/compose-go/types"
|
compose "github.com/compose-spec/compose-go/types"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// errComposeInvalid is returned when a compose file is invalid
|
func ParseComposeFiles(fs []File) (*Config, error) {
|
||||||
var errComposeInvalid = errors.New("invalid compose file")
|
envs, err := composeEnv()
|
||||||
|
|
||||||
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
|
|
||||||
options.SkipConsistencyCheck = 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err = composeValidate(cfg); err != nil {
|
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(options *loader.Options) {
|
||||||
|
options.SkipNormalization = true
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +47,7 @@ func ParseCompose(dt []byte) (*Config, error) {
|
|||||||
|
|
||||||
for _, s := range cfg.Services {
|
for _, s := range cfg.Services {
|
||||||
if s.Build == nil {
|
if s.Build == nil {
|
||||||
s.Build = &compose.BuildConfig{}
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
targetName := sanitizeTargetName(s.Name)
|
targetName := sanitizeTargetName(s.Name)
|
||||||
@@ -86,13 +75,20 @@ func ParseCompose(dt []byte) (*Config, error) {
|
|||||||
secrets = append(secrets, secret)
|
secrets = append(secrets, secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// compose does not support nil values for labels
|
||||||
|
labels := map[string]*string{}
|
||||||
|
for k, v := range s.Build.Labels {
|
||||||
|
v := v
|
||||||
|
labels[k] = &v
|
||||||
|
}
|
||||||
|
|
||||||
g.Targets = append(g.Targets, targetName)
|
g.Targets = append(g.Targets, targetName)
|
||||||
t := &Target{
|
t := &Target{
|
||||||
Name: targetName,
|
Name: targetName,
|
||||||
Context: contextPathP,
|
Context: contextPathP,
|
||||||
Dockerfile: dockerfilePathP,
|
Dockerfile: dockerfilePathP,
|
||||||
Tags: s.Build.Tags,
|
Tags: s.Build.Tags,
|
||||||
Labels: s.Build.Labels,
|
Labels: labels,
|
||||||
Args: flatten(s.Build.Args.Resolve(func(val string) (string, bool) {
|
Args: flatten(s.Build.Args.Resolve(func(val string) (string, bool) {
|
||||||
if val, ok := s.Environment[val]; ok && val != nil {
|
if val, ok := s.Environment[val]; ok && val != nil {
|
||||||
return *val, true
|
return *val, true
|
||||||
@@ -124,16 +120,96 @@ func ParseCompose(dt []byte) (*Config, error) {
|
|||||||
return &c, nil
|
return &c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func flatten(in compose.MappingWithEquals) compose.Mapping {
|
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) map[string]*string {
|
||||||
if len(in) == 0 {
|
if len(in) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
out := compose.Mapping{}
|
out := map[string]*string{}
|
||||||
for k, v := range in {
|
for k, v := range in {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
out[k] = *v
|
out[k] = v
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
@@ -151,10 +227,12 @@ type xbake struct {
|
|||||||
Pull *bool `yaml:"pull,omitempty"`
|
Pull *bool `yaml:"pull,omitempty"`
|
||||||
NoCache *bool `yaml:"no-cache,omitempty"`
|
NoCache *bool `yaml:"no-cache,omitempty"`
|
||||||
NoCacheFilter stringArray `yaml:"no-cache-filter,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:
|
// don't forget to update documentation if you add a new field:
|
||||||
// docs/guides/bake/compose-file.md#extension-field-with-x-bake
|
// docs/manuals/bake/compose-file.md#extension-field-with-x-bake
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type stringMap map[string]string
|
||||||
type stringArray []string
|
type stringArray []string
|
||||||
|
|
||||||
func (sa *stringArray) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
func (sa *stringArray) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
@@ -188,25 +266,25 @@ func (t *Target) composeExtTarget(exts map[string]interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(xb.Tags) > 0 {
|
if len(xb.Tags) > 0 {
|
||||||
t.Tags = dedupString(append(t.Tags, xb.Tags...))
|
t.Tags = dedupSlice(append(t.Tags, xb.Tags...))
|
||||||
}
|
}
|
||||||
if len(xb.CacheFrom) > 0 {
|
if len(xb.CacheFrom) > 0 {
|
||||||
t.CacheFrom = dedupString(append(t.CacheFrom, xb.CacheFrom...))
|
t.CacheFrom = dedupSlice(append(t.CacheFrom, xb.CacheFrom...))
|
||||||
}
|
}
|
||||||
if len(xb.CacheTo) > 0 {
|
if len(xb.CacheTo) > 0 {
|
||||||
t.CacheTo = dedupString(append(t.CacheTo, xb.CacheTo...))
|
t.CacheTo = dedupSlice(append(t.CacheTo, xb.CacheTo...))
|
||||||
}
|
}
|
||||||
if len(xb.Secrets) > 0 {
|
if len(xb.Secrets) > 0 {
|
||||||
t.Secrets = dedupString(append(t.Secrets, xb.Secrets...))
|
t.Secrets = dedupSlice(append(t.Secrets, xb.Secrets...))
|
||||||
}
|
}
|
||||||
if len(xb.SSH) > 0 {
|
if len(xb.SSH) > 0 {
|
||||||
t.SSH = dedupString(append(t.SSH, xb.SSH...))
|
t.SSH = dedupSlice(append(t.SSH, xb.SSH...))
|
||||||
}
|
}
|
||||||
if len(xb.Platforms) > 0 {
|
if len(xb.Platforms) > 0 {
|
||||||
t.Platforms = dedupString(append(t.Platforms, xb.Platforms...))
|
t.Platforms = dedupSlice(append(t.Platforms, xb.Platforms...))
|
||||||
}
|
}
|
||||||
if len(xb.Outputs) > 0 {
|
if len(xb.Outputs) > 0 {
|
||||||
t.Outputs = dedupString(append(t.Outputs, xb.Outputs...))
|
t.Outputs = dedupSlice(append(t.Outputs, xb.Outputs...))
|
||||||
}
|
}
|
||||||
if xb.Pull != nil {
|
if xb.Pull != nil {
|
||||||
t.Pull = xb.Pull
|
t.Pull = xb.Pull
|
||||||
@@ -215,34 +293,15 @@ func (t *Target) composeExtTarget(exts map[string]interface{}) error {
|
|||||||
t.NoCache = xb.NoCache
|
t.NoCache = xb.NoCache
|
||||||
}
|
}
|
||||||
if len(xb.NoCacheFilter) > 0 {
|
if len(xb.NoCacheFilter) > 0 {
|
||||||
t.NoCacheFilter = dedupString(append(t.NoCacheFilter, xb.NoCacheFilter...))
|
t.NoCacheFilter = dedupSlice(append(t.NoCacheFilter, xb.NoCacheFilter...))
|
||||||
|
}
|
||||||
|
if len(xb.Contexts) > 0 {
|
||||||
|
t.Contexts = dedupMap(t.Contexts, xb.Contexts)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// composeValidate validates a compose file
|
|
||||||
func composeValidate(project *compose.Project) error {
|
|
||||||
for _, s := range project.Services {
|
|
||||||
if s.Build != nil {
|
|
||||||
for _, secret := range s.Build.Secrets {
|
|
||||||
if _, ok := project.Secrets[secret.Source]; !ok {
|
|
||||||
return errors.Wrap(errComposeInvalid, fmt.Sprintf("service %q refers to undefined build secret %s", sanitizeTargetName(s.Name), secret.Source))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for name, secret := range project.Secrets {
|
|
||||||
if secret.External.External {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if secret.File == "" && secret.Environment == "" {
|
|
||||||
return errors.Wrap(errComposeInvalid, fmt.Sprintf("secret %q must declare either `file` or `environment`", name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// composeToBuildkitSecret converts secret from compose format to buildkit's
|
// composeToBuildkitSecret converts secret from compose format to buildkit's
|
||||||
// csv format.
|
// csv format.
|
||||||
func composeToBuildkitSecret(inp compose.ServiceSecretConfig, psecret compose.SecretConfig) (string, error) {
|
func composeToBuildkitSecret(inp compose.ServiceSecretConfig, psecret compose.SecretConfig) (string, error) {
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ package bake
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
compose "github.com/compose-spec/compose-go/types"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -37,11 +40,11 @@ secrets:
|
|||||||
file: /root/.aws/credentials
|
file: /root/.aws/credentials
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseCompose(dt)
|
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, 1, len(c.Groups))
|
require.Equal(t, 1, len(c.Groups))
|
||||||
require.Equal(t, c.Groups[0].Name, "default")
|
require.Equal(t, "default", c.Groups[0].Name)
|
||||||
sort.Strings(c.Groups[0].Targets)
|
sort.Strings(c.Groups[0].Targets)
|
||||||
require.Equal(t, []string{"db", "webapp"}, c.Groups[0].Targets)
|
require.Equal(t, []string{"db", "webapp"}, c.Groups[0].Targets)
|
||||||
|
|
||||||
@@ -57,9 +60,9 @@ secrets:
|
|||||||
require.Equal(t, "./dir", *c.Targets[1].Context)
|
require.Equal(t, "./dir", *c.Targets[1].Context)
|
||||||
require.Equal(t, "Dockerfile-alternate", *c.Targets[1].Dockerfile)
|
require.Equal(t, "Dockerfile-alternate", *c.Targets[1].Dockerfile)
|
||||||
require.Equal(t, 1, len(c.Targets[1].Args))
|
require.Equal(t, 1, len(c.Targets[1].Args))
|
||||||
require.Equal(t, "123", c.Targets[1].Args["buildno"])
|
require.Equal(t, ptrstr("123"), c.Targets[1].Args["buildno"])
|
||||||
require.Equal(t, c.Targets[1].CacheFrom, []string{"type=local,src=path/to/cache"})
|
require.Equal(t, []string{"type=local,src=path/to/cache"}, c.Targets[1].CacheFrom)
|
||||||
require.Equal(t, c.Targets[1].CacheTo, []string{"type=local,dest=path/to/cache"})
|
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, "none", *c.Targets[1].NetworkMode)
|
||||||
require.Equal(t, []string{
|
require.Equal(t, []string{
|
||||||
"id=token,env=ENV_TOKEN",
|
"id=token,env=ENV_TOKEN",
|
||||||
@@ -75,9 +78,10 @@ services:
|
|||||||
webapp:
|
webapp:
|
||||||
build: ./db
|
build: ./db
|
||||||
`)
|
`)
|
||||||
c, err := ParseCompose(dt)
|
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(c.Groups))
|
require.Equal(t, 1, len(c.Groups))
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseComposeTarget(t *testing.T) {
|
func TestParseComposeTarget(t *testing.T) {
|
||||||
@@ -93,7 +97,7 @@ services:
|
|||||||
target: webapp
|
target: webapp
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseCompose(dt)
|
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, 2, len(c.Targets))
|
require.Equal(t, 2, len(c.Targets))
|
||||||
@@ -118,15 +122,15 @@ services:
|
|||||||
target: webapp
|
target: webapp
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseCompose(dt)
|
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, len(c.Targets))
|
require.Equal(t, 2, len(c.Targets))
|
||||||
sort.Slice(c.Targets, func(i, j int) bool {
|
sort.Slice(c.Targets, func(i, j int) bool {
|
||||||
return c.Targets[i].Name < c.Targets[j].Name
|
return c.Targets[i].Name < c.Targets[j].Name
|
||||||
})
|
})
|
||||||
require.Equal(t, c.Targets[0].Name, "db")
|
require.Equal(t, "db", c.Targets[0].Name)
|
||||||
require.Equal(t, "db", *c.Targets[0].Target)
|
require.Equal(t, "db", *c.Targets[0].Target)
|
||||||
require.Equal(t, c.Targets[1].Name, "webapp")
|
require.Equal(t, "webapp", c.Targets[1].Name)
|
||||||
require.Equal(t, "webapp", *c.Targets[1].Target)
|
require.Equal(t, "webapp", *c.Targets[1].Target)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,18 +149,15 @@ services:
|
|||||||
BRB: FOO
|
BRB: FOO
|
||||||
`)
|
`)
|
||||||
|
|
||||||
os.Setenv("FOO", "bar")
|
t.Setenv("FOO", "bar")
|
||||||
defer os.Unsetenv("FOO")
|
t.Setenv("BAR", "foo")
|
||||||
os.Setenv("BAR", "foo")
|
t.Setenv("ZZZ_BAR", "zzz_foo")
|
||||||
defer os.Unsetenv("BAR")
|
|
||||||
os.Setenv("ZZZ_BAR", "zzz_foo")
|
|
||||||
defer os.Unsetenv("ZZZ_BAR")
|
|
||||||
|
|
||||||
c, err := ParseCompose(dt)
|
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, sliceToMap(os.Environ()))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, c.Targets[0].Args["FOO"], "bar")
|
require.Equal(t, ptrstr("bar"), c.Targets[0].Args["FOO"])
|
||||||
require.Equal(t, c.Targets[0].Args["BAR"], "zzz_foo")
|
require.Equal(t, ptrstr("zzz_foo"), c.Targets[0].Args["BAR"])
|
||||||
require.Equal(t, c.Targets[0].Args["BRB"], "FOO")
|
require.Equal(t, ptrstr("FOO"), c.Targets[0].Args["BRB"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInconsistentComposeFile(t *testing.T) {
|
func TestInconsistentComposeFile(t *testing.T) {
|
||||||
@@ -166,8 +167,8 @@ services:
|
|||||||
entrypoint: echo 1
|
entrypoint: echo 1
|
||||||
`)
|
`)
|
||||||
|
|
||||||
_, err := ParseCompose(dt)
|
_, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAdvancedNetwork(t *testing.T) {
|
func TestAdvancedNetwork(t *testing.T) {
|
||||||
@@ -191,7 +192,7 @@ networks:
|
|||||||
gateway: 10.5.0.254
|
gateway: 10.5.0.254
|
||||||
`)
|
`)
|
||||||
|
|
||||||
_, err := ParseCompose(dt)
|
_, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,9 +209,9 @@ services:
|
|||||||
- bar
|
- bar
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseCompose(dt)
|
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, c.Targets[0].Tags, []string{"foo", "bar"})
|
require.Equal(t, []string{"foo", "bar"}, c.Targets[0].Tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDependsOnList(t *testing.T) {
|
func TestDependsOnList(t *testing.T) {
|
||||||
@@ -245,7 +246,7 @@ networks:
|
|||||||
name: test-net
|
name: test-net
|
||||||
`)
|
`)
|
||||||
|
|
||||||
_, err := ParseCompose(dt)
|
_, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,6 +268,8 @@ services:
|
|||||||
CT_ECR: foo
|
CT_ECR: foo
|
||||||
CT_TAG: bar
|
CT_TAG: bar
|
||||||
x-bake:
|
x-bake:
|
||||||
|
contexts:
|
||||||
|
alpine: docker-image://alpine:3.13
|
||||||
tags:
|
tags:
|
||||||
- ct-addon:foo
|
- ct-addon:foo
|
||||||
- ct-addon:alp
|
- ct-addon:alp
|
||||||
@@ -296,24 +299,25 @@ services:
|
|||||||
no-cache: true
|
no-cache: true
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseCompose(dt)
|
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, len(c.Targets))
|
require.Equal(t, 2, len(c.Targets))
|
||||||
sort.Slice(c.Targets, func(i, j int) bool {
|
sort.Slice(c.Targets, func(i, j int) bool {
|
||||||
return c.Targets[i].Name < c.Targets[j].Name
|
return c.Targets[i].Name < c.Targets[j].Name
|
||||||
})
|
})
|
||||||
require.Equal(t, c.Targets[0].Args, map[string]string{"CT_ECR": "foo", "CT_TAG": "bar"})
|
require.Equal(t, map[string]*string{"CT_ECR": ptrstr("foo"), "CT_TAG": ptrstr("bar")}, c.Targets[0].Args)
|
||||||
require.Equal(t, c.Targets[0].Tags, []string{"ct-addon:baz", "ct-addon:foo", "ct-addon:alp"})
|
require.Equal(t, []string{"ct-addon:baz", "ct-addon:foo", "ct-addon:alp"}, c.Targets[0].Tags)
|
||||||
require.Equal(t, c.Targets[0].Platforms, []string{"linux/amd64", "linux/arm64"})
|
require.Equal(t, []string{"linux/amd64", "linux/arm64"}, c.Targets[0].Platforms)
|
||||||
require.Equal(t, c.Targets[0].CacheFrom, []string{"user/app:cache", "type=local,src=path/to/cache"})
|
require.Equal(t, []string{"user/app:cache", "type=local,src=path/to/cache"}, c.Targets[0].CacheFrom)
|
||||||
require.Equal(t, c.Targets[0].CacheTo, []string{"user/app:cache", "type=local,dest=path/to/cache"})
|
require.Equal(t, []string{"user/app:cache", "type=local,dest=path/to/cache"}, c.Targets[0].CacheTo)
|
||||||
require.Equal(t, c.Targets[0].Pull, newBool(true))
|
require.Equal(t, newBool(true), c.Targets[0].Pull)
|
||||||
require.Equal(t, c.Targets[1].Tags, []string{"ct-fake-aws:bar"})
|
require.Equal(t, map[string]string{"alpine": "docker-image://alpine:3.13"}, c.Targets[0].Contexts)
|
||||||
require.Equal(t, c.Targets[1].Secrets, []string{"id=mysecret,src=/local/secret", "id=mysecret2,src=/local/secret2"})
|
require.Equal(t, []string{"ct-fake-aws:bar"}, c.Targets[1].Tags)
|
||||||
require.Equal(t, c.Targets[1].SSH, []string{"default"})
|
require.Equal(t, []string{"id=mysecret,src=/local/secret", "id=mysecret2,src=/local/secret2"}, c.Targets[1].Secrets)
|
||||||
require.Equal(t, c.Targets[1].Platforms, []string{"linux/arm64"})
|
require.Equal(t, []string{"default"}, c.Targets[1].SSH)
|
||||||
require.Equal(t, c.Targets[1].Outputs, []string{"type=docker"})
|
require.Equal(t, []string{"linux/arm64"}, c.Targets[1].Platforms)
|
||||||
require.Equal(t, c.Targets[1].NoCache, newBool(true))
|
require.Equal(t, []string{"type=docker"}, c.Targets[1].Outputs)
|
||||||
|
require.Equal(t, newBool(true), c.Targets[1].NoCache)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestComposeExtDedup(t *testing.T) {
|
func TestComposeExtDedup(t *testing.T) {
|
||||||
@@ -339,12 +343,12 @@ services:
|
|||||||
- type=local,dest=path/to/cache
|
- type=local,dest=path/to/cache
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseCompose(dt)
|
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Tags, []string{"ct-addon:foo", "ct-addon:baz"})
|
require.Equal(t, []string{"ct-addon:foo", "ct-addon:baz"}, c.Targets[0].Tags)
|
||||||
require.Equal(t, c.Targets[0].CacheFrom, []string{"user/app:cache", "type=local,src=path/to/cache"})
|
require.Equal(t, []string{"user/app:cache", "type=local,src=path/to/cache"}, c.Targets[0].CacheFrom)
|
||||||
require.Equal(t, c.Targets[0].CacheTo, []string{"user/app:cache", "type=local,dest=path/to/cache"})
|
require.Equal(t, []string{"user/app:cache", "type=local,dest=path/to/cache"}, c.Targets[0].CacheTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnv(t *testing.T) {
|
func TestEnv(t *testing.T) {
|
||||||
@@ -372,9 +376,33 @@ services:
|
|||||||
- ` + envf.Name() + `
|
- ` + envf.Name() + `
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseCompose(dt)
|
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, c.Targets[0].Args, map[string]string{"CT_ECR": "foo", "FOO": "bsdf -csdf", "NODE_ENV": "test"})
|
require.Equal(t, map[string]*string{"CT_ECR": ptrstr("foo"), "FOO": ptrstr("bsdf -csdf"), "NODE_ENV": ptrstr("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": ptrstr("bar")}, c.Targets[0].Args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPorts(t *testing.T) {
|
func TestPorts(t *testing.T) {
|
||||||
@@ -394,7 +422,7 @@ services:
|
|||||||
published: "3306"
|
published: "3306"
|
||||||
protocol: tcp
|
protocol: tcp
|
||||||
`)
|
`)
|
||||||
_, err := ParseCompose(dt)
|
_, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,12 +468,12 @@ func TestServiceName(t *testing.T) {
|
|||||||
for _, tt := range cases {
|
for _, tt := range cases {
|
||||||
tt := tt
|
tt := tt
|
||||||
t.Run(tt.svc, func(t *testing.T) {
|
t.Run(tt.svc, func(t *testing.T) {
|
||||||
_, err := ParseCompose([]byte(`
|
_, err := ParseCompose([]compose.ConfigFile{{Content: []byte(`
|
||||||
services:
|
services:
|
||||||
` + tt.svc + `:
|
` + tt.svc + `:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
`))
|
`)}}, nil)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
} else {
|
} else {
|
||||||
@@ -511,7 +539,7 @@ services:
|
|||||||
for _, tt := range cases {
|
for _, tt := range cases {
|
||||||
tt := tt
|
tt := tt
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
_, err := ParseCompose(tt.dt)
|
_, err := ParseCompose([]compose.ConfigFile{{Content: tt.dt}}, nil)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
} else {
|
} else {
|
||||||
@@ -520,3 +548,114 @@ services:
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComposeNullArgs(t *testing.T) {
|
||||||
|
var dt = []byte(`
|
||||||
|
services:
|
||||||
|
scratch:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
args:
|
||||||
|
FOO: null
|
||||||
|
bar: "baz"
|
||||||
|
`)
|
||||||
|
|
||||||
|
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, map[string]*string{"bar": ptrstr("baz")}, c.Targets[0].Args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
265
bake/hcl_test.go
265
bake/hcl_test.go
@@ -1,7 +1,7 @@
|
|||||||
package bake
|
package bake
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -54,7 +54,7 @@ func TestHCLBasic(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, c.Targets[1].Name, "webapp")
|
require.Equal(t, c.Targets[1].Name, "webapp")
|
||||||
require.Equal(t, 1, len(c.Targets[1].Args))
|
require.Equal(t, 1, len(c.Targets[1].Args))
|
||||||
require.Equal(t, "123", c.Targets[1].Args["buildno"])
|
require.Equal(t, ptrstr("123"), c.Targets[1].Args["buildno"])
|
||||||
|
|
||||||
require.Equal(t, c.Targets[2].Name, "cross")
|
require.Equal(t, c.Targets[2].Name, "cross")
|
||||||
require.Equal(t, 2, len(c.Targets[2].Platforms))
|
require.Equal(t, 2, len(c.Targets[2].Platforms))
|
||||||
@@ -62,7 +62,7 @@ func TestHCLBasic(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, c.Targets[3].Name, "webapp-plus")
|
require.Equal(t, c.Targets[3].Name, "webapp-plus")
|
||||||
require.Equal(t, 1, len(c.Targets[3].Args))
|
require.Equal(t, 1, len(c.Targets[3].Args))
|
||||||
require.Equal(t, map[string]string{"IAMCROSS": "true"}, c.Targets[3].Args)
|
require.Equal(t, map[string]*string{"IAMCROSS": ptrstr("true")}, c.Targets[3].Args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHCLBasicInJSON(t *testing.T) {
|
func TestHCLBasicInJSON(t *testing.T) {
|
||||||
@@ -114,7 +114,7 @@ func TestHCLBasicInJSON(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, c.Targets[1].Name, "webapp")
|
require.Equal(t, c.Targets[1].Name, "webapp")
|
||||||
require.Equal(t, 1, len(c.Targets[1].Args))
|
require.Equal(t, 1, len(c.Targets[1].Args))
|
||||||
require.Equal(t, "123", c.Targets[1].Args["buildno"])
|
require.Equal(t, ptrstr("123"), c.Targets[1].Args["buildno"])
|
||||||
|
|
||||||
require.Equal(t, c.Targets[2].Name, "cross")
|
require.Equal(t, c.Targets[2].Name, "cross")
|
||||||
require.Equal(t, 2, len(c.Targets[2].Platforms))
|
require.Equal(t, 2, len(c.Targets[2].Platforms))
|
||||||
@@ -122,7 +122,7 @@ func TestHCLBasicInJSON(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, c.Targets[3].Name, "webapp-plus")
|
require.Equal(t, c.Targets[3].Name, "webapp-plus")
|
||||||
require.Equal(t, 1, len(c.Targets[3].Args))
|
require.Equal(t, 1, len(c.Targets[3].Args))
|
||||||
require.Equal(t, map[string]string{"IAMCROSS": "true"}, c.Targets[3].Args)
|
require.Equal(t, map[string]*string{"IAMCROSS": ptrstr("true")}, c.Targets[3].Args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHCLWithFunctions(t *testing.T) {
|
func TestHCLWithFunctions(t *testing.T) {
|
||||||
@@ -147,7 +147,7 @@ func TestHCLWithFunctions(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "webapp")
|
require.Equal(t, c.Targets[0].Name, "webapp")
|
||||||
require.Equal(t, "124", c.Targets[0].Args["buildno"])
|
require.Equal(t, ptrstr("124"), c.Targets[0].Args["buildno"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHCLWithUserDefinedFunctions(t *testing.T) {
|
func TestHCLWithUserDefinedFunctions(t *testing.T) {
|
||||||
@@ -177,7 +177,7 @@ func TestHCLWithUserDefinedFunctions(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "webapp")
|
require.Equal(t, c.Targets[0].Name, "webapp")
|
||||||
require.Equal(t, "124", c.Targets[0].Args["buildno"])
|
require.Equal(t, ptrstr("124"), c.Targets[0].Args["buildno"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHCLWithVariables(t *testing.T) {
|
func TestHCLWithVariables(t *testing.T) {
|
||||||
@@ -206,9 +206,9 @@ func TestHCLWithVariables(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "webapp")
|
require.Equal(t, c.Targets[0].Name, "webapp")
|
||||||
require.Equal(t, "123", c.Targets[0].Args["buildno"])
|
require.Equal(t, ptrstr("123"), c.Targets[0].Args["buildno"])
|
||||||
|
|
||||||
os.Setenv("BUILD_NUMBER", "456")
|
t.Setenv("BUILD_NUMBER", "456")
|
||||||
|
|
||||||
c, err = ParseFile(dt, "docker-bake.hcl")
|
c, err = ParseFile(dt, "docker-bake.hcl")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -219,7 +219,7 @@ func TestHCLWithVariables(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "webapp")
|
require.Equal(t, c.Targets[0].Name, "webapp")
|
||||||
require.Equal(t, "456", c.Targets[0].Args["buildno"])
|
require.Equal(t, ptrstr("456"), c.Targets[0].Args["buildno"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHCLWithVariablesInFunctions(t *testing.T) {
|
func TestHCLWithVariablesInFunctions(t *testing.T) {
|
||||||
@@ -244,7 +244,7 @@ func TestHCLWithVariablesInFunctions(t *testing.T) {
|
|||||||
require.Equal(t, c.Targets[0].Name, "webapp")
|
require.Equal(t, c.Targets[0].Name, "webapp")
|
||||||
require.Equal(t, []string{"user/repo:v1"}, c.Targets[0].Tags)
|
require.Equal(t, []string{"user/repo:v1"}, c.Targets[0].Tags)
|
||||||
|
|
||||||
os.Setenv("REPO", "docker/buildx")
|
t.Setenv("REPO", "docker/buildx")
|
||||||
|
|
||||||
c, err = ParseFile(dt, "docker-bake.hcl")
|
c, err = ParseFile(dt, "docker-bake.hcl")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -280,10 +280,10 @@ func TestHCLMultiFileSharedVariables(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "app")
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
require.Equal(t, "pre-abc", c.Targets[0].Args["v1"])
|
require.Equal(t, ptrstr("pre-abc"), c.Targets[0].Args["v1"])
|
||||||
require.Equal(t, "abc-post", c.Targets[0].Args["v2"])
|
require.Equal(t, ptrstr("abc-post"), c.Targets[0].Args["v2"])
|
||||||
|
|
||||||
os.Setenv("FOO", "def")
|
t.Setenv("FOO", "def")
|
||||||
|
|
||||||
c, err = ParseFiles([]File{
|
c, err = ParseFiles([]File{
|
||||||
{Data: dt, Name: "c1.hcl"},
|
{Data: dt, Name: "c1.hcl"},
|
||||||
@@ -293,12 +293,11 @@ func TestHCLMultiFileSharedVariables(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "app")
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
require.Equal(t, "pre-def", c.Targets[0].Args["v1"])
|
require.Equal(t, ptrstr("pre-def"), c.Targets[0].Args["v1"])
|
||||||
require.Equal(t, "def-post", c.Targets[0].Args["v2"])
|
require.Equal(t, ptrstr("def-post"), c.Targets[0].Args["v2"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHCLVarsWithVars(t *testing.T) {
|
func TestHCLVarsWithVars(t *testing.T) {
|
||||||
os.Unsetenv("FOO")
|
|
||||||
dt := []byte(`
|
dt := []byte(`
|
||||||
variable "FOO" {
|
variable "FOO" {
|
||||||
default = upper("${BASE}def")
|
default = upper("${BASE}def")
|
||||||
@@ -330,10 +329,10 @@ func TestHCLVarsWithVars(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "app")
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
require.Equal(t, "pre--ABCDEF-", c.Targets[0].Args["v1"])
|
require.Equal(t, ptrstr("pre--ABCDEF-"), c.Targets[0].Args["v1"])
|
||||||
require.Equal(t, "ABCDEF-post", c.Targets[0].Args["v2"])
|
require.Equal(t, ptrstr("ABCDEF-post"), c.Targets[0].Args["v2"])
|
||||||
|
|
||||||
os.Setenv("BASE", "new")
|
t.Setenv("BASE", "new")
|
||||||
|
|
||||||
c, err = ParseFiles([]File{
|
c, err = ParseFiles([]File{
|
||||||
{Data: dt, Name: "c1.hcl"},
|
{Data: dt, Name: "c1.hcl"},
|
||||||
@@ -343,12 +342,11 @@ func TestHCLVarsWithVars(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "app")
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
require.Equal(t, "pre--NEWDEF-", c.Targets[0].Args["v1"])
|
require.Equal(t, ptrstr("pre--NEWDEF-"), c.Targets[0].Args["v1"])
|
||||||
require.Equal(t, "NEWDEF-post", c.Targets[0].Args["v2"])
|
require.Equal(t, ptrstr("NEWDEF-post"), c.Targets[0].Args["v2"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHCLTypedVariables(t *testing.T) {
|
func TestHCLTypedVariables(t *testing.T) {
|
||||||
os.Unsetenv("FOO")
|
|
||||||
dt := []byte(`
|
dt := []byte(`
|
||||||
variable "FOO" {
|
variable "FOO" {
|
||||||
default = 3
|
default = 3
|
||||||
@@ -369,33 +367,80 @@ func TestHCLTypedVariables(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "app")
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
require.Equal(t, "lower", c.Targets[0].Args["v1"])
|
require.Equal(t, ptrstr("lower"), c.Targets[0].Args["v1"])
|
||||||
require.Equal(t, "yes", c.Targets[0].Args["v2"])
|
require.Equal(t, ptrstr("yes"), c.Targets[0].Args["v2"])
|
||||||
|
|
||||||
os.Setenv("FOO", "5.1")
|
t.Setenv("FOO", "5.1")
|
||||||
os.Setenv("IS_FOO", "0")
|
t.Setenv("IS_FOO", "0")
|
||||||
|
|
||||||
c, err = ParseFile(dt, "docker-bake.hcl")
|
c, err = ParseFile(dt, "docker-bake.hcl")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "app")
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
require.Equal(t, "higher", c.Targets[0].Args["v1"])
|
require.Equal(t, ptrstr("higher"), c.Targets[0].Args["v1"])
|
||||||
require.Equal(t, "no", c.Targets[0].Args["v2"])
|
require.Equal(t, ptrstr("no"), c.Targets[0].Args["v2"])
|
||||||
|
|
||||||
os.Setenv("FOO", "NaN")
|
t.Setenv("FOO", "NaN")
|
||||||
_, err = ParseFile(dt, "docker-bake.hcl")
|
_, err = ParseFile(dt, "docker-bake.hcl")
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Contains(t, err.Error(), "failed to parse FOO as number")
|
require.Contains(t, err.Error(), "failed to parse FOO as number")
|
||||||
|
|
||||||
os.Setenv("FOO", "0")
|
t.Setenv("FOO", "0")
|
||||||
os.Setenv("IS_FOO", "maybe")
|
t.Setenv("IS_FOO", "maybe")
|
||||||
|
|
||||||
_, err = ParseFile(dt, "docker-bake.hcl")
|
_, err = ParseFile(dt, "docker-bake.hcl")
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Contains(t, err.Error(), "failed to parse IS_FOO as bool")
|
require.Contains(t, err.Error(), "failed to parse IS_FOO as bool")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHCLNullVariables(t *testing.T) {
|
||||||
|
dt := []byte(`
|
||||||
|
variable "FOO" {
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
target "default" {
|
||||||
|
args = {
|
||||||
|
foo = FOO
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ptrstr(nil), c.Targets[0].Args["foo"])
|
||||||
|
|
||||||
|
t.Setenv("FOO", "bar")
|
||||||
|
c, err = ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ptrstr("bar"), c.Targets[0].Args["foo"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONNullVariables(t *testing.T) {
|
||||||
|
dt := []byte(`{
|
||||||
|
"variable": {
|
||||||
|
"FOO": {
|
||||||
|
"default": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"default": {
|
||||||
|
"args": {
|
||||||
|
"foo": "${FOO}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
c, err := ParseFile(dt, "docker-bake.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ptrstr(nil), c.Targets[0].Args["foo"])
|
||||||
|
|
||||||
|
t.Setenv("FOO", "bar")
|
||||||
|
c, err = ParseFile(dt, "docker-bake.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ptrstr("bar"), c.Targets[0].Args["foo"])
|
||||||
|
}
|
||||||
|
|
||||||
func TestHCLVariableCycle(t *testing.T) {
|
func TestHCLVariableCycle(t *testing.T) {
|
||||||
dt := []byte(`
|
dt := []byte(`
|
||||||
variable "FOO" {
|
variable "FOO" {
|
||||||
@@ -431,19 +476,107 @@ func TestHCLAttrs(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "app")
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
require.Equal(t, "attr-abcdef", c.Targets[0].Args["v1"])
|
require.Equal(t, ptrstr("attr-abcdef"), c.Targets[0].Args["v1"])
|
||||||
|
|
||||||
// env does not apply if no variable
|
// env does not apply if no variable
|
||||||
os.Setenv("FOO", "bar")
|
t.Setenv("FOO", "bar")
|
||||||
c, err = ParseFile(dt, "docker-bake.hcl")
|
c, err = ParseFile(dt, "docker-bake.hcl")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "app")
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
require.Equal(t, "attr-abcdef", c.Targets[0].Args["v1"])
|
require.Equal(t, ptrstr("attr-abcdef"), c.Targets[0].Args["v1"])
|
||||||
// attr-multifile
|
// 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) {
|
func TestHCLAttrsCustomType(t *testing.T) {
|
||||||
dt := []byte(`
|
dt := []byte(`
|
||||||
platforms=["linux/arm64", "linux/amd64"]
|
platforms=["linux/arm64", "linux/amd64"]
|
||||||
@@ -461,11 +594,10 @@ func TestHCLAttrsCustomType(t *testing.T) {
|
|||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "app")
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
require.Equal(t, []string{"linux/arm64", "linux/amd64"}, c.Targets[0].Platforms)
|
require.Equal(t, []string{"linux/arm64", "linux/amd64"}, c.Targets[0].Platforms)
|
||||||
require.Equal(t, "linux/arm64", c.Targets[0].Args["v1"])
|
require.Equal(t, ptrstr("linux/arm64"), c.Targets[0].Args["v1"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHCLMultiFileAttrs(t *testing.T) {
|
func TestHCLMultiFileAttrs(t *testing.T) {
|
||||||
os.Unsetenv("FOO")
|
|
||||||
dt := []byte(`
|
dt := []byte(`
|
||||||
variable "FOO" {
|
variable "FOO" {
|
||||||
default = "abc"
|
default = "abc"
|
||||||
@@ -487,9 +619,9 @@ func TestHCLMultiFileAttrs(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "app")
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
require.Equal(t, "pre-def", c.Targets[0].Args["v1"])
|
require.Equal(t, ptrstr("pre-def"), c.Targets[0].Args["v1"])
|
||||||
|
|
||||||
os.Setenv("FOO", "ghi")
|
t.Setenv("FOO", "ghi")
|
||||||
|
|
||||||
c, err = ParseFiles([]File{
|
c, err = ParseFiles([]File{
|
||||||
{Data: dt, Name: "c1.hcl"},
|
{Data: dt, Name: "c1.hcl"},
|
||||||
@@ -499,7 +631,7 @@ func TestHCLMultiFileAttrs(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "app")
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
require.Equal(t, "pre-ghi", c.Targets[0].Args["v1"])
|
require.Equal(t, ptrstr("pre-ghi"), c.Targets[0].Args["v1"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSONAttributes(t *testing.T) {
|
func TestJSONAttributes(t *testing.T) {
|
||||||
@@ -510,7 +642,7 @@ func TestJSONAttributes(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "app")
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
require.Equal(t, "pre-abc-def", c.Targets[0].Args["v1"])
|
require.Equal(t, ptrstr("pre-abc-def"), c.Targets[0].Args["v1"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSONFunctions(t *testing.T) {
|
func TestJSONFunctions(t *testing.T) {
|
||||||
@@ -535,7 +667,25 @@ func TestJSONFunctions(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "app")
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
require.Equal(t, "pre-<FOO-abc>", c.Targets[0].Args["v1"])
|
require.Equal(t, ptrstr("pre-<FOO-abc>"), c.Targets[0].Args["v1"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONInvalidFunctions(t *testing.T) {
|
||||||
|
dt := []byte(`{
|
||||||
|
"target": {
|
||||||
|
"app": {
|
||||||
|
"args": {
|
||||||
|
"v1": "myfunc(\"foo\")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}`)
|
||||||
|
|
||||||
|
c, err := ParseFile(dt, "docker-bake.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
|
require.Equal(t, ptrstr(`myfunc("foo")`), c.Targets[0].Args["v1"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHCLFunctionInAttr(t *testing.T) {
|
func TestHCLFunctionInAttr(t *testing.T) {
|
||||||
@@ -563,7 +713,7 @@ func TestHCLFunctionInAttr(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "app")
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
require.Equal(t, "FOO <> [baz]", c.Targets[0].Args["v1"])
|
require.Equal(t, ptrstr("FOO <> [baz]"), c.Targets[0].Args["v1"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHCLCombineCompose(t *testing.T) {
|
func TestHCLCombineCompose(t *testing.T) {
|
||||||
@@ -594,8 +744,8 @@ services:
|
|||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "app")
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
require.Equal(t, "foo", c.Targets[0].Args["v1"])
|
require.Equal(t, ptrstr("foo"), c.Targets[0].Args["v1"])
|
||||||
require.Equal(t, "bar", c.Targets[0].Args["v2"])
|
require.Equal(t, ptrstr("bar"), c.Targets[0].Args["v2"])
|
||||||
require.Equal(t, "dir", *c.Targets[0].Context)
|
require.Equal(t, "dir", *c.Targets[0].Context)
|
||||||
require.Equal(t, "Dockerfile-alternate", *c.Targets[0].Dockerfile)
|
require.Equal(t, "Dockerfile-alternate", *c.Targets[0].Dockerfile)
|
||||||
}
|
}
|
||||||
@@ -740,10 +890,10 @@ target "two" {
|
|||||||
require.Equal(t, 2, len(c.Targets))
|
require.Equal(t, 2, len(c.Targets))
|
||||||
|
|
||||||
require.Equal(t, c.Targets[0].Name, "one")
|
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, map[string]*string{"a": ptrstr("pre-ghi-jkl")}, c.Targets[0].Args)
|
||||||
|
|
||||||
require.Equal(t, c.Targets[1].Name, "two")
|
require.Equal(t, c.Targets[1].Name, "two")
|
||||||
require.Equal(t, map[string]string{"b": "pre-jkl"}, c.Targets[1].Args)
|
require.Equal(t, map[string]*string{"b": ptrstr("pre-jkl")}, c.Targets[1].Args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyVariableJSON(t *testing.T) {
|
func TestEmptyVariableJSON(t *testing.T) {
|
||||||
@@ -782,3 +932,24 @@ func TestFunctionNoResult(t *testing.T) {
|
|||||||
_, err := ParseFile(dt, "docker-bake.hcl")
|
_, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
require.Error(t, err)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ptrstr(s interface{}) *string {
|
||||||
|
var n *string = nil
|
||||||
|
if reflect.ValueOf(s).Kind() == reflect.String {
|
||||||
|
ss := s.(string)
|
||||||
|
n = &ss
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|||||||
103
bake/hclparser/body.go
Normal file
103
bake/hclparser/body.go
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -14,15 +14,7 @@ func funcCalls(exp hcl.Expression) ([]string, hcl.Diagnostics) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
fns, err := jsonFuncCallsRecursive(exp)
|
fns, err := jsonFuncCallsRecursive(exp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, hcl.Diagnostics{
|
return nil, wrapErrorDiagnostic("Invalid expression", err, exp.Range().Ptr(), exp.Range().Ptr())
|
||||||
&hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Invalid expression",
|
|
||||||
Detail: err.Error(),
|
|
||||||
Subject: exp.Range().Ptr(),
|
|
||||||
Context: exp.Range().Ptr(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return fns, nil
|
return fns, nil
|
||||||
}
|
}
|
||||||
@@ -83,11 +75,11 @@ func appendJSONFuncCalls(exp hcl.Expression, m map[string]struct{}) error {
|
|||||||
|
|
||||||
// hcl/v2/json/ast#stringVal
|
// hcl/v2/json/ast#stringVal
|
||||||
val := src.FieldByName("Value")
|
val := src.FieldByName("Value")
|
||||||
if val.IsZero() {
|
if !val.IsValid() || val.IsZero() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
rng := src.FieldByName("SrcRange")
|
rng := src.FieldByName("SrcRange")
|
||||||
if val.IsZero() {
|
if rng.IsZero() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var stringVal struct {
|
var stringVal struct {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/hashicorp/hcl/v2/gohcl"
|
"github.com/hashicorp/hcl/v2/gohcl"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
"github.com/zclconf/go-cty/cty/gocty"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Opt struct {
|
type Opt struct {
|
||||||
@@ -48,14 +49,22 @@ type parser struct {
|
|||||||
attrs map[string]*hcl.Attribute
|
attrs map[string]*hcl.Attribute
|
||||||
funcs map[string]*functionDef
|
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
|
ectx *hcl.EvalContext
|
||||||
|
|
||||||
progress map[string]struct{}
|
progress map[string]struct{}
|
||||||
progressF map[string]struct{}
|
progressF map[string]struct{}
|
||||||
|
progressB map[*hcl.Block]map[string]struct{}
|
||||||
doneF 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 {
|
var errUndefined = errors.New("undefined")
|
||||||
|
|
||||||
|
func (p *parser) loadDeps(exp hcl.Expression, exclude map[string]struct{}, allowMissing bool) hcl.Diagnostics {
|
||||||
fns, hcldiags := funcCalls(exp)
|
fns, hcldiags := funcCalls(exp)
|
||||||
if hcldiags.HasErrors() {
|
if hcldiags.HasErrors() {
|
||||||
return hcldiags
|
return hcldiags
|
||||||
@@ -63,15 +72,10 @@ func (p *parser) loadDeps(exp hcl.Expression, exclude map[string]struct{}) hcl.D
|
|||||||
|
|
||||||
for _, fn := range fns {
|
for _, fn := range fns {
|
||||||
if err := p.resolveFunction(fn); err != nil {
|
if err := p.resolveFunction(fn); err != nil {
|
||||||
return hcl.Diagnostics{
|
if allowMissing && errors.Is(err, errUndefined) {
|
||||||
&hcl.Diagnostic{
|
continue
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Invalid expression",
|
|
||||||
Detail: err.Error(),
|
|
||||||
Subject: exp.Range().Ptr(),
|
|
||||||
Context: exp.Range().Ptr(),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
return wrapErrorDiagnostic("Invalid expression", err, exp.Range().Ptr(), exp.Range().Ptr())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,22 +83,68 @@ func (p *parser) loadDeps(exp hcl.Expression, exclude map[string]struct{}) hcl.D
|
|||||||
if _, ok := exclude[v.RootName()]; ok {
|
if _, ok := exclude[v.RootName()]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := p.resolveValue(v.RootName()); err != nil {
|
if _, ok := p.blockTypes[v.RootName()]; ok {
|
||||||
|
blockType := v.RootName()
|
||||||
|
|
||||||
|
split := v.SimpleSplit().Rel
|
||||||
|
if len(split) == 0 {
|
||||||
return hcl.Diagnostics{
|
return hcl.Diagnostics{
|
||||||
&hcl.Diagnostic{
|
&hcl.Diagnostic{
|
||||||
Severity: hcl.DiagError,
|
Severity: hcl.DiagError,
|
||||||
Summary: "Invalid expression",
|
Summary: "Invalid expression",
|
||||||
Detail: err.Error(),
|
Detail: fmt.Sprintf("cannot access %s as a variable", blockType),
|
||||||
Subject: v.SourceRange().Ptr(),
|
Subject: exp.Range().Ptr(),
|
||||||
Context: v.SourceRange().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 {
|
||||||
|
if allowMissing && errors.Is(err, errUndefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return wrapErrorDiagnostic("Invalid expression", err, exp.Range().Ptr(), exp.Range().Ptr())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := p.resolveValue(v.RootName()); err != nil {
|
||||||
|
if allowMissing && errors.Is(err, errUndefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return wrapErrorDiagnostic("Invalid expression", err, exp.Range().Ptr(), exp.Range().Ptr())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolveFunction forces evaluation of a function, storing the result into the
|
||||||
|
// parser.
|
||||||
func (p *parser) resolveFunction(name string) error {
|
func (p *parser) resolveFunction(name string) error {
|
||||||
if _, ok := p.doneF[name]; ok {
|
if _, ok := p.doneF[name]; ok {
|
||||||
return nil
|
return nil
|
||||||
@@ -104,7 +154,7 @@ func (p *parser) resolveFunction(name string) error {
|
|||||||
if _, ok := p.ectx.Functions[name]; ok {
|
if _, ok := p.ectx.Functions[name]; ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.Errorf("undefined function %s", name)
|
return errors.Wrapf(errUndefined, "function %q does not exit", name)
|
||||||
}
|
}
|
||||||
if _, ok := p.progressF[name]; ok {
|
if _, ok := p.progressF[name]; ok {
|
||||||
return errors.Errorf("function cycle not allowed for %s", name)
|
return errors.Errorf("function cycle not allowed for %s", name)
|
||||||
@@ -154,7 +204,7 @@ func (p *parser) resolveFunction(name string) error {
|
|||||||
return diags
|
return diags
|
||||||
}
|
}
|
||||||
|
|
||||||
if diags := p.loadDeps(f.Result.Expr, params); diags.HasErrors() {
|
if diags := p.loadDeps(f.Result.Expr, params, false); diags.HasErrors() {
|
||||||
return diags
|
return diags
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +220,8 @@ func (p *parser) resolveFunction(name string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolveValue forces evaluation of a named value, storing the result into the
|
||||||
|
// parser.
|
||||||
func (p *parser) resolveValue(name string) (err error) {
|
func (p *parser) resolveValue(name string) (err error) {
|
||||||
if _, ok := p.ectx.Variables[name]; ok {
|
if _, ok := p.ectx.Variables[name]; ok {
|
||||||
return nil
|
return nil
|
||||||
@@ -190,7 +242,7 @@ func (p *parser) resolveValue(name string) (err error) {
|
|||||||
if _, builtin := p.opt.Vars[name]; !ok && !builtin {
|
if _, builtin := p.opt.Vars[name]; !ok && !builtin {
|
||||||
vr, ok := p.vars[name]
|
vr, ok := p.vars[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.Errorf("undefined variable %q", name)
|
return errors.Wrapf(errUndefined, "variable %q does not exit", name)
|
||||||
}
|
}
|
||||||
def = vr.Default
|
def = vr.Default
|
||||||
}
|
}
|
||||||
@@ -205,7 +257,7 @@ func (p *parser) resolveValue(name string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if diags := p.loadDeps(def.Expr, nil); diags.HasErrors() {
|
if diags := p.loadDeps(def.Expr, nil, true); diags.HasErrors() {
|
||||||
return diags
|
return diags
|
||||||
}
|
}
|
||||||
vv, diags := def.Expr.Value(p.ectx)
|
vv, diags := def.Expr.Value(p.ectx)
|
||||||
@@ -216,19 +268,16 @@ func (p *parser) resolveValue(name string) (err error) {
|
|||||||
_, isVar := p.vars[name]
|
_, isVar := p.vars[name]
|
||||||
|
|
||||||
if envv, ok := p.opt.LookupVar(name); ok && isVar {
|
if envv, ok := p.opt.LookupVar(name); ok && isVar {
|
||||||
if vv.Type().Equals(cty.Bool) {
|
switch {
|
||||||
|
case vv.Type().Equals(cty.Bool):
|
||||||
b, err := strconv.ParseBool(envv)
|
b, err := strconv.ParseBool(envv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "failed to parse %s as bool", name)
|
return errors.Wrapf(err, "failed to parse %s as bool", name)
|
||||||
}
|
}
|
||||||
vv := cty.BoolVal(b)
|
vv = cty.BoolVal(b)
|
||||||
v = &vv
|
case vv.Type().Equals(cty.String), vv.Type().Equals(cty.DynamicPseudoType):
|
||||||
return nil
|
vv = cty.StringVal(envv)
|
||||||
} else if vv.Type().Equals(cty.String) {
|
case vv.Type().Equals(cty.Number):
|
||||||
vv := cty.StringVal(envv)
|
|
||||||
v = &vv
|
|
||||||
return nil
|
|
||||||
} else if vv.Type().Equals(cty.Number) {
|
|
||||||
n, err := strconv.ParseFloat(envv, 64)
|
n, err := strconv.ParseFloat(envv, 64)
|
||||||
if err == nil && (math.IsNaN(n) || math.IsInf(n, 0)) {
|
if err == nil && (math.IsNaN(n) || math.IsInf(n, 0)) {
|
||||||
err = errors.Errorf("invalid number value")
|
err = errors.Errorf("invalid number value")
|
||||||
@@ -236,15 +285,157 @@ func (p *parser) resolveValue(name string) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "failed to parse %s as number", name)
|
return errors.Wrapf(err, "failed to parse %s as number", name)
|
||||||
}
|
}
|
||||||
vv := cty.NumberVal(big.NewFloat(n))
|
vv = cty.NumberVal(big.NewFloat(n))
|
||||||
|
default:
|
||||||
|
// TODO: support lists with csv values
|
||||||
|
return errors.Errorf("unsupported type %s for variable %s", vv.Type().FriendlyName(), name)
|
||||||
|
}
|
||||||
|
}
|
||||||
v = &vv
|
v = &vv
|
||||||
return nil
|
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 wrapErrorDiagnostic("Invalid name", err, &block.LabelRanges[0], &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, true)
|
||||||
|
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 {
|
} else {
|
||||||
// TODO: support lists with csv values
|
output = reflect.New(t)
|
||||||
return errors.Errorf("unsupported type %s for variable %s", v.Type(), name)
|
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{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
v = &vv
|
|
||||||
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,9 +475,16 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
|||||||
attrs: map[string]*hcl.Attribute{},
|
attrs: map[string]*hcl.Attribute{},
|
||||||
funcs: map[string]*functionDef{},
|
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{}{},
|
progress: map[string]struct{}{},
|
||||||
progressF: map[string]struct{}{},
|
progressF: map[string]struct{}{},
|
||||||
|
progressB: map[*hcl.Block]map[string]struct{}{},
|
||||||
|
|
||||||
doneF: map[string]struct{}{},
|
doneF: map[string]struct{}{},
|
||||||
|
doneB: map[*hcl.Block]map[string]struct{}{},
|
||||||
ectx: &hcl.EvalContext{
|
ectx: &hcl.EvalContext{
|
||||||
Variables: map[string]cty.Value{},
|
Variables: map[string]cty.Value{},
|
||||||
Functions: stdlibFunctions,
|
Functions: stdlibFunctions,
|
||||||
@@ -337,22 +535,17 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
|||||||
_ = p.resolveValue(k)
|
_ = p.resolveValue(k)
|
||||||
}
|
}
|
||||||
|
|
||||||
for k := range p.attrs {
|
for _, a := range content.Attributes {
|
||||||
if err := p.resolveValue(k); err != nil {
|
|
||||||
if diags, ok := err.(hcl.Diagnostics); ok {
|
|
||||||
return diags
|
|
||||||
}
|
|
||||||
return hcl.Diagnostics{
|
return hcl.Diagnostics{
|
||||||
&hcl.Diagnostic{
|
&hcl.Diagnostic{
|
||||||
Severity: hcl.DiagError,
|
Severity: hcl.DiagError,
|
||||||
Summary: "Invalid attribute",
|
Summary: "Invalid attribute",
|
||||||
Detail: err.Error(),
|
Detail: "global attributes currently not supported",
|
||||||
Subject: &p.attrs[k].Range,
|
Subject: &a.Range,
|
||||||
Context: &p.attrs[k].Range,
|
Context: &a.Range,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for k := range p.vars {
|
for k := range p.vars {
|
||||||
if err := p.resolveValue(k); err != nil {
|
if err := p.resolveValue(k); err != nil {
|
||||||
@@ -360,15 +553,7 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
|||||||
return diags
|
return diags
|
||||||
}
|
}
|
||||||
r := p.vars[k].Body.MissingItemRange()
|
r := p.vars[k].Body.MissingItemRange()
|
||||||
return hcl.Diagnostics{
|
return wrapErrorDiagnostic("Invalid value", err, &r, &r)
|
||||||
&hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Invalid value",
|
|
||||||
Detail: err.Error(),
|
|
||||||
Subject: &r,
|
|
||||||
Context: &r,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,31 +576,10 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hcl.Diagnostics{
|
return wrapErrorDiagnostic("Invalid function", err, subject, context)
|
||||||
&hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Invalid function",
|
|
||||||
Detail: err.Error(),
|
|
||||||
Subject: subject,
|
|
||||||
Context: context,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
for _, b := range content.Blocks {
|
||||||
if len(b.Labels) == 0 || len(b.Labels) > 1 {
|
if len(b.Labels) == 0 || len(b.Labels) > 1 {
|
||||||
return hcl.Diagnostics{
|
return hcl.Diagnostics{
|
||||||
@@ -428,19 +592,16 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bm, ok := m[b.Type]
|
bm, ok := p.blocks[b.Type]
|
||||||
if !ok {
|
if !ok {
|
||||||
bm = map[string][]*hcl.Block{}
|
bm = map[string][]*hcl.Block{}
|
||||||
m[b.Type] = bm
|
p.blocks[b.Type] = bm
|
||||||
}
|
}
|
||||||
|
|
||||||
lbl := b.Labels[0]
|
lbl := b.Labels[0]
|
||||||
bm[lbl] = append(bm[lbl], b)
|
bm[lbl] = append(bm[lbl], b)
|
||||||
}
|
}
|
||||||
|
|
||||||
vt := reflect.ValueOf(val).Elem().Type()
|
|
||||||
numFields := vt.NumField()
|
|
||||||
|
|
||||||
type value struct {
|
type value struct {
|
||||||
reflect.Value
|
reflect.Value
|
||||||
idx int
|
idx int
|
||||||
@@ -452,9 +613,11 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
|||||||
}
|
}
|
||||||
types := map[string]field{}
|
types := map[string]field{}
|
||||||
|
|
||||||
for i := 0; i < numFields; i++ {
|
vt := reflect.ValueOf(val).Elem().Type()
|
||||||
|
for i := 0; i < vt.NumField(); i++ {
|
||||||
tags := strings.Split(vt.Field(i).Tag.Get("hcl"), ",")
|
tags := strings.Split(vt.Field(i).Tag.Get("hcl"), ",")
|
||||||
|
|
||||||
|
p.blockTypes[tags[0]] = vt.Field(i).Type.Elem().Elem()
|
||||||
types[tags[0]] = field{
|
types[tags[0]] = field{
|
||||||
idx: i,
|
idx: i,
|
||||||
typ: vt.Field(i).Type,
|
typ: vt.Field(i).Type,
|
||||||
@@ -466,29 +629,21 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
|||||||
for _, b := range content.Blocks {
|
for _, b := range content.Blocks {
|
||||||
v := reflect.ValueOf(val)
|
v := reflect.ValueOf(val)
|
||||||
|
|
||||||
t, ok := types[b.Type]
|
err := p.resolveBlock(b, nil)
|
||||||
if !ok {
|
if err != nil {
|
||||||
continue
|
if diag, ok := err.(hcl.Diagnostics); ok {
|
||||||
}
|
|
||||||
|
|
||||||
vv := reflect.New(t.typ.Elem().Elem())
|
|
||||||
diag := gohcl.DecodeBody(b.Body, p.ectx, vv.Interface())
|
|
||||||
if diag.HasErrors() {
|
if diag.HasErrors() {
|
||||||
diags = append(diags, diag...)
|
diags = append(diags, diag...)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
if err := opt.ValidateLabel(b.Labels[0]); err != nil {
|
return wrapErrorDiagnostic("Invalid block", err, &b.LabelRanges[0], &b.DefRange)
|
||||||
return hcl.Diagnostics{
|
|
||||||
&hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Invalid name",
|
|
||||||
Detail: err.Error(),
|
|
||||||
Subject: &b.LabelRanges[0],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vv := p.blockValues[b]
|
||||||
|
|
||||||
|
t := types[b.Type]
|
||||||
lblIndex := setLabel(vv, b.Labels[0])
|
lblIndex := setLabel(vv, b.Labels[0])
|
||||||
|
|
||||||
oldValue, exists := t.values[b.Labels[0]]
|
oldValue, exists := t.values[b.Labels[0]]
|
||||||
@@ -502,7 +657,6 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if exists {
|
if exists {
|
||||||
if m := oldValue.Value.MethodByName("Merge"); m.IsValid() {
|
if m := oldValue.Value.MethodByName("Merge"); m.IsValid() {
|
||||||
@@ -523,9 +677,39 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
|||||||
return diags
|
return diags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for k := range p.attrs {
|
||||||
|
if err := p.resolveValue(k); err != nil {
|
||||||
|
if diags, ok := err.(hcl.Diagnostics); ok {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
return wrapErrorDiagnostic("Invalid attribute", err, &p.attrs[k].Range, &p.attrs[k].Range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wrapErrorDiagnostic wraps an error into a hcl.Diagnostics object.
|
||||||
|
// If the error is already an hcl.Diagnostics object, it is returned as is.
|
||||||
|
func wrapErrorDiagnostic(message string, err error, subject *hcl.Range, context *hcl.Range) hcl.Diagnostics {
|
||||||
|
switch err := err.(type) {
|
||||||
|
case *hcl.Diagnostic:
|
||||||
|
return hcl.Diagnostics{err}
|
||||||
|
case hcl.Diagnostics:
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
return hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: message,
|
||||||
|
Detail: err.Error(),
|
||||||
|
Subject: subject,
|
||||||
|
Context: context,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func setLabel(v reflect.Value, lbl string) int {
|
func setLabel(v reflect.Value, lbl string) int {
|
||||||
// cache field index?
|
// cache field index?
|
||||||
numFields := v.Elem().Type().NumField()
|
numFields := v.Elem().Type().NumField()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
@@ -20,7 +20,7 @@ type Input struct {
|
|||||||
URL string
|
URL string
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadRemoteFiles(ctx context.Context, dis []build.DriverInfo, url string, names []string, pw progress.Writer) ([]File, *Input, error) {
|
func ReadRemoteFiles(ctx context.Context, nodes []builder.Node, url string, names []string, pw progress.Writer) ([]File, *Input, error) {
|
||||||
var filename string
|
var filename string
|
||||||
st, ok := detectGitContext(url)
|
st, ok := detectGitContext(url)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -33,18 +33,18 @@ func ReadRemoteFiles(ctx context.Context, dis []build.DriverInfo, url string, na
|
|||||||
inp := &Input{State: st, URL: url}
|
inp := &Input{State: st, URL: url}
|
||||||
var files []File
|
var files []File
|
||||||
|
|
||||||
var di *build.DriverInfo
|
var node *builder.Node
|
||||||
for _, d := range dis {
|
for i, n := range nodes {
|
||||||
if d.Err == nil {
|
if n.Err == nil {
|
||||||
di = &d
|
node = &nodes[i]
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if di == nil {
|
if node == nil {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := driver.Boot(ctx, ctx, di.Driver, pw)
|
c, err := driver.Boot(ctx, ctx, node.Driver, pw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
718
build/build.go
718
build/build.go
File diff suppressed because it is too large
Load Diff
115
build/git.go
Normal file
115
build/git.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/gitutil"
|
||||||
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DockerfileLabel = "com.docker.image.source.entrypoint"
|
||||||
|
|
||||||
|
func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath string) (res map[string]string, _ error) {
|
||||||
|
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, err := strconv.ParseBool(v); err == nil {
|
||||||
|
setGitLabels = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setGitInfo := true
|
||||||
|
if v, ok := os.LookupEnv("BUILDX_GIT_INFO"); ok {
|
||||||
|
if v, err := strconv.ParseBool(v); err == nil {
|
||||||
|
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, err := gitutil.New(gitutil.WithContext(ctx), gitutil.WithWorkingDir(wd))
|
||||||
|
if err != nil {
|
||||||
|
if st, err := os.Stat(path.Join(wd, ".git")); err == nil && st.IsDir() {
|
||||||
|
return res, errors.New("buildx: git was not found in the system. Current commit information was not captured by the build")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !gitc.IsInsideWorkTree() {
|
||||||
|
if st, err := os.Stat(path.Join(wd, ".git")); err == nil && st.IsDir() {
|
||||||
|
return res, errors.New("buildx: failed to read current commit information with git rev-parse --is-inside-work-tree")
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if sha, err := gitc.FullCommit(); err != nil && !gitutil.IsUnknownRevision(err) {
|
||||||
|
return res, errors.Wrapf(err, "buildx: failed to get git commit")
|
||||||
|
} else if sha != "" {
|
||||||
|
checkDirty := false
|
||||||
|
if v, ok := os.LookupEnv("BUILDX_GIT_CHECK_DIRTY"); ok {
|
||||||
|
if v, err := strconv.ParseBool(v); err == nil {
|
||||||
|
checkDirty = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if checkDirty && gitc.IsDirty() {
|
||||||
|
sha += "-dirty"
|
||||||
|
}
|
||||||
|
if setGitLabels {
|
||||||
|
res["label:"+specs.AnnotationRevision] = sha
|
||||||
|
}
|
||||||
|
if setGitInfo {
|
||||||
|
res["vcs:revision"] = sha
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rurl, err := gitc.RemoteURL(); err == nil && rurl != "" {
|
||||||
|
if setGitLabels {
|
||||||
|
res["label:"+specs.AnnotationSource] = rurl
|
||||||
|
}
|
||||||
|
if setGitInfo {
|
||||||
|
res["vcs:source"] = rurl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if setGitLabels {
|
||||||
|
if root, err := gitc.RootDir(); err != nil {
|
||||||
|
return res, errors.Wrapf(err, "buildx: failed to get git root dir")
|
||||||
|
} else if 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, "..") {
|
||||||
|
res["label:"+DockerfileLabel] = dockerfilePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
156
build/git_test.go
Normal file
156
build/git_test.go
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/gitutil"
|
||||||
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupTest(tb testing.TB) {
|
||||||
|
gitutil.Mktmp(tb)
|
||||||
|
|
||||||
|
c, err := gitutil.New()
|
||||||
|
require.NoError(tb, err)
|
||||||
|
gitutil.GitInit(c, tb)
|
||||||
|
|
||||||
|
df := []byte("FROM alpine:latest\n")
|
||||||
|
assert.NoError(tb, os.WriteFile("Dockerfile", df, 0644))
|
||||||
|
|
||||||
|
gitutil.GitAdd(c, tb, "Dockerfile")
|
||||||
|
gitutil.GitCommit(c, tb, "initial commit")
|
||||||
|
gitutil.GitSetRemote(c, tb, "origin", "git@github.com:docker/buildx.git")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetGitAttributesNotGitRepo(t *testing.T) {
|
||||||
|
_, err := getGitAttributes(context.Background(), t.TempDir(), "Dockerfile")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetGitAttributesBadGitRepo(t *testing.T) {
|
||||||
|
tmp := t.TempDir()
|
||||||
|
require.NoError(t, os.MkdirAll(path.Join(tmp, ".git"), 0755))
|
||||||
|
|
||||||
|
_, err := getGitAttributes(context.Background(), tmp, "Dockerfile")
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetGitAttributesNoContext(t *testing.T) {
|
||||||
|
setupTest(t)
|
||||||
|
|
||||||
|
gitattrs, err := getGitAttributes(context.Background(), "", "Dockerfile")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
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",
|
||||||
|
"vcs:source",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "none",
|
||||||
|
envGitLabels: "false",
|
||||||
|
envGitInfo: "false",
|
||||||
|
expected: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gitinfo",
|
||||||
|
envGitLabels: "false",
|
||||||
|
envGitInfo: "true",
|
||||||
|
expected: []string{
|
||||||
|
"vcs:revision",
|
||||||
|
"vcs:source",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gitlabels",
|
||||||
|
envGitLabels: "true",
|
||||||
|
envGitInfo: "false",
|
||||||
|
expected: []string{
|
||||||
|
"label:" + DockerfileLabel,
|
||||||
|
"label:" + specs.AnnotationRevision,
|
||||||
|
"label:" + specs.AnnotationSource,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "both",
|
||||||
|
envGitLabels: "true",
|
||||||
|
envGitInfo: "",
|
||||||
|
expected: []string{
|
||||||
|
"label:" + DockerfileLabel,
|
||||||
|
"label:" + specs.AnnotationRevision,
|
||||||
|
"label:" + specs.AnnotationSource,
|
||||||
|
"vcs:revision",
|
||||||
|
"vcs:source",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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, err := getGitAttributes(context.Background(), ".", "Dockerfile")
|
||||||
|
require.NoError(t, err)
|
||||||
|
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])
|
||||||
|
} else if e == "label:"+specs.AnnotationSource || e == "vcs:source" {
|
||||||
|
assert.Equal(t, "git@github.com:docker/buildx.git", gitattrs[e])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetGitAttributesDirty(t *testing.T) {
|
||||||
|
setupTest(t)
|
||||||
|
t.Setenv("BUILDX_GIT_CHECK_DIRTY", "true")
|
||||||
|
|
||||||
|
// make a change to test dirty flag
|
||||||
|
df := []byte("FROM alpine:edge\n")
|
||||||
|
require.NoError(t, os.Mkdir("dir", 0755))
|
||||||
|
require.NoError(t, os.WriteFile(filepath.Join("dir", "Dockerfile"), df, 0644))
|
||||||
|
|
||||||
|
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.AnnotationSource)
|
||||||
|
assert.Equal(t, "git@github.com:docker/buildx.git", gitattrs["label:"+specs.AnnotationSource])
|
||||||
|
assert.Contains(t, gitattrs, "label:"+specs.AnnotationRevision)
|
||||||
|
assert.True(t, strings.HasSuffix(gitattrs["label:"+specs.AnnotationRevision], "-dirty"))
|
||||||
|
|
||||||
|
assert.Contains(t, gitattrs, "vcs:source")
|
||||||
|
assert.Equal(t, "git@github.com:docker/buildx.git", gitattrs["vcs:source"])
|
||||||
|
assert.Contains(t, gitattrs, "vcs:revision")
|
||||||
|
assert.True(t, strings.HasSuffix(gitattrs["vcs:revision"], "-dirty"))
|
||||||
|
}
|
||||||
@@ -11,8 +11,14 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
// archiveHeaderSize is the number of bytes in an archive header
|
// archiveHeaderSize is the number of bytes in an archive header
|
||||||
const archiveHeaderSize = 512
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
func isLocalDir(c string) bool {
|
func isLocalDir(c string) bool {
|
||||||
st, err := os.Stat(c)
|
st, err := os.Stat(c)
|
||||||
@@ -39,18 +45,23 @@ func isArchive(header []byte) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// toBuildkitExtraHosts converts hosts from docker key:value format to buildkit's csv format
|
// toBuildkitExtraHosts converts hosts from docker key:value format to buildkit's csv format
|
||||||
func toBuildkitExtraHosts(inp []string) (string, error) {
|
func toBuildkitExtraHosts(inp []string, mobyDriver bool) (string, error) {
|
||||||
if len(inp) == 0 {
|
if len(inp) == 0 {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
hosts := make([]string, 0, len(inp))
|
hosts := make([]string, 0, len(inp))
|
||||||
for _, h := range inp {
|
for _, h := range inp {
|
||||||
parts := strings.Split(h, ":")
|
host, ip, ok := strings.Cut(h, ":")
|
||||||
|
if !ok || host == "" || ip == "" {
|
||||||
if len(parts) != 2 || parts[0] == "" || net.ParseIP(parts[1]) == nil {
|
|
||||||
return "", errors.Errorf("invalid host %s", h)
|
return "", errors.Errorf("invalid host %s", h)
|
||||||
}
|
}
|
||||||
hosts = append(hosts, parts[0]+"="+parts[1])
|
// 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)
|
||||||
}
|
}
|
||||||
return strings.Join(hosts, ","), nil
|
return strings.Join(hosts, ","), nil
|
||||||
}
|
}
|
||||||
|
|||||||
292
builder/builder.go
Normal file
292
builder/builder.go
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
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.NodeGroup.Nodes[0].Endpoint
|
||||||
|
var dockerapi *dockerutil.ClientAPI
|
||||||
|
dockerapi, err = dockerutil.NewClientAPI(b.opts.dockerCli, b.NodeGroup.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
|
||||||
|
}
|
||||||
202
builder/node.go
Normal file
202
builder/node.go
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
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),
|
||||||
|
Platforms: n.Platforms,
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -9,7 +9,10 @@ import (
|
|||||||
"github.com/containerd/containerd/platforms"
|
"github.com/containerd/containerd/platforms"
|
||||||
"github.com/docker/buildx/bake"
|
"github.com/docker/buildx/bake"
|
||||||
"github.com/docker/buildx/build"
|
"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/confutil"
|
||||||
|
"github.com/docker/buildx/util/dockerutil"
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/docker/buildx/util/tracing"
|
"github.com/docker/buildx/util/tracing"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
@@ -71,11 +74,20 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error
|
|||||||
if in.pull != nil {
|
if in.pull != nil {
|
||||||
overrides = append(overrides, fmt.Sprintf("*.pull=%t", *in.pull))
|
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()
|
contextPathHash, _ := os.Getwd()
|
||||||
|
|
||||||
ctx2, cancel := context.WithCancel(context.TODO())
|
ctx2, cancel := context.WithCancel(context.TODO())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
printer := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, in.progress)
|
printer, err := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, in.progress)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if printer != nil {
|
if printer != nil {
|
||||||
@@ -86,16 +98,30 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
dis, err := getInstanceOrDefault(ctx, dockerCli, in.builder, contextPathHash)
|
var nodes []builder.Node
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var files []bake.File
|
var files []bake.File
|
||||||
var inp *bake.Input
|
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 != "" {
|
if url != "" {
|
||||||
files, inp, err = bake.ReadRemoteFiles(ctx, dis, url, in.files, printer)
|
files, inp, err = bake.ReadRemoteFiles(ctx, nodes, url, in.files, printer)
|
||||||
} else {
|
} else {
|
||||||
files, err = bake.ReadLocalFiles(in.files)
|
files, err = bake.ReadLocalFiles(in.files)
|
||||||
}
|
}
|
||||||
@@ -105,7 +131,7 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error
|
|||||||
|
|
||||||
tgts, grps, err := bake.ReadTargets(ctx, files, targets, overrides, map[string]string{
|
tgts, grps, err := bake.ReadTargets(ctx, files, targets, overrides, map[string]string{
|
||||||
// don't forget to update documentation if you add a new
|
// don't forget to update documentation if you add a new
|
||||||
// built-in variable: docs/guides/bake/file-definition.md#built-in-variables
|
// built-in variable: docs/bake-reference.md#built-in-variables
|
||||||
"BAKE_CMD_CONTEXT": cmdContext,
|
"BAKE_CMD_CONTEXT": cmdContext,
|
||||||
"BAKE_LOCAL_PLATFORM": platforms.DefaultString(),
|
"BAKE_LOCAL_PLATFORM": platforms.DefaultString(),
|
||||||
})
|
})
|
||||||
@@ -120,17 +146,11 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error
|
|||||||
}
|
}
|
||||||
|
|
||||||
if in.printOnly {
|
if in.printOnly {
|
||||||
var defg map[string]*bake.Group
|
|
||||||
if len(grps) == 1 {
|
|
||||||
defg = map[string]*bake.Group{
|
|
||||||
"default": grps[0],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dt, err := json.MarshalIndent(struct {
|
dt, err := json.MarshalIndent(struct {
|
||||||
Group map[string]*bake.Group `json:"group,omitempty"`
|
Group map[string]*bake.Group `json:"group,omitempty"`
|
||||||
Target map[string]*bake.Target `json:"target"`
|
Target map[string]*bake.Target `json:"target"`
|
||||||
}{
|
}{
|
||||||
defg,
|
grps,
|
||||||
tgts,
|
tgts,
|
||||||
}, "", " ")
|
}, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -145,7 +165,7 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := build.Build(ctx, dis, bo, dockerAPI(dockerCli), confutil.ConfigDir(dockerCli), printer)
|
resp, err := build.Build(ctx, nodes, bo, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapBuildError(err, true)
|
return wrapBuildError(err, true)
|
||||||
}
|
}
|
||||||
@@ -189,6 +209,8 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
flags.BoolVar(&options.exportLoad, "load", false, `Shorthand for "--set=*.output=type=docker"`)
|
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.printOnly, "print", false, "Print the options without building")
|
||||||
flags.BoolVar(&options.exportPush, "push", false, `Shorthand for "--set=*.output=type=registry"`)
|
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 (e.g., "targetpattern.key=value")`)
|
||||||
|
|
||||||
commonBuildFlags(&options.commonOptions, flags)
|
commonBuildFlags(&options.commonOptions, flags)
|
||||||
|
|||||||
@@ -16,9 +16,13 @@ import (
|
|||||||
|
|
||||||
"github.com/containerd/console"
|
"github.com/containerd/console"
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/build"
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/monitor"
|
"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/buildflags"
|
||||||
"github.com/docker/buildx/util/confutil"
|
"github.com/docker/buildx/util/confutil"
|
||||||
|
"github.com/docker/buildx/util/dockerutil"
|
||||||
"github.com/docker/buildx/util/platformutil"
|
"github.com/docker/buildx/util/platformutil"
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/docker/buildx/util/tracing"
|
"github.com/docker/buildx/util/tracing"
|
||||||
@@ -48,8 +52,10 @@ const defaultTargetName = "default"
|
|||||||
type buildOptions struct {
|
type buildOptions struct {
|
||||||
contextPath string
|
contextPath string
|
||||||
dockerfileName string
|
dockerfileName string
|
||||||
|
printFunc string
|
||||||
|
|
||||||
allow []string
|
allow []string
|
||||||
|
attests []string
|
||||||
buildArgs []string
|
buildArgs []string
|
||||||
cacheFrom []string
|
cacheFrom []string
|
||||||
cacheTo []string
|
cacheTo []string
|
||||||
@@ -57,6 +63,7 @@ type buildOptions struct {
|
|||||||
contexts []string
|
contexts []string
|
||||||
extraHosts []string
|
extraHosts []string
|
||||||
imageIDFile string
|
imageIDFile string
|
||||||
|
invoke string
|
||||||
labels []string
|
labels []string
|
||||||
networkMode string
|
networkMode string
|
||||||
noCacheFilter []string
|
noCacheFilter []string
|
||||||
@@ -69,7 +76,6 @@ type buildOptions struct {
|
|||||||
tags []string
|
tags []string
|
||||||
target string
|
target string
|
||||||
ulimits *dockeropts.UlimitOpt
|
ulimits *dockeropts.UlimitOpt
|
||||||
invoke string
|
|
||||||
commonOptions
|
commonOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,11 +86,11 @@ type commonOptions struct {
|
|||||||
progress string
|
progress string
|
||||||
pull *bool
|
pull *bool
|
||||||
|
|
||||||
// golangci-lint#826
|
|
||||||
// nolint:structcheck
|
|
||||||
exportPush bool
|
exportPush bool
|
||||||
// nolint:structcheck
|
|
||||||
exportLoad bool
|
exportLoad bool
|
||||||
|
|
||||||
|
sbom string
|
||||||
|
provenance string
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
|
func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
|
||||||
@@ -111,7 +117,7 @@ func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
|
|||||||
return errors.Errorf("--no-cache and --no-cache-filter cannot currently be used together")
|
return errors.Errorf("--no-cache and --no-cache-filter cannot currently be used together")
|
||||||
}
|
}
|
||||||
|
|
||||||
if in.quiet && in.progress != "auto" && in.progress != "quiet" {
|
if in.quiet && in.progress != progress.PrinterModeAuto && in.progress != progress.PrinterModeQuiet {
|
||||||
return errors.Errorf("progress=%s and quiet cannot be used together", in.progress)
|
return errors.Errorf("progress=%s and quiet cannot be used together", in.progress)
|
||||||
} else if in.quiet {
|
} else if in.quiet {
|
||||||
in.progress = "quiet"
|
in.progress = "quiet"
|
||||||
@@ -122,6 +128,11 @@ func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printFunc, err := parsePrintFunc(in.printFunc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
opts := build.Options{
|
opts := build.Options{
|
||||||
Inputs: build.Inputs{
|
Inputs: build.Inputs{
|
||||||
ContextPath: in.contextPath,
|
ContextPath: in.contextPath,
|
||||||
@@ -141,6 +152,7 @@ func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
|
|||||||
Tags: in.tags,
|
Tags: in.tags,
|
||||||
Target: in.target,
|
Target: in.target,
|
||||||
Ulimits: in.ulimits,
|
Ulimits: in.ulimits,
|
||||||
|
PrintFunc: printFunc,
|
||||||
}
|
}
|
||||||
|
|
||||||
platforms, err := platformutil.Parse(in.platforms)
|
platforms, err := platformutil.Parse(in.platforms)
|
||||||
@@ -206,9 +218,20 @@ func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.Exports = outputs
|
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
|
||||||
|
}
|
||||||
|
|
||||||
cacheImports, err := buildflags.ParseCacheEntry(in.cacheFrom)
|
cacheImports, err := buildflags.ParseCacheEntry(in.cacheFrom)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -233,7 +256,22 @@ func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
|
|||||||
contextPathHash = in.contextPath
|
contextPathHash = in.contextPath
|
||||||
}
|
}
|
||||||
|
|
||||||
imageID, res, err := buildTargets(ctx, dockerCli, map[string]build.Options{defaultTargetName: opts}, in.progress, contextPathHash, in.builder, in.metadataFile)
|
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)
|
err = wrapBuildError(err, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -250,7 +288,7 @@ func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
|
|||||||
return errors.Errorf("failed to configure terminal: %v", err)
|
return errors.Errorf("failed to configure terminal: %v", err)
|
||||||
}
|
}
|
||||||
err = monitor.RunMonitor(ctx, cfg, func(ctx context.Context) (*build.ResultContext, error) {
|
err = monitor.RunMonitor(ctx, cfg, func(ctx context.Context) (*build.ResultContext, error) {
|
||||||
_, rr, err := buildTargets(ctx, dockerCli, map[string]build.Options{defaultTargetName: opts}, in.progress, contextPathHash, in.builder, in.metadataFile)
|
_, rr, err := buildTargets(ctx, dockerCli, nodes, map[string]build.Options{defaultTargetName: opts}, in.progress, in.metadataFile, true)
|
||||||
return rr, err
|
return rr, err
|
||||||
}, io.NopCloser(os.Stdin), nopCloser{os.Stdout}, nopCloser{os.Stderr})
|
}, io.NopCloser(os.Stdin), nopCloser{os.Stdout}, nopCloser{os.Stderr})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -271,26 +309,24 @@ type nopCloser struct {
|
|||||||
|
|
||||||
func (c nopCloser) Close() error { return nil }
|
func (c nopCloser) Close() error { return nil }
|
||||||
|
|
||||||
func buildTargets(ctx context.Context, dockerCli command.Cli, opts map[string]build.Options, progressMode, contextPathHash, instance string, metadataFile string) (imageID string, res *build.ResultContext, err error) {
|
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) {
|
||||||
dis, err := getInstanceOrDefault(ctx, dockerCli, instance, contextPathHash)
|
ctx2, cancel := context.WithCancel(context.TODO())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
printer, err := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, progressMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx2, cancel := context.WithCancel(context.TODO())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
printer := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, progressMode)
|
|
||||||
|
|
||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
var idx int
|
var idx int
|
||||||
resp, err := build.BuildWithResultHandler(ctx, dis, opts, dockerAPI(dockerCli), confutil.ConfigDir(dockerCli), printer, func(driverIndex int, gotRes *build.ResultContext) {
|
resp, err := build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), printer, func(driverIndex int, gotRes *build.ResultContext) {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
if res == nil || driverIndex < idx {
|
if res == nil || driverIndex < idx {
|
||||||
idx, res = driverIndex, gotRes
|
idx, res = driverIndex, gotRes
|
||||||
}
|
}
|
||||||
})
|
}, allowNoOutput)
|
||||||
err1 := printer.Wait()
|
err1 := printer.Wait()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = err1
|
err = err1
|
||||||
@@ -307,22 +343,32 @@ func buildTargets(ctx context.Context, dockerCli command.Cli, opts map[string]bu
|
|||||||
|
|
||||||
printWarnings(os.Stderr, printer.Warnings(), progressMode)
|
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
|
return resp[defaultTargetName].ExporterResponse["containerimage.digest"], res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseInvokeConfig(invoke string) (cfg build.ContainerConfig, err error) {
|
func parseInvokeConfig(invoke string) (cfg build.ContainerConfig, err error) {
|
||||||
|
cfg.Tty = true
|
||||||
|
if invoke == "default" {
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
csvReader := csv.NewReader(strings.NewReader(invoke))
|
csvReader := csv.NewReader(strings.NewReader(invoke))
|
||||||
fields, err := csvReader.Read()
|
fields, err := csvReader.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
cfg.Tty = true
|
|
||||||
if len(fields) == 1 && !strings.Contains(fields[0], "=") {
|
if len(fields) == 1 && !strings.Contains(fields[0], "=") {
|
||||||
cfg.Args = []string{fields[0]}
|
cfg.Cmd = []string{fields[0]}
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
var entrypoint string
|
|
||||||
var args []string
|
|
||||||
for _, field := range fields {
|
for _, field := range fields {
|
||||||
parts := strings.SplitN(field, "=", 2)
|
parts := strings.SplitN(field, "=", 2)
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
@@ -332,15 +378,15 @@ func parseInvokeConfig(invoke string) (cfg build.ContainerConfig, err error) {
|
|||||||
value := parts[1]
|
value := parts[1]
|
||||||
switch key {
|
switch key {
|
||||||
case "args":
|
case "args":
|
||||||
args = append(args, value) // TODO: support JSON
|
cfg.Cmd = append(cfg.Cmd, value) // TODO: support JSON
|
||||||
case "entrypoint":
|
case "entrypoint":
|
||||||
entrypoint = value // TODO: support JSON
|
cfg.Entrypoint = append(cfg.Entrypoint, value) // TODO: support JSON
|
||||||
case "env":
|
case "env":
|
||||||
cfg.Env = append(cfg.Env, value)
|
cfg.Env = append(cfg.Env, value)
|
||||||
case "user":
|
case "user":
|
||||||
cfg.User = value
|
cfg.User = &value
|
||||||
case "cwd":
|
case "cwd":
|
||||||
cfg.Cwd = value
|
cfg.Cwd = &value
|
||||||
case "tty":
|
case "tty":
|
||||||
cfg.Tty, err = strconv.ParseBool(value)
|
cfg.Tty, err = strconv.ParseBool(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -350,13 +396,6 @@ func parseInvokeConfig(invoke string) (cfg build.ContainerConfig, err error) {
|
|||||||
return cfg, errors.Errorf("unknown key %q", key)
|
return cfg, errors.Errorf("unknown key %q", key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cfg.Args = args
|
|
||||||
if entrypoint != "" {
|
|
||||||
cfg.Args = append([]string{entrypoint}, cfg.Args...)
|
|
||||||
}
|
|
||||||
if len(cfg.Args) == 0 {
|
|
||||||
cfg.Args = []string{"sh"}
|
|
||||||
}
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,7 +470,7 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
||||||
flags.StringSliceVar(&options.extraHosts, "add-host", []string{}, `Add a custom host-to-IP mapping (format: "host:ip")`)
|
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.SetAnnotation("add-host", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#add-host"})
|
||||||
|
|
||||||
flags.StringSliceVar(&options.allow, "allow", []string{}, `Allow extra privileged entitlement (e.g., "network.host", "security.insecure")`)
|
flags.StringSliceVar(&options.allow, "allow", []string{}, `Allow extra privileged entitlement (e.g., "network.host", "security.insecure")`)
|
||||||
|
|
||||||
@@ -442,12 +481,12 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
flags.StringArrayVar(&options.cacheTo, "cache-to", []string{}, `Cache export destinations (e.g., "user/app:cache", "type=local,dest=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.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.SetAnnotation("cgroup-parent", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#cgroup-parent"})
|
||||||
|
|
||||||
flags.StringArrayVar(&options.contexts, "build-context", []string{}, "Additional build contexts (e.g., name=path)")
|
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.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.SetAnnotation("file", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#file"})
|
||||||
|
|
||||||
flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file")
|
flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file")
|
||||||
|
|
||||||
@@ -463,6 +502,10 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
|
|
||||||
flags.StringArrayVar(&options.platforms, "platform", platformsDefault, "Set target platform for build")
|
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.BoolVar(&options.exportPush, "push", false, `Shorthand for "--output=type=registry"`)
|
||||||
|
|
||||||
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the build output and print image ID on success")
|
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the build output and print image ID on success")
|
||||||
@@ -474,15 +517,19 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
flags.StringArrayVar(&options.ssh, "ssh", []string{}, `SSH agent socket or keys to expose to the build (format: "default|<id>[=<socket>|<key>[,<key>]]")`)
|
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.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.SetAnnotation("tag", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#tag"})
|
||||||
|
|
||||||
flags.StringVar(&options.target, "target", "", "Set the target build stage to build")
|
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.SetAnnotation("target", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#target"})
|
||||||
|
|
||||||
flags.Var(options.ulimits, "ulimit", "Ulimit options")
|
flags.Var(options.ulimits, "ulimit", "Ulimit options")
|
||||||
|
|
||||||
if os.Getenv("BUILDX_EXPERIMENTAL") == "1" {
|
flags.StringArrayVar(&options.attests, "attest", []string{}, `Attestation parameters (format: "type=sbom,generator=image")`)
|
||||||
flags.StringVar(&options.invoke, "invoke", "", "Invoke a command after the build. BUILDX_EXPERIMENTAL=1 is required.")
|
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]")
|
||||||
}
|
}
|
||||||
|
|
||||||
// hidden flags
|
// hidden flags
|
||||||
@@ -596,6 +643,34 @@ func parseContextNames(values []string) (map[string]build.NamedContext, error) {
|
|||||||
return result, nil
|
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 {
|
func writeMetadataFile(filename string, dt interface{}) error {
|
||||||
b, err := json.MarshalIndent(dt, "", " ")
|
b, err := json.MarshalIndent(dt, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -652,3 +727,20 @@ func (w *wrapped) Error() string {
|
|||||||
func (w *wrapped) Unwrap() error {
|
func (w *wrapped) Unwrap() error {
|
||||||
return w.err
|
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,13 +10,17 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
|
remoteutil "github.com/docker/buildx/driver/remote/util"
|
||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/store"
|
||||||
"github.com/docker/buildx/store/storeutil"
|
"github.com/docker/buildx/store/storeutil"
|
||||||
"github.com/docker/buildx/util/cobrautil"
|
"github.com/docker/buildx/util/cobrautil"
|
||||||
"github.com/docker/buildx/util/confutil"
|
"github.com/docker/buildx/util/confutil"
|
||||||
|
"github.com/docker/buildx/util/dockerutil"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
dopts "github.com/docker/cli/opts"
|
||||||
"github.com/google/shlex"
|
"github.com/google/shlex"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
"github.com/moby/buildkit/util/appcontext"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -61,32 +65,6 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildkitHost := os.Getenv("BUILDKIT_HOST")
|
|
||||||
|
|
||||||
driverName := in.driver
|
|
||||||
if driverName == "" {
|
|
||||||
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 driver.GetFactory(driverName, true) == nil {
|
|
||||||
return errors.Errorf("failed to find driver %q", in.driver)
|
|
||||||
}
|
|
||||||
|
|
||||||
txn, release, err := storeutil.GetStore(dockerCli)
|
txn, release, err := storeutil.GetStore(dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -121,17 +99,48 @@ 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)
|
logrus.Warnf("failed to find %q for append, creating a new instance instead", in.name)
|
||||||
}
|
}
|
||||||
if in.actionLeave {
|
if in.actionLeave {
|
||||||
return errors.Errorf("failed to find instance %q for leave", name)
|
return errors.Errorf("failed to find instance %q for leave", in.name)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return err
|
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 ng != nil {
|
||||||
if in.nodeName == "" && !in.actionAppend {
|
if in.nodeName == "" && !in.actionAppend {
|
||||||
return errors.Errorf("existing instance for %s but no append mode, specify --node to make changes for existing instances", name)
|
return errors.Errorf("existing instance for %q 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
|
ngOriginal := ng
|
||||||
@@ -142,13 +151,10 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
if ng == nil {
|
if ng == nil {
|
||||||
ng = &store.NodeGroup{
|
ng = &store.NodeGroup{
|
||||||
Name: name,
|
Name: name,
|
||||||
|
Driver: driverName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ng.Driver == "" || in.driver != "" {
|
|
||||||
ng.Driver = driverName
|
|
||||||
}
|
|
||||||
|
|
||||||
var flags []string
|
var flags []string
|
||||||
if in.flags != "" {
|
if in.flags != "" {
|
||||||
flags, err = shlex.Split(in.flags)
|
flags, err = shlex.Split(in.flags)
|
||||||
@@ -166,6 +172,9 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
} else {
|
} else {
|
||||||
switch {
|
switch {
|
||||||
case driverName == "kubernetes":
|
case driverName == "kubernetes":
|
||||||
|
if len(args) > 0 {
|
||||||
|
logrus.Warnf("kubernetes driver does not support endpoint args %q", args[0])
|
||||||
|
}
|
||||||
// naming endpoint to make --append works
|
// naming endpoint to make --append works
|
||||||
ep = (&url.URL{
|
ep = (&url.URL{
|
||||||
Scheme: driverName,
|
Scheme: driverName,
|
||||||
@@ -199,7 +208,7 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
if dockerCli.CurrentContext() == "default" && dockerCli.DockerEndpoint().TLSData != nil {
|
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>`")
|
return errors.Errorf("could not create a builder instance with TLS data loaded from environment. Please use `docker context create <context-name>` to create a context for current environment and then create a builder instance with `docker buildx create <context-name>`")
|
||||||
}
|
}
|
||||||
ep, err = storeutil.GetCurrentEndpoint(dockerCli)
|
ep, err = dockerutil.GetCurrentEndpoint(dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -229,17 +238,26 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ngi := &nginfo{ng: ng}
|
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)
|
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err = loadNodeGroupData(timeoutCtx, dockerCli, ngi); err != nil {
|
nodes, err := b.LoadNodes(timeoutCtx, true)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, info := range ngi.drivers {
|
|
||||||
if err := info.di.Err; err != nil {
|
for _, node := range nodes {
|
||||||
err := errors.Errorf("failed to initialize builder %s (%s): %s", ng.Name, info.di.Name, err)
|
if err := node.Err; err != nil {
|
||||||
|
err := errors.Errorf("failed to initialize builder %s (%s): %s", ng.Name, node.Name, err)
|
||||||
var err2 error
|
var err2 error
|
||||||
if ngOriginal == nil {
|
if ngOriginal == nil {
|
||||||
err2 = txn.Remove(ng.Name)
|
err2 = txn.Remove(ng.Name)
|
||||||
@@ -254,7 +272,7 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if in.use && ep != "" {
|
if in.use && ep != "" {
|
||||||
current, err := storeutil.GetCurrentEndpoint(dockerCli)
|
current, err := dockerutil.GetCurrentEndpoint(dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -264,7 +282,7 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if in.bootstrap {
|
if in.bootstrap {
|
||||||
if _, err = boot(ctx, ngi); err != nil {
|
if _, err = b.Boot(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,7 +295,7 @@ func createCmd(dockerCli command.Cli) *cobra.Command {
|
|||||||
var options createOptions
|
var options createOptions
|
||||||
|
|
||||||
var drivers bytes.Buffer
|
var drivers bytes.Buffer
|
||||||
for _, d := range driver.GetFactories() {
|
for _, d := range driver.GetFactories(true) {
|
||||||
if len(drivers.String()) > 0 {
|
if len(drivers.String()) > 0 {
|
||||||
drivers.WriteString(", ")
|
drivers.WriteString(", ")
|
||||||
}
|
}
|
||||||
@@ -315,6 +333,9 @@ func createCmd(dockerCli command.Cli) *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func csvToMap(in []string) (map[string]string, error) {
|
func csvToMap(in []string) (map[string]string, error) {
|
||||||
|
if len(in) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
m := make(map[string]string, len(in))
|
m := make(map[string]string, len(in))
|
||||||
for _, s := range in {
|
for _, s := range in {
|
||||||
csvReader := csv.NewReader(strings.NewReader(s))
|
csvReader := csv.NewReader(strings.NewReader(s))
|
||||||
@@ -332,3 +353,27 @@ func csvToMap(in []string) (map[string]string, error) {
|
|||||||
}
|
}
|
||||||
return m, nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
@@ -33,25 +33,29 @@ func runDiskUsage(dockerCli command.Cli, opts duOptions) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dis, err := getInstanceOrDefault(ctx, dockerCli, opts.builder, "")
|
b, err := builder.New(dockerCli, builder.WithName(opts.builder))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, di := range dis {
|
nodes, err := b.LoadNodes(ctx, false)
|
||||||
if di.Err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
for _, node := range nodes {
|
||||||
|
if node.Err != nil {
|
||||||
|
return node.Err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out := make([][]*client.UsageInfo, len(dis))
|
out := make([][]*client.UsageInfo, len(nodes))
|
||||||
|
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
for i, di := range dis {
|
for i, node := range nodes {
|
||||||
func(i int, di build.DriverInfo) {
|
func(i int, node builder.Node) {
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
if di.Driver != nil {
|
if node.Driver != nil {
|
||||||
c, err := di.Driver.Client(ctx)
|
c, err := node.Driver.Client(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -64,7 +68,7 @@ func runDiskUsage(dockerCli command.Cli, opts duOptions) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}(i, di)
|
}(i, node)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := eg.Wait(); err != nil {
|
if err := eg.Wait(); err != nil {
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/store/storeutil"
|
|
||||||
"github.com/docker/buildx/util/imagetools"
|
"github.com/docker/buildx/util/imagetools"
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
@@ -90,47 +89,34 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, s := range srcs {
|
for i, s := range srcs {
|
||||||
if s.Ref == nil && s.Desc.MediaType == "" && s.Desc.Digest != "" {
|
if s.Ref == nil {
|
||||||
if defaultRepo == nil {
|
if defaultRepo == nil {
|
||||||
return errors.Errorf("multiple repositories specified, cannot infer repository for %q", args[i])
|
return errors.Errorf("multiple repositories specified, cannot infer repository for %q", args[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err := reference.ParseNormalizedNamed(*defaultRepo)
|
n, err := reference.ParseNormalizedNamed(*defaultRepo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if s.Desc.MediaType == "" && s.Desc.Digest != "" {
|
||||||
r, err := reference.WithDigest(n, s.Desc.Digest)
|
r, err := reference.WithDigest(n, s.Desc.Digest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
srcs[i].Ref = r
|
srcs[i].Ref = r
|
||||||
sourceRefs = true
|
sourceRefs = true
|
||||||
|
} else {
|
||||||
|
srcs[i].Ref = reference.TagNameOnly(n)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := appcontext.Context()
|
ctx := appcontext.Context()
|
||||||
|
|
||||||
txn, release, err := storeutil.GetStore(dockerCli)
|
b, err := builder.New(dockerCli, builder.WithName(in.builder))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer release()
|
imageopt, err := b.ImageOpt()
|
||||||
|
|
||||||
var ng *store.NodeGroup
|
|
||||||
|
|
||||||
if in.builder != "" {
|
|
||||||
ng, err = storeutil.GetNodeGroup(txn, dockerCli, in.builder)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ng, err = storeutil.GetCurrentInstance(txn, dockerCli)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
imageopt, err := storeutil.GetImageConfig(dockerCli, ng)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -182,7 +168,10 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
|
|
||||||
ctx2, cancel := context.WithCancel(context.TODO())
|
ctx2, cancel := context.WithCancel(context.TODO())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
printer := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, in.progress)
|
printer, err := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, in.progress)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
eg, _ := errgroup.WithContext(ctx)
|
eg, _ := errgroup.WithContext(ctx)
|
||||||
pw := progress.WithPrefix(printer, "internal", true)
|
pw := progress.WithPrefix(printer, "internal", true)
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/store/storeutil"
|
|
||||||
"github.com/docker/buildx/util/imagetools"
|
"github.com/docker/buildx/util/imagetools"
|
||||||
"github.com/docker/cli-docs-tool/annotation"
|
"github.com/docker/cli-docs-tool/annotation"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
@@ -25,27 +24,11 @@ func runInspect(dockerCli command.Cli, in inspectOptions, name string) error {
|
|||||||
return errors.Errorf("format and raw cannot be used together")
|
return errors.Errorf("format and raw cannot be used together")
|
||||||
}
|
}
|
||||||
|
|
||||||
txn, release, err := storeutil.GetStore(dockerCli)
|
b, err := builder.New(dockerCli, builder.WithName(in.builder))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer release()
|
imageopt, err := b.ImageOpt()
|
||||||
|
|
||||||
var ng *store.NodeGroup
|
|
||||||
|
|
||||||
if in.builder != "" {
|
|
||||||
ng, err = storeutil.GetNodeGroup(txn, dockerCli, in.builder)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ng, err = storeutil.GetCurrentInstance(txn, dockerCli)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
imageopt, err := storeutil.GetImageConfig(dockerCli, ng)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ import (
|
|||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/store/storeutil"
|
|
||||||
"github.com/docker/buildx/util/platformutil"
|
"github.com/docker/buildx/util/platformutil"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
@@ -25,71 +24,46 @@ type inspectOptions struct {
|
|||||||
func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
||||||
ctx := appcontext.Context()
|
ctx := appcontext.Context()
|
||||||
|
|
||||||
txn, release, err := storeutil.GetStore(dockerCli)
|
b, err := builder.New(dockerCli,
|
||||||
|
builder.WithName(in.builder),
|
||||||
|
builder.WithSkippedValidation(),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer release()
|
|
||||||
|
|
||||||
var ng *store.NodeGroup
|
|
||||||
|
|
||||||
if in.builder != "" {
|
|
||||||
ng, err = storeutil.GetNodeGroup(txn, dockerCli, in.builder)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ng, err = storeutil.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)
|
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
err = loadNodeGroupData(timeoutCtx, dockerCli, ngi)
|
nodes, err := b.LoadNodes(timeoutCtx, true)
|
||||||
|
|
||||||
var bootNgi *nginfo
|
|
||||||
if in.bootstrap {
|
if in.bootstrap {
|
||||||
var ok bool
|
var ok bool
|
||||||
ok, err = boot(ctx, ngi)
|
ok, err = b.Boot(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
bootNgi = ngi
|
|
||||||
if ok {
|
if ok {
|
||||||
ngi = &nginfo{ng: ng}
|
nodes, err = b.LoadNodes(timeoutCtx, true)
|
||||||
err = loadNodeGroupData(ctx, dockerCli, ngi)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||||
fmt.Fprintf(w, "Name:\t%s\n", ngi.ng.Name)
|
fmt.Fprintf(w, "Name:\t%s\n", b.Name)
|
||||||
fmt.Fprintf(w, "Driver:\t%s\n", ngi.ng.Driver)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
||||||
} else if ngi.err != nil {
|
} else if b.Err() != nil {
|
||||||
fmt.Fprintf(w, "Error:\t%s\n", ngi.err.Error())
|
fmt.Fprintf(w, "Error:\t%s\n", b.Err().Error())
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Fprintln(w, "")
|
fmt.Fprintln(w, "")
|
||||||
fmt.Fprintln(w, "Nodes:")
|
fmt.Fprintln(w, "Nodes:")
|
||||||
|
|
||||||
for i, n := range ngi.ng.Nodes {
|
for i, n := range nodes {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
fmt.Fprintln(w, "")
|
fmt.Fprintln(w, "")
|
||||||
}
|
}
|
||||||
@@ -104,18 +78,17 @@ func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
|||||||
fmt.Fprintf(w, "Driver Options:\t%s\n", strings.Join(driverOpts, " "))
|
fmt.Fprintf(w, "Driver Options:\t%s\n", strings.Join(driverOpts, " "))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ngi.drivers[i].di.Err; err != nil {
|
if err := n.Err; err != nil {
|
||||||
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
||||||
} else if err := ngi.drivers[i].err; err != nil {
|
|
||||||
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 {
|
} else {
|
||||||
fmt.Fprintf(w, "Status:\t%s\n", ngi.drivers[i].info.Status)
|
fmt.Fprintf(w, "Status:\t%s\n", nodes[i].DriverInfo.Status)
|
||||||
if len(n.Flags) > 0 {
|
if len(n.Flags) > 0 {
|
||||||
fmt.Fprintf(w, "Flags:\t%s\n", strings.Join(n.Flags, " "))
|
fmt.Fprintf(w, "Flags:\t%s\n", strings.Join(n.Flags, " "))
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "Platforms:\t%s\n", strings.Join(platformutil.FormatInGroups(n.Platforms, ngi.drivers[i].platforms), ", "))
|
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), ", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
115
commands/ls.go
115
commands/ls.go
@@ -4,12 +4,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/store/storeutil"
|
"github.com/docker/buildx/store/storeutil"
|
||||||
"github.com/docker/buildx/util/cobrautil"
|
"github.com/docker/buildx/util/cobrautil"
|
||||||
"github.com/docker/buildx/util/platformutil"
|
"github.com/docker/buildx/util/platformutil"
|
||||||
@@ -32,52 +31,24 @@ func runLs(dockerCli command.Cli, in lsOptions) error {
|
|||||||
}
|
}
|
||||||
defer release()
|
defer release()
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
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)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
ll, err := txn.List()
|
eg, _ := errgroup.WithContext(timeoutCtx)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
builders := make([]*nginfo, len(ll))
|
|
||||||
for i, ng := range ll {
|
|
||||||
builders[i] = &nginfo{ng: ng}
|
|
||||||
}
|
|
||||||
|
|
||||||
contexts, err := dockerCli.ContextStore().List()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sort.Slice(contexts, func(i, j int) bool {
|
|
||||||
return contexts[i].Name < contexts[j].Name
|
|
||||||
})
|
|
||||||
for _, c := range contexts {
|
|
||||||
ngi := &nginfo{ng: &store.NodeGroup{
|
|
||||||
Name: c.Name,
|
|
||||||
Nodes: []store.Node{{
|
|
||||||
Name: c.Name,
|
|
||||||
Endpoint: c.Name,
|
|
||||||
}},
|
|
||||||
}}
|
|
||||||
// if a context has the same name as an instance from the store, do not
|
|
||||||
// add it to the builders list. An instance from the store takes
|
|
||||||
// precedence over context builders.
|
|
||||||
if hasNodeGroup(builders, ngi) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
builders = append(builders, ngi)
|
|
||||||
}
|
|
||||||
|
|
||||||
eg, _ := errgroup.WithContext(ctx)
|
|
||||||
|
|
||||||
for _, b := range builders {
|
for _, b := range builders {
|
||||||
func(b *nginfo) {
|
func(b *builder.Builder) {
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
err = loadNodeGroupData(ctx, dockerCli, b)
|
_, _ = b.LoadNodes(timeoutCtx, true)
|
||||||
if b.err == nil && err != nil {
|
|
||||||
b.err = err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}(b)
|
}(b)
|
||||||
@@ -87,29 +58,15 @@ func runLs(dockerCli command.Cli, in lsOptions) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
currentName := "default"
|
|
||||||
current, err := storeutil.GetCurrentInstance(txn, dockerCli)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if current != nil {
|
|
||||||
currentName = current.Name
|
|
||||||
if current.Name == "default" {
|
|
||||||
currentName = current.Nodes[0].Endpoint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w := tabwriter.NewWriter(dockerCli.Out(), 0, 0, 1, ' ', 0)
|
w := tabwriter.NewWriter(dockerCli.Out(), 0, 0, 1, ' ', 0)
|
||||||
fmt.Fprintf(w, "NAME/NODE\tDRIVER/ENDPOINT\tSTATUS\tBUILDKIT\tPLATFORMS\n")
|
fmt.Fprintf(w, "NAME/NODE\tDRIVER/ENDPOINT\tSTATUS\tBUILDKIT\tPLATFORMS\n")
|
||||||
|
|
||||||
currentSet := false
|
|
||||||
printErr := false
|
printErr := false
|
||||||
for _, b := range builders {
|
for _, b := range builders {
|
||||||
if !currentSet && b.ng.Name == currentName {
|
if current.Name == b.Name {
|
||||||
b.ng.Name += " *"
|
b.Name += " *"
|
||||||
currentSet = true
|
|
||||||
}
|
}
|
||||||
if ok := printngi(w, b); !ok {
|
if ok := printBuilder(w, b); !ok {
|
||||||
printErr = true
|
printErr = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,19 +76,12 @@ func runLs(dockerCli command.Cli, in lsOptions) error {
|
|||||||
if printErr {
|
if printErr {
|
||||||
_, _ = fmt.Fprintf(dockerCli.Err(), "\n")
|
_, _ = fmt.Fprintf(dockerCli.Err(), "\n")
|
||||||
for _, b := range builders {
|
for _, b := range builders {
|
||||||
if b.err != nil {
|
if b.Err() != nil {
|
||||||
_, _ = fmt.Fprintf(dockerCli.Err(), "Cannot load builder %s: %s\n", b.ng.Name, strings.TrimSpace(b.err.Error()))
|
_, _ = fmt.Fprintf(dockerCli.Err(), "Cannot load builder %s: %s\n", b.Name, strings.TrimSpace(b.Err().Error()))
|
||||||
} else {
|
} else {
|
||||||
for idx, n := range b.ng.Nodes {
|
for _, d := range b.Nodes() {
|
||||||
d := b.drivers[idx]
|
if d.Err != nil {
|
||||||
var nodeErr string
|
_, _ = fmt.Fprintf(dockerCli.Err(), "Failed to get status for %s (%s): %s\n", b.Name, d.Name, strings.TrimSpace(d.Err.Error()))
|
||||||
if d.err != nil {
|
|
||||||
nodeErr = d.err.Error()
|
|
||||||
} else if d.di.Err != nil {
|
|
||||||
nodeErr = d.di.Err.Error()
|
|
||||||
}
|
|
||||||
if nodeErr != "" {
|
|
||||||
_, _ = fmt.Fprintf(dockerCli.Err(), "Failed to get status for %s (%s): %s\n", b.ng.Name, n.Name, strings.TrimSpace(nodeErr))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,26 +91,25 @@ func runLs(dockerCli command.Cli, in lsOptions) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func printngi(w io.Writer, ngi *nginfo) (ok bool) {
|
func printBuilder(w io.Writer, b *builder.Builder) (ok bool) {
|
||||||
ok = true
|
ok = true
|
||||||
var err string
|
var err string
|
||||||
if ngi.err != nil {
|
if b.Err() != nil {
|
||||||
ok = false
|
ok = false
|
||||||
err = "error"
|
err = "error"
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "%s\t%s\t%s\t\t\n", ngi.ng.Name, ngi.ng.Driver, err)
|
fmt.Fprintf(w, "%s\t%s\t%s\t\t\n", b.Name, b.Driver, err)
|
||||||
if ngi.err == nil {
|
if b.Err() == nil {
|
||||||
for idx, n := range ngi.ng.Nodes {
|
for _, n := range b.Nodes() {
|
||||||
d := ngi.drivers[idx]
|
|
||||||
var status string
|
var status string
|
||||||
if d.info != nil {
|
if n.DriverInfo != nil {
|
||||||
status = d.info.Status.String()
|
status = n.DriverInfo.Status.String()
|
||||||
}
|
}
|
||||||
if d.err != nil || d.di.Err != nil {
|
if n.Err != nil {
|
||||||
ok = false
|
ok = false
|
||||||
fmt.Fprintf(w, " %s\t%s\t%s\t\t\n", n.Name, n.Endpoint, "error")
|
fmt.Fprintf(w, " %s\t%s\t%s\t\t\n", n.Name, n.Endpoint, "error")
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(w, " %s\t%s\t%s\t%s\t%s\n", n.Name, n.Endpoint, status, d.version, strings.Join(platformutil.FormatInGroups(n.Platforms, d.platforms), ", "))
|
fmt.Fprintf(w, " %s\t%s\t%s\t%s\t%s\n", n.Name, n.Endpoint, status, n.Version, strings.Join(platformutil.FormatInGroups(n.Node.Platforms, n.Platforms), ", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
48
commands/print.go
Normal file
48
commands/print.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
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,7 +7,7 @@ import (
|
|||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
@@ -54,15 +54,19 @@ func runPrune(dockerCli command.Cli, opts pruneOptions) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dis, err := getInstanceOrDefault(ctx, dockerCli, opts.builder, "")
|
b, err := builder.New(dockerCli, builder.WithName(opts.builder))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, di := range dis {
|
nodes, err := b.LoadNodes(ctx, false)
|
||||||
if di.Err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
for _, node := range nodes {
|
||||||
|
if node.Err != nil {
|
||||||
|
return node.Err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := make(chan client.UsageInfo)
|
ch := make(chan client.UsageInfo)
|
||||||
@@ -90,11 +94,11 @@ func runPrune(dockerCli command.Cli, opts pruneOptions) error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
for _, di := range dis {
|
for _, node := range nodes {
|
||||||
func(di build.DriverInfo) {
|
func(node builder.Node) {
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
if di.Driver != nil {
|
if node.Driver != nil {
|
||||||
c, err := di.Driver.Client(ctx)
|
c, err := node.Driver.Client(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -109,7 +113,7 @@ func runPrune(dockerCli command.Cli, opts pruneOptions) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}(di)
|
}(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := eg.Wait(); err != nil {
|
if err := eg.Wait(); err != nil {
|
||||||
@@ -138,7 +142,7 @@ func pruneCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&options.all, "all", "a", false, "Remove all unused images, not just dangling ones")
|
flags.BoolVarP(&options.all, "all", "a", false, "Include internal/frontend images")
|
||||||
flags.Var(&options.filter, "filter", `Provide filter values (e.g., "until=24h")`)
|
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.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.BoolVar(&options.verbose, "verbose", false, "Provide a more verbose output")
|
||||||
@@ -155,9 +159,9 @@ func toBuildkitPruneInfo(f filters.Args) (*client.PruneInfo, error) {
|
|||||||
if len(untilValues) > 0 && len(unusedForValues) > 0 {
|
if len(untilValues) > 0 && len(unusedForValues) > 0 {
|
||||||
return nil, errors.Errorf("conflicting filters %q and %q", "until", "unused-for")
|
return nil, errors.Errorf("conflicting filters %q and %q", "until", "unused-for")
|
||||||
}
|
}
|
||||||
filterKey := "until"
|
untilKey := "until"
|
||||||
if len(unusedForValues) > 0 {
|
if len(unusedForValues) > 0 {
|
||||||
filterKey = "unused-for"
|
untilKey = "unused-for"
|
||||||
}
|
}
|
||||||
untilValues = append(untilValues, unusedForValues...)
|
untilValues = append(untilValues, unusedForValues...)
|
||||||
|
|
||||||
@@ -168,23 +172,27 @@ func toBuildkitPruneInfo(f filters.Args) (*client.PruneInfo, error) {
|
|||||||
var err error
|
var err error
|
||||||
until, err = time.ParseDuration(untilValues[0])
|
until, err = time.ParseDuration(untilValues[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "%q filter expects a duration (e.g., '24h')", filterKey)
|
return nil, errors.Wrapf(err, "%q filter expects a duration (e.g., '24h')", untilKey)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil, errors.Errorf("filters expect only one value")
|
return nil, errors.Errorf("filters expect only one value")
|
||||||
}
|
}
|
||||||
|
|
||||||
bkFilter := make([]string, 0, f.Len())
|
filters := make([]string, 0, f.Len())
|
||||||
for _, field := range f.Keys() {
|
for _, filterKey := range f.Keys() {
|
||||||
values := f.Get(field)
|
if filterKey == untilKey {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
values := f.Get(filterKey)
|
||||||
switch len(values) {
|
switch len(values) {
|
||||||
case 0:
|
case 0:
|
||||||
bkFilter = append(bkFilter, field)
|
filters = append(filters, filterKey)
|
||||||
case 1:
|
case 1:
|
||||||
if field == "id" {
|
if filterKey == "id" {
|
||||||
bkFilter = append(bkFilter, field+"~="+values[0])
|
filters = append(filters, filterKey+"~="+values[0])
|
||||||
} else {
|
} else {
|
||||||
bkFilter = append(bkFilter, field+"=="+values[0])
|
filters = append(filters, filterKey+"=="+values[0])
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil, errors.Errorf("filters expect only one value")
|
return nil, errors.Errorf("filters expect only one value")
|
||||||
@@ -192,6 +200,6 @@ func toBuildkitPruneInfo(f filters.Args) (*client.PruneInfo, error) {
|
|||||||
}
|
}
|
||||||
return &client.PruneInfo{
|
return &client.PruneInfo{
|
||||||
KeepDuration: until,
|
KeepDuration: until,
|
||||||
Filter: []string{strings.Join(bkFilter, ",")},
|
Filter: []string{strings.Join(filters, ",")},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/store"
|
||||||
"github.com/docker/buildx/store/storeutil"
|
"github.com/docker/buildx/store/storeutil"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
@@ -44,41 +45,33 @@ func runRm(dockerCli command.Cli, in rmOptions) error {
|
|||||||
return rmAllInactive(ctx, txn, dockerCli, in)
|
return rmAllInactive(ctx, txn, dockerCli, in)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ng *store.NodeGroup
|
b, err := builder.New(dockerCli,
|
||||||
if in.builder != "" {
|
builder.WithName(in.builder),
|
||||||
ng, err = storeutil.GetNodeGroup(txn, dockerCli, in.builder)
|
builder.WithStore(txn),
|
||||||
|
builder.WithSkippedValidation(),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
ng, err = storeutil.GetCurrentInstance(txn, dockerCli)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ng == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ctxbuilders, err := dockerCli.ContextStore().List()
|
nodes, err := b.LoadNodes(ctx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, cb := range ctxbuilders {
|
|
||||||
if ng.Driver == "docker" && len(ng.Nodes) == 1 && ng.Nodes[0].Endpoint == cb.Name {
|
if cb := b.ContextName(); cb != "" {
|
||||||
return errors.Errorf("context builder cannot be removed, run `docker context rm %s` to remove this context", cb.Name)
|
return errors.Errorf("context builder cannot be removed, run `docker context rm %s` to remove this context", cb)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err1 := rm(ctx, dockerCli, in, ng)
|
err1 := rm(ctx, nodes, in)
|
||||||
if err := txn.Remove(ng.Name); err != nil {
|
if err := txn.Remove(b.Name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
return err1
|
return err1
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", ng.Name)
|
_, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", b.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,61 +103,53 @@ func rmCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func rm(ctx context.Context, dockerCli command.Cli, in rmOptions, ng *store.NodeGroup) error {
|
func rm(ctx context.Context, nodes []builder.Node, in rmOptions) (err error) {
|
||||||
dis, err := driversForNodeGroup(ctx, dockerCli, ng, "")
|
for _, node := range nodes {
|
||||||
if err != nil {
|
if node.Driver == nil {
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, di := range dis {
|
|
||||||
if di.Driver == nil {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Do not stop the buildkitd daemon when --keep-daemon is provided
|
// Do not stop the buildkitd daemon when --keep-daemon is provided
|
||||||
if !in.keepDaemon {
|
if !in.keepDaemon {
|
||||||
if err := di.Driver.Stop(ctx, true); err != nil {
|
if err := node.Driver.Stop(ctx, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := di.Driver.Rm(ctx, true, !in.keepState, !in.keepDaemon); err != nil {
|
if err := node.Driver.Rm(ctx, true, !in.keepState, !in.keepDaemon); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if di.Err != nil {
|
if node.Err != nil {
|
||||||
err = di.Err
|
err = node.Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func rmAllInactive(ctx context.Context, txn *store.Txn, dockerCli command.Cli, in rmOptions) error {
|
func rmAllInactive(ctx context.Context, txn *store.Txn, dockerCli command.Cli, in rmOptions) error {
|
||||||
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
builders, err := builder.GetBuilders(dockerCli, txn)
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
ll, err := txn.List()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
builders := make([]*nginfo, len(ll))
|
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||||
for i, ng := range ll {
|
defer cancel()
|
||||||
builders[i] = &nginfo{ng: ng}
|
|
||||||
}
|
|
||||||
|
|
||||||
eg, _ := errgroup.WithContext(ctx)
|
eg, _ := errgroup.WithContext(timeoutCtx)
|
||||||
for _, b := range builders {
|
for _, b := range builders {
|
||||||
func(b *nginfo) {
|
func(b *builder.Builder) {
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
if err := loadNodeGroupData(ctx, dockerCli, b); err != nil {
|
nodes, err := b.LoadNodes(timeoutCtx, true)
|
||||||
return errors.Wrapf(err, "cannot load %s", b.ng.Name)
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "cannot load %s", b.Name)
|
||||||
}
|
}
|
||||||
if b.ng.Dynamic {
|
if b.Dynamic {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if b.inactive() {
|
if b.Inactive() {
|
||||||
rmerr := rm(ctx, dockerCli, in, b.ng)
|
rmerr := rm(ctx, nodes, in)
|
||||||
if err := txn.Remove(b.ng.Name); err != nil {
|
if err := txn.Remove(b.Name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", b.ng.Name)
|
_, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", b.Name)
|
||||||
return rmerr
|
return rmerr
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ package commands
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/store/storeutil"
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
"github.com/moby/buildkit/util/appcontext"
|
||||||
@@ -18,32 +17,19 @@ type stopOptions struct {
|
|||||||
func runStop(dockerCli command.Cli, in stopOptions) error {
|
func runStop(dockerCli command.Cli, in stopOptions) error {
|
||||||
ctx := appcontext.Context()
|
ctx := appcontext.Context()
|
||||||
|
|
||||||
txn, release, err := storeutil.GetStore(dockerCli)
|
b, err := builder.New(dockerCli,
|
||||||
|
builder.WithName(in.builder),
|
||||||
|
builder.WithSkippedValidation(),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer release()
|
nodes, err := b.LoadNodes(ctx, false)
|
||||||
|
|
||||||
if in.builder != "" {
|
|
||||||
ng, err := storeutil.GetNodeGroup(txn, dockerCli, in.builder)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := stop(ctx, dockerCli, ng); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ng, err := storeutil.GetCurrentInstance(txn, dockerCli)
|
return stop(ctx, nodes)
|
||||||
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 {
|
func stopCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||||
@@ -65,37 +51,15 @@ func stopCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func stop(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup) error {
|
func stop(ctx context.Context, nodes []builder.Node) (err error) {
|
||||||
dis, err := driversForNodeGroup(ctx, dockerCli, ng, "")
|
for _, node := range nodes {
|
||||||
if err != nil {
|
if node.Driver != nil {
|
||||||
return err
|
if err := node.Driver.Stop(ctx, true); err != nil {
|
||||||
}
|
|
||||||
for _, di := range dis {
|
|
||||||
if di.Driver != nil {
|
|
||||||
if err := di.Driver.Stop(ctx, true); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if di.Err != nil {
|
if node.Err != nil {
|
||||||
err = di.Err
|
err = node.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
|
return err
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/docker/buildx/store/storeutil"
|
"github.com/docker/buildx/store/storeutil"
|
||||||
|
"github.com/docker/buildx/util/dockerutil"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -29,7 +30,7 @@ func runUse(dockerCli command.Cli, in useOptions) error {
|
|||||||
return errors.Errorf("run `docker context use default` to switch to default context")
|
return errors.Errorf("run `docker context use default` to switch to default context")
|
||||||
}
|
}
|
||||||
if in.builder == "default" || in.builder == dockerCli.CurrentContext() {
|
if in.builder == "default" || in.builder == dockerCli.CurrentContext() {
|
||||||
ep, err := storeutil.GetCurrentEndpoint(dockerCli)
|
ep, err := dockerutil.GetCurrentEndpoint(dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -52,7 +53,7 @@ func runUse(dockerCli command.Cli, in useOptions) error {
|
|||||||
return errors.Wrapf(err, "failed to find instance %q", in.builder)
|
return errors.Wrapf(err, "failed to find instance %q", in.builder)
|
||||||
}
|
}
|
||||||
|
|
||||||
ep, err := storeutil.GetCurrentEndpoint(dockerCli)
|
ep, err := dockerutil.GetCurrentEndpoint(dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
486
commands/util.go
486
commands/util.go
@@ -1,486 +0,0 @@
|
|||||||
package commands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/buildx/build"
|
|
||||||
"github.com/docker/buildx/driver"
|
|
||||||
ctxkube "github.com/docker/buildx/driver/kubernetes/context"
|
|
||||||
remoteutil "github.com/docker/buildx/driver/remote/util"
|
|
||||||
"github.com/docker/buildx/store"
|
|
||||||
"github.com/docker/buildx/store/storeutil"
|
|
||||||
"github.com/docker/buildx/util/platformutil"
|
|
||||||
"github.com/docker/buildx/util/progress"
|
|
||||||
"github.com/docker/cli/cli/command"
|
|
||||||
"github.com/docker/cli/cli/context/docker"
|
|
||||||
ctxstore "github.com/docker/cli/cli/context/store"
|
|
||||||
dopts "github.com/docker/cli/opts"
|
|
||||||
dockerclient "github.com/docker/docker/client"
|
|
||||||
"github.com/moby/buildkit/util/grpcerrors"
|
|
||||||
specs "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"
|
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
|
||||||
)
|
|
||||||
|
|
||||||
// validateEndpoint validates that endpoint is either a context or a docker host
|
|
||||||
func validateEndpoint(dockerCli command.Cli, ep string) (string, error) {
|
|
||||||
de, err := storeutil.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
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateBuildkitEndpoint validates that endpoint is a valid buildkit host
|
|
||||||
func validateBuildkitEndpoint(ep string) (string, error) {
|
|
||||||
if err := remoteutil.IsValidEndpoint(ep); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return ep, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// driversForNodeGroup returns drivers for a nodegroup instance
|
|
||||||
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 {
|
|
||||||
// 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 := ng.Nodes[0].Endpoint
|
|
||||||
dockerapi, err := clientForEndpoint(dockerCli, ep)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// 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 nil, err
|
|
||||||
}
|
|
||||||
f, err = driver.GetDefaultFactory(ctx, ep, dockerapi, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ng.Driver = f.Name()
|
|
||||||
}
|
|
||||||
imageopt, err := storeutil.GetImageConfig(dockerCli, ng)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
ProxyConfig: storeutil.GetProxyConfig(dockerCli),
|
|
||||||
}
|
|
||||||
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, n.Endpoint, dockerapi, imageopt.Auth, kcc, n.Flags, n.Files, n.DriverOpts, n.Platforms, contextPathHash)
|
|
||||||
if err != nil {
|
|
||||||
di.Err = err
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
di.Driver = d
|
|
||||||
di.ImageOpt = imageopt
|
|
||||||
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 != "" {
|
|
||||||
_ = os.Setenv(clientcmd.RecommendedConfigPathEnvVar, kubeconfig)
|
|
||||||
}
|
|
||||||
rules := clientcmd.NewDefaultClientConfigLoadingRules()
|
|
||||||
apiConfig, err := rules.Load()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return clientcmd.NewDefaultClientConfig(*apiConfig, &clientcmd.ConfigOverrides{}), nil
|
|
||||||
}
|
|
||||||
return ctxkube.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 := storeutil.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 := storeutil.GetStore(dockerCli)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer release()
|
|
||||||
|
|
||||||
if !defaultOnly {
|
|
||||||
ng, err := storeutil.GetCurrentInstance(txn, dockerCli)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ng != nil {
|
|
||||||
return driversForNodeGroup(ctx, dockerCli, ng, contextPathHash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
imageopt, err := storeutil.GetImageConfig(dockerCli, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
d, err := driver.GetDriver(ctx, "buildx_buildkit_default", nil, "", dockerCli.Client(), imageopt.Auth, nil, nil, nil, nil, nil, contextPathHash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return []build.DriverInfo{
|
|
||||||
{
|
|
||||||
Name: "default",
|
|
||||||
Driver: d,
|
|
||||||
ImageOpt: imageopt,
|
|
||||||
ProxyConfig: storeutil.GetProxyConfig(dockerCli),
|
|
||||||
},
|
|
||||||
}, 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 {
|
|
||||||
d.platforms = append(d.platforms, w.Platforms...)
|
|
||||||
}
|
|
||||||
d.platforms = platformutil.Dedupe(d.platforms)
|
|
||||||
inf, err := c.Info(ctx)
|
|
||||||
if err != nil {
|
|
||||||
if st, ok := grpcerrors.AsGRPCStatus(err); ok && st.Code() == codes.Unimplemented {
|
|
||||||
d.version, err = d.di.Driver.Version(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "getting version")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
d.version = inf.BuildkitVersion.Version
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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 hasNodeGroup(list []*nginfo, ngi *nginfo) bool {
|
|
||||||
for _, l := range list {
|
|
||||||
if ngi.ng.Name == l.ng.Name {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func dockerAPI(dockerCli command.Cli) *api {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
type dinfo struct {
|
|
||||||
di *build.DriverInfo
|
|
||||||
info *driver.Info
|
|
||||||
platforms []specs.Platform
|
|
||||||
version string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
type nginfo struct {
|
|
||||||
ng *store.NodeGroup
|
|
||||||
drivers []dinfo
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// inactive checks if all nodes are inactive for this builder
|
|
||||||
func (n *nginfo) inactive() bool {
|
|
||||||
for idx := range n.ng.Nodes {
|
|
||||||
d := n.drivers[idx]
|
|
||||||
if d.info != nil && d.info.Status == driver.Running {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func boot(ctx context.Context, ngi *nginfo) (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, 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
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,14 @@
|
|||||||
variable "GO_VERSION" {
|
variable "GO_VERSION" {
|
||||||
default = "1.18"
|
default = "1.19"
|
||||||
}
|
|
||||||
variable "BIN_OUT" {
|
|
||||||
default = "./bin"
|
|
||||||
}
|
|
||||||
variable "RELEASE_OUT" {
|
|
||||||
default = "./release-out"
|
|
||||||
}
|
}
|
||||||
variable "DOCS_FORMATS" {
|
variable "DOCS_FORMATS" {
|
||||||
default = "md"
|
default = "md"
|
||||||
}
|
}
|
||||||
|
variable "DESTDIR" {
|
||||||
|
default = "./bin"
|
||||||
|
}
|
||||||
|
|
||||||
// Special target: https://github.com/docker/metadata-action#bake-definition
|
# Special target: https://github.com/docker/metadata-action#bake-definition
|
||||||
target "meta-helper" {
|
target "meta-helper" {
|
||||||
tags = ["docker/buildx-bin:local"]
|
tags = ["docker/buildx-bin:local"]
|
||||||
}
|
}
|
||||||
@@ -48,6 +45,7 @@ target "validate-docs" {
|
|||||||
inherits = ["_common"]
|
inherits = ["_common"]
|
||||||
args = {
|
args = {
|
||||||
FORMATS = DOCS_FORMATS
|
FORMATS = DOCS_FORMATS
|
||||||
|
BUILDX_EXPERIMENTAL = 1 // enables experimental cmds/flags for docs generation
|
||||||
}
|
}
|
||||||
dockerfile = "./hack/dockerfiles/docs.Dockerfile"
|
dockerfile = "./hack/dockerfiles/docs.Dockerfile"
|
||||||
target = "validate"
|
target = "validate"
|
||||||
@@ -72,6 +70,7 @@ target "update-docs" {
|
|||||||
inherits = ["_common"]
|
inherits = ["_common"]
|
||||||
args = {
|
args = {
|
||||||
FORMATS = DOCS_FORMATS
|
FORMATS = DOCS_FORMATS
|
||||||
|
BUILDX_EXPERIMENTAL = 1 // enables experimental cmds/flags for docs generation
|
||||||
}
|
}
|
||||||
dockerfile = "./hack/dockerfiles/docs.Dockerfile"
|
dockerfile = "./hack/dockerfiles/docs.Dockerfile"
|
||||||
target = "update"
|
target = "update"
|
||||||
@@ -96,13 +95,13 @@ target "mod-outdated" {
|
|||||||
target "test" {
|
target "test" {
|
||||||
inherits = ["_common"]
|
inherits = ["_common"]
|
||||||
target = "test-coverage"
|
target = "test-coverage"
|
||||||
output = ["./coverage"]
|
output = ["${DESTDIR}/coverage"]
|
||||||
}
|
}
|
||||||
|
|
||||||
target "binaries" {
|
target "binaries" {
|
||||||
inherits = ["_common"]
|
inherits = ["_common"]
|
||||||
target = "binaries"
|
target = "binaries"
|
||||||
output = [BIN_OUT]
|
output = ["${DESTDIR}/build"]
|
||||||
platforms = ["local"]
|
platforms = ["local"]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +125,7 @@ target "binaries-cross" {
|
|||||||
target "release" {
|
target "release" {
|
||||||
inherits = ["binaries-cross"]
|
inherits = ["binaries-cross"]
|
||||||
target = "release"
|
target = "release"
|
||||||
output = [RELEASE_OUT]
|
output = ["${DESTDIR}/release"]
|
||||||
}
|
}
|
||||||
|
|
||||||
target "image" {
|
target "image" {
|
||||||
|
|||||||
818
docs/bake-reference.md
Normal file
818
docs/bake-reference.md
Normal file
@@ -0,0 +1,818 @@
|
|||||||
|
# Bake file reference
|
||||||
|
|
||||||
|
The Bake file is a file for defining workflows that you run using `docker buildx bake`.
|
||||||
|
|
||||||
|
## File format
|
||||||
|
|
||||||
|
You can define your Bake file in the following file formats:
|
||||||
|
|
||||||
|
- HashiCorp Configuration Language (HCL)
|
||||||
|
- JSON
|
||||||
|
- YAML (Compose file)
|
||||||
|
|
||||||
|
By default, Bake uses the following lookup order to find the configuration file:
|
||||||
|
|
||||||
|
1. `docker-bake.override.hcl`
|
||||||
|
2. `docker-bake.hcl`
|
||||||
|
3. `docker-bake.override.json`
|
||||||
|
4. `docker-bake.json`
|
||||||
|
5. `docker-compose.yaml`
|
||||||
|
6. `docker-compose.yml`
|
||||||
|
|
||||||
|
Bake searches for the file in the current working directory.
|
||||||
|
You can specify the file location explicitly using the `--file` flag:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --file=../docker/bake.hcl --print
|
||||||
|
```
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
The Bake file supports the following property types:
|
||||||
|
|
||||||
|
- `target`: build targets
|
||||||
|
- `group`: collections of build targets
|
||||||
|
- `variable`: build arguments and variables
|
||||||
|
- `function`: custom Bake functions
|
||||||
|
|
||||||
|
You define properties as hierarchical blocks in the Bake file.
|
||||||
|
You can assign one or more attributes to a property.
|
||||||
|
|
||||||
|
The following snippet shows a JSON representation of a simple Bake file.
|
||||||
|
This Bake file defines three properties: a variable, a group, and a target.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"variable": {
|
||||||
|
"TAG": {
|
||||||
|
"default": "latest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": ["webapp"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"webapp": {
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"tags": ["docker.io/username/webapp:${TAG}"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the JSON representation of a Bake file, properties are objects,
|
||||||
|
and attributes are values assigned to those objects.
|
||||||
|
|
||||||
|
The following example shows the same Bake file in the HCL format:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
variable "TAG" {
|
||||||
|
"default" = "latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
group "default" {
|
||||||
|
"targets" = ["latest"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp" {
|
||||||
|
"dockerfile" = "Dockerfile"
|
||||||
|
"tags" = ["docker.io/username/webapp:${TAG}"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
HCL is the preferred format for Bake files.
|
||||||
|
Aside from syntactic differences,
|
||||||
|
HCL lets you use features that the JSON and YAML formats don't support.
|
||||||
|
|
||||||
|
The examples in this document use the HCL format.
|
||||||
|
|
||||||
|
## Target
|
||||||
|
|
||||||
|
A target reflects a single `docker build` invocation.
|
||||||
|
Consider the following build command:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker build \
|
||||||
|
--file=Dockerfile.webapp \
|
||||||
|
--tag=docker.io/username/webapp:latest \
|
||||||
|
https://github.com/username/webapp
|
||||||
|
```
|
||||||
|
|
||||||
|
You can express this command in a Bake file as follows:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "webapp" {
|
||||||
|
dockerfile = "Dockerfile.webapp"
|
||||||
|
tags = ["docker.io/username/webapp:latest"]
|
||||||
|
context = "https://github.com/username/webapp"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The following table shows the complete list of attributes that you can assign to a target:
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ----------------------------------------------- | ------- | -------------------------------------------------------------------- |
|
||||||
|
| [`args`](#targetargs) | Map | Build arguments |
|
||||||
|
| [`attest`](#targetattest) | List | Build attestations |
|
||||||
|
| [`cache-from`](#targetcache-from) | List | External cache sources |
|
||||||
|
| [`cache-to`](#targetcache-to) | List | External cache destinations |
|
||||||
|
| [`context`](#targetcontext) | String | Set of files located in the specified path or URL |
|
||||||
|
| [`contexts`](#targetcontexts) | Map | Additional build contexts |
|
||||||
|
| [`dockerfile-inline`](#targetdockerfile-inline) | String | Inline Dockerfile string |
|
||||||
|
| [`dockerfile`](#targetdockerfile) | String | Dockerfile location |
|
||||||
|
| [`inherits`](#targetinherits) | List | Inherit attributes from other targets |
|
||||||
|
| [`labels`](#targetlabels) | Map | Metadata for images |
|
||||||
|
| [`no-cache-filter`](#targetno-cache-filter) | List | Disable build cache for specific stages |
|
||||||
|
| [`no-cache`](#targetno-cache) | Boolean | Disable build cache completely |
|
||||||
|
| [`output`](#targetoutput) | List | Output destinations |
|
||||||
|
| [`platforms`](#targetplatforms) | List | Target platforms |
|
||||||
|
| [`pull`](#targetpull) | Boolean | Always pull images |
|
||||||
|
| [`secret`](#targetsecret) | List | Secrets to expose to the build |
|
||||||
|
| [`ssh`](#targetssh) | List | SSH agent sockets or keys to expose to the build |
|
||||||
|
| [`tags`](#targettags) | List | Image names and tags |
|
||||||
|
| [`target`](#targettarget) | String | Target build stage |
|
||||||
|
|
||||||
|
### `target.args`
|
||||||
|
|
||||||
|
Use the `args` attribute to define build arguments for the target.
|
||||||
|
This has the same effect as passing a [`--build-arg`][build-arg] flag to the build command.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "default" {
|
||||||
|
args = {
|
||||||
|
VERSION = "0.0.0+unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can set `args` attributes to use `null` values.
|
||||||
|
Doing so forces the `target` to use the `ARG` value specified in the Dockerfile.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
variable "GO_VERSION" {
|
||||||
|
default = "1.20.3"
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp" {
|
||||||
|
dockerfile = "webapp.Dockerfile"
|
||||||
|
tags = ["docker.io/username/webapp"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "db" {
|
||||||
|
args = {
|
||||||
|
GO_VERSION = null
|
||||||
|
}
|
||||||
|
dockerfile = "db.Dockerfile"
|
||||||
|
tags = ["docker.io/username/db"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `target.attest`
|
||||||
|
|
||||||
|
The `attest` attribute lets you apply [build attestations][attestations] to the target.
|
||||||
|
This attribute accepts the long-form CSV version of attestation parameters.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "default" {
|
||||||
|
attest = [
|
||||||
|
"type=provenance,mode=min",
|
||||||
|
"type=sbom"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `target.cache-from`
|
||||||
|
|
||||||
|
Build cache sources.
|
||||||
|
The builder imports cache from the locations you specify.
|
||||||
|
It uses the [Buildx cache storage backends][cache-backends],
|
||||||
|
and it works the same way as the [`--cache-from`][cache-from] flag.
|
||||||
|
This takes a list value, so you can specify multiple cache sources.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "app" {
|
||||||
|
cache-from = [
|
||||||
|
"type=s3,region=eu-west-1,bucket=mybucket",
|
||||||
|
"user/repo:cache",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `target.cache-to`
|
||||||
|
|
||||||
|
Build cache export destinations.
|
||||||
|
The builder exports its build cache to the locations you specify.
|
||||||
|
It uses the [Buildx cache storage backends][cache-backends],
|
||||||
|
and it works the same way as the [`--cache-to` flag][cache-to].
|
||||||
|
This takes a list value, so you can specify multiple cache export targets.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "app" {
|
||||||
|
cache-to = [
|
||||||
|
"type=s3,region=eu-west-1,bucket=mybucket",
|
||||||
|
"type=inline"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `target.context`
|
||||||
|
|
||||||
|
Specifies the location of the build context to use for this target.
|
||||||
|
Accepts a URL or a directory path.
|
||||||
|
This is the same as the [build context][context] positional argument
|
||||||
|
that you pass to the build command.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "app" {
|
||||||
|
context = "./src/www"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This resolves to the current working directory (`"."`) by default.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print -f - <<< 'target "default" {}'
|
||||||
|
[+] Building 0.0s (0/0)
|
||||||
|
{
|
||||||
|
"target": {
|
||||||
|
"default": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `target.contexts`
|
||||||
|
|
||||||
|
Additional build contexts.
|
||||||
|
This is the same as the [`--build-context` flag][build-context].
|
||||||
|
This attribute takes a map, where keys result in named contexts that you can
|
||||||
|
reference in your builds.
|
||||||
|
|
||||||
|
You can specify different types of contexts, such local directories, Git URLs,
|
||||||
|
and even other Bake targets. Bake automatically determines the type of
|
||||||
|
a context based on the pattern of the context value.
|
||||||
|
|
||||||
|
| Context type | Example |
|
||||||
|
| --------------- | ----------------------------------------- |
|
||||||
|
| Container image | `docker-image://alpine@sha256:0123456789` |
|
||||||
|
| Git URL | `https://github.com/user/proj.git` |
|
||||||
|
| HTTP URL | `https://example.com/files` |
|
||||||
|
| Local directory | `../path/to/src` |
|
||||||
|
| Bake target | `target:base` |
|
||||||
|
|
||||||
|
#### Pin an image version
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
target "app" {
|
||||||
|
contexts = {
|
||||||
|
alpine = "docker-image://alpine:3.13"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```Dockerfile
|
||||||
|
# Dockerfile
|
||||||
|
FROM alpine
|
||||||
|
RUN echo "Hello world"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Use a local directory
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
target "app" {
|
||||||
|
contexts = {
|
||||||
|
src = "../path/to/source"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```Dockerfile
|
||||||
|
# Dockerfile
|
||||||
|
FROM scratch AS src
|
||||||
|
FROM golang
|
||||||
|
COPY --from=src . .
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Use another target as base
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> You should prefer to use regular multi-stage builds over this option. You can
|
||||||
|
> Use this feature when you have multiple Dockerfiles that can't be easily
|
||||||
|
> merged into one.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
target "base" {
|
||||||
|
dockerfile = "baseapp.Dockerfile"
|
||||||
|
}
|
||||||
|
target "app" {
|
||||||
|
contexts = {
|
||||||
|
baseapp = "target:base"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```Dockerfile
|
||||||
|
# Dockerfile
|
||||||
|
FROM baseapp
|
||||||
|
RUN echo "Hello world"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `target.dockerfile-inline`
|
||||||
|
|
||||||
|
Uses the string value as an inline Dockerfile for the build target.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "default" {
|
||||||
|
dockerfile-inline = "FROM alpine\nENTRYPOINT [\"echo\", \"hello\"]"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `dockerfile-inline` takes precedence over the `dockerfile` attribute.
|
||||||
|
If you specify both, Bake uses the inline version.
|
||||||
|
|
||||||
|
### `target.dockerfile`
|
||||||
|
|
||||||
|
Name of the Dockerfile to use for the build.
|
||||||
|
This is the same as the [`--file` flag][file] for the `docker build` command.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "default" {
|
||||||
|
dockerfile = "./src/www/Dockerfile"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Resolves to `"Dockerfile"` by default.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print -f - <<< 'target "default" {}'
|
||||||
|
[+] Building 0.0s (0/0)
|
||||||
|
{
|
||||||
|
"target": {
|
||||||
|
"default": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `target.inherits`
|
||||||
|
|
||||||
|
A target can inherit attributes from other targets.
|
||||||
|
Use `inherits` to reference from one target to another.
|
||||||
|
|
||||||
|
In the following example,
|
||||||
|
the `app-dev` target specifies an image name and tag.
|
||||||
|
The `app-release` target uses `inherits` to reuse the tag name.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
variable "TAG" {
|
||||||
|
default = "latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
target "app-dev" {
|
||||||
|
tags = ["docker.io/username/myapp:${TAG}"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "app-release" {
|
||||||
|
inherits = ["app-dev"]
|
||||||
|
platforms = ["linux/amd64", "linux/arm64"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `inherits` attribute is a list,
|
||||||
|
meaning you can reuse attributes from multiple other targets.
|
||||||
|
In the following example, the `app-release` target reuses attributes
|
||||||
|
from both the `app-dev` and `_release` targets.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "app-dev" {
|
||||||
|
args = {
|
||||||
|
GO_VERSION = "1.20"
|
||||||
|
BUILDX_EXPERIMENTAL = 1
|
||||||
|
}
|
||||||
|
tags = ["docker.io/username/myapp"]
|
||||||
|
dockerfile = "app.Dockerfile"
|
||||||
|
labels = {
|
||||||
|
"org.opencontainers.image.source" = "https://github.com/username/myapp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
target "_release" {
|
||||||
|
args = {
|
||||||
|
BUILDKIT_CONTEXT_KEEP_GIT_DIR = 1
|
||||||
|
BUILDX_EXPERIMENTAL = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
target "app-release" {
|
||||||
|
inherits = ["app-dev", "_release"]
|
||||||
|
platforms = ["linux/amd64", "linux/arm64"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When inheriting attributes from multiple targets and there's a conflict,
|
||||||
|
the target that appears last in the `inherits` list takes precedence.
|
||||||
|
The previous example defines the `BUILDX_EXPERIMENTAL` argument twice for the `app-release` target.
|
||||||
|
It resolves to `0` because the `_release` target appears last in the inheritance chain:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print app-release
|
||||||
|
[+] Building 0.0s (0/0)
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"app-release"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"app-release": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "app.Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"BUILDKIT_CONTEXT_KEEP_GIT_DIR": "1",
|
||||||
|
"BUILDX_EXPERIMENTAL": "0",
|
||||||
|
"GO_VERSION": "1.20"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"org.opencontainers.image.source": "https://github.com/username/myapp"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"docker.io/username/myapp"
|
||||||
|
],
|
||||||
|
"platforms": [
|
||||||
|
"linux/amd64",
|
||||||
|
"linux/arm64"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `target.labels`
|
||||||
|
|
||||||
|
Assigns image labels to the build.
|
||||||
|
This is the same as the `--label` flag for `docker build`.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "default" {
|
||||||
|
labels = {
|
||||||
|
"org.opencontainers.image.source" = "https://github.com/username/myapp"
|
||||||
|
"com.docker.image.source.entrypoint" = "Dockerfile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It's possible to use a `null` value for labels.
|
||||||
|
If you do, the builder uses the label value specified in the Dockerfile.
|
||||||
|
|
||||||
|
### `target.no-cache-filter`
|
||||||
|
|
||||||
|
Don't use build cache for the specified stages.
|
||||||
|
This is the same as the `--no-cache-filter` flag for `docker build`.
|
||||||
|
The following example avoids build cache for the `foo` build stage.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "default" {
|
||||||
|
no-cache-filter = ["foo"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `target.no-cache`
|
||||||
|
|
||||||
|
Don't use cache when building the image.
|
||||||
|
This is the same as the `--no-cache` flag for `docker build`.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "default" {
|
||||||
|
no-cache = 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `target.output`
|
||||||
|
|
||||||
|
Configuration for exporting the build output.
|
||||||
|
This is the same as the [`--output` flag][output].
|
||||||
|
The following example configures the target to use a cache-only output,
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "default" {
|
||||||
|
output = ["type=cacheonly"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `target.platforms`
|
||||||
|
|
||||||
|
Set target platforms for the build target.
|
||||||
|
This is the same as the [`--platform` flag][platform].
|
||||||
|
The following example creates a multi-platform build for three architectures.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "default" {
|
||||||
|
platforms = ["linux/amd64", "linux/arm64", "linux/arm/v7"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `target.pull`
|
||||||
|
|
||||||
|
Configures whether the builder should attempt to pull images when building the target.
|
||||||
|
This is the same as the `--pull` flag for `docker build`.
|
||||||
|
The following example forces the builder to always pull all images referenced in the build target.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "default" {
|
||||||
|
pull = "always"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `target.secret`
|
||||||
|
|
||||||
|
Defines secrets to expose to the build target.
|
||||||
|
This is the same as the [`--secret` flag][secret].
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
variable "HOME" {
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
|
||||||
|
target "default" {
|
||||||
|
secret = [
|
||||||
|
"type=env,id=KUBECONFIG",
|
||||||
|
"type=file,id=aws,src=${HOME}/.aws/credentials"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This lets you [mount the secret][run_mount_secret] in your Dockerfile.
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \
|
||||||
|
aws cloudfront create-invalidation ...
|
||||||
|
RUN --mount=type=secret,id=KUBECONFIG \
|
||||||
|
KUBECONFIG=$(cat /run/secrets/KUBECONFIG) helm upgrade --install
|
||||||
|
```
|
||||||
|
|
||||||
|
### `target.ssh`
|
||||||
|
|
||||||
|
Defines SSH agent sockets or keys to expose to the build.
|
||||||
|
This is the same as the [`--ssh` flag][ssh].
|
||||||
|
This can be useful if you need to access private repositories during a build.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "default" {
|
||||||
|
ssh = ["default"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM alpine
|
||||||
|
RUN --mount=type=ssh \
|
||||||
|
apk add git openssh-client \
|
||||||
|
&& install -m 0700 -d ~/.ssh \
|
||||||
|
&& ssh-keyscan github.com >> ~/.ssh/known_hosts \
|
||||||
|
&& git clone git@github.com:user/my-private-repo.git
|
||||||
|
```
|
||||||
|
|
||||||
|
### `target.tags`
|
||||||
|
|
||||||
|
Image names and tags to use for the build target.
|
||||||
|
This is the same as the [`--tag` flag][tag].
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "default" {
|
||||||
|
tags = [
|
||||||
|
"org/repo:latest",
|
||||||
|
"myregistry.azurecr.io/team/image:v1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `target.target`
|
||||||
|
|
||||||
|
Set the target build stage to build.
|
||||||
|
This is the same as the [`--target` flag][target].
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "default" {
|
||||||
|
target = "binaries"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Group
|
||||||
|
|
||||||
|
Groups allow you to invoke multiple builds (targets) at once.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
group "default" {
|
||||||
|
targets = ["db", "webapp-dev"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp-dev" {
|
||||||
|
dockerfile = "Dockerfile.webapp"
|
||||||
|
tags = ["docker.io/username/webapp:latest"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "db" {
|
||||||
|
dockerfile = "Dockerfile.db"
|
||||||
|
tags = ["docker.io/username/db"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Groups take precedence over targets, if both exist with the same name.
|
||||||
|
The following bake file builds the `default` group.
|
||||||
|
Bake ignores the `default` target.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "default" {
|
||||||
|
dockerfile-inline = "FROM ubuntu"
|
||||||
|
}
|
||||||
|
|
||||||
|
group "default" {
|
||||||
|
targets = ["alpine", "debian"]
|
||||||
|
}
|
||||||
|
target "alpine" {
|
||||||
|
dockerfile-inline = "FROM alpine"
|
||||||
|
}
|
||||||
|
target "debian" {
|
||||||
|
dockerfile-inline = "FROM debian"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Variable
|
||||||
|
|
||||||
|
The HCL file format supports variable block definitions.
|
||||||
|
You can use variables as build arguments in your Dockerfile,
|
||||||
|
or interpolate them in attribute values in your Bake file.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
variable "TAG" {
|
||||||
|
default = "latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp-dev" {
|
||||||
|
dockerfile = "Dockerfile.webapp"
|
||||||
|
tags = ["docker.io/username/webapp:${TAG}"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can assign a default value for a variable in the Bake file,
|
||||||
|
or assign a `null` value to it. If you assign a `null` value,
|
||||||
|
Buildx uses the default value from the Dockerfile instead.
|
||||||
|
|
||||||
|
You can override variable defaults set in the Bake file using environment variables.
|
||||||
|
The following example sets the `TAG` variable to `dev`,
|
||||||
|
overriding the default `latest` value shown in the previous example.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ TAG=dev docker buildx bake webapp-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Built-in variables
|
||||||
|
|
||||||
|
The following variables are built-ins that you can use with Bake without having
|
||||||
|
to define them.
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
| --------------------- | ----------------------------------------------------------------------------------- |
|
||||||
|
| `BAKE_CMD_CONTEXT` | Holds the main context when building using a remote Bake file. |
|
||||||
|
| `BAKE_LOCAL_PLATFORM` | Returns the current platform’s default platform specification (e.g. `linux/amd64`). |
|
||||||
|
|
||||||
|
### Use environment variable as default
|
||||||
|
|
||||||
|
You can set a Bake variable to use the value of an environment variable as a default value:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
variable "HOME" {
|
||||||
|
default = "$HOME"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Interpolate variables into attributes
|
||||||
|
|
||||||
|
To interpolate a variable into an attribute string value,
|
||||||
|
you must use curly brackets.
|
||||||
|
The following doesn't work:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
variable "HOME" {
|
||||||
|
default = "$HOME"
|
||||||
|
}
|
||||||
|
|
||||||
|
target "default" {
|
||||||
|
ssh = ["default=$HOME/.ssh/id_rsa"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Wrap the variable in curly brackets where you want to insert it:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
variable "HOME" {
|
||||||
|
default = "$HOME"
|
||||||
|
}
|
||||||
|
|
||||||
|
target "default" {
|
||||||
|
- ssh = ["default=$HOME/.ssh/id_rsa"]
|
||||||
|
+ ssh = ["default=${HOME}/.ssh/id_rsa"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Before you can interpolate a variable into an attribute,
|
||||||
|
first you must declare it in the bake file,
|
||||||
|
as demonstrated in the following example.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ cat docker-bake.hcl
|
||||||
|
target "default" {
|
||||||
|
dockerfile-inline = "FROM ${BASE_IMAGE}"
|
||||||
|
}
|
||||||
|
$ docker buildx bake
|
||||||
|
[+] Building 0.0s (0/0)
|
||||||
|
docker-bake.hcl:2
|
||||||
|
--------------------
|
||||||
|
1 | target "default" {
|
||||||
|
2 | >>> dockerfile-inline = "FROM ${BASE_IMAGE}"
|
||||||
|
3 | }
|
||||||
|
4 |
|
||||||
|
--------------------
|
||||||
|
ERROR: docker-bake.hcl:2,31-41: Unknown variable; There is no variable named "BASE_IMAGE"., and 1 other diagnostic(s)
|
||||||
|
$ cat >> docker-bake.hcl
|
||||||
|
|
||||||
|
variable "BASE_IMAGE" {
|
||||||
|
default = "alpine"
|
||||||
|
}
|
||||||
|
|
||||||
|
$ docker buildx bake
|
||||||
|
[+] Building 0.6s (5/5) FINISHED
|
||||||
|
```
|
||||||
|
|
||||||
|
## Function
|
||||||
|
|
||||||
|
A [set of general-purpose functions][bake_stdlib]
|
||||||
|
provided by [go-cty][go-cty]
|
||||||
|
are available for use in HCL files:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
target "webapp-dev" {
|
||||||
|
dockerfile = "Dockerfile.webapp"
|
||||||
|
tags = ["docker.io/username/webapp:latest"]
|
||||||
|
args = {
|
||||||
|
buildno = "${add(123, 1)}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In addition, [user defined functions][userfunc]
|
||||||
|
are also supported:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
function "increment" {
|
||||||
|
params = [number]
|
||||||
|
result = number + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp-dev" {
|
||||||
|
dockerfile = "Dockerfile.webapp"
|
||||||
|
tags = ["docker.io/username/webapp:latest"]
|
||||||
|
args = {
|
||||||
|
buildno = "${increment(123)}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> See [User defined HCL functions][hcl-funcs] page for more details.
|
||||||
|
|
||||||
|
<!-- external links -->
|
||||||
|
|
||||||
|
[attestations]: https://docs.docker.com/build/attestations/
|
||||||
|
[bake_stdlib]: https://github.com/docker/buildx/blob/master/bake/hclparser/stdlib.go
|
||||||
|
[build-arg]: https://docs.docker.com/engine/reference/commandline/build/#build-arg
|
||||||
|
[build-context]: https://docs.docker.com/engine/reference/commandline/buildx_build/#build-context
|
||||||
|
[cache-backends]: https://docs.docker.com/build/cache/backends/
|
||||||
|
[cache-from]: https://docs.docker.com/engine/reference/commandline/buildx_build/#cache-from
|
||||||
|
[cache-to]: https://docs.docker.com/engine/reference/commandline/buildx_build/#cache-to
|
||||||
|
[context]: https://docs.docker.com/engine/reference/commandline/buildx_build/#build-context
|
||||||
|
[file]: https://docs.docker.com/engine/reference/commandline/build/#file
|
||||||
|
[go-cty]: https://github.com/zclconf/go-cty/tree/main/cty/function/stdlib
|
||||||
|
[hcl-funcs]: https://docs.docker.com/build/bake/hcl-funcs/
|
||||||
|
[output]: https://docs.docker.com/engine/reference/commandline/buildx_build/#output
|
||||||
|
[platform]: https://docs.docker.com/engine/reference/commandline/buildx_build/#platform
|
||||||
|
[run_mount_secret]: https://docs.docker.com/engine/reference/builder/#run---mounttypesecret
|
||||||
|
[secret]: https://docs.docker.com/engine/reference/commandline/buildx_build/#secret
|
||||||
|
[ssh]: https://docs.docker.com/engine/reference/commandline/buildx_build/#ssh
|
||||||
|
[tag]: https://docs.docker.com/engine/reference/commandline/build/#tag
|
||||||
|
[target]: https://docs.docker.com/engine/reference/commandline/build/#target
|
||||||
|
[userfunc]: https://github.com/hashicorp/hcl/tree/main/ext/userfunc
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
# Defining additional build contexts and linking targets
|
|
||||||
|
|
||||||
In addition to the main `context` key that defines the build context each target
|
|
||||||
can also define additional named contexts with a map defined with key `contexts`.
|
|
||||||
These values map to the `--build-context` flag in the [build command](https://docs.docker.com/engine/reference/commandline/buildx_build/#build-context).
|
|
||||||
|
|
||||||
Inside the Dockerfile these contexts can be used with the `FROM` instruction or `--from` flag.
|
|
||||||
|
|
||||||
The value can be a local source directory, container image (with `docker-image://` prefix),
|
|
||||||
Git URL, HTTP URL or a name of another target in the Bake file (with `target:` prefix).
|
|
||||||
|
|
||||||
## Pinning alpine image
|
|
||||||
|
|
||||||
```dockerfile
|
|
||||||
# syntax=docker/dockerfile:1
|
|
||||||
FROM alpine
|
|
||||||
RUN echo "Hello world"
|
|
||||||
```
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
target "app" {
|
|
||||||
contexts = {
|
|
||||||
alpine = "docker-image://alpine:3.13"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using a secondary source directory
|
|
||||||
|
|
||||||
```dockerfile
|
|
||||||
# syntax=docker/dockerfile:1
|
|
||||||
FROM scratch AS src
|
|
||||||
|
|
||||||
FROM golang
|
|
||||||
COPY --from=src . .
|
|
||||||
```
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
target "app" {
|
|
||||||
contexts = {
|
|
||||||
src = "../path/to/source"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using a result of one target as a base image in another target
|
|
||||||
|
|
||||||
To use a result of one target as a build context of another, specity the target
|
|
||||||
name with `target:` prefix.
|
|
||||||
|
|
||||||
```dockerfile
|
|
||||||
# syntax=docker/dockerfile:1
|
|
||||||
FROM baseapp
|
|
||||||
RUN echo "Hello world"
|
|
||||||
```
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
target "base" {
|
|
||||||
dockerfile = "baseapp.Dockerfile"
|
|
||||||
}
|
|
||||||
|
|
||||||
target "app" {
|
|
||||||
contexts = {
|
|
||||||
baseapp = "target:base"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Please note that in most cases you should just use a single multi-stage
|
|
||||||
Dockerfile with multiple targets for similar behavior. This case is recommended
|
|
||||||
when you have multiple Dockerfiles that can't be easily merged into one.
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
# Building from Compose file
|
|
||||||
|
|
||||||
## Specification
|
|
||||||
|
|
||||||
Bake uses the [compose-spec](https://docs.docker.com/compose/compose-file/) to
|
|
||||||
parse a compose file and translate each service to a [target](file-definition.md#target).
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# docker-compose.yml
|
|
||||||
services:
|
|
||||||
webapp-dev:
|
|
||||||
build: &build-dev
|
|
||||||
dockerfile: Dockerfile.webapp
|
|
||||||
tags:
|
|
||||||
- docker.io/username/webapp:latest
|
|
||||||
cache_from:
|
|
||||||
- docker.io/username/webapp:cache
|
|
||||||
cache_to:
|
|
||||||
- docker.io/username/webapp:cache
|
|
||||||
|
|
||||||
webapp-release:
|
|
||||||
build:
|
|
||||||
<<: *build-dev
|
|
||||||
x-bake:
|
|
||||||
platforms:
|
|
||||||
- linux/amd64
|
|
||||||
- linux/arm64
|
|
||||||
|
|
||||||
db:
|
|
||||||
image: docker.io/username/db
|
|
||||||
build:
|
|
||||||
dockerfile: Dockerfile.db
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --print
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"db",
|
|
||||||
"webapp-dev",
|
|
||||||
"webapp-release"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"db": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile.db",
|
|
||||||
"tags": [
|
|
||||||
"docker.io/username/db"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"webapp-dev": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile.webapp",
|
|
||||||
"tags": [
|
|
||||||
"docker.io/username/webapp:latest"
|
|
||||||
],
|
|
||||||
"cache-from": [
|
|
||||||
"docker.io/username/webapp:cache"
|
|
||||||
],
|
|
||||||
"cache-to": [
|
|
||||||
"docker.io/username/webapp:cache"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"webapp-release": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile.webapp",
|
|
||||||
"tags": [
|
|
||||||
"docker.io/username/webapp:latest"
|
|
||||||
],
|
|
||||||
"cache-from": [
|
|
||||||
"docker.io/username/webapp:cache"
|
|
||||||
],
|
|
||||||
"cache-to": [
|
|
||||||
"docker.io/username/webapp:cache"
|
|
||||||
],
|
|
||||||
"platforms": [
|
|
||||||
"linux/amd64",
|
|
||||||
"linux/arm64"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Unlike the [HCL format](file-definition.md#hcl-definition), there are some
|
|
||||||
limitations with the compose format:
|
|
||||||
|
|
||||||
* Specifying variables or global scope attributes is not yet supported
|
|
||||||
* `inherits` service field is not supported, but you can use [YAML anchors](https://docs.docker.com/compose/compose-file/#fragments) to reference other services like the example above
|
|
||||||
|
|
||||||
## Extension field with `x-bake`
|
|
||||||
|
|
||||||
Even if some fields are not (yet) available in the compose specification, you
|
|
||||||
can use the [special extension](https://docs.docker.com/compose/compose-file/#extension)
|
|
||||||
field `x-bake` in your compose file to evaluate extra fields:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# docker-compose.yml
|
|
||||||
services:
|
|
||||||
addon:
|
|
||||||
image: ct-addon:bar
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: ./Dockerfile
|
|
||||||
args:
|
|
||||||
CT_ECR: foo
|
|
||||||
CT_TAG: bar
|
|
||||||
x-bake:
|
|
||||||
tags:
|
|
||||||
- ct-addon:foo
|
|
||||||
- ct-addon:alp
|
|
||||||
platforms:
|
|
||||||
- linux/amd64
|
|
||||||
- linux/arm64
|
|
||||||
cache-from:
|
|
||||||
- user/app:cache
|
|
||||||
- type=local,src=path/to/cache
|
|
||||||
cache-to:
|
|
||||||
- type=local,dest=path/to/cache
|
|
||||||
pull: true
|
|
||||||
|
|
||||||
aws:
|
|
||||||
image: ct-fake-aws:bar
|
|
||||||
build:
|
|
||||||
dockerfile: ./aws.Dockerfile
|
|
||||||
args:
|
|
||||||
CT_ECR: foo
|
|
||||||
CT_TAG: bar
|
|
||||||
x-bake:
|
|
||||||
secret:
|
|
||||||
- id=mysecret,src=./secret
|
|
||||||
- id=mysecret2,src=./secret2
|
|
||||||
platforms: linux/arm64
|
|
||||||
output: type=docker
|
|
||||||
no-cache: true
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --print
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"aws",
|
|
||||||
"addon"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"addon": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "./Dockerfile",
|
|
||||||
"args": {
|
|
||||||
"CT_ECR": "foo",
|
|
||||||
"CT_TAG": "bar"
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"ct-addon:foo",
|
|
||||||
"ct-addon:alp"
|
|
||||||
],
|
|
||||||
"cache-from": [
|
|
||||||
"user/app:cache",
|
|
||||||
"type=local,src=path/to/cache"
|
|
||||||
],
|
|
||||||
"cache-to": [
|
|
||||||
"type=local,dest=path/to/cache"
|
|
||||||
],
|
|
||||||
"platforms": [
|
|
||||||
"linux/amd64",
|
|
||||||
"linux/arm64"
|
|
||||||
],
|
|
||||||
"pull": true
|
|
||||||
},
|
|
||||||
"aws": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "./aws.Dockerfile",
|
|
||||||
"args": {
|
|
||||||
"CT_ECR": "foo",
|
|
||||||
"CT_TAG": "bar"
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"ct-fake-aws:bar"
|
|
||||||
],
|
|
||||||
"secret": [
|
|
||||||
"id=mysecret,src=./secret",
|
|
||||||
"id=mysecret2,src=./secret2"
|
|
||||||
],
|
|
||||||
"platforms": [
|
|
||||||
"linux/arm64"
|
|
||||||
],
|
|
||||||
"output": [
|
|
||||||
"type=docker"
|
|
||||||
],
|
|
||||||
"no-cache": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Complete list of valid fields for `x-bake`:
|
|
||||||
|
|
||||||
* `cache-from`
|
|
||||||
* `cache-to`
|
|
||||||
* `no-cache`
|
|
||||||
* `no-cache-filter`
|
|
||||||
* `output`
|
|
||||||
* `platforms`
|
|
||||||
* `pull`
|
|
||||||
* `secret`
|
|
||||||
* `ssh`
|
|
||||||
* `tags`
|
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
# Configuring builds
|
|
||||||
|
|
||||||
Bake supports loading build definition from files, but sometimes you need even
|
|
||||||
more flexibility to configure this definition.
|
|
||||||
|
|
||||||
For this use case, you can define variables inside the bake files that can be
|
|
||||||
set by the user with environment variables or by [attribute definitions](#global-scope-attributes)
|
|
||||||
in other bake files. If you wish to change a specific value for a single
|
|
||||||
invocation you can use the `--set` flag [from the command line](#from-command-line).
|
|
||||||
|
|
||||||
## Global scope attributes
|
|
||||||
|
|
||||||
You can define global scope attributes in HCL/JSON and use them for code reuse
|
|
||||||
and setting values for variables. This means you can do a "data-only" HCL file
|
|
||||||
with the values you want to set/override and use it in the list of regular
|
|
||||||
output files.
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
variable "FOO" {
|
|
||||||
default = "abc"
|
|
||||||
}
|
|
||||||
|
|
||||||
target "app" {
|
|
||||||
args = {
|
|
||||||
v1 = "pre-${FOO}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You can use this file directly:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --print app
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"app"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"app": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"args": {
|
|
||||||
"v1": "pre-abc"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Or create an override configuration file:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# env.hcl
|
|
||||||
WHOAMI="myuser"
|
|
||||||
FOO="def-${WHOAMI}"
|
|
||||||
```
|
|
||||||
|
|
||||||
And invoke bake together with both of the files:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake -f docker-bake.hcl -f env.hcl --print app
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"app"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"app": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"args": {
|
|
||||||
"v1": "pre-def-myuser"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## From command line
|
|
||||||
|
|
||||||
You can also override target configurations from the command line with the
|
|
||||||
[`--set` flag](https://docs.docker.com/engine/reference/commandline/buildx_bake/#set):
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
target "app" {
|
|
||||||
args = {
|
|
||||||
mybuildarg = "foo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --set app.args.mybuildarg=bar --set app.platform=linux/arm64 app --print
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"app"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"app": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"args": {
|
|
||||||
"mybuildarg": "bar"
|
|
||||||
},
|
|
||||||
"platforms": [
|
|
||||||
"linux/arm64"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Pattern matching syntax defined in [https://golang.org/pkg/path/#Match](https://golang.org/pkg/path/#Match)
|
|
||||||
is also supported:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --set foo*.args.mybuildarg=value # overrides build arg for all targets starting with "foo"
|
|
||||||
$ docker buildx bake --set *.platform=linux/arm64 # overrides platform for all targets
|
|
||||||
$ docker buildx bake --set foo*.no-cache # bypass caching only for targets starting with "foo"
|
|
||||||
```
|
|
||||||
|
|
||||||
Complete list of overridable fields:
|
|
||||||
|
|
||||||
* `args`
|
|
||||||
* `cache-from`
|
|
||||||
* `cache-to`
|
|
||||||
* `context`
|
|
||||||
* `dockerfile`
|
|
||||||
* `labels`
|
|
||||||
* `no-cache`
|
|
||||||
* `output`
|
|
||||||
* `platform`
|
|
||||||
* `pull`
|
|
||||||
* `secrets`
|
|
||||||
* `ssh`
|
|
||||||
* `tags`
|
|
||||||
* `target`
|
|
||||||
|
|
||||||
## Using variables in variables across files
|
|
||||||
|
|
||||||
When multiple files are specified, one file can use variables defined in
|
|
||||||
another file.
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake1.hcl
|
|
||||||
variable "FOO" {
|
|
||||||
default = upper("${BASE}def")
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "BAR" {
|
|
||||||
default = "-${FOO}-"
|
|
||||||
}
|
|
||||||
|
|
||||||
target "app" {
|
|
||||||
args = {
|
|
||||||
v1 = "pre-${BAR}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake2.hcl
|
|
||||||
variable "BASE" {
|
|
||||||
default = "abc"
|
|
||||||
}
|
|
||||||
|
|
||||||
target "app" {
|
|
||||||
args = {
|
|
||||||
v2 = "${FOO}-post"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake -f docker-bake1.hcl -f docker-bake2.hcl --print app
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"app"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"app": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"args": {
|
|
||||||
"v1": "pre--ABCDEF-",
|
|
||||||
"v2": "ABCDEF-post"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,440 +0,0 @@
|
|||||||
# Bake file definition
|
|
||||||
|
|
||||||
`buildx bake` supports HCL, JSON and Compose file format for defining build
|
|
||||||
[groups](#group), [targets](#target) as well as [variables](#variable) and
|
|
||||||
[functions](#functions). It looks for build definition files in the current
|
|
||||||
directory in the following order:
|
|
||||||
|
|
||||||
* `docker-compose.yml`
|
|
||||||
* `docker-compose.yaml`
|
|
||||||
* `docker-bake.json`
|
|
||||||
* `docker-bake.override.json`
|
|
||||||
* `docker-bake.hcl`
|
|
||||||
* `docker-bake.override.hcl`
|
|
||||||
|
|
||||||
## Specification
|
|
||||||
|
|
||||||
Inside a bake file you can declare group, target and variable blocks to define
|
|
||||||
project specific reusable build flows.
|
|
||||||
|
|
||||||
### Target
|
|
||||||
|
|
||||||
A target reflects a single docker build invocation with the same options that
|
|
||||||
you would specify for `docker build`:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
target "webapp-dev" {
|
|
||||||
dockerfile = "Dockerfile.webapp"
|
|
||||||
tags = ["docker.io/username/webapp:latest"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
```console
|
|
||||||
$ docker buildx bake webapp-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Note**
|
|
||||||
>
|
|
||||||
> In the case of compose files, each service corresponds to a target.
|
|
||||||
> If compose service name contains a dot it will be replaced with an underscore.
|
|
||||||
|
|
||||||
Complete list of valid target fields available for [HCL](#hcl-definition) and
|
|
||||||
[JSON](#json-definition) definitions:
|
|
||||||
|
|
||||||
| Name | Type | Description |
|
|
||||||
|---------------------|--------|-------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
||||||
| `inherits` | List | [Inherit build options](#merging-and-inheritance) from other targets |
|
|
||||||
| `args` | Map | Set build-time variables (same as [`--build-arg` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `cache-from` | List | External cache sources (same as [`--cache-from` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `cache-to` | List | Cache export destinations (same as [`--cache-to` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `context` | String | Set of files located in the specified path or URL |
|
|
||||||
| `contexts` | Map | Additional build contexts (same as [`--build-context` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `dockerfile` | String | Name of the Dockerfile (same as [`--file` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `dockerfile-inline` | String | Inline Dockerfile content |
|
|
||||||
| `labels` | Map | Set metadata for an image (same as [`--label` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `no-cache` | Bool | Do not use cache when building the image (same as [`--no-cache` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `no-cache-filter` | List | Do not cache specified stages (same as [`--no-cache-filter` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `output` | List | Output destination (same as [`--output` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `platforms` | List | Set target platforms for build (same as [`--platform` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `pull` | Bool | Always attempt to pull all referenced images (same as [`--pull` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `secret` | List | Secret to expose to the build (same as [`--secret` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `ssh` | List | SSH agent socket or keys to expose to the build (same as [`--ssh` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `tags` | List | Name and optionally a tag in the format `name:tag` (same as [`--tag` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
| `target` | String | Set the target build stage to build (same as [`--target` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
|
||||||
|
|
||||||
### Group
|
|
||||||
|
|
||||||
A group is a grouping of targets:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
group "build" {
|
|
||||||
targets = ["db", "webapp-dev"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp-dev" {
|
|
||||||
dockerfile = "Dockerfile.webapp"
|
|
||||||
tags = ["docker.io/username/webapp:latest"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "db" {
|
|
||||||
dockerfile = "Dockerfile.db"
|
|
||||||
tags = ["docker.io/username/db"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
```console
|
|
||||||
$ docker buildx bake build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Variable
|
|
||||||
|
|
||||||
Similar to how Terraform provides a way to [define variables](https://www.terraform.io/docs/configuration/variables.html#declaring-an-input-variable),
|
|
||||||
the HCL file format also supports variable block definitions. These can be used
|
|
||||||
to define variables with values provided by the current environment, or a
|
|
||||||
default value when unset:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
variable "TAG" {
|
|
||||||
default = "latest"
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp-dev" {
|
|
||||||
dockerfile = "Dockerfile.webapp"
|
|
||||||
tags = ["docker.io/username/webapp:${TAG}"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
```console
|
|
||||||
$ docker buildx bake webapp-dev # will use the default value "latest"
|
|
||||||
$ TAG=dev docker buildx bake webapp-dev # will use the TAG environment variable value
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Tip**
|
|
||||||
>
|
|
||||||
> See also the [Configuring builds](configuring-build.md) page for advanced usage.
|
|
||||||
|
|
||||||
### Functions
|
|
||||||
|
|
||||||
A [set of generally useful functions](https://github.com/docker/buildx/blob/master/bake/hclparser/stdlib.go)
|
|
||||||
provided by [go-cty](https://github.com/zclconf/go-cty/tree/main/cty/function/stdlib)
|
|
||||||
are available for use in HCL files:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
target "webapp-dev" {
|
|
||||||
dockerfile = "Dockerfile.webapp"
|
|
||||||
tags = ["docker.io/username/webapp:latest"]
|
|
||||||
args = {
|
|
||||||
buildno = "${add(123, 1)}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In addition, [user defined functions](https://github.com/hashicorp/hcl/tree/main/ext/userfunc)
|
|
||||||
are also supported:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
function "increment" {
|
|
||||||
params = [number]
|
|
||||||
result = number + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp-dev" {
|
|
||||||
dockerfile = "Dockerfile.webapp"
|
|
||||||
tags = ["docker.io/username/webapp:latest"]
|
|
||||||
args = {
|
|
||||||
buildno = "${increment(123)}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Note**
|
|
||||||
>
|
|
||||||
> See [User defined HCL functions](hcl-funcs.md) page for more details.
|
|
||||||
|
|
||||||
## Built-in variables
|
|
||||||
|
|
||||||
* `BAKE_CMD_CONTEXT` can be used to access the main `context` for bake command
|
|
||||||
from a bake file that has been [imported remotely](file-definition.md#remote-definition).
|
|
||||||
* `BAKE_LOCAL_PLATFORM` returns the current platform's default platform
|
|
||||||
specification (e.g. `linux/amd64`).
|
|
||||||
|
|
||||||
## Merging and inheritance
|
|
||||||
|
|
||||||
Multiple files can include the same target and final build options will be
|
|
||||||
determined by merging them together:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
target "webapp-dev" {
|
|
||||||
dockerfile = "Dockerfile.webapp"
|
|
||||||
tags = ["docker.io/username/webapp:latest"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
```hcl
|
|
||||||
# docker-bake2.hcl
|
|
||||||
target "webapp-dev" {
|
|
||||||
tags = ["docker.io/username/webapp:dev"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
```console
|
|
||||||
$ docker buildx bake -f docker-bake.hcl -f docker-bake2.hcl webapp-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
A group can specify its list of targets with the `targets` option. A target can
|
|
||||||
inherit build options by setting the `inherits` option to the list of targets or
|
|
||||||
groups to inherit from:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
target "webapp-dev" {
|
|
||||||
dockerfile = "Dockerfile.webapp"
|
|
||||||
tags = ["docker.io/username/webapp:${TAG}"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp-release" {
|
|
||||||
inherits = ["webapp-dev"]
|
|
||||||
platforms = ["linux/amd64", "linux/arm64"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## `default` target/group
|
|
||||||
|
|
||||||
When you invoke `bake` you specify what targets/groups you want to build. If no
|
|
||||||
arguments is specified, the group/target named `default` will be built:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
target "default" {
|
|
||||||
dockerfile = "Dockerfile.webapp"
|
|
||||||
tags = ["docker.io/username/webapp:latest"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
```console
|
|
||||||
$ docker buildx bake
|
|
||||||
```
|
|
||||||
|
|
||||||
## Definitions
|
|
||||||
|
|
||||||
### HCL definition
|
|
||||||
|
|
||||||
HCL definition file is recommended as its experience is more aligned with buildx UX
|
|
||||||
and also allows better code reuse, different target groups and extended features.
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
variable "TAG" {
|
|
||||||
default = "latest"
|
|
||||||
}
|
|
||||||
|
|
||||||
group "default" {
|
|
||||||
targets = ["db", "webapp-dev"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp-dev" {
|
|
||||||
dockerfile = "Dockerfile.webapp"
|
|
||||||
tags = ["docker.io/username/webapp:${TAG}"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp-release" {
|
|
||||||
inherits = ["webapp-dev"]
|
|
||||||
platforms = ["linux/amd64", "linux/arm64"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "db" {
|
|
||||||
dockerfile = "Dockerfile.db"
|
|
||||||
tags = ["docker.io/username/db"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### JSON definition
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"variable": {
|
|
||||||
"TAG": {
|
|
||||||
"default": "latest"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"db",
|
|
||||||
"webapp-dev"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"webapp-dev": {
|
|
||||||
"dockerfile": "Dockerfile.webapp",
|
|
||||||
"tags": [
|
|
||||||
"docker.io/username/webapp:${TAG}"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"webapp-release": {
|
|
||||||
"inherits": [
|
|
||||||
"webapp-dev"
|
|
||||||
],
|
|
||||||
"platforms": [
|
|
||||||
"linux/amd64",
|
|
||||||
"linux/arm64"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"db": {
|
|
||||||
"dockerfile": "Dockerfile.db",
|
|
||||||
"tags": [
|
|
||||||
"docker.io/username/db"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compose file
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# docker-compose.yml
|
|
||||||
services:
|
|
||||||
webapp:
|
|
||||||
image: docker.io/username/webapp:latest
|
|
||||||
build:
|
|
||||||
dockerfile: Dockerfile.webapp
|
|
||||||
|
|
||||||
db:
|
|
||||||
image: docker.io/username/db
|
|
||||||
build:
|
|
||||||
dockerfile: Dockerfile.db
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Note**
|
|
||||||
>
|
|
||||||
> See [Building from Compose file](compose-file.md) page for more details.
|
|
||||||
|
|
||||||
## Remote definition
|
|
||||||
|
|
||||||
You can also build bake files directly from a remote Git repository or HTTPS URL:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake "https://github.com/docker/cli.git#v20.10.11" --print
|
|
||||||
#1 [internal] load git source https://github.com/docker/cli.git#v20.10.11
|
|
||||||
#1 0.745 e8f1871b077b64bcb4a13334b7146492773769f7 refs/tags/v20.10.11
|
|
||||||
#1 2.022 From https://github.com/docker/cli
|
|
||||||
#1 2.022 * [new tag] v20.10.11 -> v20.10.11
|
|
||||||
#1 DONE 2.9s
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"binary"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"binary": {
|
|
||||||
"context": "https://github.com/docker/cli.git#v20.10.11",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"args": {
|
|
||||||
"BASE_VARIANT": "alpine",
|
|
||||||
"GO_STRIP": "",
|
|
||||||
"VERSION": ""
|
|
||||||
},
|
|
||||||
"target": "binary",
|
|
||||||
"platforms": [
|
|
||||||
"local"
|
|
||||||
],
|
|
||||||
"output": [
|
|
||||||
"build"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
As you can see the context is fixed to `https://github.com/docker/cli.git` even if
|
|
||||||
[no context is actually defined](https://github.com/docker/cli/blob/2776a6d694f988c0c1df61cad4bfac0f54e481c8/docker-bake.hcl#L17-L26)
|
|
||||||
in the definition.
|
|
||||||
|
|
||||||
If you want to access the main context for bake command from a bake file
|
|
||||||
that has been imported remotely, you can use the [`BAKE_CMD_CONTEXT` built-in var](#built-in-variables).
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ cat https://raw.githubusercontent.com/tonistiigi/buildx/remote-test/docker-bake.hcl
|
|
||||||
```
|
|
||||||
```hcl
|
|
||||||
target "default" {
|
|
||||||
context = BAKE_CMD_CONTEXT
|
|
||||||
dockerfile-inline = <<EOT
|
|
||||||
FROM alpine
|
|
||||||
WORKDIR /src
|
|
||||||
COPY . .
|
|
||||||
RUN ls -l && stop
|
|
||||||
EOT
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake "https://github.com/tonistiigi/buildx.git#remote-test" --print
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"target": {
|
|
||||||
"default": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"dockerfile-inline": "FROM alpine\nWORKDIR /src\nCOPY . .\nRUN ls -l \u0026\u0026 stop\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ touch foo bar
|
|
||||||
$ docker buildx bake "https://github.com/tonistiigi/buildx.git#remote-test"
|
|
||||||
```
|
|
||||||
```text
|
|
||||||
...
|
|
||||||
> [4/4] RUN ls -l && stop:
|
|
||||||
#8 0.101 total 0
|
|
||||||
#8 0.102 -rw-r--r-- 1 root root 0 Jul 27 18:47 bar
|
|
||||||
#8 0.102 -rw-r--r-- 1 root root 0 Jul 27 18:47 foo
|
|
||||||
#8 0.102 /bin/sh: stop: not found
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake "https://github.com/tonistiigi/buildx.git#remote-test" "https://github.com/docker/cli.git#v20.10.11" --print
|
|
||||||
#1 [internal] load git source https://github.com/tonistiigi/buildx.git#remote-test
|
|
||||||
#1 0.429 577303add004dd7efeb13434d69ea030d35f7888 refs/heads/remote-test
|
|
||||||
#1 CACHED
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"target": {
|
|
||||||
"default": {
|
|
||||||
"context": "https://github.com/docker/cli.git#v20.10.11",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"dockerfile-inline": "FROM alpine\nWORKDIR /src\nCOPY . .\nRUN ls -l \u0026\u0026 stop\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake "https://github.com/tonistiigi/buildx.git#remote-test" "https://github.com/docker/cli.git#v20.10.11"
|
|
||||||
```
|
|
||||||
```text
|
|
||||||
...
|
|
||||||
> [4/4] RUN ls -l && stop:
|
|
||||||
#8 0.136 drwxrwxrwx 5 root root 4096 Jul 27 18:31 kubernetes
|
|
||||||
#8 0.136 drwxrwxrwx 3 root root 4096 Jul 27 18:31 man
|
|
||||||
#8 0.136 drwxrwxrwx 2 root root 4096 Jul 27 18:31 opts
|
|
||||||
#8 0.136 -rw-rw-rw- 1 root root 1893 Jul 27 18:31 poule.yml
|
|
||||||
#8 0.136 drwxrwxrwx 7 root root 4096 Jul 27 18:31 scripts
|
|
||||||
#8 0.136 drwxrwxrwx 3 root root 4096 Jul 27 18:31 service
|
|
||||||
#8 0.136 drwxrwxrwx 2 root root 4096 Jul 27 18:31 templates
|
|
||||||
#8 0.136 drwxrwxrwx 10 root root 4096 Jul 27 18:31 vendor
|
|
||||||
#8 0.136 -rwxrwxrwx 1 root root 9620 Jul 27 18:31 vendor.conf
|
|
||||||
#8 0.136 /bin/sh: stop: not found
|
|
||||||
```
|
|
||||||
@@ -1,327 +0,0 @@
|
|||||||
# User defined HCL functions
|
|
||||||
|
|
||||||
## Using interpolation to tag an image with the git sha
|
|
||||||
|
|
||||||
As shown in the [File definition](file-definition.md#variable) page, `bake`
|
|
||||||
supports variable blocks which are assigned to matching environment variables
|
|
||||||
or default values:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
variable "TAG" {
|
|
||||||
default = "latest"
|
|
||||||
}
|
|
||||||
|
|
||||||
group "default" {
|
|
||||||
targets = ["webapp"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp" {
|
|
||||||
tags = ["docker.io/username/webapp:${TAG}"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
alternatively, in json format:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"variable": {
|
|
||||||
"TAG": {
|
|
||||||
"default": "latest"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": ["webapp"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"webapp": {
|
|
||||||
"tags": ["docker.io/username/webapp:${TAG}"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --print webapp
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"webapp"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"webapp": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"tags": [
|
|
||||||
"docker.io/username/webapp:latest"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ TAG=$(git rev-parse --short HEAD) docker buildx bake --print webapp
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"webapp"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"webapp": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"tags": [
|
|
||||||
"docker.io/username/webapp:985e9e9"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using the `add` function
|
|
||||||
|
|
||||||
You can use [`go-cty` stdlib functions](https://github.com/zclconf/go-cty/tree/main/cty/function/stdlib).
|
|
||||||
Here we are using the `add` function.
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
variable "TAG" {
|
|
||||||
default = "latest"
|
|
||||||
}
|
|
||||||
|
|
||||||
group "default" {
|
|
||||||
targets = ["webapp"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp" {
|
|
||||||
args = {
|
|
||||||
buildno = "${add(123, 1)}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --print webapp
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"webapp"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"webapp": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"args": {
|
|
||||||
"buildno": "124"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Defining an `increment` function
|
|
||||||
|
|
||||||
It also supports [user defined functions](https://github.com/hashicorp/hcl/tree/main/ext/userfunc).
|
|
||||||
The following example defines a simple an `increment` function.
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
function "increment" {
|
|
||||||
params = [number]
|
|
||||||
result = number + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
group "default" {
|
|
||||||
targets = ["webapp"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp" {
|
|
||||||
args = {
|
|
||||||
buildno = "${increment(123)}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --print webapp
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"webapp"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"webapp": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"args": {
|
|
||||||
"buildno": "124"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Only adding tags if a variable is not empty using an `notequal`
|
|
||||||
|
|
||||||
Here we are using the conditional `notequal` function which is just for
|
|
||||||
symmetry with the `equal` one.
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
variable "TAG" {default="" }
|
|
||||||
|
|
||||||
group "default" {
|
|
||||||
targets = [
|
|
||||||
"webapp",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp" {
|
|
||||||
context="."
|
|
||||||
dockerfile="Dockerfile"
|
|
||||||
tags = [
|
|
||||||
"my-image:latest",
|
|
||||||
notequal("",TAG) ? "my-image:${TAG}": "",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --print webapp
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"webapp"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"webapp": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"tags": [
|
|
||||||
"my-image:latest"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using variables in functions
|
|
||||||
|
|
||||||
You can refer variables to other variables like the target blocks can. Stdlib
|
|
||||||
functions can also be called but user functions can't at the moment.
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
variable "REPO" {
|
|
||||||
default = "user/repo"
|
|
||||||
}
|
|
||||||
|
|
||||||
function "tag" {
|
|
||||||
params = [tag]
|
|
||||||
result = ["${REPO}:${tag}"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "webapp" {
|
|
||||||
tags = tag("v1")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --print webapp
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"webapp"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"webapp": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"tags": [
|
|
||||||
"user/repo:v1"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using typed variables
|
|
||||||
|
|
||||||
Non-string variables are also accepted. The value passed with env is parsed
|
|
||||||
into suitable type first.
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
# docker-bake.hcl
|
|
||||||
variable "FOO" {
|
|
||||||
default = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "IS_FOO" {
|
|
||||||
default = true
|
|
||||||
}
|
|
||||||
|
|
||||||
target "app" {
|
|
||||||
args = {
|
|
||||||
v1 = FOO > 5 ? "higher" : "lower"
|
|
||||||
v2 = IS_FOO ? "yes" : "no"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx bake --print app
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"group": {
|
|
||||||
"default": {
|
|
||||||
"targets": [
|
|
||||||
"app"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"app": {
|
|
||||||
"context": ".",
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"args": {
|
|
||||||
"v1": "lower",
|
|
||||||
"v2": "yes"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# High-level build options with Bake
|
|
||||||
|
|
||||||
> This command is experimental.
|
|
||||||
>
|
|
||||||
> The design of bake is in early stages, and we are looking for [feedback from users](https://github.com/docker/buildx/issues).
|
|
||||||
{: .experimental }
|
|
||||||
|
|
||||||
Buildx also aims to provide support for high-level build concepts that go beyond
|
|
||||||
invoking a single build command. We want to support building all the images in
|
|
||||||
your application together and let the users define project specific reusable
|
|
||||||
build flows that can then be easily invoked by anyone.
|
|
||||||
|
|
||||||
[BuildKit](https://github.com/moby/buildkit) efficiently handles multiple
|
|
||||||
concurrent build requests and de-duplicating work. The build commands can be
|
|
||||||
combined with general-purpose command runners (for example, `make`). However,
|
|
||||||
these tools generally invoke builds in sequence and therefore cannot leverage
|
|
||||||
the full potential of BuildKit parallelization, or combine BuildKit's output
|
|
||||||
for the user. For this use case, we have added a command called
|
|
||||||
[`docker buildx bake`](https://docs.docker.com/engine/reference/commandline/buildx_bake/).
|
|
||||||
|
|
||||||
The `bake` command supports building images from HCL, JSON and Compose files.
|
|
||||||
This is similar to [`docker compose build`](https://docs.docker.com/compose/reference/build/),
|
|
||||||
but allowing all the services to be built concurrently as part of a single
|
|
||||||
request. If multiple files are specified they are all read and configurations are
|
|
||||||
combined.
|
|
||||||
|
|
||||||
We recommend using HCL files as its experience is more aligned with buildx UX
|
|
||||||
and also allows better code reuse, different target groups and extended features.
|
|
||||||
|
|
||||||
## Next steps
|
|
||||||
|
|
||||||
* [File definition](file-definition.md)
|
|
||||||
* [Configuring builds](configuring-build.md)
|
|
||||||
* [User defined HCL functions](hcl-funcs.md)
|
|
||||||
* [Defining additional build contexts and linking targets](build-contexts.md)
|
|
||||||
* [Building from Compose file](compose-file.md)
|
|
||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
In this example we are also using 3 other actions:
|
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
|
* [`setup-buildx`](https://github.com/docker/setup-buildx-action) action will create and boot a builder using by
|
||||||
default the `docker-container` [builder driver](../reference/buildx_create.md#driver).
|
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.
|
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
|
* [`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.
|
to add emulation support with QEMU to be able to build against more platforms.
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
# Docker container driver
|
|
||||||
|
|
||||||
The buildx docker-container driver allows creation of a managed and
|
|
||||||
customizable BuildKit environment inside a dedicated Docker container.
|
|
||||||
|
|
||||||
Using the docker-container driver has a couple of advantages over the basic
|
|
||||||
docker driver. Firstly, we can manually override the version of buildkit to
|
|
||||||
use, meaning that we can access the latest and greatest features as soon as
|
|
||||||
they're released, instead of waiting to upgrade to a newer version of Docker.
|
|
||||||
Additionally, we can access more complex features like multi-architecture
|
|
||||||
builds and the more advanced cache exporters, which are currently unsupported
|
|
||||||
in the default docker driver.
|
|
||||||
|
|
||||||
We can easily create a new builder that uses the docker-container driver:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create --name container --driver docker-container
|
|
||||||
container
|
|
||||||
```
|
|
||||||
|
|
||||||
We should then be able to see it on our list of available builders:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx ls
|
|
||||||
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
|
|
||||||
container docker-container
|
|
||||||
container0 desktop-linux inactive
|
|
||||||
default docker
|
|
||||||
default default running 20.10.17 linux/amd64, linux/386
|
|
||||||
```
|
|
||||||
|
|
||||||
If we trigger a build, the appropriate `moby/buildkit` image will be pulled
|
|
||||||
from [Docker Hub](https://hub.docker.com/u/moby/buildkit), the image started,
|
|
||||||
and our build submitted to our containerized build server.
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx build -t <image> --builder=container .
|
|
||||||
WARNING: No output specified with docker-container driver. Build result will only remain in the build cache. To push result image into registry use --push or to load image into docker use --load
|
|
||||||
#1 [internal] booting buildkit
|
|
||||||
#1 pulling image moby/buildkit:buildx-stable-1
|
|
||||||
#1 pulling image moby/buildkit:buildx-stable-1 1.9s done
|
|
||||||
#1 creating container buildx_buildkit_container0
|
|
||||||
#1 creating container buildx_buildkit_container0 0.5s done
|
|
||||||
#1 DONE 2.4s
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Note the warning "Build result will only remain in the build cache" - unlike
|
|
||||||
the `docker` driver, the built image must be explicitly loaded into the local
|
|
||||||
image store. We can use the `--load` flag for this:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx build --load -t <image> --builder=container .
|
|
||||||
...
|
|
||||||
=> exporting to oci image format 7.7s
|
|
||||||
=> => exporting layers 4.9s
|
|
||||||
=> => exporting manifest sha256:4e4ca161fa338be2c303445411900ebbc5fc086153a0b846ac12996960b479d3 0.0s
|
|
||||||
=> => exporting config sha256:adf3eec768a14b6e183a1010cb96d91155a82fd722a1091440c88f3747f1f53f 0.0s
|
|
||||||
=> => sending tarball 2.8s
|
|
||||||
=> importing to docker
|
|
||||||
```
|
|
||||||
|
|
||||||
The image should then be available in the image store:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker image ls
|
|
||||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
|
||||||
<image> latest adf3eec768a1 2 minutes ago 197MB
|
|
||||||
```
|
|
||||||
|
|
||||||
## Further reading
|
|
||||||
|
|
||||||
For more information on the docker-container driver, see the [buildx reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver).
|
|
||||||
|
|
||||||
<!--- FIXME: for 0.9, make reference link relative --->
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
# Docker driver
|
|
||||||
|
|
||||||
The buildx docker driver is the default builtin driver, that uses the BuildKit
|
|
||||||
server components built directly into the docker engine.
|
|
||||||
|
|
||||||
No setup should be required for the docker driver - it should already be
|
|
||||||
configured for you:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx ls
|
|
||||||
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
|
|
||||||
default docker
|
|
||||||
default default running 20.10.17 linux/amd64, linux/386
|
|
||||||
```
|
|
||||||
|
|
||||||
This builder is ready to build with out-of-the-box, requiring no extra setup,
|
|
||||||
so you can get going with a `docker buildx build` as soon as you like.
|
|
||||||
|
|
||||||
Depending on your personal setup, you may find multiple builders in your list
|
|
||||||
the use the docker driver. For example, on a system that runs both a package
|
|
||||||
managed version of dockerd, as well as Docker Desktop, you might have the
|
|
||||||
following:
|
|
||||||
|
|
||||||
```console
|
|
||||||
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
|
|
||||||
default docker
|
|
||||||
default default running 20.10.17 linux/amd64, linux/386
|
|
||||||
desktop-linux * docker
|
|
||||||
desktop-linux desktop-linux running 20.10.17 linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
|
|
||||||
```
|
|
||||||
|
|
||||||
This is because the docker driver builders are automatically pulled from
|
|
||||||
the available [Docker Contexts](https://docs.docker.com/engine/context/working-with-contexts/).
|
|
||||||
When you add new contexts using `docker context create`, these will appear in
|
|
||||||
your list of buildx builders.
|
|
||||||
|
|
||||||
Unlike the [other drivers](../index.md), builders using the docker driver
|
|
||||||
cannot be manually created, and can only be automatically created from the
|
|
||||||
docker context. Additionally, they cannot be configured to a specific BuildKit
|
|
||||||
version, and cannot take any extra parameters, as these are both preset by the
|
|
||||||
Docker engine internally.
|
|
||||||
|
|
||||||
If you want the extra configuration and flexibility without too much more
|
|
||||||
overhead, then see the help page for the [docker-container driver](./docker-container.md).
|
|
||||||
|
|
||||||
## Further reading
|
|
||||||
|
|
||||||
For more information on the docker driver, see the [buildx reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver).
|
|
||||||
|
|
||||||
<!--- FIXME: for 0.9, make reference link relative --->
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# Buildx drivers overview
|
|
||||||
|
|
||||||
The buildx client connects out to the BuildKit backend to execute builds -
|
|
||||||
Buildx drivers allow fine-grained control over management of the backend, and
|
|
||||||
supports several different options for where and how BuildKit should run.
|
|
||||||
|
|
||||||
Currently, we support the following drivers:
|
|
||||||
|
|
||||||
- The `docker` driver, that uses the BuildKit library bundled into the Docker
|
|
||||||
daemon.
|
|
||||||
([guide](./docker.md), [reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver))
|
|
||||||
- The `docker-container` driver, that launches a dedicated BuildKit container
|
|
||||||
using Docker, for access to advanced features.
|
|
||||||
([guide](./docker-container.md), [reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver))
|
|
||||||
- The `kubernetes` driver, that launches dedicated BuildKit pods in a
|
|
||||||
remote Kubernetes cluster, for scalable builds.
|
|
||||||
([guide](./kubernetes.md), [reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver))
|
|
||||||
- The `remote` driver, that allows directly connecting to a manually managed
|
|
||||||
BuildKit daemon, for more custom setups.
|
|
||||||
([guide](./remote.md))
|
|
||||||
|
|
||||||
<!--- FIXME: for 0.9, make links relative, and add reference link for remote --->
|
|
||||||
|
|
||||||
To create a new builder that uses one of the above drivers, you can use the
|
|
||||||
[`docker buildx create`](https://docs.docker.com/engine/reference/commandline/buildx_create/) command:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create --name=<builder-name> --driver=<driver> --driver-opt=<driver-options>
|
|
||||||
```
|
|
||||||
|
|
||||||
The build experience is very similar across drivers, however, there are some
|
|
||||||
features that are not evenly supported across the board, notably, the `docker`
|
|
||||||
driver does not include support for certain output/caching types.
|
|
||||||
|
|
||||||
| Feature | `docker` | `docker-container` | `kubernetes` | `remote` |
|
|
||||||
| :---------------------------- | :-------------: | :----------------: | :----------: | :--------------------: |
|
|
||||||
| **Automatic `--load`** | ✅ | ❌ | ❌ | ❌ |
|
|
||||||
| **Cache export** | ❔ (inline only) | ✅ | ✅ | ✅ |
|
|
||||||
| **Docker/OCI tarball output** | ❌ | ✅ | ✅ | ✅ |
|
|
||||||
| **Multi-arch images** | ❌ | ✅ | ✅ | ✅ |
|
|
||||||
| **BuildKit configuration** | ❌ | ✅ | ✅ | ❔ (managed externally) |
|
|
||||||
@@ -1,238 +0,0 @@
|
|||||||
# Kubernetes driver
|
|
||||||
|
|
||||||
The buildx kubernetes driver allows connecting your local development or ci
|
|
||||||
environments to your kubernetes cluster to allow access to more powerful
|
|
||||||
and varied compute resources.
|
|
||||||
|
|
||||||
This guide assumes you already have an existing kubernetes cluster - if you don't already
|
|
||||||
have one, you can easily follow along by installing
|
|
||||||
[minikube](https://minikube.sigs.k8s.io/docs/).
|
|
||||||
|
|
||||||
Before connecting buildx to your cluster, you may want to create a dedicated
|
|
||||||
namespace using `kubectl` to keep your buildx-managed resources separate. You
|
|
||||||
can call your namespace anything you want, or use the existing `default`
|
|
||||||
namespace, but we'll create a `buildkit` namespace for now:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ kubectl create namespace buildkit
|
|
||||||
```
|
|
||||||
|
|
||||||
Then create a new buildx builder:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--bootstrap \
|
|
||||||
--name=kube \
|
|
||||||
--driver=kubernetes \
|
|
||||||
--driver-opt=namespace=buildkit
|
|
||||||
```
|
|
||||||
|
|
||||||
This assumes that the kubernetes cluster you want to connect to is currently
|
|
||||||
accessible via the kubectl command, with the `KUBECONFIG` environment variable
|
|
||||||
[set appropriately](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/#set-the-kubeconfig-environment-variable)
|
|
||||||
if neccessary.
|
|
||||||
|
|
||||||
You should now be able to see the builder in the list of buildx builders:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx ls
|
|
||||||
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
|
|
||||||
kube kubernetes
|
|
||||||
kube0-6977cdcb75-k9h9m running linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386
|
|
||||||
default * docker
|
|
||||||
default default running linux/amd64, linux/386
|
|
||||||
```
|
|
||||||
|
|
||||||
The buildx driver creates the neccessary resources on your cluster in the
|
|
||||||
specified namespace (in this case, `buildkit`), while keeping your
|
|
||||||
driver configuration locally. You can see the running pods with:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ kubectl -n buildkit get deployments
|
|
||||||
NAME READY UP-TO-DATE AVAILABLE AGE
|
|
||||||
kube0 1/1 1 1 32s
|
|
||||||
|
|
||||||
$ kubectl -n buildkit get pods
|
|
||||||
NAME READY STATUS RESTARTS AGE
|
|
||||||
kube0-6977cdcb75-k9h9m 1/1 Running 0 32s
|
|
||||||
```
|
|
||||||
|
|
||||||
You can use your new builder by including the `--builder` flag when running
|
|
||||||
buildx commands. For example (replacing `<user>` and `<image>` with your Docker
|
|
||||||
Hub username and desired image output respectively):
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx build . \
|
|
||||||
--builder=kube \
|
|
||||||
-t <user>/<image> \
|
|
||||||
--push
|
|
||||||
```
|
|
||||||
|
|
||||||
## Scaling Buildkit
|
|
||||||
|
|
||||||
One of the main advantages of the kubernetes builder is that you can easily
|
|
||||||
scale your builder up and down to handle increased build load. These controls
|
|
||||||
are exposed via the following options:
|
|
||||||
|
|
||||||
- `replicas=N`
|
|
||||||
- This scales the number of buildkit pods to the desired size. By default,
|
|
||||||
only a single pod will be created, but increasing this allows taking of
|
|
||||||
advantage of multiple nodes in your cluster.
|
|
||||||
- `requests.cpu`, `requests.memory`, `limits.cpu`, `limits.memory`
|
|
||||||
- These options allow requesting and limiting the resources available to each
|
|
||||||
buildkit pod according to the official kubernetes documentation
|
|
||||||
[here](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/).
|
|
||||||
|
|
||||||
For example, to create 4 replica buildkit pods:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--bootstrap \
|
|
||||||
--name=kube \
|
|
||||||
--driver=kubernetes \
|
|
||||||
--driver-opt=namespace=buildkit,replicas=4
|
|
||||||
```
|
|
||||||
|
|
||||||
Listing the pods, we get:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ kubectl -n buildkit get deployments
|
|
||||||
NAME READY UP-TO-DATE AVAILABLE AGE
|
|
||||||
kube0 4/4 4 4 8s
|
|
||||||
|
|
||||||
$ kubectl -n buildkit get pods
|
|
||||||
NAME READY STATUS RESTARTS AGE
|
|
||||||
kube0-6977cdcb75-48ld2 1/1 Running 0 8s
|
|
||||||
kube0-6977cdcb75-rkc6b 1/1 Running 0 8s
|
|
||||||
kube0-6977cdcb75-vb4ks 1/1 Running 0 8s
|
|
||||||
kube0-6977cdcb75-z4fzs 1/1 Running 0 8s
|
|
||||||
```
|
|
||||||
|
|
||||||
Additionally, you can use the `loadbalance=(sticky|random)` option to control
|
|
||||||
the load-balancing behavior when there are multiple replicas. While `random`
|
|
||||||
should selects random nodes from the available pool, which should provide
|
|
||||||
better balancing across all replicas, `sticky` (the default) attempts to
|
|
||||||
connect the same build performed multiple times to the same node each time,
|
|
||||||
ensuring better local cache utilization.
|
|
||||||
|
|
||||||
For more information on scalability, see the options for [buildx create](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver-opt).
|
|
||||||
|
|
||||||
## Multi-platform builds
|
|
||||||
|
|
||||||
The kubernetes buildx driver has support for creating [multi-platform images](https://docs.docker.com/build/buildx/multiplatform-images/),
|
|
||||||
for easily building for multiple platforms at once.
|
|
||||||
|
|
||||||
### QEMU
|
|
||||||
|
|
||||||
Like the other containerized driver `docker-container`, the kubernetes driver
|
|
||||||
also supports using [QEMU](https://www.qemu.org/) (user mode) to build
|
|
||||||
non-native platforms. If using a default setup like above, no extra setup
|
|
||||||
should be needed, you should just be able to start building for other
|
|
||||||
architectures, by including the `--platform` flag.
|
|
||||||
|
|
||||||
For example, to build a Linux image for `amd64` and `arm64`:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx build . \
|
|
||||||
--builder=kube \
|
|
||||||
--platform=linux/amd64,linux/arm64 \
|
|
||||||
-t <user>/<image> \
|
|
||||||
--push
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Warning**
|
|
||||||
> QEMU performs full-system emulation of non-native platforms, which is *much*
|
|
||||||
> slower than native builds. Compute-heavy tasks like compilation and
|
|
||||||
> compression/decompression will likely take a large performance hit.
|
|
||||||
|
|
||||||
Note, if you're using a custom buildkit image using the `image=<image>` driver
|
|
||||||
option, or invoking non-native binaries from within your build, you may need to
|
|
||||||
explicitly enable QEMU using the `qemu.install` option during driver creation:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--bootstrap \
|
|
||||||
--name=kube \
|
|
||||||
--driver=kubernetes \
|
|
||||||
--driver-opt=namespace=buildkit,qemu.install=true
|
|
||||||
```
|
|
||||||
|
|
||||||
### Native
|
|
||||||
|
|
||||||
If you have access to cluster nodes of different architectures, we can
|
|
||||||
configure the kubernetes driver to take advantage of these for native builds.
|
|
||||||
To do this, we need to use the `--append` feature of `docker buildx create`.
|
|
||||||
|
|
||||||
To start, we can create our builder with explicit support for a single
|
|
||||||
architecture, `amd64`:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--bootstrap \
|
|
||||||
--name=kube \
|
|
||||||
--driver=kubernetes \
|
|
||||||
--platform=linux/amd64 \
|
|
||||||
--node=builder-amd64 \
|
|
||||||
--driver-opt=namespace=buildkit,nodeselector="kubernetes.io/arch=amd64"
|
|
||||||
```
|
|
||||||
|
|
||||||
This creates a buildx builder `kube` containing a single builder node `builder-amd64`.
|
|
||||||
Note that the buildx concept of a node is not the same as the kubernetes
|
|
||||||
concept of a node - the buildx node in this case could connect multiple
|
|
||||||
kubernetes nodes of the same architecture together.
|
|
||||||
|
|
||||||
With our `kube` driver created, we can now introduce another architecture into
|
|
||||||
the mix, for example, like before we can use `arm64`:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--append \
|
|
||||||
--bootstrap \
|
|
||||||
--name=kube \
|
|
||||||
--driver=kubernetes \
|
|
||||||
--platform=linux/arm64 \
|
|
||||||
--node=builder-arm64 \
|
|
||||||
--driver-opt=namespace=buildkit,nodeselector="kubernetes.io/arch=arm64"
|
|
||||||
```
|
|
||||||
|
|
||||||
If you list builders now, you should be able to see both nodes present:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx ls
|
|
||||||
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
|
|
||||||
kube kubernetes
|
|
||||||
builder-amd64 kubernetes:///kube?deployment=builder-amd64&kubeconfig= running linux/amd64*, linux/amd64/v2, linux/amd64/v3, linux/386
|
|
||||||
builder-arm64 kubernetes:///kube?deployment=builder-arm64&kubeconfig= running linux/arm64*
|
|
||||||
```
|
|
||||||
|
|
||||||
You should now be able to build multi-arch images with `amd64` and `arm64`
|
|
||||||
combined, by specifying those platforms together in your buildx command:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx build --builder=kube --platform=linux/amd64,linux/arm64 -t <user>/<image> --push .
|
|
||||||
```
|
|
||||||
|
|
||||||
You can repeat the `buildx create --append` command for as many different
|
|
||||||
architectures that you want to support.
|
|
||||||
|
|
||||||
## Rootless mode
|
|
||||||
|
|
||||||
The kubernetes driver supports rootless mode. For more information on how
|
|
||||||
rootless mode works, and it's requirements, see [here](https://github.com/moby/buildkit/blob/master/docs/rootless.md).
|
|
||||||
|
|
||||||
To enable it in your cluster, you can use the `rootless=true` driver option:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--name=kube \
|
|
||||||
--driver=kubernetes \
|
|
||||||
--driver-opt=namespace=buildkit,rootless=true
|
|
||||||
```
|
|
||||||
|
|
||||||
This will create your pods without `securityContext.privileged`.
|
|
||||||
|
|
||||||
## Further reading
|
|
||||||
|
|
||||||
For more information on the kubernetes driver, see the [buildx reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver).
|
|
||||||
|
|
||||||
<!--- FIXME: for 0.9, make reference link relative --->
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
# Remote driver
|
|
||||||
|
|
||||||
The buildx remote driver allows for more complex custom build workloads that
|
|
||||||
allow users to connect to external buildkit instances. This is useful for
|
|
||||||
scenarios that require manual management of the buildkit daemon, or where a
|
|
||||||
buildkit daemon is exposed from another source.
|
|
||||||
|
|
||||||
To connect to a running buildkitd instance:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--name remote \
|
|
||||||
--driver remote \
|
|
||||||
tcp://localhost:1234
|
|
||||||
```
|
|
||||||
|
|
||||||
## Remote Buildkit over Unix sockets
|
|
||||||
|
|
||||||
In this scenario, we'll create a setup with buildkitd listening on a unix
|
|
||||||
socket, and have buildx connect through it.
|
|
||||||
|
|
||||||
Firstly, ensure that [buildkit](https://github.com/moby/buildkit) is installed.
|
|
||||||
For example, you can launch an instance of buildkitd with:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ sudo ./buildkitd --group $(id -gn) --addr unix://$HOME/buildkitd.sock
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, [see here](https://github.com/moby/buildkit/blob/master/docs/rootless.md)
|
|
||||||
for running buildkitd in rootless mode or [here](https://github.com/moby/buildkit/tree/master/examples/systemd)
|
|
||||||
for examples of running it as a systemd service.
|
|
||||||
|
|
||||||
You should now have a unix socket accessible to your user, that is available to
|
|
||||||
connect to:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ ls -lh /home/user/buildkitd.sock
|
|
||||||
srw-rw---- 1 root user 0 May 5 11:04 /home/user/buildkitd.sock
|
|
||||||
```
|
|
||||||
|
|
||||||
You can then connect buildx to it with the remote driver:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--name remote-unix \
|
|
||||||
--driver remote \
|
|
||||||
unix://$HOME/buildkitd.sock
|
|
||||||
```
|
|
||||||
|
|
||||||
If you list available builders, you should then see `remote-unix` among them:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx ls
|
|
||||||
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
|
|
||||||
remote-unix remote
|
|
||||||
remote-unix0 unix:///home/.../buildkitd.sock running linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386
|
|
||||||
default * docker
|
|
||||||
default default running linux/amd64, linux/386
|
|
||||||
```
|
|
||||||
|
|
||||||
We can switch to this new builder as the default using `docker buildx use remote-unix`,
|
|
||||||
or specify it per build:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx build --builder=remote-unix -t test --load .
|
|
||||||
```
|
|
||||||
|
|
||||||
(remember that `--load` is necessary when not using the default `docker`
|
|
||||||
driver, to load the build result into the docker daemon)
|
|
||||||
|
|
||||||
## Remote Buildkit in Docker container
|
|
||||||
|
|
||||||
In this scenario, we'll create a similar setup to the `docker-container`
|
|
||||||
driver, by manually booting a buildkit docker container and connecting to it
|
|
||||||
using the buildx remote driver. In most cases you'd probably just use the
|
|
||||||
`docker-container` driver that connects to buildkit through the Docker daemon,
|
|
||||||
but in this case we manually create a container and access it via it's exposed
|
|
||||||
port.
|
|
||||||
|
|
||||||
First, we need to generate certificates for buildkit - you can use the
|
|
||||||
[create-certs.sh](https://github.com/moby/buildkit/v0.10.3/master/examples/kubernetes/create-certs.sh)
|
|
||||||
script as a starting point. Note, that while it is *possible* to expose
|
|
||||||
buildkit over TCP without using TLS, it is **not recommended**, since this will
|
|
||||||
allow arbitrary access to buildkit without credentials.
|
|
||||||
|
|
||||||
With our certificates generated in `.certs/`, we startup the container:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker run -d --rm \
|
|
||||||
--name=remote-buildkitd \
|
|
||||||
--privileged \
|
|
||||||
-p 1234:1234 \
|
|
||||||
-v $PWD/.certs:/etc/buildkit/certs \
|
|
||||||
moby/buildkit:latest \
|
|
||||||
--addr tcp://0.0.0.0:1234 \
|
|
||||||
--tlscacert /etc/buildkit/certs/ca.pem \
|
|
||||||
--tlscert /etc/buildkit/certs/daemon-cert.pem \
|
|
||||||
--tlskey /etc/buildkit/certs/daemon-key.pem
|
|
||||||
```
|
|
||||||
|
|
||||||
The above command starts a buildkit container and exposes the daemon's port
|
|
||||||
1234 to localhost.
|
|
||||||
|
|
||||||
We can now connect to this running container using buildx:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--name remote-container \
|
|
||||||
--driver remote \
|
|
||||||
--driver-opt cacert=.certs/ca.pem,cert=.certs/client-cert.pem,key=.certs/client-key.pem,servername=... \
|
|
||||||
tcp://localhost:1234
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, we could use the `docker-container://` URL scheme to connect
|
|
||||||
to the buildkit container without specifying a port:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--name remote-container \
|
|
||||||
--driver remote \
|
|
||||||
docker-container://remote-container
|
|
||||||
```
|
|
||||||
|
|
||||||
## Remote Buildkit in Kubernetes
|
|
||||||
|
|
||||||
In this scenario, we'll create a similar setup to the `kubernetes` driver by
|
|
||||||
manually creating a buildkit `Deployment`. While the `kubernetes` driver will
|
|
||||||
do this under-the-hood, it might sometimes be desirable to scale buildkit
|
|
||||||
manually. Additionally, when executing builds from inside Kubernetes pods,
|
|
||||||
the buildx builder will need to be recreated from within each pod or copied
|
|
||||||
between them.
|
|
||||||
|
|
||||||
Firstly, we can create a kubernetes deployment of buildkitd, as per the
|
|
||||||
instructions [here](https://github.com/moby/buildkit/tree/master/examples/kubernetes).
|
|
||||||
Following the guide, we setup certificates for the buildkit daemon and client
|
|
||||||
(as above using [create-certs.sh](https://github.com/moby/buildkit/blob/v0.10.3/examples/kubernetes/create-certs.sh))
|
|
||||||
and create a `Deployment` of buildkit pods with a service that connects to
|
|
||||||
them.
|
|
||||||
|
|
||||||
Assuming that the service is called `buildkitd`, we can create a remote builder
|
|
||||||
in buildx, ensuring that the listed certificate files are present:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker buildx create \
|
|
||||||
--name remote-kubernetes \
|
|
||||||
--driver remote \
|
|
||||||
--driver-opt cacert=.certs/ca.pem,cert=.certs/client-cert.pem,key=.certs/client-key.pem \
|
|
||||||
tcp://buildkitd.default.svc:1234
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that the above will only work in-cluster (since the buildkit setup guide
|
|
||||||
only creates a ClusterIP service). To configure the builder to be accessible
|
|
||||||
remotely, you can use an appropriately configured Ingress, which is outside the
|
|
||||||
scope of this guide.
|
|
||||||
|
|
||||||
To access the service remotely, we can use the port forwarding mechanism in
|
|
||||||
kubectl:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ kubectl port-forward svc/buildkitd 1234:1234
|
|
||||||
```
|
|
||||||
|
|
||||||
Then you can simply point the remote driver at `tcp://localhost:1234`.
|
|
||||||
|
|
||||||
Alternatively, we could use the `kube-pod://` URL scheme to connect
|
|
||||||
directly to a buildkit pod through the kubernetes api (note that this method
|
|
||||||
will only connect to a single pod in the deployment):
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ kubectl get pods --selector=app=buildkitd -o json | jq -r '.items[].metadata.name
|
|
||||||
buildkitd-XXXXXXXXXX-xxxxx
|
|
||||||
$ docker buildx create \
|
|
||||||
--name remote-container \
|
|
||||||
--driver remote \
|
|
||||||
kube-pod://buildkitd-XXXXXXXXXX-xxxxx
|
|
||||||
```
|
|
||||||
|
|
||||||
<!--- FIXME: for 0.9, add further reading section with link to reference --->
|
|
||||||
14
docs/manuals/README.md
Normal file
14
docs/manuals/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# 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!
|
||||||
3
docs/manuals/bake/build-contexts.md
Normal file
3
docs/manuals/bake/build-contexts.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Defining additional build contexts and linking targets
|
||||||
|
|
||||||
|
Moved to [docs.docker.com](https://docs.docker.com/build/bake/build-contexts)
|
||||||
3
docs/manuals/bake/compose-file.md
Normal file
3
docs/manuals/bake/compose-file.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Building from Compose file
|
||||||
|
|
||||||
|
Moved to [docs.docker.com](https://docs.docker.com/build/bake/compose-file)
|
||||||
3
docs/manuals/bake/configuring-build.md
Normal file
3
docs/manuals/bake/configuring-build.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Configuring builds
|
||||||
|
|
||||||
|
Moved to [docs.docker.com](https://docs.docker.com/build/bake/configuring-build)
|
||||||
3
docs/manuals/bake/file-definition.md
Normal file
3
docs/manuals/bake/file-definition.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Bake file definition
|
||||||
|
|
||||||
|
This page has moved to [docs/bake-reference.md](../../bake-reference.md)
|
||||||
3
docs/manuals/bake/hcl-funcs.md
Normal file
3
docs/manuals/bake/hcl-funcs.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# User defined HCL functions
|
||||||
|
|
||||||
|
Moved to [docs.docker.com](https://docs.docker.com/build/bake/hcl-funcs)
|
||||||
3
docs/manuals/bake/index.md
Normal file
3
docs/manuals/bake/index.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# High-level build options with Bake
|
||||||
|
|
||||||
|
Moved to [docs.docker.com](https://docs.docker.com/build/bake)
|
||||||
3
docs/manuals/cache/backends/azblob.md
vendored
Normal file
3
docs/manuals/cache/backends/azblob.md
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# 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
Normal file
3
docs/manuals/cache/backends/gha.md
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# 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
Normal file
3
docs/manuals/cache/backends/index.md
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Cache storage backends
|
||||||
|
|
||||||
|
Moved to [docs.docker.com](https://docs.docker.com/build/building/cache/backends)
|
||||||
3
docs/manuals/cache/backends/inline.md
vendored
Normal file
3
docs/manuals/cache/backends/inline.md
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# 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
Normal file
3
docs/manuals/cache/backends/local.md
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# 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
Normal file
3
docs/manuals/cache/backends/registry.md
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# 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
Normal file
3
docs/manuals/cache/backends/s3.md
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Amazon S3 cache storage
|
||||||
|
|
||||||
|
Moved to [docs.docker.com](https://docs.docker.com/build/building/cache/backends/s3)
|
||||||
3
docs/manuals/drivers/docker-container.md
Normal file
3
docs/manuals/drivers/docker-container.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Docker container driver
|
||||||
|
|
||||||
|
Moved to [docs.docker.com](https://docs.docker.com/build/building/drivers/docker-container)
|
||||||
3
docs/manuals/drivers/docker.md
Normal file
3
docs/manuals/drivers/docker.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Docker driver
|
||||||
|
|
||||||
|
Moved to [docs.docker.com](https://docs.docker.com/build/building/drivers/docker)
|
||||||
3
docs/manuals/drivers/index.md
Normal file
3
docs/manuals/drivers/index.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Buildx drivers overview
|
||||||
|
|
||||||
|
Moved to [docs.docker.com](https://docs.docker.com/build/building/drivers)
|
||||||
3
docs/manuals/drivers/kubernetes.md
Normal file
3
docs/manuals/drivers/kubernetes.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Kubernetes driver
|
||||||
|
|
||||||
|
Moved to [docs.docker.com](https://docs.docker.com/build/building/drivers/kubernetes)
|
||||||
3
docs/manuals/drivers/remote.md
Normal file
3
docs/manuals/drivers/remote.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Remote driver
|
||||||
|
|
||||||
|
Moved to [docs.docker.com](https://docs.docker.com/build/building/drivers/remote)
|
||||||
3
docs/manuals/exporters/image-registry.md
Normal file
3
docs/manuals/exporters/image-registry.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Image and registry exporters
|
||||||
|
|
||||||
|
Moved to [docs.docker.com](https://docs.docker.com/build/building/exporters/image-registry)
|
||||||
3
docs/manuals/exporters/index.md
Normal file
3
docs/manuals/exporters/index.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Exporters overview
|
||||||
|
|
||||||
|
Moved to [docs.docker.com](https://docs.docker.com/build/building/exporters)
|
||||||
3
docs/manuals/exporters/local-tar.md
Normal file
3
docs/manuals/exporters/local-tar.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Local and tar exporters
|
||||||
|
|
||||||
|
Moved to [docs.docker.com](https://docs.docker.com/build/building/exporters/local-tar)
|
||||||
3
docs/manuals/exporters/oci-docker.md
Normal file
3
docs/manuals/exporters/oci-docker.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# OCI and Docker exporters
|
||||||
|
|
||||||
|
Moved to [docs.docker.com](https://docs.docker.com/build/building/exporters/oci-docker)
|
||||||
@@ -10,7 +10,7 @@ Extended build capabilities with BuildKit
|
|||||||
### Subcommands
|
### Subcommands
|
||||||
|
|
||||||
| Name | Description |
|
| Name | Description |
|
||||||
| --- | --- |
|
|:-------------------------------------|:-------------------------------------------|
|
||||||
| [`bake`](buildx_bake.md) | Build from a file |
|
| [`bake`](buildx_bake.md) | Build from a file |
|
||||||
| [`build`](buildx_build.md) | Start a build |
|
| [`build`](buildx_build.md) | Start a build |
|
||||||
| [`create`](buildx_create.md) | Create a new builder instance |
|
| [`create`](buildx_create.md) | Create a new builder instance |
|
||||||
@@ -30,7 +30,7 @@ Extended build capabilities with BuildKit
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
|:------------------------|:---------|:--------|:-----------------------------------------|
|
||||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Build from a file
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
|:---------------------------------|:--------------|:--------|:-----------------------------------------------------------------------------------------|
|
||||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
| [`-f`](#file), [`--file`](#file) | `stringArray` | | Build definition file |
|
| [`-f`](#file), [`--file`](#file) | `stringArray` | | Build definition file |
|
||||||
| `--load` | | | Shorthand for `--set=*.output=type=docker` |
|
| `--load` | | | Shorthand for `--set=*.output=type=docker` |
|
||||||
@@ -22,8 +22,10 @@ Build from a file
|
|||||||
| [`--no-cache`](#no-cache) | | | Do not use cache when building the image |
|
| [`--no-cache`](#no-cache) | | | Do not use cache when building the image |
|
||||||
| [`--print`](#print) | | | Print the options without building |
|
| [`--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 |
|
| [`--progress`](#progress) | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output |
|
||||||
|
| [`--provenance`](#provenance) | `string` | | Shorthand for `--set=*.attest=type=provenance` |
|
||||||
| [`--pull`](#pull) | | | Always attempt to pull all referenced images |
|
| [`--pull`](#pull) | | | Always attempt to pull all referenced images |
|
||||||
| `--push` | | | Shorthand for `--set=*.output=type=registry` |
|
| `--push` | | | Shorthand for `--set=*.output=type=registry` |
|
||||||
|
| [`--sbom`](#sbom) | `string` | | Shorthand for `--set=*.attest=type=sbom` |
|
||||||
| [`--set`](#set) | `stringArray` | | Override target value (e.g., `targetpattern.key=value`) |
|
| [`--set`](#set) | `stringArray` | | Override target value (e.g., `targetpattern.key=value`) |
|
||||||
|
|
||||||
|
|
||||||
@@ -85,8 +87,8 @@ target "db" {
|
|||||||
$ docker buildx bake -f docker-bake.dev.hcl db webapp-release
|
$ docker buildx bake -f docker-bake.dev.hcl db webapp-release
|
||||||
```
|
```
|
||||||
|
|
||||||
See our [file definition](https://docs.docker.com/build/bake/file-definition/)
|
See the [Bake file reference](https://docs.docker.com/build/bake/reference/)
|
||||||
guide for more details.
|
for more details.
|
||||||
|
|
||||||
### <a name="no-cache"></a> Do not use cache when building the image (--no-cache)
|
### <a name="no-cache"></a> Do not use cache when building the image (--no-cache)
|
||||||
|
|
||||||
@@ -123,10 +125,18 @@ $ docker buildx bake -f docker-bake.hcl --print db
|
|||||||
|
|
||||||
Same as [`build --progress`](buildx_build.md#progress).
|
Same as [`build --progress`](buildx_build.md#progress).
|
||||||
|
|
||||||
|
### <a name="provenance"></a> Create provenance attestations (--provenance)
|
||||||
|
|
||||||
|
Same as [`build --provenance`](buildx_build.md#provenance).
|
||||||
|
|
||||||
### <a name="pull"></a> Always attempt to pull a newer version of the image (--pull)
|
### <a name="pull"></a> Always attempt to pull a newer version of the image (--pull)
|
||||||
|
|
||||||
Same as `build --pull`.
|
Same as `build --pull`.
|
||||||
|
|
||||||
|
### <a name="sbom"></a> Create SBOM attestations (--sbom)
|
||||||
|
|
||||||
|
Same as [`build --sbom`](buildx_build.md#sbom).
|
||||||
|
|
||||||
### <a name="set"></a> Override target configurations from command line (--set)
|
### <a name="set"></a> Override target configurations from command line (--set)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -14,17 +14,19 @@ Start a build
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| 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`) |
|
| [`--add-host`](https://docs.docker.com/engine/reference/commandline/build/#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`) |
|
| [`--allow`](#allow) | `stringSlice` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`) |
|
||||||
|
| [`--attest`](#attest) | `stringArray` | | Attestation parameters (format: `type=sbom,generator=image`) |
|
||||||
| [`--build-arg`](#build-arg) | `stringArray` | | Set build-time variables |
|
| [`--build-arg`](#build-arg) | `stringArray` | | Set build-time variables |
|
||||||
| [`--build-context`](#build-context) | `stringArray` | | Additional build contexts (e.g., name=path) |
|
| [`--build-context`](#build-context) | `stringArray` | | Additional build contexts (e.g., name=path) |
|
||||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
| [`--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-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`) |
|
| [`--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 |
|
| [`--cgroup-parent`](https://docs.docker.com/engine/reference/commandline/build/#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`) |
|
| [`-f`](https://docs.docker.com/engine/reference/commandline/build/#file), [`--file`](https://docs.docker.com/engine/reference/commandline/build/#file) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
|
||||||
| `--iidfile` | `string` | | Write the image ID to the file |
|
| `--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 |
|
| `--label` | `stringArray` | | Set metadata for an image |
|
||||||
| [`--load`](#load) | | | Shorthand for `--output=type=docker` |
|
| [`--load`](#load) | | | Shorthand for `--output=type=docker` |
|
||||||
| [`--metadata-file`](#metadata-file) | `string` | | Write build result metadata to the file |
|
| [`--metadata-file`](#metadata-file) | `string` | | Write build result metadata to the file |
|
||||||
@@ -33,20 +35,26 @@ Start a build
|
|||||||
| `--no-cache-filter` | `stringArray` | | Do not cache specified stages |
|
| `--no-cache-filter` | `stringArray` | | Do not cache specified stages |
|
||||||
| [`-o`](#output), [`--output`](#output) | `stringArray` | | Output destination (format: `type=local,dest=path`) |
|
| [`-o`](#output), [`--output`](#output) | `stringArray` | | Output destination (format: `type=local,dest=path`) |
|
||||||
| [`--platform`](#platform) | `stringArray` | | Set target platform for build |
|
| [`--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 |
|
| [`--progress`](#progress) | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output |
|
||||||
|
| [`--provenance`](#provenance) | `string` | | Shortand for `--attest=type=provenance` |
|
||||||
| `--pull` | | | Always attempt to pull all referenced images |
|
| `--pull` | | | Always attempt to pull all referenced images |
|
||||||
| [`--push`](#push) | | | Shorthand for `--output=type=registry` |
|
| [`--push`](#push) | | | Shorthand for `--output=type=registry` |
|
||||||
| `-q`, `--quiet` | | | Suppress the build output and print image ID on success |
|
| `-q`, `--quiet` | | | Suppress the build output and print image ID on success |
|
||||||
|
| [`--sbom`](#sbom) | `string` | | Shorthand for `--attest=type=sbom` |
|
||||||
| [`--secret`](#secret) | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
| [`--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` |
|
| [`--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>]]`) |
|
| [`--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`) |
|
| [`-t`](https://docs.docker.com/engine/reference/commandline/build/#tag), [`--tag`](https://docs.docker.com/engine/reference/commandline/build/#tag) | `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 |
|
| [`--target`](https://docs.docker.com/engine/reference/commandline/build/#target) | `string` | | Set the target build stage to build |
|
||||||
| [`--ulimit`](#ulimit) | `ulimit` | | Ulimit options |
|
| [`--ulimit`](#ulimit) | `ulimit` | | Ulimit options |
|
||||||
|
|
||||||
|
|
||||||
<!---MARKER_GEN_END-->
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
Flags marked with `[experimental]` need to be explicitly enabled by setting the
|
||||||
|
`BUILDX_EXPERIMENTAL=1` environment variable.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
The `buildx build` command starts a build using BuildKit. This command is similar
|
The `buildx build` command starts a build using BuildKit. This command is similar
|
||||||
@@ -58,6 +66,33 @@ here we'll document a subset of the new flags.
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
### <a name="attest"></a> Create attestations (--attest)
|
||||||
|
|
||||||
|
```
|
||||||
|
--attest=type=sbom,...
|
||||||
|
--attest=type=provenance,...
|
||||||
|
```
|
||||||
|
|
||||||
|
Create [image attestations](https://docs.docker.com/build/attestations/).
|
||||||
|
BuildKit currently supports:
|
||||||
|
|
||||||
|
- `sbom` - Software Bill of Materials.
|
||||||
|
|
||||||
|
Use `--attest=type=sbom` to generate an SBOM for an image at build-time.
|
||||||
|
Alternatively, you can use the [`--sbom` shorthand](#sbom).
|
||||||
|
|
||||||
|
For more information, see [here](https://docs.docker.com/build/attestations/sbom/).
|
||||||
|
|
||||||
|
- `provenance` - SLSA Provenance
|
||||||
|
|
||||||
|
Use `--attest=type=provenance` to generate provenance for an image at
|
||||||
|
build-time. Alternatively, you can use the [`--provenance` shorthand](#provenance).
|
||||||
|
|
||||||
|
By default, a minimal provenance attestation will be created for the build
|
||||||
|
result, which will only be attached for images pushed to registries.
|
||||||
|
|
||||||
|
For more information, see [here](https://docs.docker.com/build/attestations/slsa-provenance/).
|
||||||
|
|
||||||
### <a name="allow"></a> Allow extra privileged entitlement (--allow)
|
### <a name="allow"></a> Allow extra privileged entitlement (--allow)
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -82,7 +117,7 @@ $ docker buildx build --allow security.insecure .
|
|||||||
|
|
||||||
### <a name="build-arg"></a> Set build-time variables (--build-arg)
|
### <a name="build-arg"></a> Set build-time variables (--build-arg)
|
||||||
|
|
||||||
Same as [`docker build` command](https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables---build-arg).
|
Same as [`docker build` command](https://docs.docker.com/engine/reference/commandline/build/#build-arg).
|
||||||
|
|
||||||
There are also useful built-in build args like:
|
There are also useful built-in build args like:
|
||||||
|
|
||||||
@@ -123,36 +158,33 @@ $ docker buildx build --build-context project=path/to/project/source .
|
|||||||
# docker buildx build --build-context project=https://github.com/myuser/project.git .
|
# docker buildx build --build-context project=https://github.com/myuser/project.git .
|
||||||
```
|
```
|
||||||
|
|
||||||
```Dockerfile
|
```dockerfile
|
||||||
|
# syntax=docker/dockerfile:1
|
||||||
FROM alpine
|
FROM alpine
|
||||||
COPY --from=project myfile /
|
COPY --from=project myfile /
|
||||||
```
|
```
|
||||||
|
|
||||||
#### <a name="source-oci-layout"></a> Source image from OCI layout directory
|
#### <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):
|
Source an image from a local [OCI layout compliant directory](https://github.com/opencontainers/image-spec/blob/main/image-layout.md),
|
||||||
|
either by tag, or by digest:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker buildx build --build-context foo=oci-layout:///path/to/local/layout@sha256:abcd12345 .
|
$ docker buildx build --build-context foo=oci-layout:///path/to/local/layout:<tag>
|
||||||
|
$ docker buildx build --build-context foo=oci-layout:///path/to/local/layout@sha256:<digest>
|
||||||
```
|
```
|
||||||
|
|
||||||
```Dockerfile
|
```dockerfile
|
||||||
|
# syntax=docker/dockerfile:1
|
||||||
FROM alpine
|
FROM alpine
|
||||||
RUN apk add git
|
RUN apk add git
|
||||||
|
|
||||||
COPY --from=foo myfile /
|
COPY --from=foo myfile /
|
||||||
|
|
||||||
FROM foo
|
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
|
The OCI layout directory must be compliant with the [OCI layout specification](https://github.com/opencontainers/image-spec/blob/main/image-layout.md).
|
||||||
do any form of `image:tag` resolution to find the hash of the manifest; that is up to you.
|
You can reference an image in the layout using either tags, or the exact digest.
|
||||||
|
|
||||||
The format of the `--build-context` must be: `<context>=oci-layout://<path-to-local-layout>@sha256:<hash-of-manifest>`, where:
|
|
||||||
|
|
||||||
* `context` is the name of the build context as used in the `Dockerfile`.
|
|
||||||
* `path-to-local-layout` is the path on the local machine, where you are running `docker build`, to the spec-compliant OCI layout.
|
|
||||||
* `hash-of-manifest` is the hash of the manifest for the image. It can be a single-architecture manifest or a multi-architecture index.
|
|
||||||
|
|
||||||
### <a name="builder"></a> Override the configured builder instance (--builder)
|
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||||
|
|
||||||
@@ -165,7 +197,7 @@ Same as [`buildx --builder`](buildx.md#builder).
|
|||||||
```
|
```
|
||||||
|
|
||||||
Use an external cache source for a build. Supported types are `registry`,
|
Use an external cache source for a build. Supported types are `registry`,
|
||||||
`local` and `gha`.
|
`local`, `gha` and `s3`.
|
||||||
|
|
||||||
- [`registry` source](https://github.com/moby/buildkit#registry-push-image-and-cache-separately)
|
- [`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
|
can import cache from a cache manifest or (special) image configuration on the
|
||||||
@@ -175,6 +207,9 @@ Use an external cache source for a build. Supported types are `registry`,
|
|||||||
- [`gha` source](https://github.com/moby/buildkit#github-actions-cache-experimental)
|
- [`gha` source](https://github.com/moby/buildkit#github-actions-cache-experimental)
|
||||||
can import cache from a previously exported cache with `--cache-to` in your
|
can import cache from a previously exported cache with `--cache-to` in your
|
||||||
GitHub repository
|
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.
|
If no type is specified, `registry` exporter is used with a specified reference.
|
||||||
|
|
||||||
@@ -186,6 +221,7 @@ $ docker buildx build --cache-from=user/app .
|
|||||||
$ docker buildx build --cache-from=type=registry,ref=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=local,src=path/to/cache .
|
||||||
$ docker buildx build --cache-from=type=gha .
|
$ 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
|
More info about cache exporters and available attributes: https://github.com/moby/buildkit#export-cache
|
||||||
@@ -197,15 +233,17 @@ More info about cache exporters and available attributes: https://github.com/mob
|
|||||||
```
|
```
|
||||||
|
|
||||||
Export build cache to an external cache destination. Supported types are
|
Export build cache to an external cache destination. Supported types are
|
||||||
`registry`, `local`, `inline` and `gha`.
|
`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.
|
- [`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) type
|
- [`local` type](https://github.com/moby/buildkit#local-directory-1) exports
|
||||||
exports cache to a local directory on the client.
|
cache to a local directory on the client.
|
||||||
- [`inline` type](https://github.com/moby/buildkit#inline-push-image-and-cache-together)
|
- [`inline` type](https://github.com/moby/buildkit#inline-push-image-and-cache-together)
|
||||||
type writes the cache metadata into the image configuration.
|
writes the cache metadata into the image configuration.
|
||||||
- [`gha` type](https://github.com/moby/buildkit#github-actions-cache-experimental)
|
- [`gha` type](https://github.com/moby/buildkit#github-actions-cache-experimental)
|
||||||
type exports cache through the [Github Actions Cache service API](https://github.com/tonistiigi/go-actions-cache/blob/master/api.md#authentication).
|
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
|
`docker` driver currently only supports exporting inline cache metadata to image
|
||||||
configuration. Alternatively, `--build-arg BUILDKIT_INLINE_CACHE=1` can be used
|
configuration. Alternatively, `--build-arg BUILDKIT_INLINE_CACHE=1` can be used
|
||||||
@@ -223,6 +261,7 @@ $ docker buildx build --cache-to=type=inline .
|
|||||||
$ docker buildx build --cache-to=type=registry,ref=user/app .
|
$ 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=local,dest=path/to/cache .
|
||||||
$ docker buildx build --cache-to=type=gha .
|
$ 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
|
More info about cache exporters and available attributes: https://github.com/moby/buildkit#export-cache
|
||||||
@@ -375,8 +414,13 @@ The `registry` exporter is a shortcut for `type=image,push=true`.
|
|||||||
|
|
||||||
Set the target platform for the build. All `FROM` commands inside the Dockerfile
|
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
|
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
|
this value will also be the platform of the resulting image.
|
||||||
will be the current platform of the buildkit daemon.
|
|
||||||
|
The default value is the platform of the BuildKit daemon where the build runs.
|
||||||
|
The value takes the form of `os/arch` or `os/arch/variant`. For example,
|
||||||
|
`linux/amd64` or `linux/arm/v7`. Additionally, the `--platform` flag also supports
|
||||||
|
a special `local` value, which tells BuildKit to use the platform of the BuildKit
|
||||||
|
client that invokes the build.
|
||||||
|
|
||||||
When using `docker-container` driver with `buildx`, this flag can accept multiple
|
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
|
values as an input separated by a comma. With multiple values the result will be
|
||||||
@@ -436,14 +480,44 @@ $ docker buildx build --load --progress=plain .
|
|||||||
|
|
||||||
> **Note**
|
> **Note**
|
||||||
>
|
>
|
||||||
> Check also our [Color output controls guide](https://docs.docker.com/build/guides/color-output/)
|
> 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.
|
> for modifying the colors that are used to output information to the terminal.
|
||||||
|
|
||||||
|
### <a name="provenance"></a> Create provenance attestations (--provenance)
|
||||||
|
|
||||||
|
Shorthand for [`--attest=type=provenance`](#attest), used to configure
|
||||||
|
provenance attestations for the build result. For example,
|
||||||
|
`--provenance=mode=max` can be used as an abbreviation for
|
||||||
|
`--attest=type=provenance,mode=max`.
|
||||||
|
|
||||||
|
Additionally, `--provenance` can be used with boolean values to broadly enable
|
||||||
|
or disable provenance attestations. For example, `--provenance=false` can be
|
||||||
|
used to disable all provenance attestations, while `--provenance=true` can be
|
||||||
|
used to enable all provenance attestations.
|
||||||
|
|
||||||
|
By default, a minimal provenance attestation will be created for the build
|
||||||
|
result, which will only be attached for images pushed to registries.
|
||||||
|
|
||||||
|
For more information, see [here](https://docs.docker.com/build/attestations/slsa-provenance/).
|
||||||
|
|
||||||
### <a name="push"></a> Push the build result to a registry (--push)
|
### <a name="push"></a> Push the build result to a registry (--push)
|
||||||
|
|
||||||
Shorthand for [`--output=type=registry`](#registry). Will automatically push the
|
Shorthand for [`--output=type=registry`](#registry). Will automatically push the
|
||||||
build result to registry.
|
build result to registry.
|
||||||
|
|
||||||
|
### <a name="sbom"></a> Create SBOM attestations (--sbom)
|
||||||
|
|
||||||
|
Shorthand for [`--attest=type=sbom`](#attest), used to configure SBOM
|
||||||
|
attestations for the build result. For example,
|
||||||
|
`--sbom=generator=<user>/<generator-image>` can be used as an abbreviation for
|
||||||
|
`--attest=type=sbom,generator=<user>/<generator-image>`.
|
||||||
|
|
||||||
|
Additionally, `--sbom` can be used with boolean values to broadly enable or
|
||||||
|
disable SBOM attestations. For example, `--sbom=false` can be used to disable
|
||||||
|
all SBOM attestations.
|
||||||
|
|
||||||
|
For more information, see [here](https://docs.docker.com/build/attestations/sbom/).
|
||||||
|
|
||||||
### <a name="secret"></a> Secret to expose to the build (--secret)
|
### <a name="secret"></a> Secret to expose to the build (--secret)
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -463,7 +537,7 @@ Attribute keys:
|
|||||||
- `src`, `source` - Secret filename. `id` used if unset.
|
- `src`, `source` - Secret filename. `id` used if unset.
|
||||||
|
|
||||||
```dockerfile
|
```dockerfile
|
||||||
# syntax=docker/dockerfile:1.4
|
# syntax=docker/dockerfile:1
|
||||||
FROM python:3
|
FROM python:3
|
||||||
RUN pip install awscli
|
RUN pip install awscli
|
||||||
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \
|
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \
|
||||||
@@ -482,7 +556,7 @@ Attribute keys:
|
|||||||
- `env` - Secret environment variable. `id` used if unset, otherwise will look for `src`, `source` if `id` unset.
|
- `env` - Secret environment variable. `id` used if unset, otherwise will look for `src`, `source` if `id` unset.
|
||||||
|
|
||||||
```dockerfile
|
```dockerfile
|
||||||
# syntax=docker/dockerfile:1.4
|
# syntax=docker/dockerfile:1
|
||||||
FROM node:alpine
|
FROM node:alpine
|
||||||
RUN --mount=type=bind,target=. \
|
RUN --mount=type=bind,target=. \
|
||||||
--mount=type=secret,id=SECRET_TOKEN \
|
--mount=type=secret,id=SECRET_TOKEN \
|
||||||
@@ -514,7 +588,7 @@ authentication (e.g., cloning a private repository).
|
|||||||
Example to access Gitlab using an SSH agent socket:
|
Example to access Gitlab using an SSH agent socket:
|
||||||
|
|
||||||
```dockerfile
|
```dockerfile
|
||||||
# syntax=docker/dockerfile:1.4
|
# syntax=docker/dockerfile:1
|
||||||
FROM alpine
|
FROM alpine
|
||||||
RUN apk add --no-cache openssh-client
|
RUN apk add --no-cache openssh-client
|
||||||
RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan gitlab.com >> ~/.ssh/known_hosts
|
RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan gitlab.com >> ~/.ssh/known_hosts
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ Create a new builder instance
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
|:----------------------------------------|:--------------|:--------|:----------------------------------------------------------------------|
|
||||||
| [`--append`](#append) | | | Append a node to builder instead of changing it |
|
| [`--append`](#append) | | | Append a node to builder instead of changing it |
|
||||||
| `--bootstrap` | | | Boot builder after creation |
|
| `--bootstrap` | | | Boot builder after creation |
|
||||||
| [`--buildkitd-flags`](#buildkitd-flags) | `string` | | Flags for buildkitd daemon |
|
| [`--buildkitd-flags`](#buildkitd-flags) | `string` | | Flags for buildkitd daemon |
|
||||||
| [`--config`](#config) | `string` | | BuildKit config file |
|
| [`--config`](#config) | `string` | | BuildKit config file |
|
||||||
| [`--driver`](#driver) | `string` | | Driver to use (available: `docker`, `docker-container`, `kubernetes`, `remote`) |
|
| [`--driver`](#driver) | `string` | | Driver to use (available: `docker-container`, `kubernetes`, `remote`) |
|
||||||
| [`--driver-opt`](#driver-opt) | `stringArray` | | Options for the driver |
|
| [`--driver-opt`](#driver-opt) | `stringArray` | | Options for the driver |
|
||||||
| [`--leave`](#leave) | | | Remove a node from builder instead of changing it |
|
| [`--leave`](#leave) | | | Remove a node from builder instead of changing it |
|
||||||
| [`--name`](#name) | `string` | | Builder instance name |
|
| [`--name`](#name) | `string` | | Builder instance name |
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Disk usage
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
|:------------------------|:---------|:--------|:-----------------------------------------|
|
||||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
| `--filter` | `filter` | | Provide filter values |
|
| `--filter` | `filter` | | Provide filter values |
|
||||||
| `--verbose` | | | Provide a more verbose output |
|
| `--verbose` | | | Provide a more verbose output |
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Commands to work on images in registry
|
|||||||
### Subcommands
|
### Subcommands
|
||||||
|
|
||||||
| Name | Description |
|
| Name | Description |
|
||||||
| --- | --- |
|
|:------------------------------------------|:------------------------------------------|
|
||||||
| [`create`](buildx_imagetools_create.md) | Create a new image based on source images |
|
| [`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 an image in the registry |
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ Commands to work on images in registry
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
|:------------------------|:---------|:--------|:-----------------------------------------|
|
||||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Create a new image based on source images
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
|:---------------------------------|:--------------|:--------|:-----------------------------------------------------------------------------------------|
|
||||||
| [`--append`](#append) | | | Append to existing manifest |
|
| [`--append`](#append) | | | Append to existing manifest |
|
||||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
| [`--dry-run`](#dry-run) | | | Show final image instead of pushing |
|
| [`--dry-run`](#dry-run) | | | Show final image instead of pushing |
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Show details of an image in the registry
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
|:------------------------|:---------|:----------------|:----------------------------------------------|
|
||||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
| [`--format`](#format) | `string` | `{{.Manifest}}` | Format the output using the given Go template |
|
| [`--format`](#format) | `string` | `{{.Manifest}}` | Format the output using the given Go template |
|
||||||
| [`--raw`](#raw) | | | Show original, unformatted JSON manifest |
|
| [`--raw`](#raw) | | | Show original, unformatted JSON manifest |
|
||||||
@@ -72,7 +72,6 @@ unset. Following fields are available:
|
|||||||
* `.Name`: provides the reference of the image
|
* `.Name`: provides the reference of the image
|
||||||
* `.Manifest`: provides the manifest or manifest list
|
* `.Manifest`: provides the manifest or manifest list
|
||||||
* `.Image`: provides the image config
|
* `.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`
|
#### `.Name`
|
||||||
|
|
||||||
@@ -122,39 +121,6 @@ Manifests:
|
|||||||
Platform: linux/riscv64
|
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
|
#### JSON output
|
||||||
|
|
||||||
A `json` go template func is also available if you want to render fields as
|
A `json` go template func is also available if you want to render fields as
|
||||||
@@ -166,7 +132,7 @@ $ docker buildx imagetools inspect crazymax/loop --format "{{json .Manifest}}"
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
"digest": "sha256:08602e7340970e92bde5e0a2e887c1fde4d9ae753d1e05efb4c8ef3b609f97f1",
|
"digest": "sha256:a9ca35b798e0b198f9be7f3b8b53982e9a6cf96814cb10d78083f40ad8c127f1",
|
||||||
"size": 949
|
"size": 949
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -177,23 +143,23 @@ $ docker buildx imagetools inspect moby/buildkit:master --format "{{json .Manife
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"schemaVersion": 2,
|
"schemaVersion": 2,
|
||||||
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
|
"mediaType": "application/vnd.oci.image.index.v1+json",
|
||||||
"digest": "sha256:79d97f205e2799d99a3a8ae2a1ef17acb331e11784262c3faada847dc6972c52",
|
"digest": "sha256:d895e8fdcf5e2bb39acb5966f97fc4cd87a2d13d27c939c320025eb4aca5440c",
|
||||||
"size": 2010,
|
"size": 4654,
|
||||||
"manifests": [
|
"manifests": [
|
||||||
{
|
{
|
||||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||||
"digest": "sha256:bd1e78f06de26610fadf4eb9d04b1a45a545799d6342701726e952cc0c11c912",
|
"digest": "sha256:ac9dd4fbec9e36b562f910618975a2936533f8e411a3fea2858aacc0ac972e1c",
|
||||||
"size": 1158,
|
"size": 1054,
|
||||||
"platform": {
|
"platform": {
|
||||||
"architecture": "amd64",
|
"architecture": "amd64",
|
||||||
"os": "linux"
|
"os": "linux"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||||
"digest": "sha256:d37dcced63ec0965824fca644f0ac9efad8569434ec15b4c83adfcb3dcfc743b",
|
"digest": "sha256:0f4dc6797db467372cbf52c7236816203654a839f64a6542c9135d1973c9d744",
|
||||||
"size": 1158,
|
"size": 1054,
|
||||||
"platform": {
|
"platform": {
|
||||||
"architecture": "arm",
|
"architecture": "arm",
|
||||||
"os": "linux",
|
"os": "linux",
|
||||||
@@ -201,260 +167,356 @@ $ docker buildx imagetools inspect moby/buildkit:master --format "{{json .Manife
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||||
"digest": "sha256:ce142eb2255e6af46f2809e159fd03081697c7605a3de03b9cbe9a52ddb244bf",
|
"digest": "sha256:d62bb533d95afe17c4a9caf1e7c57a3b0a7a67409ccfa7af947aeb0f670ffb87",
|
||||||
"size": 1158,
|
"size": 1054,
|
||||||
"platform": {
|
"platform": {
|
||||||
"architecture": "arm64",
|
"architecture": "arm64",
|
||||||
"os": "linux"
|
"os": "linux"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||||
"digest": "sha256:f59bfb5062fff76ce464bfa4e25ebaaaac887d6818238e119d68613c456d360c",
|
"digest": "sha256:b4944057e0c68203cdcc3dceff3b2df3c7d9e3dd801724fa977b01081da7771e",
|
||||||
"size": 1158,
|
"size": 1054,
|
||||||
"platform": {
|
"platform": {
|
||||||
"architecture": "s390x",
|
"architecture": "s390x",
|
||||||
"os": "linux"
|
"os": "linux"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||||
"digest": "sha256:cc96426e0c50a78105d5637d31356db5dd6ec594f21b24276e534a32da09645c",
|
"digest": "sha256:825702a51eb4234904fc9253d8b0bf0a584787ffd8fc3fd6fa374188233ce399",
|
||||||
"size": 1159,
|
"size": 1054,
|
||||||
"platform": {
|
"platform": {
|
||||||
"architecture": "ppc64le",
|
"architecture": "ppc64le",
|
||||||
"os": "linux"
|
"os": "linux"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||||
"digest": "sha256:39f9c1e2878e6c333acb23187d6b205ce82ed934c60da326cb2c698192631478",
|
"digest": "sha256:dfb27c6acc9b9f3a7c9d47366d137089565062f43c8063c9f5e408d34c87ee4a",
|
||||||
"size": 1158,
|
"size": 1054,
|
||||||
"platform": {
|
"platform": {
|
||||||
"architecture": "riscv64",
|
"architecture": "riscv64",
|
||||||
"os": "linux"
|
"os": "linux"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||||
|
"digest": "sha256:f2fe69bccc878e658caf21dfc99eaf726fb20d28f17398c1d66a90e62cc019f9",
|
||||||
|
"size": 1113,
|
||||||
|
"annotations": {
|
||||||
|
"vnd.docker.reference.digest": "sha256:ac9dd4fbec9e36b562f910618975a2936533f8e411a3fea2858aacc0ac972e1c",
|
||||||
|
"vnd.docker.reference.type": "attestation-manifest"
|
||||||
|
},
|
||||||
|
"platform": {
|
||||||
|
"architecture": "unknown",
|
||||||
|
"os": "unknown"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||||
|
"digest": "sha256:9e112f8d4e383186f36369fba7b454e246d2e9ca5def797f1b84ede265e9f3ca",
|
||||||
|
"size": 1113,
|
||||||
|
"annotations": {
|
||||||
|
"vnd.docker.reference.digest": "sha256:0f4dc6797db467372cbf52c7236816203654a839f64a6542c9135d1973c9d744",
|
||||||
|
"vnd.docker.reference.type": "attestation-manifest"
|
||||||
|
},
|
||||||
|
"platform": {
|
||||||
|
"architecture": "unknown",
|
||||||
|
"os": "unknown"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||||
|
"digest": "sha256:09d593587f8665269ec6753eaed7fbdb09968f71587dd53e06519502cbc16775",
|
||||||
|
"size": 1113,
|
||||||
|
"annotations": {
|
||||||
|
"vnd.docker.reference.digest": "sha256:d62bb533d95afe17c4a9caf1e7c57a3b0a7a67409ccfa7af947aeb0f670ffb87",
|
||||||
|
"vnd.docker.reference.type": "attestation-manifest"
|
||||||
|
},
|
||||||
|
"platform": {
|
||||||
|
"architecture": "unknown",
|
||||||
|
"os": "unknown"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||||
|
"digest": "sha256:985a3f4544dfb042db6a8703f5f76438667dd7958aba14cb04bebe3b4cbd9307",
|
||||||
|
"size": 1113,
|
||||||
|
"annotations": {
|
||||||
|
"vnd.docker.reference.digest": "sha256:b4944057e0c68203cdcc3dceff3b2df3c7d9e3dd801724fa977b01081da7771e",
|
||||||
|
"vnd.docker.reference.type": "attestation-manifest"
|
||||||
|
},
|
||||||
|
"platform": {
|
||||||
|
"architecture": "unknown",
|
||||||
|
"os": "unknown"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||||
|
"digest": "sha256:cfccb6afeede7dc29bf8abef4815d56f2723fa482ea63c9cd519cd991c379294",
|
||||||
|
"size": 1113,
|
||||||
|
"annotations": {
|
||||||
|
"vnd.docker.reference.digest": "sha256:825702a51eb4234904fc9253d8b0bf0a584787ffd8fc3fd6fa374188233ce399",
|
||||||
|
"vnd.docker.reference.type": "attestation-manifest"
|
||||||
|
},
|
||||||
|
"platform": {
|
||||||
|
"architecture": "unknown",
|
||||||
|
"os": "unknown"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||||
|
"digest": "sha256:2e93733432c6a14cb57db33928b3a17d7ca298b3babe24d9f56dca2754dbde3b",
|
||||||
|
"size": 1113,
|
||||||
|
"annotations": {
|
||||||
|
"vnd.docker.reference.digest": "sha256:dfb27c6acc9b9f3a7c9d47366d137089565062f43c8063c9f5e408d34c87ee4a",
|
||||||
|
"vnd.docker.reference.type": "attestation-manifest"
|
||||||
|
},
|
||||||
|
"platform": {
|
||||||
|
"architecture": "unknown",
|
||||||
|
"os": "unknown"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Following command provides [SLSA](https://github.com/moby/buildkit/blob/master/docs/attestations/slsa-provenance.md) JSON output:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker buildx imagetools inspect crazymax/buildx:buildinfo --format "{{json .BuildInfo}}"
|
$ docker buildx imagetools inspect crazymax/buildkit:attest --format "{{json .Provenance}}"
|
||||||
```
|
```
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"SLSA": {
|
||||||
|
"builder": {
|
||||||
|
"id": ""
|
||||||
|
},
|
||||||
|
"buildType": "https://mobyproject.org/buildkit@v1",
|
||||||
|
"materials": [
|
||||||
|
{
|
||||||
|
"uri": "pkg:docker/docker/buildkit-syft-scanner@stable-1",
|
||||||
|
"digest": {
|
||||||
|
"sha256": "b45f1d207e16c3a3a5a10b254ad8ad358d01f7ea090d382b95c6b2ee2b3ef765"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uri": "pkg:docker/alpine@latest?platform=linux%2Famd64",
|
||||||
|
"digest": {
|
||||||
|
"sha256": "8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"invocation": {
|
||||||
|
"configSource": {},
|
||||||
|
"parameters": {
|
||||||
"frontend": "dockerfile.v0",
|
"frontend": "dockerfile.v0",
|
||||||
"attrs": {
|
"locals": [
|
||||||
"build-arg:bar": "foo",
|
|
||||||
"build-arg:foo": "bar",
|
|
||||||
"filename": "Dockerfile",
|
|
||||||
"source": "crazymax/dockerfile:buildattrs"
|
|
||||||
},
|
|
||||||
"sources": [
|
|
||||||
{
|
{
|
||||||
"type": "docker-image",
|
"name": "context"
|
||||||
"ref": "docker.io/docker/buildx-bin:0.6.1@sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0",
|
|
||||||
"pin": "sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "docker-image",
|
"name": "dockerfile"
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"platform": "linux/amd64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"buildInvocationID": "02tdha2xkbxvin87mz9drhag4",
|
||||||
|
"buildStartedOn": "2022-12-01T11:50:07.264704131Z",
|
||||||
|
"buildFinishedOn": "2022-12-01T11:50:08.243788739Z",
|
||||||
|
"reproducible": false,
|
||||||
|
"completeness": {
|
||||||
|
"parameters": true,
|
||||||
|
"environment": true,
|
||||||
|
"materials": false
|
||||||
|
},
|
||||||
|
"https://mobyproject.org/buildkit@v1#metadata": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Following command provides [SBOM](https://github.com/moby/buildkit/blob/master/docs/attestations/sbom.md) JSON output:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx imagetools inspect crazymax/buildkit:attest --format "{{json .SBOM}}"
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"SPDX": {
|
||||||
|
"SPDXID": "SPDXRef-DOCUMENT",
|
||||||
|
"creationInfo": {
|
||||||
|
"created": "2022-12-01T11:46:48.063400162Z",
|
||||||
|
"creators": [
|
||||||
|
"Tool: syft-v0.60.3",
|
||||||
|
"Tool: buildkit-1ace2bb",
|
||||||
|
"Organization: Anchore, Inc"
|
||||||
|
],
|
||||||
|
"licenseListVersion": "3.18"
|
||||||
|
},
|
||||||
|
"dataLicense": "CC0-1.0",
|
||||||
|
"documentNamespace": "https://anchore.com/syft/dir/run/src/core-0a4ccc6d-1a72-4c3a-a40e-3df1a2ffca94",
|
||||||
|
"files": [...],
|
||||||
|
"spdxVersion": "SPDX-2.2"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker buildx imagetools inspect crazymax/buildx:buildinfo --format "{{json .}}"
|
$ docker buildx imagetools inspect crazymax/buildkit:attest --format "{{json .}}"
|
||||||
```
|
```
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"name": "crazymax/buildx:buildinfo",
|
"name": "crazymax/buildkit:attest",
|
||||||
"manifest": {
|
"manifest": {
|
||||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
"schemaVersion": 2,
|
||||||
"digest": "sha256:899d2c7acbc124d406820857bb51d9089717bbe4e22b97eb4bc5789e99f09f83",
|
"mediaType": "application/vnd.oci.image.index.v1+json",
|
||||||
"size": 2628
|
"digest": "sha256:7007b387ccd52bd42a050f2e8020e56e64622c9269bf7bbe257b326fe99daf19",
|
||||||
|
"size": 855,
|
||||||
|
"manifests": [
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||||
|
"digest": "sha256:fbd10fe50b4b174bb9ea273e2eb9827fa8bf5c88edd8635a93dc83e0d1aecb55",
|
||||||
|
"size": 673,
|
||||||
|
"platform": {
|
||||||
|
"architecture": "amd64",
|
||||||
|
"os": "linux"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||||
|
"digest": "sha256:a9de632c16998489fd63fbca42a03431df00639cfb2ecb8982bf9984b83c5b2b",
|
||||||
|
"size": 839,
|
||||||
|
"annotations": {
|
||||||
|
"vnd.docker.reference.digest": "sha256:fbd10fe50b4b174bb9ea273e2eb9827fa8bf5c88edd8635a93dc83e0d1aecb55",
|
||||||
|
"vnd.docker.reference.type": "attestation-manifest"
|
||||||
|
},
|
||||||
|
"platform": {
|
||||||
|
"architecture": "unknown",
|
||||||
|
"os": "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"image": {
|
"image": {
|
||||||
"created": "2022-02-24T12:27:43.627154558Z",
|
"created": "2022-12-01T11:46:47.713777178Z",
|
||||||
"architecture": "amd64",
|
"architecture": "amd64",
|
||||||
"os": "linux",
|
"os": "linux",
|
||||||
"config": {
|
"config": {
|
||||||
"Env": [
|
"Env": [
|
||||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
"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": [
|
"Cmd": [
|
||||||
"sh"
|
"/bin/sh"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"rootfs": {
|
"rootfs": {
|
||||||
"type": "layers",
|
"type": "layers",
|
||||||
"diff_ids": [
|
"diff_ids": [
|
||||||
"sha256:7fcb75871b2101082203959c83514ac8a9f4ecfee77a0fe9aa73bbe56afdf1b4",
|
"sha256:ded7a220bb058e28ee3254fbba04ca90b679070424424761a53a043b93b612bf",
|
||||||
"sha256:d3c0b963ff5684160641f936d6a4aa14efc8ff27b6edac255c07f2d03ff92e82",
|
"sha256:d85d09ab4b4e921666ccc2db8532e857bf3476b7588e52c9c17741d7af14204f"
|
||||||
"sha256:3f8d78f13fa9b1f35d3bc3f1351d03a027c38018c37baca73f93eecdea17f244",
|
|
||||||
"sha256:8e6eb1137b182ae0c3f5d40ca46341fda2eaeeeb5fa516a9a2bf96171238e2e0",
|
|
||||||
"sha256:fde4c869a56b54dd76d7352ddaa813fd96202bda30b9dceb2c2f2ad22fa2e6ce",
|
|
||||||
"sha256:52025823edb284321af7846419899234b3c66219bf06061692b709875ed0760f",
|
|
||||||
"sha256:50adb5982dbf6126c7cf279ac3181d1e39fc9116b610b947a3dadae6f7e7c5bc",
|
|
||||||
"sha256:9801c319e1c66c5d295e78b2d3e80547e73c7e3c63a4b71e97c8ca357224af24",
|
|
||||||
"sha256:dfbfac44d5d228c49b42194c8a2f470abd6916d072f612a6fb14318e94fde8ae",
|
|
||||||
"sha256:3dfb74e19dedf61568b917c19b0fd3ee4580870027ca0b6054baf239855d1322",
|
|
||||||
"sha256:b182e707c23e4f19be73f9022a99d2d1ca7bf1ca8f280d40e4d1c10a6f51550e"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"history": [
|
"history": [
|
||||||
{
|
{
|
||||||
"created": "2021-11-12T17:19:58.698676655Z",
|
"created": "2022-11-22T22:19:28.870801855Z",
|
||||||
"created_by": "/bin/sh -c #(nop) ADD file:5a707b9d6cb5fff532e4c2141bc35707593f21da5528c9e71ae2ddb6ba4a4eb6 in / "
|
"created_by": "/bin/sh -c #(nop) ADD file:587cae71969871d3c6456d844a8795df9b64b12c710c275295a1182b46f630e7 in / "
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"created": "2021-11-12T17:19:58.948920855Z",
|
"created": "2022-11-22T22:19:29.008562326Z",
|
||||||
"created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]",
|
"created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]",
|
||||||
"empty_layer": true
|
"empty_layer": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"created": "2022-02-24T12:27:38.285594601Z",
|
"created": "2022-12-01T11:46:47.713777178Z",
|
||||||
"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",
|
"created_by": "RUN /bin/sh -c apk add curl # buildkit",
|
||||||
"comment": "buildkit.dockerfile.v0"
|
"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": {
|
"Provenance": {
|
||||||
|
"SLSA": {
|
||||||
|
"builder": {
|
||||||
|
"id": ""
|
||||||
|
},
|
||||||
|
"buildType": "https://mobyproject.org/buildkit@v1",
|
||||||
|
"materials": [
|
||||||
|
{
|
||||||
|
"uri": "pkg:docker/docker/buildkit-syft-scanner@stable-1",
|
||||||
|
"digest": {
|
||||||
|
"sha256": "b45f1d207e16c3a3a5a10b254ad8ad358d01f7ea090d382b95c6b2ee2b3ef765"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uri": "pkg:docker/alpine@latest?platform=linux%2Famd64",
|
||||||
|
"digest": {
|
||||||
|
"sha256": "8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"invocation": {
|
||||||
|
"configSource": {},
|
||||||
|
"parameters": {
|
||||||
"frontend": "dockerfile.v0",
|
"frontend": "dockerfile.v0",
|
||||||
"attrs": {
|
"locals": [
|
||||||
"build-arg:bar": "foo",
|
|
||||||
"build-arg:foo": "bar",
|
|
||||||
"filename": "Dockerfile",
|
|
||||||
"source": "docker/dockerfile-upstream:master-labs"
|
|
||||||
},
|
|
||||||
"sources": [
|
|
||||||
{
|
{
|
||||||
"type": "docker-image",
|
"name": "context"
|
||||||
"ref": "docker.io/docker/buildx-bin:0.6.1@sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0",
|
|
||||||
"pin": "sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "docker-image",
|
"name": "dockerfile"
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"platform": "linux/amd64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"buildInvocationID": "02tdha2xkbxvin87mz9drhag4",
|
||||||
|
"buildStartedOn": "2022-12-01T11:50:07.264704131Z",
|
||||||
|
"buildFinishedOn": "2022-12-01T11:50:08.243788739Z",
|
||||||
|
"reproducible": false,
|
||||||
|
"completeness": {
|
||||||
|
"parameters": true,
|
||||||
|
"environment": true,
|
||||||
|
"materials": false
|
||||||
|
},
|
||||||
|
"https://mobyproject.org/buildkit@v1#metadata": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SBOM": {
|
||||||
|
"SPDX": {
|
||||||
|
"SPDXID": "SPDXRef-DOCUMENT",
|
||||||
|
"creationInfo": {
|
||||||
|
"created": "2022-12-01T11:46:48.063400162Z",
|
||||||
|
"creators": [
|
||||||
|
"Tool: syft-v0.60.3",
|
||||||
|
"Tool: buildkit-1ace2bb",
|
||||||
|
"Organization: Anchore, Inc"
|
||||||
|
],
|
||||||
|
"licenseListVersion": "3.18"
|
||||||
|
},
|
||||||
|
"dataLicense": "CC0-1.0",
|
||||||
|
"documentNamespace": "https://anchore.com/syft/dir/run/src/core-0a4ccc6d-1a72-4c3a-a40e-3df1a2ffca94",
|
||||||
|
"files": [...],
|
||||||
|
"spdxVersion": "SPDX-2.2"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Multi-platform
|
#### Multi-platform
|
||||||
|
|
||||||
Multi-platform images are supported for `.Image` and `.BuildInfo` fields. If
|
Multi-platform images are supported for `.Image`, `.SLSA` and `.SBOM` fields.
|
||||||
you want to pick up a specific platform, you can specify it using the `index`
|
If you want to pick up a specific platform, you can specify it using the `index`
|
||||||
go template function:
|
go template function:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
@@ -462,7 +524,7 @@ $ docker buildx imagetools inspect --format '{{json (index .Image "linux/s390x")
|
|||||||
```
|
```
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"created": "2022-02-25T17:13:27.89891722Z",
|
"created": "2022-11-30T17:42:26.414957336Z",
|
||||||
"architecture": "s390x",
|
"architecture": "s390x",
|
||||||
"os": "linux",
|
"os": "linux",
|
||||||
"config": {
|
"config": {
|
||||||
@@ -481,8 +543,8 @@ $ docker buildx imagetools inspect --format '{{json (index .Image "linux/s390x")
|
|||||||
"diff_ids": [
|
"diff_ids": [
|
||||||
"sha256:41048e32d0684349141cf05f629c5fc3c5915d1f3426b66dbb8953a540e01e1e",
|
"sha256:41048e32d0684349141cf05f629c5fc3c5915d1f3426b66dbb8953a540e01e1e",
|
||||||
"sha256:2651209b9208fff6c053bc3c17353cb07874e50f1a9bc96d6afd03aef63de76a",
|
"sha256:2651209b9208fff6c053bc3c17353cb07874e50f1a9bc96d6afd03aef63de76a",
|
||||||
"sha256:6741ed7e73039d853fa8902246a4c7e8bf9dd09652fd1b08251bc5f9e8876a7f",
|
"sha256:88577322e65f094ce8ac27435880f1a8a9baadb569258026bb141770451bafcb",
|
||||||
"sha256:92ac046adeeb65c86ae3f0b458dee04ad4a462e417661c04d77642c66494f69b"
|
"sha256:de8f9a790e4ed10ff1f1f8ea923c9da4f97246a7e200add2dc6650eba3f10a20"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"history": [
|
"history": [
|
||||||
@@ -501,23 +563,23 @@ $ docker buildx imagetools inspect --format '{{json (index .Image "linux/s390x")
|
|||||||
"comment": "buildkit.dockerfile.v0"
|
"comment": "buildkit.dockerfile.v0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"created": "2022-02-24T00:34:00.924540012Z",
|
"created": "2022-08-25T00:39:25.652811078Z",
|
||||||
"created_by": "COPY examples/buildctl-daemonless/buildctl-daemonless.sh /usr/bin/ # buildkit",
|
"created_by": "COPY examples/buildctl-daemonless/buildctl-daemonless.sh /usr/bin/ # buildkit",
|
||||||
"comment": "buildkit.dockerfile.v0"
|
"comment": "buildkit.dockerfile.v0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"created": "2022-02-25T17:13:27.89891722Z",
|
"created": "2022-11-30T17:42:26.414957336Z",
|
||||||
"created_by": "VOLUME [/var/lib/buildkit]",
|
"created_by": "VOLUME [/var/lib/buildkit]",
|
||||||
"comment": "buildkit.dockerfile.v0",
|
"comment": "buildkit.dockerfile.v0",
|
||||||
"empty_layer": true
|
"empty_layer": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"created": "2022-02-25T17:13:27.89891722Z",
|
"created": "2022-11-30T17:42:26.414957336Z",
|
||||||
"created_by": "COPY / /usr/bin/ # buildkit",
|
"created_by": "COPY / /usr/bin/ # buildkit",
|
||||||
"comment": "buildkit.dockerfile.v0"
|
"comment": "buildkit.dockerfile.v0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"created": "2022-02-25T17:13:27.89891722Z",
|
"created": "2022-11-30T17:42:26.414957336Z",
|
||||||
"created_by": "ENTRYPOINT [\"buildkitd\"]",
|
"created_by": "ENTRYPOINT [\"buildkitd\"]",
|
||||||
"comment": "buildkit.dockerfile.v0",
|
"comment": "buildkit.dockerfile.v0",
|
||||||
"empty_layer": true
|
"empty_layer": true
|
||||||
@@ -541,24 +603,24 @@ $ docker buildx imagetools inspect --raw crazymax/loop | jq
|
|||||||
"schemaVersion": 2,
|
"schemaVersion": 2,
|
||||||
"config": {
|
"config": {
|
||||||
"mediaType": "application/vnd.docker.container.image.v1+json",
|
"mediaType": "application/vnd.docker.container.image.v1+json",
|
||||||
"digest": "sha256:7ace7d324e79b360b2db8b820d83081863d96d22e734cdf297a8e7fd83f6ceb3",
|
"digest": "sha256:a98999183d2c7a8845f6d56496e51099ce6e4359ee7255504174b05430c4b78b",
|
||||||
"size": 2298
|
"size": 2762
|
||||||
},
|
},
|
||||||
"layers": [
|
"layers": [
|
||||||
{
|
{
|
||||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||||
"digest": "sha256:5843afab387455b37944e709ee8c78d7520df80f8d01cf7f861aae63beeddb6b",
|
"digest": "sha256:8663204ce13b2961da55026a2034abb9e5afaaccf6a9cfb44ad71406dcd07c7b",
|
||||||
"size": 2811478
|
"size": 2818370
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||||
"digest": "sha256:726d3732a87e1c430d67e8969de6b222a889d45e045ebae1a008a37ba38f3b1f",
|
"digest": "sha256:f0868a92f8e1e5018ed4e60eb845ed4ff0e2229897f4105e5a4735c1d6fd874f",
|
||||||
"size": 1776812
|
"size": 1821402
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||||
"digest": "sha256:5d7cf9b33148a8f220c84f27dd2cfae46aca019a3ea3fbf7274f6d6dbfae8f3b",
|
"digest": "sha256:d010066dbdfcf7c12fca30cd4b567aa7218eb6762ab53169d043655b7a8d7f2e",
|
||||||
"size": 382855
|
"size": 404457
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -574,7 +636,7 @@ $ docker buildx imagetools inspect --raw moby/buildkit:master | jq
|
|||||||
"manifests": [
|
"manifests": [
|
||||||
{
|
{
|
||||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
"digest": "sha256:667d28c9fb33820ce686887a717a148e89fa77f9097f9352996bbcce99d352b1",
|
"digest": "sha256:f9f41c85124686c2afe330a985066748a91d7a5d505777fe274df804ab5e077e",
|
||||||
"size": 1158,
|
"size": 1158,
|
||||||
"platform": {
|
"platform": {
|
||||||
"architecture": "amd64",
|
"architecture": "amd64",
|
||||||
@@ -583,7 +645,7 @@ $ docker buildx imagetools inspect --raw moby/buildkit:master | jq
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
"digest": "sha256:71789527b64ab3d7b3de01d364b449cd7f7a3da758218fbf73b9c9aae05a6775",
|
"digest": "sha256:82097c2be19c617aafb3c3e43c88548738d4b2bf3db5c36666283a918b390266",
|
||||||
"size": 1158,
|
"size": 1158,
|
||||||
"platform": {
|
"platform": {
|
||||||
"architecture": "arm",
|
"architecture": "arm",
|
||||||
@@ -593,7 +655,7 @@ $ docker buildx imagetools inspect --raw moby/buildkit:master | jq
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
"digest": "sha256:fb64667e1ce6ab0d05478f3a8402af07b27737598dcf9a510fb1d792b13a66be",
|
"digest": "sha256:b6b91e6c823d7220ded7d3b688e571ba800b13d91bbc904c1d8053593e3ee42c",
|
||||||
"size": 1158,
|
"size": 1158,
|
||||||
"platform": {
|
"platform": {
|
||||||
"architecture": "arm64",
|
"architecture": "arm64",
|
||||||
@@ -602,7 +664,7 @@ $ docker buildx imagetools inspect --raw moby/buildkit:master | jq
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
"digest": "sha256:1c3ddf95a0788e23f72f25800c05abc4458946685e2b66788c3d978cde6da92b",
|
"digest": "sha256:797061bcc16778de048b96f769c018ec24da221088050bbe926ea3b8d51d77e8",
|
||||||
"size": 1158,
|
"size": 1158,
|
||||||
"platform": {
|
"platform": {
|
||||||
"architecture": "s390x",
|
"architecture": "s390x",
|
||||||
@@ -611,7 +673,7 @@ $ docker buildx imagetools inspect --raw moby/buildkit:master | jq
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
"digest": "sha256:05bcde6d460a284e5bc88026cd070277e8380355de3126cbc8fe8a452708c6b1",
|
"digest": "sha256:b93d3a84d18c4d0b8c279e77343d854d9b5177df7ea55cf468d461aa2523364e",
|
||||||
"size": 1159,
|
"size": 1159,
|
||||||
"platform": {
|
"platform": {
|
||||||
"architecture": "ppc64le",
|
"architecture": "ppc64le",
|
||||||
@@ -620,7 +682,7 @@ $ docker buildx imagetools inspect --raw moby/buildkit:master | jq
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
"digest": "sha256:c04c57765304ab84f4f9807fff3e11605c3a60e16435c734b02c723680f6bd6e",
|
"digest": "sha256:d5c950dd1b270d437c838187112a0cb44c9258248d7a3a8bcb42fae8f717dc01",
|
||||||
"size": 1158,
|
"size": 1158,
|
||||||
"platform": {
|
"platform": {
|
||||||
"architecture": "riscv64",
|
"architecture": "riscv64",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Inspect current builder instance
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
|:----------------------------|:---------|:--------|:--------------------------------------------|
|
||||||
| [`--bootstrap`](#bootstrap) | | | Ensure builder has booted before inspecting |
|
| [`--bootstrap`](#bootstrap) | | | Ensure builder has booted before inspecting |
|
||||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
|
|
||||||
@@ -57,10 +57,12 @@ Nodes:
|
|||||||
Name: elated_tesla0
|
Name: elated_tesla0
|
||||||
Endpoint: unix:///var/run/docker.sock
|
Endpoint: unix:///var/run/docker.sock
|
||||||
Status: running
|
Status: running
|
||||||
|
Buildkit: v0.10.3
|
||||||
Platforms: linux/amd64
|
Platforms: linux/amd64
|
||||||
|
|
||||||
Name: elated_tesla1
|
Name: elated_tesla1
|
||||||
Endpoint: ssh://ubuntu@1.2.3.4
|
Endpoint: ssh://ubuntu@1.2.3.4
|
||||||
Status: running
|
Status: running
|
||||||
|
Buildkit: v0.10.3
|
||||||
Platforms: linux/arm64*, linux/arm/v7, linux/arm/v6
|
Platforms: linux/arm64*, linux/arm/v7, linux/arm/v6
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ Remove build cache
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
|:------------------------|:---------|:--------|:------------------------------------------|
|
||||||
| `-a`, `--all` | | | Remove all unused images, not just dangling ones |
|
| `-a`, `--all` | | | Include internal/frontend images |
|
||||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
| `--filter` | `filter` | | Provide filter values (e.g., `until=24h`) |
|
| `--filter` | `filter` | | Provide filter values (e.g., `until=24h`) |
|
||||||
| `-f`, `--force` | | | Do not prompt for confirmation |
|
| `-f`, `--force` | | | Do not prompt for confirmation |
|
||||||
@@ -21,6 +21,26 @@ Remove build cache
|
|||||||
|
|
||||||
<!---MARKER_GEN_END-->
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Clears the build cache of the selected builder.
|
||||||
|
|
||||||
|
You can finely control what cache data is kept using:
|
||||||
|
|
||||||
|
- The `--filter=until=<duration>` flag to keep images that have been used in
|
||||||
|
the last `<duration>` time.
|
||||||
|
|
||||||
|
`<duration>` is a duration string, e.g. `24h` or `2h30m`, with allowable
|
||||||
|
units of `(h)ours`, `(m)inutes` and `(s)econds`.
|
||||||
|
|
||||||
|
- The `--keep-storage=<size>` flag to keep `<size>` bytes of data in the cache.
|
||||||
|
|
||||||
|
`<size>` is a human-readable memory string, e.g. `128mb`, `2gb`, etc. Units
|
||||||
|
are case-insensitive.
|
||||||
|
|
||||||
|
- The `--all` flag to allow clearing internal helper images and frontend images
|
||||||
|
set using the `#syntax=` directive or the `BUILDKIT_SYNTAX` build argument.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### <a name="builder"></a> Override the configured builder instance (--builder)
|
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Remove a builder instance
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
|:------------------------------------|:---------|:--------|:-----------------------------------------|
|
||||||
| [`--all-inactive`](#all-inactive) | | | Remove all inactive builders |
|
| [`--all-inactive`](#all-inactive) | | | Remove all inactive builders |
|
||||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
| [`-f`](#force), [`--force`](#force) | | | Do not prompt for confirmation |
|
| [`-f`](#force), [`--force`](#force) | | | Do not prompt for confirmation |
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Stop builder instance
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
|:------------------------|:---------|:--------|:-----------------------------------------|
|
||||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Set the current builder instance
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
|:------------------------|:---------|:--------|:-------------------------------------------|
|
||||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
| `--default` | | | Set builder as default for current context |
|
| `--default` | | | Set builder as default for current context |
|
||||||
| `--global` | | | Builder persists context changes |
|
| `--global` | | | Builder persists context changes |
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
"github.com/docker/buildx/util/confutil"
|
"github.com/docker/buildx/util/confutil"
|
||||||
"github.com/docker/buildx/util/imagetools"
|
"github.com/docker/buildx/util/imagetools"
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
dockertypes "github.com/docker/docker/api/types"
|
dockertypes "github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/mount"
|
"github.com/docker/docker/api/types/mount"
|
||||||
@@ -37,7 +36,6 @@ const (
|
|||||||
type Driver struct {
|
type Driver struct {
|
||||||
driver.InitConfig
|
driver.InitConfig
|
||||||
factory driver.Factory
|
factory driver.Factory
|
||||||
userNSRemap bool // true if dockerd is running with userns-remap mode
|
|
||||||
netMode string
|
netMode string
|
||||||
image string
|
image string
|
||||||
cgroupParent string
|
cgroupParent string
|
||||||
@@ -84,7 +82,7 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rc, err := d.DockerAPI.ImageCreate(ctx, imageName, types.ImageCreateOptions{
|
rc, err := d.DockerAPI.ImageCreate(ctx, imageName, dockertypes.ImageCreateOptions{
|
||||||
RegistryAuth: ra,
|
RegistryAuth: ra,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -121,13 +119,11 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if d.userNSRemap {
|
|
||||||
hc.UsernsMode = "host"
|
|
||||||
}
|
|
||||||
if d.netMode != "" {
|
if d.netMode != "" {
|
||||||
hc.NetworkMode = container.NetworkMode(d.netMode)
|
hc.NetworkMode = container.NetworkMode(d.netMode)
|
||||||
}
|
}
|
||||||
if info, err := d.DockerAPI.Info(ctx); err == nil && info.CgroupDriver == "cgroupfs" {
|
if info, err := d.DockerAPI.Info(ctx); err == nil {
|
||||||
|
if info.CgroupDriver == "cgroupfs" {
|
||||||
// Place all buildkit containers inside this cgroup by default so limits can be attached
|
// Place all buildkit containers inside this cgroup by default so limits can be attached
|
||||||
// to all build activity on the host.
|
// to all build activity on the host.
|
||||||
hc.CgroupParent = "/docker/buildx"
|
hc.CgroupParent = "/docker/buildx"
|
||||||
@@ -135,6 +131,19 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
|
|||||||
hc.CgroupParent = d.cgroupParent
|
hc.CgroupParent = d.cgroupParent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
secOpts, err := dockertypes.DecodeSecurityOptions(info.SecurityOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, f := range secOpts {
|
||||||
|
if f.Name == "userns" {
|
||||||
|
hc.UsernsMode = "host"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
_, err := d.DockerAPI.ContainerCreate(ctx, cfg, hc, &network.NetworkingConfig{}, nil, d.Name)
|
_, err := d.DockerAPI.ContainerCreate(ctx, cfg, hc, &network.NetworkingConfig{}, nil, d.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -186,7 +195,7 @@ func (d *Driver) wait(ctx context.Context, l progress.SubLogger) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) copyLogs(ctx context.Context, l progress.SubLogger) error {
|
func (d *Driver) copyLogs(ctx context.Context, l progress.SubLogger) error {
|
||||||
rc, err := d.DockerAPI.ContainerLogs(ctx, d.Name, types.ContainerLogsOptions{
|
rc, err := d.DockerAPI.ContainerLogs(ctx, d.Name, dockertypes.ContainerLogsOptions{
|
||||||
ShowStdout: true, ShowStderr: true,
|
ShowStdout: true, ShowStderr: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -219,7 +228,7 @@ func (d *Driver) copyToContainer(ctx context.Context, files map[string][]byte) e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) exec(ctx context.Context, cmd []string) (string, net.Conn, error) {
|
func (d *Driver) exec(ctx context.Context, cmd []string) (string, net.Conn, error) {
|
||||||
execConfig := types.ExecConfig{
|
execConfig := dockertypes.ExecConfig{
|
||||||
Cmd: cmd,
|
Cmd: cmd,
|
||||||
AttachStdin: true,
|
AttachStdin: true,
|
||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
@@ -235,7 +244,7 @@ func (d *Driver) exec(ctx context.Context, cmd []string) (string, net.Conn, erro
|
|||||||
return "", nil, errors.New("exec ID empty")
|
return "", nil, errors.New("exec ID empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := d.DockerAPI.ContainerExecAttach(ctx, execID, types.ExecStartCheck{})
|
resp, err := d.DockerAPI.ContainerExecAttach(ctx, execID, dockertypes.ExecStartCheck{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
@@ -262,7 +271,7 @@ func (d *Driver) run(ctx context.Context, cmd []string, stdout, stderr io.Writer
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) start(ctx context.Context, l progress.SubLogger) error {
|
func (d *Driver) start(ctx context.Context, l progress.SubLogger) error {
|
||||||
return d.DockerAPI.ContainerStart(ctx, d.Name, types.ContainerStartOptions{})
|
return d.DockerAPI.ContainerStart(ctx, d.Name, dockertypes.ContainerStartOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
|
func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
|
||||||
@@ -357,11 +366,14 @@ func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
td, _ := exp.(client.TracerDelegate)
|
var opts []client.ClientOpt
|
||||||
|
opts = append(opts, client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
||||||
return client.New(ctx, "", client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}), client.WithTracerDelegate(td))
|
}))
|
||||||
|
if td, ok := exp.(client.TracerDelegate); ok {
|
||||||
|
opts = append(opts, client.WithTracerDelegate(td))
|
||||||
|
}
|
||||||
|
return client.New(ctx, "", opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Factory() driver.Factory {
|
func (d *Driver) Factory() driver.Factory {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
dockertypes "github.com/docker/docker/api/types"
|
|
||||||
dockerclient "github.com/docker/docker/client"
|
dockerclient "github.com/docker/docker/client"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
@@ -41,20 +40,6 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
|
|||||||
return nil, errors.Errorf("%s driver requires docker API access", f.Name())
|
return nil, errors.Errorf("%s driver requires docker API access", f.Name())
|
||||||
}
|
}
|
||||||
d := &Driver{factory: f, InitConfig: cfg}
|
d := &Driver{factory: f, InitConfig: cfg}
|
||||||
dockerInfo, err := cfg.DockerAPI.Info(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
secOpts, err := dockertypes.DecodeSecurityOptions(dockerInfo.SecurityOptions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, f := range secOpts {
|
|
||||||
if f.Name == "userns" {
|
|
||||||
d.userNSRemap = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for k, v := range cfg.DriverOpts {
|
for k, v := range cfg.DriverOpts {
|
||||||
switch {
|
switch {
|
||||||
case k == "network":
|
case k == "network":
|
||||||
|
|||||||
@@ -54,11 +54,22 @@ func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Features() map[driver.Feature]bool {
|
func (d *Driver) Features() map[driver.Feature]bool {
|
||||||
|
var useContainerdSnapshotter bool
|
||||||
|
ctx := context.Background()
|
||||||
|
c, err := d.Client(ctx)
|
||||||
|
if err == nil {
|
||||||
|
workers, _ := c.ListWorkers(ctx)
|
||||||
|
for _, w := range workers {
|
||||||
|
if _, ok := w.Labels["org.mobyproject.buildkit.worker.snapshotter"]; ok {
|
||||||
|
useContainerdSnapshotter = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return map[driver.Feature]bool{
|
return map[driver.Feature]bool{
|
||||||
driver.OCIExporter: false,
|
driver.OCIExporter: useContainerdSnapshotter,
|
||||||
driver.DockerExporter: false,
|
driver.DockerExporter: useContainerdSnapshotter,
|
||||||
driver.CacheExport: false,
|
driver.CacheExport: useContainerdSnapshotter,
|
||||||
driver.MultiPlatform: false,
|
driver.MultiPlatform: useContainerdSnapshotter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/context"
|
"github.com/docker/cli/cli/context"
|
||||||
@@ -153,3 +155,20 @@ func NewKubernetesConfig(configPath string) clientcmd.ClientConfig {
|
|||||||
&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeConfig},
|
&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeConfig},
|
||||||
&clientcmd.ConfigOverrides{})
|
&clientcmd.ConfigOverrides{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConfigFromEndpoint loads kubernetes config from endpoint
|
||||||
|
func ConfigFromEndpoint(endpointName string, s store.Reader) (clientcmd.ClientConfig, error) {
|
||||||
|
if strings.HasPrefix(endpointName, "kubernetes://") {
|
||||||
|
u, _ := url.Parse(endpointName)
|
||||||
|
if kubeconfig := u.Query().Get("kubeconfig"); kubeconfig != "" {
|
||||||
|
_ = os.Setenv(clientcmd.RecommendedConfigPathEnvVar, kubeconfig)
|
||||||
|
}
|
||||||
|
rules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||||
|
apiConfig, err := rules.Load()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return clientcmd.NewDefaultClientConfig(*apiConfig, &clientcmd.ConfigOverrides{}), nil
|
||||||
|
}
|
||||||
|
return ConfigFromContext(endpointName, s)
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,18 +5,15 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/config/configfile"
|
|
||||||
cliflags "github.com/docker/cli/cli/flags"
|
cliflags "github.com/docker/cli/cli/flags"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDefaultContextInitializer(t *testing.T) {
|
func TestDefaultContextInitializer(t *testing.T) {
|
||||||
cli, err := command.NewDockerCli()
|
|
||||||
require.NoError(t, err)
|
|
||||||
os.Setenv("KUBECONFIG", "./fixtures/test-kubeconfig")
|
os.Setenv("KUBECONFIG", "./fixtures/test-kubeconfig")
|
||||||
defer os.Unsetenv("KUBECONFIG")
|
defer os.Unsetenv("KUBECONFIG")
|
||||||
ctx, err := command.ResolveDefaultContext(&cliflags.CommonOptions{}, &configfile.ConfigFile{}, command.DefaultContextStoreConfig(), cli.Err())
|
ctx, err := command.ResolveDefaultContext(&cliflags.ClientOptions{}, command.DefaultContextStoreConfig())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "default", ctx.Meta.Name)
|
assert.Equal(t, "default", ctx.Meta.Name)
|
||||||
assert.Equal(t, "zoinx", ctx.Meta.Endpoints[KubernetesEndpoint].(EndpointMeta).DefaultNamespace)
|
assert.Equal(t, "zoinx", ctx.Meta.Endpoints[KubernetesEndpoint].(EndpointMeta).DefaultNamespace)
|
||||||
|
|||||||
@@ -215,11 +215,14 @@ func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
td, _ := exp.(client.TracerDelegate)
|
var opts []client.ClientOpt
|
||||||
|
opts = append(opts, client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
||||||
return client.New(ctx, "", client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}), client.WithTracerDelegate(td))
|
}))
|
||||||
|
if td, ok := exp.(client.TracerDelegate); ok {
|
||||||
|
opts = append(opts, client.WithTracerDelegate(td))
|
||||||
|
}
|
||||||
|
return client.New(ctx, "", opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Factory() driver.Factory {
|
func (d *Driver) Factory() driver.Factory {
|
||||||
|
|||||||
@@ -213,6 +213,24 @@ func toRootless(d *appsv1.Deployment) error {
|
|||||||
d.Spec.Template.ObjectMeta.Annotations = make(map[string]string, 1)
|
d.Spec.Template.ObjectMeta.Annotations = make(map[string]string, 1)
|
||||||
}
|
}
|
||||||
d.Spec.Template.ObjectMeta.Annotations["container.apparmor.security.beta.kubernetes.io/"+containerName] = "unconfined"
|
d.Spec.Template.ObjectMeta.Annotations["container.apparmor.security.beta.kubernetes.io/"+containerName] = "unconfined"
|
||||||
|
|
||||||
|
// Dockerfile has `VOLUME /home/user/.local/share/buildkit` by default too,
|
||||||
|
// but the default VOLUME does not work with rootless on Google's Container-Optimized OS
|
||||||
|
// as it is mounted with `nosuid,nodev`.
|
||||||
|
// https://github.com/moby/buildkit/issues/879#issuecomment-1240347038
|
||||||
|
// https://github.com/moby/buildkit/pull/3097
|
||||||
|
const emptyDirVolName = "buildkitd"
|
||||||
|
d.Spec.Template.Spec.Containers[0].VolumeMounts = append(d.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
|
||||||
|
Name: emptyDirVolName,
|
||||||
|
MountPath: "/home/user/.local/share/buildkit",
|
||||||
|
})
|
||||||
|
d.Spec.Template.Spec.Volumes = append(d.Spec.Template.Spec.Volumes, corev1.Volume{
|
||||||
|
Name: emptyDirVolName,
|
||||||
|
VolumeSource: corev1.VolumeSource{
|
||||||
|
EmptyDir: &corev1.EmptyDirVolumeSource{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,16 +92,16 @@ func GetDefaultFactory(ctx context.Context, ep string, c dockerclient.APIClient,
|
|||||||
return dd[0].f, nil
|
return dd[0].f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFactory(name string, instanceRequired bool) Factory {
|
func GetFactory(name string, instanceRequired bool) (Factory, error) {
|
||||||
for _, f := range drivers {
|
for _, f := range drivers {
|
||||||
if instanceRequired && !f.AllowsInstances() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if f.Name() == name {
|
if f.Name() == name {
|
||||||
return f
|
if instanceRequired && !f.AllowsInstances() {
|
||||||
|
return nil, errors.Errorf("additional instances of driver %q cannot be created", name)
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil, errors.Errorf("failed to find driver %q", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDriver(ctx context.Context, name string, f Factory, endpointAddr string, api dockerclient.APIClient, auth Auth, kcc KubeClientConfig, flags []string, files map[string][]byte, do map[string]string, platforms []specs.Platform, contextPathHash string) (Driver, error) {
|
func GetDriver(ctx context.Context, name string, f Factory, endpointAddr string, api dockerclient.APIClient, auth Auth, kcc KubeClientConfig, flags []string, files map[string][]byte, do map[string]string, platforms []specs.Platform, contextPathHash string) (Driver, error) {
|
||||||
@@ -131,9 +131,12 @@ func GetDriver(ctx context.Context, name string, f Factory, endpointAddr string,
|
|||||||
return &cachedDriver{Driver: d}, nil
|
return &cachedDriver{Driver: d}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFactories() []Factory {
|
func GetFactories(instanceRequired bool) []Factory {
|
||||||
ds := make([]Factory, 0, len(drivers))
|
ds := make([]Factory, 0, len(drivers))
|
||||||
for _, d := range drivers {
|
for _, d := range drivers {
|
||||||
|
if instanceRequired && !d.AllowsInstances() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
ds = append(ds, d)
|
ds = append(ds, d)
|
||||||
}
|
}
|
||||||
sort.Slice(ds, func(i, j int) bool {
|
sort.Slice(ds, func(i, j int) bool {
|
||||||
|
|||||||
98
go.mod
98
go.mod
@@ -3,35 +3,37 @@ module github.com/docker/buildx
|
|||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/compose-spec/compose-go v1.3.0
|
github.com/aws/aws-sdk-go-v2/config v1.15.5
|
||||||
|
github.com/compose-spec/compose-go v1.6.0
|
||||||
github.com/containerd/console v1.0.3
|
github.com/containerd/console v1.0.3
|
||||||
github.com/containerd/containerd v1.6.6
|
github.com/containerd/containerd v1.6.20
|
||||||
github.com/docker/cli v20.10.17+incompatible // v22.06.x - see "replace" for the actual version
|
github.com/docker/cli v23.0.0-rc.1+incompatible
|
||||||
github.com/docker/cli-docs-tool v0.5.0
|
github.com/docker/cli-docs-tool v0.5.1
|
||||||
github.com/docker/distribution v2.8.1+incompatible
|
github.com/docker/distribution v2.8.1+incompatible
|
||||||
github.com/docker/docker v20.10.17+incompatible // v22.06.x - see "replace" for the actual version
|
github.com/docker/docker v23.0.0-rc.1+incompatible
|
||||||
github.com/docker/go-units v0.4.0
|
github.com/docker/go-units v0.5.0
|
||||||
github.com/gofrs/flock v0.7.3
|
github.com/gofrs/flock v0.8.1
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840
|
github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840
|
||||||
github.com/hashicorp/hcl/v2 v2.8.2
|
github.com/hashicorp/hcl/v2 v2.8.2
|
||||||
github.com/moby/buildkit v0.10.1-0.20220721175135-c75998aec3d4
|
github.com/moby/buildkit v0.11.7-0.20230519102302-348e79dfed17
|
||||||
|
github.com/moby/sys/mountinfo v0.6.2
|
||||||
github.com/morikuni/aec v1.0.0
|
github.com/morikuni/aec v1.0.0
|
||||||
github.com/opencontainers/go-digest v1.0.0
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799
|
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b
|
||||||
github.com/pelletier/go-toml v1.9.4
|
github.com/pelletier/go-toml v1.9.5
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002
|
github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002
|
||||||
github.com/sirupsen/logrus v1.9.0
|
github.com/sirupsen/logrus v1.9.0
|
||||||
github.com/spf13/cobra v1.5.0
|
github.com/spf13/cobra v1.6.1
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.8.0
|
github.com/stretchr/testify v1.8.1
|
||||||
github.com/zclconf/go-cty v1.10.0
|
github.com/zclconf/go-cty v1.10.0
|
||||||
go.opentelemetry.io/otel v1.4.1
|
go.opentelemetry.io/otel v1.4.1
|
||||||
go.opentelemetry.io/otel/trace v1.4.1
|
go.opentelemetry.io/otel/trace v1.4.1
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
|
golang.org/x/sync v0.1.0
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
golang.org/x/term v0.3.0
|
||||||
google.golang.org/grpc v1.45.0
|
google.golang.org/grpc v1.50.1
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
k8s.io/api v0.23.5
|
k8s.io/api v0.23.5
|
||||||
k8s.io/apimachinery v0.23.5
|
k8s.io/apimachinery v0.23.5
|
||||||
@@ -39,7 +41,8 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.81.0 // indirect
|
cloud.google.com/go/compute v1.12.1 // indirect
|
||||||
|
cloud.google.com/go/compute/metadata v0.2.1 // indirect
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||||
github.com/Azure/go-autorest/autorest v0.11.24 // indirect
|
github.com/Azure/go-autorest/autorest v0.11.24 // indirect
|
||||||
@@ -53,6 +56,16 @@ require (
|
|||||||
github.com/apparentlymart/go-cidr v1.0.1 // indirect
|
github.com/apparentlymart/go-cidr v1.0.1 // indirect
|
||||||
github.com/apparentlymart/go-textseg/v12 v12.0.0 // indirect
|
github.com/apparentlymart/go-textseg/v12 v12.0.0 // indirect
|
||||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.16.3 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.12.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.10 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.11.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.16.4 // indirect
|
||||||
|
github.com/aws/smithy-go v1.11.2 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bugsnag/bugsnag-go v1.4.1 // indirect
|
github.com/bugsnag/bugsnag-go v1.4.1 // indirect
|
||||||
github.com/bugsnag/panicwrap v1.2.0 // indirect
|
github.com/bugsnag/panicwrap v1.2.0 // indirect
|
||||||
@@ -61,11 +74,11 @@ require (
|
|||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||||
github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e // indirect
|
github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e // indirect
|
||||||
github.com/containerd/continuity v0.3.0 // indirect
|
github.com/containerd/continuity v0.3.0 // indirect
|
||||||
github.com/containerd/ttrpc v1.1.0 // indirect
|
github.com/containerd/ttrpc v1.1.1 // indirect
|
||||||
github.com/containerd/typeurl v1.0.2 // indirect
|
github.com/containerd/typeurl v1.0.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/distribution/distribution/v3 v3.0.0-20220725133111-4bf3547399eb // indirect
|
github.com/distribution/distribution/v3 v3.0.0-20220725133111-4bf3547399eb // indirect
|
||||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
github.com/docker/go-metrics v0.0.1 // indirect
|
github.com/docker/go-metrics v0.0.1 // indirect
|
||||||
@@ -73,15 +86,15 @@ require (
|
|||||||
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484 // indirect
|
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.2 // indirect
|
github.com/felixge/httpsnoop v1.0.2 // indirect
|
||||||
github.com/fvbommel/sortorder v1.0.1 // indirect
|
github.com/fvbommel/sortorder v1.0.1 // indirect
|
||||||
github.com/go-logr/logr v1.2.2 // indirect
|
github.com/go-logr/logr v1.2.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
||||||
github.com/gogo/googleapis v1.4.1 // indirect
|
github.com/gogo/googleapis v1.4.1 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
|
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/google/certificate-transparency-go v1.0.21 // indirect
|
github.com/google/certificate-transparency-go v1.0.21 // indirect
|
||||||
github.com/google/go-cmp v0.5.8 // indirect
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
github.com/google/gofuzz v1.2.0 // indirect
|
github.com/google/gofuzz v1.2.0 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||||
@@ -90,33 +103,36 @@ require (
|
|||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
|
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
|
||||||
github.com/imdario/mergo v0.3.13 // indirect
|
github.com/imdario/mergo v0.3.13 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||||
github.com/jinzhu/gorm v1.9.2 // indirect
|
github.com/jinzhu/gorm v1.9.2 // indirect
|
||||||
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect
|
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||||
github.com/klauspost/compress v1.15.1 // indirect
|
github.com/klauspost/compress v1.15.12 // indirect
|
||||||
github.com/kr/pretty v0.3.0 // indirect
|
github.com/kr/pretty v0.3.0 // indirect
|
||||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
|
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/moby/locker v1.0.1 // indirect
|
github.com/moby/locker v1.0.1 // indirect
|
||||||
|
github.com/moby/patternmatcher v0.5.0 // indirect
|
||||||
github.com/moby/spdystream v0.2.0 // indirect
|
github.com/moby/spdystream v0.2.0 // indirect
|
||||||
github.com/moby/sys/signal v0.6.0 // indirect
|
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
github.com/moby/sys/signal v0.7.0 // indirect
|
||||||
|
github.com/moby/term v0.0.0-20221120202655-abb19827d345 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/opencontainers/runc v1.1.3 // indirect
|
github.com/opencontainers/runc v1.1.5 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_golang v1.12.1 // indirect
|
github.com/prometheus/client_golang v1.14.0 // indirect
|
||||||
github.com/prometheus/client_model v0.2.0 // indirect
|
github.com/prometheus/client_model v0.3.0 // indirect
|
||||||
github.com/prometheus/common v0.32.1 // indirect
|
github.com/prometheus/common v0.37.0 // indirect
|
||||||
github.com/prometheus/procfs v0.7.3 // indirect
|
github.com/prometheus/procfs v0.8.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.8.1 // indirect
|
github.com/rogpeppe/go-internal v1.8.1 // indirect
|
||||||
|
github.com/spf13/viper v1.14.0 // indirect
|
||||||
github.com/theupdateframework/notary v0.6.1 // indirect
|
github.com/theupdateframework/notary v0.6.1 // indirect
|
||||||
github.com/tonistiigi/fsutil v0.0.0-20220510150904-0dbf3a8a7d58 // indirect
|
github.com/tonistiigi/fsutil v0.0.0-20230105215944-fb433841cbfa // indirect
|
||||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
||||||
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect
|
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
||||||
@@ -133,15 +149,15 @@ require (
|
|||||||
go.opentelemetry.io/otel/metric v0.27.0 // indirect
|
go.opentelemetry.io/otel/metric v0.27.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk v1.4.1 // indirect
|
go.opentelemetry.io/otel/sdk v1.4.1 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
|
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
|
golang.org/x/crypto v0.2.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
golang.org/x/net v0.4.0 // indirect
|
||||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
|
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
golang.org/x/sys v0.3.0 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.5.0 // indirect
|
||||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
golang.org/x/time v0.1.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect
|
google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e // indirect
|
||||||
google.golang.org/protobuf v1.27.1 // indirect
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect
|
gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect
|
||||||
gopkg.in/fatih/pool.v2 v2.0.0 // indirect
|
gopkg.in/fatih/pool.v2 v2.0.0 // indirect
|
||||||
@@ -155,8 +171,6 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
github.com/docker/cli => github.com/docker/cli v20.10.3-0.20220721163225-f1615facb1ca+incompatible // master (v22.06-dev)
|
|
||||||
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220720171342-a60b458179aa+incompatible // 22.06 branch (v22.06-dev)
|
|
||||||
k8s.io/api => k8s.io/api v0.22.4
|
k8s.io/api => k8s.io/api v0.22.4
|
||||||
k8s.io/apimachinery => k8s.io/apimachinery v0.22.4
|
k8s.io/apimachinery => k8s.io/apimachinery v0.22.4
|
||||||
k8s.io/client-go => k8s.io/client-go v0.22.4
|
k8s.io/client-go => k8s.io/client-go v0.22.4
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user