mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-08-17 01:05:55 +08:00
Compare commits
213 Commits
v0.12
...
v0.13.0-rc
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e273a53c88 | ||
![]() |
dcdcce6c52 | ||
![]() |
c5b4ce9e7b | ||
![]() |
8f484f6ac1 | ||
![]() |
b748185f48 | ||
![]() |
a6228ed78f | ||
![]() |
fcbe2803c8 | ||
![]() |
83c30c6c5a | ||
![]() |
8db86e4031 | ||
![]() |
e705cafcd5 | ||
![]() |
32f17b0de1 | ||
![]() |
d40c4bb046 | ||
![]() |
25f8011825 | ||
![]() |
d0f9655aa2 | ||
![]() |
ce9a486a0e | ||
![]() |
85abcc413e | ||
![]() |
e5acb010c9 | ||
![]() |
79f50ad924 | ||
![]() |
5723ceefb6 | ||
![]() |
95185e9525 | ||
![]() |
e423a67f7b | ||
![]() |
545a5c97c6 | ||
![]() |
625d90b983 | ||
![]() |
9999fc63e8 | ||
![]() |
303e509bbf | ||
![]() |
ae0a5e495a | ||
![]() |
2edb7a04a9 | ||
![]() |
a0599c1c31 | ||
![]() |
eedf9f10e8 | ||
![]() |
d891634fc6 | ||
![]() |
af75d0bd7d | ||
![]() |
e008b846bb | ||
![]() |
fd11d93381 | ||
![]() |
aa518f9b88 | ||
![]() |
b16bd02f95 | ||
![]() |
69bd408964 | ||
![]() |
d8e9c7f5b5 | ||
![]() |
fd54daf184 | ||
![]() |
9057bd27af | ||
![]() |
5a466918f9 | ||
![]() |
56fc68eb7e | ||
![]() |
ccfcf4bc37 | ||
![]() |
560eaf0e78 | ||
![]() |
daaa8f2482 | ||
![]() |
97052cf203 | ||
![]() |
2eccaadce5 | ||
![]() |
aa4317bfce | ||
![]() |
953cbf6696 | ||
![]() |
414f215929 | ||
![]() |
698eb840a3 | ||
![]() |
714b85aaaf | ||
![]() |
fb604d4b57 | ||
![]() |
73d8969158 | ||
![]() |
64e2b2532a | ||
![]() |
c2befc0c12 | ||
![]() |
345551ae0d | ||
![]() |
97e8fa7aaf | ||
![]() |
cdfc35d0b6 | ||
![]() |
ce66d8830d | ||
![]() |
fe08cf2981 | ||
![]() |
c9d1c41d20 | ||
![]() |
bda968ad5d | ||
![]() |
481384b185 | ||
![]() |
67d9385ce0 | ||
![]() |
598bc16e5d | ||
![]() |
760244ee3e | ||
![]() |
d0177c6da3 | ||
![]() |
8f8ed68b61 | ||
![]() |
981cc8c2aa | ||
![]() |
9822409b67 | ||
![]() |
328666dc6a | ||
![]() |
42d2719b08 | ||
![]() |
3b33ac48d2 | ||
![]() |
e0303dd65a | ||
![]() |
dab7af617a | ||
![]() |
0326d2a5b1 | ||
![]() |
b4c81a4d27 | ||
![]() |
7b3c4fc714 | ||
![]() |
43ed470208 | ||
![]() |
089982153f | ||
![]() |
7393650008 | ||
![]() |
b36c5196dd | ||
![]() |
1484862a50 | ||
![]() |
e5c3fa5293 | ||
![]() |
2c58e6003f | ||
![]() |
30ae5ceb6e | ||
![]() |
6ffb77dcda | ||
![]() |
2c1f46450a | ||
![]() |
052f279de7 | ||
![]() |
89684021b3 | ||
![]() |
95bdecc145 | ||
![]() |
082d5d70b2 | ||
![]() |
5b75930a6d | ||
![]() |
e41ab8d10d | ||
![]() |
4b408c79fe | ||
![]() |
cff7baff1c | ||
![]() |
5130700981 | ||
![]() |
13beda8b11 | ||
![]() |
8babd5a147 | ||
![]() |
cb856682e9 | ||
![]() |
c65b7ed24f | ||
![]() |
2c3d7dab3f | ||
![]() |
13467c1f5d | ||
![]() |
d0c4bed484 | ||
![]() |
dbaad32f49 | ||
![]() |
528e3ba259 | ||
![]() |
1ff261d38e | ||
![]() |
bef5d567b0 | ||
![]() |
da95d9f0ca | ||
![]() |
7206e2d179 | ||
![]() |
736094794c | ||
![]() |
a399a97949 | ||
![]() |
62a416fe12 | ||
![]() |
f6564c3147 | ||
![]() |
b49911416c | ||
![]() |
22c2538466 | ||
![]() |
1861405b1e | ||
![]() |
c9aeca19ce | ||
![]() |
59827f5c27 | ||
![]() |
827622421e | ||
![]() |
f0c5dfaf48 | ||
![]() |
703c765ec8 | ||
![]() |
fb2c62a038 | ||
![]() |
eabbee797b | ||
![]() |
7e4021a43d | ||
![]() |
2478f300aa | ||
![]() |
620c57c86c | ||
![]() |
8bea1cb417 | ||
![]() |
147c7135b0 | ||
![]() |
650a7af0ae | ||
![]() |
4f738020fd | ||
![]() |
d852568a29 | ||
![]() |
68c3ac4f66 | ||
![]() |
38afdf1f52 | ||
![]() |
b2e723e2a3 | ||
![]() |
02c2073feb | ||
![]() |
61dff684ad | ||
![]() |
78adfc80a9 | ||
![]() |
7c590ecb9a | ||
![]() |
24e043e375 | ||
![]() |
7094eb86c9 | ||
![]() |
81ea718ea4 | ||
![]() |
9060cab077 | ||
![]() |
3cd6d8d6e4 | ||
![]() |
ba43fe08f4 | ||
![]() |
6b63e7e3de | ||
![]() |
57d737a13c | ||
![]() |
671347dc35 | ||
![]() |
02bc4e8992 | ||
![]() |
1cdefbe901 | ||
![]() |
7694f0b9d8 | ||
![]() |
fa9126c61f | ||
![]() |
ebae070f7e | ||
![]() |
617f538cb3 | ||
![]() |
0f45b629ad | ||
![]() |
ac5b3241b1 | ||
![]() |
ee24a36c4f | ||
![]() |
8484fcdd57 | ||
![]() |
45deb29f09 | ||
![]() |
6641167e7d | ||
![]() |
9f4987997c | ||
![]() |
8337c25fa4 | ||
![]() |
6b048e2316 | ||
![]() |
54a1f0f0ea | ||
![]() |
57dc45774a | ||
![]() |
9d8ac1ce2d | ||
![]() |
0a0252d9b3 | ||
![]() |
c6535e9675 | ||
![]() |
d762c76a68 | ||
![]() |
1091707bd5 | ||
![]() |
a4c392f4db | ||
![]() |
e4880c5dd1 | ||
![]() |
b2510c6b94 | ||
![]() |
5b5c4c8c9d | ||
![]() |
ceb5bc807c | ||
![]() |
b2f705ad71 | ||
![]() |
6028094e6b | ||
![]() |
9516ce8e25 | ||
![]() |
d82637582c | ||
![]() |
1e80c70990 | ||
![]() |
54032316f9 | ||
![]() |
aac7a47469 | ||
![]() |
aa0aeac297 | ||
![]() |
cec4496d3b | ||
![]() |
9368ecb67e | ||
![]() |
20c947990c | ||
![]() |
eeeff1cf23 | ||
![]() |
752680e289 | ||
![]() |
5bf02d9f7b | ||
![]() |
0962fdbb04 | ||
![]() |
1f5562315b | ||
![]() |
a102d33738 | ||
![]() |
940e0a4a3c | ||
![]() |
a978b2b7a3 | ||
![]() |
1326634c7d | ||
![]() |
7a724ac445 | ||
![]() |
55e164a540 | ||
![]() |
707ae87060 | ||
![]() |
cb37886658 | ||
![]() |
c855277d53 | ||
![]() |
898a8eeddf | ||
![]() |
c857eaa380 | ||
![]() |
55db25c21c | ||
![]() |
f353814390 | ||
![]() |
271a467612 | ||
![]() |
b3b8c62ad4 | ||
![]() |
eacf2bdf3d | ||
![]() |
da5f853b44 | ||
![]() |
4932eecc3f | ||
![]() |
7f93616ff1 | ||
![]() |
ab58333311 | ||
![]() |
9efaa2793d | ||
![]() |
8819ac1b65 |
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
@@ -5,6 +5,11 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
|
ignore:
|
||||||
|
# ignore this dependency
|
||||||
|
# it seems a bug with dependabot as pining to commit sha should not
|
||||||
|
# trigger a new version: https://github.com/docker/buildx/pull/2222#issuecomment-1919092153
|
||||||
|
- dependency-name: "docker/docs"
|
||||||
labels:
|
labels:
|
||||||
- "dependencies"
|
- "dependencies"
|
||||||
- "bot"
|
- "bot"
|
||||||
|
119
.github/workflows/build.yml
vendored
119
.github/workflows/build.yml
vendored
@@ -24,9 +24,13 @@ env:
|
|||||||
REPO_SLUG: "docker/buildx-bin"
|
REPO_SLUG: "docker/buildx-bin"
|
||||||
DESTDIR: "./bin"
|
DESTDIR: "./bin"
|
||||||
TEST_CACHE_SCOPE: "test"
|
TEST_CACHE_SCOPE: "test"
|
||||||
|
TESTFLAGS: "-v --parallel=6 --timeout=30m"
|
||||||
|
GOTESTSUM_FORMAT: "standard-verbose"
|
||||||
|
GO_VERSION: "1.21"
|
||||||
|
GOTESTSUM_VERSION: "v1.9.0" # same as one in Dockerfile
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
prepare-test:
|
prepare-test-integration:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
@@ -51,14 +55,12 @@ jobs:
|
|||||||
*.cache-from=type=gha,scope=${{ env.TEST_CACHE_SCOPE }}
|
*.cache-from=type=gha,scope=${{ env.TEST_CACHE_SCOPE }}
|
||||||
*.cache-to=type=gha,scope=${{ env.TEST_CACHE_SCOPE }}
|
*.cache-to=type=gha,scope=${{ env.TEST_CACHE_SCOPE }}
|
||||||
|
|
||||||
test:
|
test-integration:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs:
|
needs:
|
||||||
- prepare-test
|
- prepare-test-integration
|
||||||
env:
|
env:
|
||||||
TESTFLAGS: "-v --parallel=6 --timeout=30m"
|
|
||||||
TESTFLAGS_DOCKER: "-v --parallel=1 --timeout=30m"
|
TESTFLAGS_DOCKER: "-v --parallel=1 --timeout=30m"
|
||||||
GOTESTSUM_FORMAT: "standard-verbose"
|
|
||||||
TEST_IMAGE_BUILD: "0"
|
TEST_IMAGE_BUILD: "0"
|
||||||
TEST_IMAGE_ID: "buildx-tests"
|
TEST_IMAGE_ID: "buildx-tests"
|
||||||
strategy:
|
strategy:
|
||||||
@@ -71,10 +73,11 @@ jobs:
|
|||||||
- remote
|
- remote
|
||||||
pkg:
|
pkg:
|
||||||
- ./tests
|
- ./tests
|
||||||
include:
|
|
||||||
- pkg: ./...
|
|
||||||
skip-integration-tests: 1
|
|
||||||
steps:
|
steps:
|
||||||
|
-
|
||||||
|
name: Prepare
|
||||||
|
run: |
|
||||||
|
echo "TESTREPORTS_NAME=${{ github.job }}-$(echo "${{ matrix.pkg }}-${{ matrix.worker }}" | tr -dc '[:alnum:]-\n\r' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -101,33 +104,106 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Test
|
name: Test
|
||||||
run: |
|
run: |
|
||||||
export TEST_REPORT_SUFFIX=-${{ github.job }}-$(echo "${{ matrix.pkg }}-${{ matrix.skip-integration-tests }}-${{ matrix.worker }}" | tr -dc '[:alnum:]-\n\r' | tr '[:upper:]' '[:lower:]')
|
|
||||||
./hack/test
|
./hack/test
|
||||||
env:
|
env:
|
||||||
|
TEST_REPORT_SUFFIX: "-${{ env.TESTREPORTS_NAME }}"
|
||||||
TEST_DOCKERD: "${{ startsWith(matrix.worker, 'docker') && '1' || '0' }}"
|
TEST_DOCKERD: "${{ startsWith(matrix.worker, 'docker') && '1' || '0' }}"
|
||||||
TESTFLAGS: "${{ (matrix.worker == 'docker' || matrix.worker == 'docker\\+containerd') && env.TESTFLAGS_DOCKER || env.TESTFLAGS }} --run=//worker=${{ matrix.worker }}$"
|
TESTFLAGS: "${{ (matrix.worker == 'docker' || matrix.worker == 'docker\\+containerd') && env.TESTFLAGS_DOCKER || env.TESTFLAGS }} --run=//worker=${{ matrix.worker }}$"
|
||||||
TESTPKGS: "${{ matrix.pkg }}"
|
TESTPKGS: "${{ matrix.pkg }}"
|
||||||
SKIP_INTEGRATION_TESTS: "${{ matrix.skip-integration-tests }}"
|
|
||||||
-
|
-
|
||||||
name: Send to Codecov
|
name: Send to Codecov
|
||||||
if: always()
|
if: always()
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v4
|
||||||
with:
|
with:
|
||||||
directory: ./bin/testreports
|
directory: ./bin/testreports
|
||||||
|
flags: integration
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
-
|
-
|
||||||
name: Generate annotations
|
name: Generate annotations
|
||||||
if: always()
|
if: always()
|
||||||
uses: crazy-max/.github/.github/actions/gotest-annotations@1a64ea6d01db9a48aa61954cb20e265782c167d9
|
uses: crazy-max/.github/.github/actions/gotest-annotations@fa6141aedf23596fb8bdcceab9cce8dadaa31bd9
|
||||||
with:
|
with:
|
||||||
directory: ./bin/testreports
|
directory: ./bin/testreports
|
||||||
-
|
-
|
||||||
name: Upload test reports
|
name: Upload test reports
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: test-reports
|
name: test-reports-${{ env.TESTREPORTS_NAME }}
|
||||||
path: ./bin/testreports
|
path: ./bin/testreports
|
||||||
|
|
||||||
|
test-unit:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- ubuntu-22.04
|
||||||
|
- macos-12
|
||||||
|
- windows-2022
|
||||||
|
env:
|
||||||
|
SKIP_INTEGRATION_TESTS: 1
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
-
|
||||||
|
name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: "${{ env.GO_VERSION }}"
|
||||||
|
-
|
||||||
|
name: Prepare
|
||||||
|
run: |
|
||||||
|
testreportsName=${{ github.job }}--${{ matrix.os }}
|
||||||
|
testreportsBaseDir=./bin/testreports
|
||||||
|
testreportsDir=$testreportsBaseDir/$testreportsName
|
||||||
|
echo "TESTREPORTS_NAME=$testreportsName" >> $GITHUB_ENV
|
||||||
|
echo "TESTREPORTS_BASEDIR=$testreportsBaseDir" >> $GITHUB_ENV
|
||||||
|
echo "TESTREPORTS_DIR=$testreportsDir" >> $GITHUB_ENV
|
||||||
|
mkdir -p $testreportsDir
|
||||||
|
shell: bash
|
||||||
|
-
|
||||||
|
name: Install gotestsum
|
||||||
|
run: |
|
||||||
|
go install gotest.tools/gotestsum@${{ env.GOTESTSUM_VERSION }}
|
||||||
|
-
|
||||||
|
name: Test
|
||||||
|
env:
|
||||||
|
TMPDIR: ${{ runner.temp }}
|
||||||
|
run: |
|
||||||
|
gotestsum \
|
||||||
|
--jsonfile="${{ env.TESTREPORTS_DIR }}/go-test-report.json" \
|
||||||
|
--junitfile="${{ env.TESTREPORTS_DIR }}/junit-report.xml" \
|
||||||
|
--packages="./..." \
|
||||||
|
-- \
|
||||||
|
"-mod=vendor" \
|
||||||
|
"-coverprofile" "${{ env.TESTREPORTS_DIR }}/coverage.txt" \
|
||||||
|
"-covermode" "atomic" ${{ env.TESTFLAGS }}
|
||||||
|
shell: bash
|
||||||
|
-
|
||||||
|
name: Send to Codecov
|
||||||
|
if: always()
|
||||||
|
uses: codecov/codecov-action@v4
|
||||||
|
with:
|
||||||
|
directory: ${{ env.TESTREPORTS_DIR }}
|
||||||
|
env_vars: RUNNER_OS
|
||||||
|
flags: unit
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
-
|
||||||
|
name: Generate annotations
|
||||||
|
if: always()
|
||||||
|
uses: crazy-max/.github/.github/actions/gotest-annotations@fa6141aedf23596fb8bdcceab9cce8dadaa31bd9
|
||||||
|
with:
|
||||||
|
directory: ${{ env.TESTREPORTS_DIR }}
|
||||||
|
-
|
||||||
|
name: Upload test reports
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: test-reports-${{ env.TESTREPORTS_NAME }}
|
||||||
|
path: ${{ env.TESTREPORTS_BASEDIR }}
|
||||||
|
|
||||||
prepare-binaries:
|
prepare-binaries:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
outputs:
|
outputs:
|
||||||
@@ -183,16 +259,17 @@ jobs:
|
|||||||
CACHE_TO: type=gha,scope=binaries-${{ env.PLATFORM_PAIR }},mode=max
|
CACHE_TO: type=gha,scope=binaries-${{ env.PLATFORM_PAIR }},mode=max
|
||||||
-
|
-
|
||||||
name: Upload artifacts
|
name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: buildx
|
name: buildx-${{ env.PLATFORM_PAIR }}
|
||||||
path: ${{ env.DESTDIR }}/*
|
path: ${{ env.DESTDIR }}/*
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
bin-image:
|
bin-image:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs:
|
needs:
|
||||||
- test
|
- test-integration
|
||||||
|
- test-unit
|
||||||
if: ${{ github.event_name != 'pull_request' && github.repository == 'docker/buildx' }}
|
if: ${{ github.event_name != 'pull_request' && github.repository == 'docker/buildx' }}
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
@@ -244,7 +321,8 @@ jobs:
|
|||||||
release:
|
release:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs:
|
needs:
|
||||||
- test
|
- test-integration
|
||||||
|
- test-unit
|
||||||
- binaries
|
- binaries
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
@@ -252,10 +330,11 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
-
|
-
|
||||||
name: Download binaries
|
name: Download binaries
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: buildx
|
|
||||||
path: ${{ env.DESTDIR }}
|
path: ${{ env.DESTDIR }}
|
||||||
|
pattern: buildx-*
|
||||||
|
merge-multiple: true
|
||||||
-
|
-
|
||||||
name: Create checksums
|
name: Create checksums
|
||||||
run: ./hack/hash-files
|
run: ./hack/hash-files
|
||||||
|
10
.github/workflows/codeql.yml
vendored
10
.github/workflows/codeql.yml
vendored
@@ -13,7 +13,7 @@ permissions:
|
|||||||
security-events: write
|
security-events: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GO_VERSION: 1.21.6
|
GO_VERSION: "1.21"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
codeql:
|
codeql:
|
||||||
@@ -24,19 +24,19 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
-
|
-
|
||||||
name: Set up Go
|
name: Set up Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GO_VERSION }}
|
go-version: ${{ env.GO_VERSION }}
|
||||||
-
|
-
|
||||||
name: Initialize CodeQL
|
name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: go
|
languages: go
|
||||||
-
|
-
|
||||||
name: Autobuild
|
name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v2
|
uses: github/codeql-action/autobuild@v3
|
||||||
-
|
-
|
||||||
name: Perform CodeQL Analysis
|
name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v3
|
||||||
with:
|
with:
|
||||||
category: "/language:go"
|
category: "/language:go"
|
||||||
|
2
.github/workflows/docs-release.yml
vendored
2
.github/workflows/docs-release.yml
vendored
@@ -44,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@153407881ec5c347639a548ade7d8ad1d6740e38
|
uses: peter-evans/create-pull-request@a4f52f8033a6168103c2538976c07b467e8163bc
|
||||||
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
|
||||||
|
6
.github/workflows/docs-upstream.yml
vendored
6
.github/workflows/docs-upstream.yml
vendored
@@ -45,18 +45,18 @@ jobs:
|
|||||||
DOCS_FORMATS: yaml
|
DOCS_FORMATS: yaml
|
||||||
-
|
-
|
||||||
name: Upload reference YAML docs
|
name: Upload reference YAML docs
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: docs-yaml
|
name: docs-yaml
|
||||||
path: /tmp/buildx-docs/out/reference
|
path: /tmp/buildx-docs/out/reference
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
validate:
|
validate:
|
||||||
uses: docker/docs/.github/workflows/validate-upstream.yml@e864c797e391fa35aecf1ddcecdbdeb683546424 # pin for artifact v3 support
|
uses: docker/docs/.github/workflows/validate-upstream.yml@6b73b05acb21edf7995cc5b3c6672d8e314cee7a # pin for artifact v4 support: https://github.com/docker/docs/pull/19220
|
||||||
needs:
|
needs:
|
||||||
- docs-yaml
|
- docs-yaml
|
||||||
with:
|
with:
|
||||||
module-name: docker/buildx
|
module-name: docker/buildx
|
||||||
data-files-id: docs-yaml
|
data-files-id: docs-yaml
|
||||||
data-files-folder: buildx
|
data-files-folder: buildx
|
||||||
data-files-placeholder-folder: engine/reference/commandline
|
create-placeholder-stubs: true
|
||||||
|
6
.github/workflows/e2e.yml
vendored
6
.github/workflows/e2e.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
|||||||
mv ${{ env.DESTDIR }}/build/buildx ${{ env.DESTDIR }}/build/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@v4
|
||||||
with:
|
with:
|
||||||
name: binary
|
name: binary
|
||||||
path: ${{ env.DESTDIR }}/build
|
path: ${{ env.DESTDIR }}/build
|
||||||
@@ -103,7 +103,7 @@ jobs:
|
|||||||
if: matrix.driver == 'docker' || matrix.driver == 'docker-container'
|
if: matrix.driver == 'docker' || matrix.driver == 'docker-container'
|
||||||
-
|
-
|
||||||
name: Install buildx
|
name: Install buildx
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binary
|
name: binary
|
||||||
path: /home/runner/.docker/cli-plugins
|
path: /home/runner/.docker/cli-plugins
|
||||||
@@ -132,7 +132,7 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Install k3s
|
name: Install k3s
|
||||||
if: matrix.driver == 'kubernetes'
|
if: matrix.driver == 'kubernetes'
|
||||||
uses: actions/github-script@v6
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
2
.github/workflows/validate.yml
vendored
2
.github/workflows/validate.yml
vendored
@@ -19,6 +19,8 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
validate:
|
validate:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
env:
|
||||||
|
GOLANGCI_LINT_MULTIPLATFORM: 1
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
run:
|
run:
|
||||||
timeout: 10m
|
timeout: 30m
|
||||||
skip-files:
|
skip-files:
|
||||||
- ".*\\.pb\\.go$"
|
- ".*\\.pb\\.go$"
|
||||||
|
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
ARG GO_VERSION=1.21.6
|
ARG GO_VERSION=1.21
|
||||||
ARG XX_VERSION=1.2.1
|
ARG XX_VERSION=1.4.0
|
||||||
|
|
||||||
ARG DOCKER_VERSION=24.0.6
|
ARG DOCKER_VERSION=25.0.2
|
||||||
ARG GOTESTSUM_VERSION=v1.9.0
|
ARG GOTESTSUM_VERSION=v1.9.0
|
||||||
ARG REGISTRY_VERSION=2.8.0
|
ARG REGISTRY_VERSION=2.8.0
|
||||||
ARG BUILDKIT_VERSION=v0.11.6
|
ARG BUILDKIT_VERSION=v0.12.5
|
||||||
|
|
||||||
# xx is a helper for cross-compilation
|
# xx is a helper for cross-compilation
|
||||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||||
|
39
bake/bake.go
39
bake/bake.go
@@ -13,7 +13,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
composecli "github.com/compose-spec/compose-go/cli"
|
composecli "github.com/compose-spec/compose-go/v2/cli"
|
||||||
"github.com/docker/buildx/bake/hclparser"
|
"github.com/docker/buildx/bake/hclparser"
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/build"
|
||||||
controllerapi "github.com/docker/buildx/controller/pb"
|
controllerapi "github.com/docker/buildx/controller/pb"
|
||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"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/cli/cli/config"
|
"github.com/docker/cli/cli/config"
|
||||||
|
dockeropts "github.com/docker/cli/opts"
|
||||||
hcl "github.com/hashicorp/hcl/v2"
|
hcl "github.com/hashicorp/hcl/v2"
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
"github.com/moby/buildkit/client/llb"
|
"github.com/moby/buildkit/client/llb"
|
||||||
@@ -699,6 +700,8 @@ type Target struct {
|
|||||||
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional" cty:"no-cache"`
|
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional" cty:"no-cache"`
|
||||||
NetworkMode *string `json:"-" hcl:"-" cty:"-"`
|
NetworkMode *string `json:"-" hcl:"-" cty:"-"`
|
||||||
NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional" cty:"no-cache-filter"`
|
NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional" cty:"no-cache-filter"`
|
||||||
|
ShmSize *string `json:"shm-size,omitempty" hcl:"shm-size,optional"`
|
||||||
|
Ulimits []string `json:"ulimits,omitempty" hcl:"ulimits,optional"`
|
||||||
// IMPORTANT: if you add more fields here, do not forget to update newOverrides and docs/bake-reference.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
|
||||||
@@ -721,6 +724,7 @@ func (t *Target) normalize() {
|
|||||||
t.CacheTo = removeDupes(t.CacheTo)
|
t.CacheTo = removeDupes(t.CacheTo)
|
||||||
t.Outputs = removeDupes(t.Outputs)
|
t.Outputs = removeDupes(t.Outputs)
|
||||||
t.NoCacheFilter = removeDupes(t.NoCacheFilter)
|
t.NoCacheFilter = removeDupes(t.NoCacheFilter)
|
||||||
|
t.Ulimits = removeDupes(t.Ulimits)
|
||||||
|
|
||||||
for k, v := range t.Contexts {
|
for k, v := range t.Contexts {
|
||||||
if v == "" {
|
if v == "" {
|
||||||
@@ -809,6 +813,12 @@ func (t *Target) Merge(t2 *Target) {
|
|||||||
if t2.NoCacheFilter != nil { // merge
|
if t2.NoCacheFilter != nil { // merge
|
||||||
t.NoCacheFilter = append(t.NoCacheFilter, t2.NoCacheFilter...)
|
t.NoCacheFilter = append(t.NoCacheFilter, t2.NoCacheFilter...)
|
||||||
}
|
}
|
||||||
|
if t2.ShmSize != nil { // no merge
|
||||||
|
t.ShmSize = t2.ShmSize
|
||||||
|
}
|
||||||
|
if t2.Ulimits != nil { // merge
|
||||||
|
t.Ulimits = append(t.Ulimits, t2.Ulimits...)
|
||||||
|
}
|
||||||
t.Inherits = append(t.Inherits, t2.Inherits...)
|
t.Inherits = append(t.Inherits, t2.Inherits...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -873,6 +883,10 @@ func (t *Target) AddOverrides(overrides map[string]Override) error {
|
|||||||
t.NoCache = &noCache
|
t.NoCache = &noCache
|
||||||
case "no-cache-filter":
|
case "no-cache-filter":
|
||||||
t.NoCacheFilter = o.ArrValue
|
t.NoCacheFilter = o.ArrValue
|
||||||
|
case "shm-size":
|
||||||
|
t.ShmSize = &value
|
||||||
|
case "ulimits":
|
||||||
|
t.Ulimits = o.ArrValue
|
||||||
case "pull":
|
case "pull":
|
||||||
pull, err := strconv.ParseBool(value)
|
pull, err := strconv.ParseBool(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1011,12 +1025,17 @@ func (t *Target) GetName(ectx *hcl.EvalContext, block *hcl.Block, loadDeps func(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TargetsToBuildOpt(m map[string]*Target, inp *Input) (map[string]build.Options, error) {
|
func TargetsToBuildOpt(m map[string]*Target, inp *Input) (map[string]build.Options, error) {
|
||||||
|
// make sure local credentials are loaded multiple times for different targets
|
||||||
|
dockerConfig := config.LoadDefaultConfigFile(os.Stderr)
|
||||||
|
authProvider := authprovider.NewDockerAuthProvider(dockerConfig, nil)
|
||||||
|
|
||||||
m2 := make(map[string]build.Options, len(m))
|
m2 := make(map[string]build.Options, len(m))
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
bo, err := toBuildOpt(v, inp)
|
bo, err := toBuildOpt(v, inp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
bo.Session = append(bo.Session, authProvider)
|
||||||
m2[k] = *bo
|
m2[k] = *bo
|
||||||
}
|
}
|
||||||
return m2, nil
|
return m2, nil
|
||||||
@@ -1228,6 +1247,12 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
|||||||
if t.NetworkMode != nil {
|
if t.NetworkMode != nil {
|
||||||
networkMode = *t.NetworkMode
|
networkMode = *t.NetworkMode
|
||||||
}
|
}
|
||||||
|
shmSize := new(dockeropts.MemBytes)
|
||||||
|
if t.ShmSize != nil {
|
||||||
|
if err := shmSize.Set(*t.ShmSize); err != nil {
|
||||||
|
return nil, errors.Errorf("invalid value %s for membytes key shm-size", *t.ShmSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bo := &build.Options{
|
bo := &build.Options{
|
||||||
Inputs: bi,
|
Inputs: bi,
|
||||||
@@ -1239,6 +1264,7 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
|||||||
Pull: pull,
|
Pull: pull,
|
||||||
NetworkMode: networkMode,
|
NetworkMode: networkMode,
|
||||||
Linked: t.linked,
|
Linked: t.linked,
|
||||||
|
ShmSize: *shmSize,
|
||||||
}
|
}
|
||||||
|
|
||||||
platforms, err := platformutil.Parse(t.Platforms)
|
platforms, err := platformutil.Parse(t.Platforms)
|
||||||
@@ -1247,9 +1273,6 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
|||||||
}
|
}
|
||||||
bo.Platforms = platforms
|
bo.Platforms = platforms
|
||||||
|
|
||||||
dockerConfig := config.LoadDefaultConfigFile(os.Stderr)
|
|
||||||
bo.Session = append(bo.Session, authprovider.NewDockerAuthProvider(dockerConfig, nil))
|
|
||||||
|
|
||||||
secrets, err := buildflags.ParseSecretSpecs(t.Secrets)
|
secrets, err := buildflags.ParseSecretSpecs(t.Secrets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1319,6 +1342,14 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ulimits := dockeropts.NewUlimitOpt(nil)
|
||||||
|
for _, field := range t.Ulimits {
|
||||||
|
if err := ulimits.Set(field); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bo.Ulimits = ulimits
|
||||||
|
|
||||||
return bo, nil
|
return bo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -22,6 +22,8 @@ target "webDEP" {
|
|||||||
VAR_BOTH = "webDEP"
|
VAR_BOTH = "webDEP"
|
||||||
}
|
}
|
||||||
no-cache = true
|
no-cache = true
|
||||||
|
shm-size = "128m"
|
||||||
|
ulimits = ["nofile=1024:1024"]
|
||||||
}
|
}
|
||||||
|
|
||||||
target "webapp" {
|
target "webapp" {
|
||||||
@@ -45,6 +47,8 @@ target "webapp" {
|
|||||||
require.Equal(t, ".", *m["webapp"].Context)
|
require.Equal(t, ".", *m["webapp"].Context)
|
||||||
require.Equal(t, ptrstr("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.Equal(t, "128m", *m["webapp"].ShmSize)
|
||||||
|
require.Equal(t, []string{"nofile=1024:1024"}, m["webapp"].Ulimits)
|
||||||
require.Nil(t, m["webapp"].Pull)
|
require.Nil(t, m["webapp"].Pull)
|
||||||
|
|
||||||
require.Equal(t, 1, len(g))
|
require.Equal(t, 1, len(g))
|
||||||
@@ -129,6 +133,12 @@ target "webapp" {
|
|||||||
require.Equal(t, []string{"webapp"}, g["default"].Targets)
|
require.Equal(t, []string{"webapp"}, g["default"].Targets)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("ShmSizeOverride", func(t *testing.T) {
|
||||||
|
m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.shm-size=256m"}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "256m", *m["webapp"].ShmSize)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("PullOverride", func(t *testing.T) {
|
t.Run("PullOverride", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
m, g, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.pull=false"}, nil)
|
m, g, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.pull=false"}, nil)
|
||||||
@@ -297,9 +307,6 @@ services:
|
|||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
|
|
||||||
cwd, err := os.Getwd()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
m, g, err := ReadTargets(ctx, []File{fp, fp2, fp3}, []string{"default"}, nil, nil)
|
m, g, err := ReadTargets(ctx, []File{fp, fp2, fp3}, []string{"default"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -308,7 +315,7 @@ 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, cwd, *m["webapp"].Context)
|
require.Equal(t, ".", *m["webapp"].Context)
|
||||||
require.Equal(t, ptrstr("1"), m["webapp"].Args["buildno"])
|
require.Equal(t, ptrstr("1"), m["webapp"].Args["buildno"])
|
||||||
require.Equal(t, ptrstr("12"), m["webapp"].Args["buildno2"])
|
require.Equal(t, ptrstr("12"), m["webapp"].Args["buildno2"])
|
||||||
|
|
||||||
@@ -347,9 +354,6 @@ services:
|
|||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
|
|
||||||
cwd, err := os.Getwd()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
m, _, err := ReadTargets(ctx, []File{fp}, []string{"web.app"}, nil, nil)
|
m, _, err := ReadTargets(ctx, []File{fp}, []string{"web.app"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(m))
|
require.Equal(t, 1, len(m))
|
||||||
@@ -372,7 +376,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, cwd, *m["web_app"].Context)
|
require.Equal(t, ".", *m["web_app"].Context)
|
||||||
require.Equal(t, ptrstr("1"), m["web_app"].Args["buildno"])
|
require.Equal(t, ptrstr("1"), m["web_app"].Args["buildno"])
|
||||||
require.Equal(t, ptrstr("12"), m["web_app"].Args["buildno2"])
|
require.Equal(t, ptrstr("12"), m["web_app"].Args["buildno2"])
|
||||||
|
|
||||||
@@ -581,9 +585,6 @@ services:
|
|||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
|
|
||||||
cwd, err := os.Getwd()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
m, _, err := ReadTargets(ctx, []File{fp, fp2}, []string{"app1", "app2"}, nil, nil)
|
m, _, err := ReadTargets(ctx, []File{fp, fp2}, []string{"app1", "app2"}, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -596,7 +597,7 @@ services:
|
|||||||
require.Equal(t, "Dockerfile", *m["app1"].Dockerfile)
|
require.Equal(t, "Dockerfile", *m["app1"].Dockerfile)
|
||||||
require.Equal(t, ".", *m["app1"].Context)
|
require.Equal(t, ".", *m["app1"].Context)
|
||||||
require.Equal(t, "Dockerfile", *m["app2"].Dockerfile)
|
require.Equal(t, "Dockerfile", *m["app2"].Dockerfile)
|
||||||
require.Equal(t, cwd, *m["app2"].Context)
|
require.Equal(t, ".", *m["app2"].Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadContextFromTargetChain(t *testing.T) {
|
func TestReadContextFromTargetChain(t *testing.T) {
|
||||||
|
@@ -2,13 +2,16 @@ package bake
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/dotenv"
|
"github.com/compose-spec/compose-go/v2/dotenv"
|
||||||
"github.com/compose-spec/compose-go/loader"
|
"github.com/compose-spec/compose-go/v2/loader"
|
||||||
compose "github.com/compose-spec/compose-go/types"
|
composetypes "github.com/compose-spec/compose-go/v2/types"
|
||||||
|
dockeropts "github.com/docker/cli/opts"
|
||||||
|
"github.com/docker/go-units"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
@@ -18,9 +21,9 @@ func ParseComposeFiles(fs []File) (*Config, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var cfgs []compose.ConfigFile
|
var cfgs []composetypes.ConfigFile
|
||||||
for _, f := range fs {
|
for _, f := range fs {
|
||||||
cfgs = append(cfgs, compose.ConfigFile{
|
cfgs = append(cfgs, composetypes.ConfigFile{
|
||||||
Filename: f.Name,
|
Filename: f.Name,
|
||||||
Content: f.Data,
|
Content: f.Data,
|
||||||
})
|
})
|
||||||
@@ -28,11 +31,11 @@ func ParseComposeFiles(fs []File) (*Config, error) {
|
|||||||
return ParseCompose(cfgs, envs)
|
return ParseCompose(cfgs, envs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseCompose(cfgs []compose.ConfigFile, envs map[string]string) (*Config, error) {
|
func ParseCompose(cfgs []composetypes.ConfigFile, envs map[string]string) (*Config, error) {
|
||||||
if envs == nil {
|
if envs == nil {
|
||||||
envs = make(map[string]string)
|
envs = make(map[string]string)
|
||||||
}
|
}
|
||||||
cfg, err := loader.LoadWithContext(context.Background(), compose.ConfigDetails{
|
cfg, err := loader.LoadWithContext(context.Background(), composetypes.ConfigDetails{
|
||||||
ConfigFiles: cfgs,
|
ConfigFiles: cfgs,
|
||||||
Environment: envs,
|
Environment: envs,
|
||||||
}, func(options *loader.Options) {
|
}, func(options *loader.Options) {
|
||||||
@@ -86,6 +89,24 @@ func ParseCompose(cfgs []compose.ConfigFile, envs map[string]string) (*Config, e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var shmSize *string
|
||||||
|
if s.Build.ShmSize > 0 {
|
||||||
|
shmSizeBytes := dockeropts.MemBytes(s.Build.ShmSize)
|
||||||
|
shmSizeStr := shmSizeBytes.String()
|
||||||
|
shmSize = &shmSizeStr
|
||||||
|
}
|
||||||
|
|
||||||
|
var ulimits []string
|
||||||
|
if s.Build.Ulimits != nil {
|
||||||
|
for n, u := range s.Build.Ulimits {
|
||||||
|
ulimit, err := units.ParseUlimit(fmt.Sprintf("%s=%d:%d", n, u.Soft, u.Hard))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ulimits = append(ulimits, ulimit.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var secrets []string
|
var secrets []string
|
||||||
for _, bs := range s.Build.Secrets {
|
for _, bs := range s.Build.Secrets {
|
||||||
secret, err := composeToBuildkitSecret(bs, cfg.Secrets[bs.Source])
|
secret, err := composeToBuildkitSecret(bs, cfg.Secrets[bs.Source])
|
||||||
@@ -122,6 +143,8 @@ func ParseCompose(cfgs []compose.ConfigFile, envs map[string]string) (*Config, e
|
|||||||
CacheTo: s.Build.CacheTo,
|
CacheTo: s.Build.CacheTo,
|
||||||
NetworkMode: &s.Build.Network,
|
NetworkMode: &s.Build.Network,
|
||||||
Secrets: secrets,
|
Secrets: secrets,
|
||||||
|
ShmSize: shmSize,
|
||||||
|
Ulimits: ulimits,
|
||||||
}
|
}
|
||||||
if err = t.composeExtTarget(s.Build.Extensions); err != nil {
|
if err = t.composeExtTarget(s.Build.Extensions); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -159,8 +182,8 @@ func validateComposeFile(dt []byte, fn string) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validateCompose(dt []byte, envs map[string]string) error {
|
func validateCompose(dt []byte, envs map[string]string) error {
|
||||||
_, err := loader.Load(compose.ConfigDetails{
|
_, err := loader.Load(composetypes.ConfigDetails{
|
||||||
ConfigFiles: []compose.ConfigFile{
|
ConfigFiles: []composetypes.ConfigFile{
|
||||||
{
|
{
|
||||||
Content: dt,
|
Content: dt,
|
||||||
},
|
},
|
||||||
@@ -223,7 +246,7 @@ func loadDotEnv(curenv map[string]string, workingDir string) (map[string]string,
|
|||||||
return curenv, nil
|
return curenv, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func flatten(in compose.MappingWithEquals) map[string]*string {
|
func flatten(in composetypes.MappingWithEquals) map[string]*string {
|
||||||
if len(in) == 0 {
|
if len(in) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -327,8 +350,8 @@ func (t *Target) composeExtTarget(exts map[string]interface{}) error {
|
|||||||
|
|
||||||
// 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 composetypes.ServiceSecretConfig, psecret composetypes.SecretConfig) (string, error) {
|
||||||
if psecret.External.External {
|
if psecret.External {
|
||||||
return "", errors.Errorf("unsupported external secret %s", psecret.Name)
|
return "", errors.Errorf("unsupported external secret %s", psecret.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,7 +6,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
compose "github.com/compose-spec/compose-go/types"
|
composetypes "github.com/compose-spec/compose-go/v2/types"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -49,10 +49,7 @@ secrets:
|
|||||||
file: /root/.aws/credentials
|
file: /root/.aws/credentials
|
||||||
`)
|
`)
|
||||||
|
|
||||||
cwd, err := os.Getwd()
|
c, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
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))
|
||||||
@@ -65,12 +62,12 @@ secrets:
|
|||||||
return c.Targets[i].Name < c.Targets[j].Name
|
return c.Targets[i].Name < c.Targets[j].Name
|
||||||
})
|
})
|
||||||
require.Equal(t, "db", c.Targets[0].Name)
|
require.Equal(t, "db", c.Targets[0].Name)
|
||||||
require.Equal(t, filepath.Join(cwd, "db"), *c.Targets[0].Context)
|
require.Equal(t, "db", *c.Targets[0].Context)
|
||||||
require.Equal(t, []string{"docker.io/tonistiigi/db"}, c.Targets[0].Tags)
|
require.Equal(t, []string{"docker.io/tonistiigi/db"}, c.Targets[0].Tags)
|
||||||
|
|
||||||
require.Equal(t, "webapp", c.Targets[1].Name)
|
require.Equal(t, "webapp", c.Targets[1].Name)
|
||||||
require.Equal(t, filepath.Join(cwd, "dir"), *c.Targets[1].Context)
|
require.Equal(t, "dir", *c.Targets[1].Context)
|
||||||
require.Equal(t, map[string]string{"foo": filepath.Join(cwd, "bar")}, c.Targets[1].Contexts)
|
require.Equal(t, map[string]string{"foo": "bar"}, c.Targets[1].Contexts)
|
||||||
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, ptrstr("123"), c.Targets[1].Args["buildno"])
|
require.Equal(t, ptrstr("123"), c.Targets[1].Args["buildno"])
|
||||||
@@ -83,7 +80,7 @@ secrets:
|
|||||||
}, c.Targets[1].Secrets)
|
}, c.Targets[1].Secrets)
|
||||||
|
|
||||||
require.Equal(t, "webapp2", c.Targets[2].Name)
|
require.Equal(t, "webapp2", c.Targets[2].Name)
|
||||||
require.Equal(t, filepath.Join(cwd, "dir"), *c.Targets[2].Context)
|
require.Equal(t, "dir", *c.Targets[2].Context)
|
||||||
require.Equal(t, "FROM alpine\n", *c.Targets[2].DockerfileInline)
|
require.Equal(t, "FROM alpine\n", *c.Targets[2].DockerfileInline)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +92,7 @@ services:
|
|||||||
webapp:
|
webapp:
|
||||||
build: ./db
|
build: ./db
|
||||||
`)
|
`)
|
||||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
c, err := ParseCompose([]composetypes.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))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
@@ -114,7 +111,7 @@ services:
|
|||||||
target: webapp
|
target: webapp
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
c, err := ParseCompose([]composetypes.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))
|
||||||
@@ -139,7 +136,7 @@ services:
|
|||||||
target: webapp
|
target: webapp
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
c, err := ParseCompose([]composetypes.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 {
|
||||||
@@ -170,7 +167,7 @@ services:
|
|||||||
t.Setenv("BAR", "foo")
|
t.Setenv("BAR", "foo")
|
||||||
t.Setenv("ZZZ_BAR", "zzz_foo")
|
t.Setenv("ZZZ_BAR", "zzz_foo")
|
||||||
|
|
||||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, sliceToMap(os.Environ()))
|
c, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, sliceToMap(os.Environ()))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, ptrstr("bar"), c.Targets[0].Args["FOO"])
|
require.Equal(t, ptrstr("bar"), c.Targets[0].Args["FOO"])
|
||||||
require.Equal(t, ptrstr("zzz_foo"), c.Targets[0].Args["BAR"])
|
require.Equal(t, ptrstr("zzz_foo"), c.Targets[0].Args["BAR"])
|
||||||
@@ -184,7 +181,7 @@ services:
|
|||||||
entrypoint: echo 1
|
entrypoint: echo 1
|
||||||
`)
|
`)
|
||||||
|
|
||||||
_, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
_, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +206,7 @@ networks:
|
|||||||
gateway: 10.5.0.254
|
gateway: 10.5.0.254
|
||||||
`)
|
`)
|
||||||
|
|
||||||
_, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
_, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,7 +223,7 @@ services:
|
|||||||
- bar
|
- bar
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
c, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, []string{"foo", "bar"}, c.Targets[0].Tags)
|
require.Equal(t, []string{"foo", "bar"}, c.Targets[0].Tags)
|
||||||
}
|
}
|
||||||
@@ -263,7 +260,7 @@ networks:
|
|||||||
name: test-net
|
name: test-net
|
||||||
`)
|
`)
|
||||||
|
|
||||||
_, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
_, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,6 +303,11 @@ services:
|
|||||||
args:
|
args:
|
||||||
CT_ECR: foo
|
CT_ECR: foo
|
||||||
CT_TAG: bar
|
CT_TAG: bar
|
||||||
|
shm_size: 128m
|
||||||
|
ulimits:
|
||||||
|
nofile:
|
||||||
|
soft: 1024
|
||||||
|
hard: 1024
|
||||||
x-bake:
|
x-bake:
|
||||||
secret:
|
secret:
|
||||||
- id=mysecret,src=/local/secret
|
- id=mysecret,src=/local/secret
|
||||||
@@ -316,7 +318,7 @@ services:
|
|||||||
no-cache: true
|
no-cache: true
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
c, err := ParseCompose([]composetypes.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 {
|
||||||
@@ -335,6 +337,8 @@ services:
|
|||||||
require.Equal(t, []string{"linux/arm64"}, c.Targets[1].Platforms)
|
require.Equal(t, []string{"linux/arm64"}, c.Targets[1].Platforms)
|
||||||
require.Equal(t, []string{"type=docker"}, c.Targets[1].Outputs)
|
require.Equal(t, []string{"type=docker"}, c.Targets[1].Outputs)
|
||||||
require.Equal(t, newBool(true), c.Targets[1].NoCache)
|
require.Equal(t, newBool(true), c.Targets[1].NoCache)
|
||||||
|
require.Equal(t, ptrstr("128MiB"), c.Targets[1].ShmSize)
|
||||||
|
require.Equal(t, []string{"nofile=1024:1024"}, c.Targets[1].Ulimits)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestComposeExtDedup(t *testing.T) {
|
func TestComposeExtDedup(t *testing.T) {
|
||||||
@@ -360,7 +364,7 @@ services:
|
|||||||
- type=local,dest=path/to/cache
|
- type=local,dest=path/to/cache
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
c, err := ParseCompose([]composetypes.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, []string{"ct-addon:foo", "ct-addon:baz"}, c.Targets[0].Tags)
|
require.Equal(t, []string{"ct-addon:foo", "ct-addon:baz"}, c.Targets[0].Tags)
|
||||||
@@ -393,7 +397,7 @@ services:
|
|||||||
- ` + envf.Name() + `
|
- ` + envf.Name() + `
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
c, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, map[string]*string{"CT_ECR": ptrstr("foo"), "FOO": ptrstr("bsdf -csdf"), "NODE_ENV": ptrstr("test")}, c.Targets[0].Args)
|
require.Equal(t, map[string]*string{"CT_ECR": ptrstr("foo"), "FOO": ptrstr("bsdf -csdf"), "NODE_ENV": ptrstr("test")}, c.Targets[0].Args)
|
||||||
}
|
}
|
||||||
@@ -439,7 +443,7 @@ services:
|
|||||||
published: "3306"
|
published: "3306"
|
||||||
protocol: tcp
|
protocol: tcp
|
||||||
`)
|
`)
|
||||||
_, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
_, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,7 +489,7 @@ 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([]compose.ConfigFile{{Content: []byte(`
|
_, err := ParseCompose([]composetypes.ConfigFile{{Content: []byte(`
|
||||||
services:
|
services:
|
||||||
` + tt.svc + `:
|
` + tt.svc + `:
|
||||||
build:
|
build:
|
||||||
@@ -556,7 +560,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([]compose.ConfigFile{{Content: tt.dt}}, nil)
|
_, err := ParseCompose([]composetypes.ConfigFile{{Content: tt.dt}}, nil)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
} else {
|
} else {
|
||||||
@@ -654,7 +658,7 @@ services:
|
|||||||
bar: "baz"
|
bar: "baz"
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
c, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, map[string]*string{"bar": ptrstr("baz")}, c.Targets[0].Args)
|
require.Equal(t, map[string]*string{"bar": ptrstr("baz")}, c.Targets[0].Args)
|
||||||
}
|
}
|
||||||
@@ -673,7 +677,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
`)
|
`)
|
||||||
_, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
_, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -704,7 +708,7 @@ services:
|
|||||||
|
|
||||||
chdir(t, tmpdir)
|
chdir(t, tmpdir)
|
||||||
c, err := ParseComposeFiles([]File{{
|
c, err := ParseComposeFiles([]File{{
|
||||||
Name: "compose.yml",
|
Name: "composetypes.yml",
|
||||||
Data: dt,
|
Data: dt,
|
||||||
}})
|
}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -734,7 +738,7 @@ services:
|
|||||||
- node_modules/
|
- node_modules/
|
||||||
`)
|
`)
|
||||||
|
|
||||||
_, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
_, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
357
build/build.go
357
build/build.go
@@ -4,10 +4,8 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
|
||||||
_ "crypto/sha256" // ensure digests can be computed
|
_ "crypto/sha256" // ensure digests can be computed
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -26,9 +24,11 @@ import (
|
|||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/buildx/builder"
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
|
"github.com/docker/buildx/util/confutil"
|
||||||
"github.com/docker/buildx/util/desktop"
|
"github.com/docker/buildx/util/desktop"
|
||||||
"github.com/docker/buildx/util/dockerutil"
|
"github.com/docker/buildx/util/dockerutil"
|
||||||
"github.com/docker/buildx/util/imagetools"
|
"github.com/docker/buildx/util/imagetools"
|
||||||
|
"github.com/docker/buildx/util/osutil"
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/docker/buildx/util/resolver"
|
"github.com/docker/buildx/util/resolver"
|
||||||
"github.com/docker/buildx/util/waitmap"
|
"github.com/docker/buildx/util/waitmap"
|
||||||
@@ -118,6 +118,11 @@ type NamedContext struct {
|
|||||||
State *llb.State
|
State *llb.State
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type reqForNode struct {
|
||||||
|
*resolvedNode
|
||||||
|
so *client.SolveOpt
|
||||||
|
}
|
||||||
|
|
||||||
func filterAvailableNodes(nodes []builder.Node) ([]builder.Node, error) {
|
func filterAvailableNodes(nodes []builder.Node) ([]builder.Node, error) {
|
||||||
out := make([]builder.Node, 0, len(nodes))
|
out := make([]builder.Node, 0, len(nodes))
|
||||||
err := errors.Errorf("no drivers found")
|
err := errors.Errorf("no drivers found")
|
||||||
@@ -135,218 +140,6 @@ func filterAvailableNodes(nodes []builder.Node) ([]builder.Node, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type driverPair struct {
|
|
||||||
driverIndex int
|
|
||||||
platforms []specs.Platform
|
|
||||||
so *client.SolveOpt
|
|
||||||
bopts gateway.BuildOpts
|
|
||||||
}
|
|
||||||
|
|
||||||
func driverIndexes(m map[string][]driverPair) []int {
|
|
||||||
out := make([]int, 0, len(m))
|
|
||||||
visited := map[int]struct{}{}
|
|
||||||
for _, dp := range m {
|
|
||||||
for _, d := range dp {
|
|
||||||
if _, ok := visited[d.driverIndex]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
visited[d.driverIndex] = struct{}{}
|
|
||||||
out = append(out, d.driverIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func allIndexes(l int) []int {
|
|
||||||
out := make([]int, 0, l)
|
|
||||||
for i := 0; i < l; i++ {
|
|
||||||
out = append(out, i)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func ensureBooted(ctx context.Context, nodes []builder.Node, idxs []int, pw progress.Writer) ([]*client.Client, error) {
|
|
||||||
clients := make([]*client.Client, len(nodes))
|
|
||||||
|
|
||||||
baseCtx := ctx
|
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
|
||||||
|
|
||||||
for _, i := range idxs {
|
|
||||||
func(i int) {
|
|
||||||
eg.Go(func() error {
|
|
||||||
c, err := driver.Boot(ctx, baseCtx, nodes[i].Driver, pw)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
clients[i] = c
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := eg.Wait(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return clients, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitToDriverPairs(availablePlatforms map[string]int, opt map[string]Options) map[string][]driverPair {
|
|
||||||
m := map[string][]driverPair{}
|
|
||||||
for k, opt := range opt {
|
|
||||||
mm := map[int][]specs.Platform{}
|
|
||||||
for _, p := range opt.Platforms {
|
|
||||||
k := platforms.Format(p)
|
|
||||||
idx := availablePlatforms[k] // default 0
|
|
||||||
pp := mm[idx]
|
|
||||||
pp = append(pp, p)
|
|
||||||
mm[idx] = pp
|
|
||||||
}
|
|
||||||
// if no platform is specified, use first driver
|
|
||||||
if len(mm) == 0 {
|
|
||||||
mm[0] = nil
|
|
||||||
}
|
|
||||||
dps := make([]driverPair, 0, 2)
|
|
||||||
for idx, pp := range mm {
|
|
||||||
dps = append(dps, driverPair{driverIndex: idx, platforms: pp})
|
|
||||||
}
|
|
||||||
m[k] = dps
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveDrivers(ctx context.Context, nodes []builder.Node, opt map[string]Options, pw progress.Writer) (map[string][]driverPair, []*client.Client, error) {
|
|
||||||
dps, clients, err := resolveDriversBase(ctx, nodes, opt, pw)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
bopts := make([]gateway.BuildOpts, len(clients))
|
|
||||||
|
|
||||||
span, ctx := tracing.StartSpan(ctx, "load buildkit capabilities", trace.WithSpanKind(trace.SpanKindInternal))
|
|
||||||
|
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
|
||||||
for i, c := range clients {
|
|
||||||
if c == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
func(i int, c *client.Client) {
|
|
||||||
eg.Go(func() error {
|
|
||||||
clients[i].Build(ctx, client.SolveOpt{
|
|
||||||
Internal: true,
|
|
||||||
}, "buildx", func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
|
|
||||||
bopts[i] = c.BuildOpts()
|
|
||||||
return nil, nil
|
|
||||||
}, nil)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}(i, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = eg.Wait()
|
|
||||||
tracing.FinishWithError(span, err)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
for key := range dps {
|
|
||||||
for i, dp := range dps[key] {
|
|
||||||
dps[key][i].bopts = bopts[dp.driverIndex]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dps, clients, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveDriversBase(ctx context.Context, nodes []builder.Node, opt map[string]Options, pw progress.Writer) (map[string][]driverPair, []*client.Client, error) {
|
|
||||||
availablePlatforms := map[string]int{}
|
|
||||||
for i, node := range nodes {
|
|
||||||
for _, p := range node.Platforms {
|
|
||||||
availablePlatforms[platforms.Format(p)] = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
undetectedPlatform := false
|
|
||||||
allPlatforms := map[string]struct{}{}
|
|
||||||
for _, opt := range opt {
|
|
||||||
for _, p := range opt.Platforms {
|
|
||||||
k := platforms.Format(p)
|
|
||||||
allPlatforms[k] = struct{}{}
|
|
||||||
if _, ok := availablePlatforms[k]; !ok {
|
|
||||||
undetectedPlatform = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fast path
|
|
||||||
if len(nodes) == 1 || len(allPlatforms) == 0 {
|
|
||||||
m := map[string][]driverPair{}
|
|
||||||
for k, opt := range opt {
|
|
||||||
m[k] = []driverPair{{driverIndex: 0, platforms: opt.Platforms}}
|
|
||||||
}
|
|
||||||
clients, err := ensureBooted(ctx, nodes, driverIndexes(m), pw)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return m, clients, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// map based on existing platforms
|
|
||||||
if !undetectedPlatform {
|
|
||||||
m := splitToDriverPairs(availablePlatforms, opt)
|
|
||||||
clients, err := ensureBooted(ctx, nodes, driverIndexes(m), pw)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return m, clients, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// boot all drivers in k
|
|
||||||
clients, err := ensureBooted(ctx, nodes, allIndexes(len(nodes)), pw)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
|
||||||
workers := make([][]*client.WorkerInfo, len(clients))
|
|
||||||
|
|
||||||
for i, c := range clients {
|
|
||||||
if c == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
func(i int) {
|
|
||||||
eg.Go(func() error {
|
|
||||||
ww, err := clients[i].ListWorkers(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "listing workers")
|
|
||||||
}
|
|
||||||
workers[i] = ww
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := eg.Wait(); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, ww := range workers {
|
|
||||||
for _, w := range ww {
|
|
||||||
for _, p := range w.Platforms {
|
|
||||||
p = platforms.Normalize(p)
|
|
||||||
ps := platforms.Format(p)
|
|
||||||
|
|
||||||
if _, ok := availablePlatforms[ps]; !ok {
|
|
||||||
availablePlatforms[ps] = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return splitToDriverPairs(availablePlatforms, opt), clients, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func toRepoOnly(in string) (string, error) {
|
func toRepoOnly(in string) (string, error) {
|
||||||
m := map[string]struct{}{}
|
m := map[string]struct{}{}
|
||||||
p := strings.Split(in, ",")
|
p := strings.Split(in, ",")
|
||||||
@@ -459,17 +252,21 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt Op
|
|||||||
attests[k] = *v
|
attests[k] = *v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
supportsAttestations := bopts.LLBCaps.Contains(apicaps.CapID("exporter.image.attestations")) && nodeDriver.Features(ctx)[driver.MultiPlatform]
|
|
||||||
|
supportAttestations := bopts.LLBCaps.Contains(apicaps.CapID("exporter.image.attestations")) && nodeDriver.Features(ctx)[driver.MultiPlatform]
|
||||||
if len(attests) > 0 {
|
if len(attests) > 0 {
|
||||||
if !supportsAttestations {
|
if !supportAttestations {
|
||||||
return nil, nil, errors.Errorf("attestations are not supported by the current buildkitd")
|
if !nodeDriver.Features(ctx)[driver.MultiPlatform] {
|
||||||
|
return nil, nil, notSupported("Attestation", nodeDriver, "https://docs.docker.com/go/attestations/")
|
||||||
|
}
|
||||||
|
return nil, nil, errors.Errorf("Attestations are not supported by the current BuildKit daemon")
|
||||||
}
|
}
|
||||||
for k, v := range attests {
|
for k, v := range attests {
|
||||||
so.FrontendAttrs["attest:"+k] = v
|
so.FrontendAttrs["attest:"+k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := opt.Attests["provenance"]; !ok && supportsAttestations {
|
if _, ok := opt.Attests["provenance"]; !ok && supportAttestations {
|
||||||
const noAttestEnv = "BUILDX_NO_DEFAULT_ATTESTATIONS"
|
const noAttestEnv = "BUILDX_NO_DEFAULT_ATTESTATIONS"
|
||||||
var noProv bool
|
var noProv bool
|
||||||
if v, ok := os.LookupEnv(noAttestEnv); ok {
|
if v, ok := os.LookupEnv(noAttestEnv); ok {
|
||||||
@@ -493,7 +290,9 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt Op
|
|||||||
opt.Exports = []client.ExportEntry{{Type: "image", Attrs: map[string]string{}}}
|
opt.Exports = []client.ExportEntry{{Type: "image", Attrs: map[string]string{}}}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil, nil, errors.Errorf("multiple outputs currently unsupported")
|
if err := bopts.LLBCaps.Supports(pb.CapMultipleExporters); err != nil {
|
||||||
|
return nil, nil, errors.Errorf("multiple outputs currently unsupported by the current BuildKit daemon, please upgrade to version v0.13+ or use a single output")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fill in image exporter names from tags
|
// fill in image exporter names from tags
|
||||||
@@ -562,7 +361,7 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt Op
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if !nodeDriver.Features(ctx)[driver.DockerExporter] {
|
} else if !nodeDriver.Features(ctx)[driver.DockerExporter] {
|
||||||
return nil, nil, notSupported(driver.DockerExporter, nodeDriver, "https:/docs.docker.com/go/build-exporters/")
|
return nil, nil, notSupported(driver.DockerExporter, nodeDriver, "https://docs.docker.com/go/build-exporters/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if e.Type == "image" && nodeDriver.IsMobyDriver() {
|
if e.Type == "image" && nodeDriver.IsMobyDriver() {
|
||||||
@@ -596,7 +395,7 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt Op
|
|||||||
if p, err := filepath.Abs(sharedKey); err == nil {
|
if p, err := filepath.Abs(sharedKey); err == nil {
|
||||||
sharedKey = filepath.Base(p)
|
sharedKey = filepath.Base(p)
|
||||||
}
|
}
|
||||||
so.SharedKey = sharedKey + ":" + tryNodeIdentifier(configDir)
|
so.SharedKey = sharedKey + ":" + confutil.TryNodeIdentifier(configDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opt.Pull {
|
if opt.Pull {
|
||||||
@@ -717,7 +516,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m, clients, err := resolveDrivers(ctx, nodes, opt, w)
|
drivers, err := resolveDrivers(ctx, nodes, opt, w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -731,34 +530,46 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
reqForNodes := make(map[string][]*reqForNode)
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
for k, opt := range opt {
|
for k, opt := range opt {
|
||||||
multiDriver := len(m[k]) > 1
|
multiDriver := len(drivers[k]) > 1
|
||||||
hasMobyDriver := false
|
hasMobyDriver := false
|
||||||
gitattrs, err := getGitAttributes(ctx, opt.Inputs.ContextPath, opt.Inputs.DockerfilePath)
|
gitattrs, addVCSLocalDir, err := getGitAttributes(ctx, opt.Inputs.ContextPath, opt.Inputs.DockerfilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Warn("current commit information was not captured by the build")
|
logrus.WithError(err).Warn("current commit information was not captured by the build")
|
||||||
}
|
}
|
||||||
for i, np := range m[k] {
|
var reqn []*reqForNode
|
||||||
node := nodes[np.driverIndex]
|
for _, np := range drivers[k] {
|
||||||
if node.Driver.IsMobyDriver() {
|
if np.Node().Driver.IsMobyDriver() {
|
||||||
hasMobyDriver = true
|
hasMobyDriver = true
|
||||||
}
|
}
|
||||||
opt.Platforms = np.platforms
|
opt.Platforms = np.platforms
|
||||||
so, release, err := toSolveOpt(ctx, node, multiDriver, opt, np.bopts, configDir, w, docker)
|
gatewayOpts, err := np.BuildOpts(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := saveLocalState(so, k, opt, node, configDir); err != nil {
|
so, release, err := toSolveOpt(ctx, np.Node(), multiDriver, opt, gatewayOpts, configDir, w, docker)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := saveLocalState(so, k, opt, np.Node(), configDir); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for k, v := range gitattrs {
|
for k, v := range gitattrs {
|
||||||
so.FrontendAttrs[k] = v
|
so.FrontendAttrs[k] = v
|
||||||
}
|
}
|
||||||
|
if addVCSLocalDir != nil {
|
||||||
|
addVCSLocalDir(so)
|
||||||
|
}
|
||||||
defers = append(defers, release)
|
defers = append(defers, release)
|
||||||
m[k][i].so = so
|
reqn = append(reqn, &reqForNode{
|
||||||
|
resolvedNode: np,
|
||||||
|
so: so,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
reqForNodes[k] = reqn
|
||||||
for _, at := range opt.Session {
|
for _, at := range opt.Session {
|
||||||
if s, ok := at.(interface {
|
if s, ok := at.(interface {
|
||||||
SetLogger(progresswriter.Logger)
|
SetLogger(progresswriter.Logger)
|
||||||
@@ -771,8 +582,8 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
|
|||||||
|
|
||||||
// validate for multi-node push
|
// validate for multi-node push
|
||||||
if hasMobyDriver && multiDriver {
|
if hasMobyDriver && multiDriver {
|
||||||
for _, dp := range m[k] {
|
for _, np := range reqForNodes[k] {
|
||||||
for _, e := range dp.so.Exports {
|
for _, e := range np.so.Exports {
|
||||||
if e.Type == "moby" {
|
if e.Type == "moby" {
|
||||||
if ok, _ := strconv.ParseBool(e.Attrs["push"]); ok {
|
if ok, _ := strconv.ParseBool(e.Attrs["push"]); ok {
|
||||||
return nil, errors.Errorf("multi-node push can't currently be performed with the docker driver, please switch to a different driver")
|
return nil, errors.Errorf("multi-node push can't currently be performed with the docker driver, please switch to a different driver")
|
||||||
@@ -785,12 +596,13 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
|
|||||||
|
|
||||||
// validate that all links between targets use same drivers
|
// validate that all links between targets use same drivers
|
||||||
for name := range opt {
|
for name := range opt {
|
||||||
dps := m[name]
|
dps := reqForNodes[name]
|
||||||
for _, dp := range dps {
|
for i, dp := range dps {
|
||||||
for k, v := range dp.so.FrontendAttrs {
|
so := reqForNodes[name][i].so
|
||||||
|
for k, v := range so.FrontendAttrs {
|
||||||
if strings.HasPrefix(k, "context:") && strings.HasPrefix(v, "target:") {
|
if strings.HasPrefix(k, "context:") && strings.HasPrefix(v, "target:") {
|
||||||
k2 := strings.TrimPrefix(v, "target:")
|
k2 := strings.TrimPrefix(v, "target:")
|
||||||
dps2, ok := m[k2]
|
dps2, ok := drivers[k2]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.Errorf("failed to find target %s for context %s", k2, strings.TrimPrefix(k, "context:")) // should be validated before already
|
return nil, errors.Errorf("failed to find target %s for context %s", k2, strings.TrimPrefix(k, "context:")) // should be validated before already
|
||||||
}
|
}
|
||||||
@@ -814,13 +626,13 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
|
|||||||
results := waitmap.New()
|
results := waitmap.New()
|
||||||
|
|
||||||
multiTarget := len(opt) > 1
|
multiTarget := len(opt) > 1
|
||||||
childTargets := calculateChildTargets(m, opt)
|
childTargets := calculateChildTargets(reqForNodes, opt)
|
||||||
|
|
||||||
for k, opt := range opt {
|
for k, opt := range opt {
|
||||||
err := func(k string) error {
|
err := func(k string) error {
|
||||||
opt := opt
|
opt := opt
|
||||||
dps := m[k]
|
dps := drivers[k]
|
||||||
multiDriver := len(m[k]) > 1
|
multiDriver := len(drivers[k]) > 1
|
||||||
|
|
||||||
var span trace.Span
|
var span trace.Span
|
||||||
ctx := ctx
|
ctx := ctx
|
||||||
@@ -836,8 +648,9 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
|
|||||||
var insecurePush bool
|
var insecurePush bool
|
||||||
|
|
||||||
for i, dp := range dps {
|
for i, dp := range dps {
|
||||||
i, dp, so := i, dp, *dp.so
|
i, dp := i, dp
|
||||||
node := nodes[dp.driverIndex]
|
node := dp.Node()
|
||||||
|
so := reqForNodes[k][i].so
|
||||||
if multiDriver {
|
if multiDriver {
|
||||||
for i, e := range so.Exports {
|
for i, e := range so.Exports {
|
||||||
switch e.Type {
|
switch e.Type {
|
||||||
@@ -868,11 +681,14 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
|
|||||||
|
|
||||||
pw := progress.WithPrefix(w, k, multiTarget)
|
pw := progress.WithPrefix(w, k, multiTarget)
|
||||||
|
|
||||||
c := clients[dp.driverIndex]
|
c, err := dp.Client(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
eg2.Go(func() error {
|
eg2.Go(func() error {
|
||||||
pw = progress.ResetTime(pw)
|
pw = progress.ResetTime(pw)
|
||||||
|
|
||||||
if err := waitContextDeps(ctx, dp.driverIndex, results, &so); err != nil {
|
if err := waitContextDeps(ctx, dp.driverIndex, results, so); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -949,6 +765,11 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
|
|||||||
results.Set(rKey, res)
|
results.Set(rKey, res)
|
||||||
|
|
||||||
if children, ok := childTargets[rKey]; ok && len(children) > 0 {
|
if children, ok := childTargets[rKey]; ok && len(children) > 0 {
|
||||||
|
// wait for the child targets to register their LLB before evaluating
|
||||||
|
_, err := results.Get(ctx, children...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
// we need to wait until the child targets have completed before we can release
|
// we need to wait until the child targets have completed before we can release
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
@@ -967,16 +788,16 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
|
|||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
buildRef := fmt.Sprintf("%s/%s/%s", node.Builder, node.Name, so.Ref)
|
||||||
var rr *client.SolveResponse
|
var rr *client.SolveResponse
|
||||||
if resultHandleFunc != nil {
|
if resultHandleFunc != nil {
|
||||||
var resultHandle *ResultHandle
|
var resultHandle *ResultHandle
|
||||||
resultHandle, rr, err = NewResultHandle(ctx, cc, so, "buildx", buildFunc, ch)
|
resultHandle, rr, err = NewResultHandle(ctx, cc, *so, "buildx", buildFunc, ch)
|
||||||
resultHandleFunc(dp.driverIndex, resultHandle)
|
resultHandleFunc(dp.driverIndex, resultHandle)
|
||||||
} else {
|
} else {
|
||||||
rr, err = c.Build(ctx, so, "buildx", buildFunc, ch)
|
rr, err = c.Build(ctx, *so, "buildx", buildFunc, ch)
|
||||||
}
|
}
|
||||||
if desktop.BuildBackendEnabled() && node.Driver.HistoryAPISupported(ctx) {
|
if desktop.BuildBackendEnabled() && node.Driver.HistoryAPISupported(ctx) {
|
||||||
buildRef := fmt.Sprintf("%s/%s/%s", node.Builder, node.Name, so.Ref)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &desktop.ErrorWithBuildRef{
|
return &desktop.ErrorWithBuildRef{
|
||||||
Ref: buildRef,
|
Ref: buildRef,
|
||||||
@@ -996,8 +817,9 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
|
|||||||
for k, v := range printRes {
|
for k, v := range printRes {
|
||||||
rr.ExporterResponse[k] = string(v)
|
rr.ExporterResponse[k] = string(v)
|
||||||
}
|
}
|
||||||
|
rr.ExporterResponse["buildx.build.ref"] = buildRef
|
||||||
|
|
||||||
node := nodes[dp.driverIndex].Driver
|
node := dp.Node().Driver
|
||||||
if node.IsMobyDriver() {
|
if node.IsMobyDriver() {
|
||||||
for _, e := range so.Exports {
|
for _, e := range so.Exports {
|
||||||
if e.Type == "moby" && e.Attrs["push"] != "" {
|
if e.Type == "moby" && e.Attrs["push"] != "" {
|
||||||
@@ -1089,7 +911,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
|
|||||||
if len(descs) > 0 {
|
if len(descs) > 0 {
|
||||||
var imageopt imagetools.Opt
|
var imageopt imagetools.Opt
|
||||||
for _, dp := range dps {
|
for _, dp := range dps {
|
||||||
imageopt = nodes[dp.driverIndex].ImageOpt
|
imageopt = dp.Node().ImageOpt
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
names := strings.Split(pushNames, ",")
|
names := strings.Split(pushNames, ",")
|
||||||
@@ -1338,7 +1160,7 @@ func LoadInputs(ctx context.Context, d *driver.DriverHandle, inp Inputs, pw prog
|
|||||||
target.LocalDirs["context"] = inp.ContextPath
|
target.LocalDirs["context"] = inp.ContextPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case isLocalDir(inp.ContextPath):
|
case osutil.IsLocalDir(inp.ContextPath):
|
||||||
target.LocalDirs["context"] = inp.ContextPath
|
target.LocalDirs["context"] = inp.ContextPath
|
||||||
switch inp.DockerfilePath {
|
switch inp.DockerfilePath {
|
||||||
case "-":
|
case "-":
|
||||||
@@ -1503,16 +1325,16 @@ func resultKey(index int, name string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculateChildTargets returns all the targets that depend on current target for reverse index
|
// calculateChildTargets returns all the targets that depend on current target for reverse index
|
||||||
func calculateChildTargets(drivers map[string][]driverPair, opt map[string]Options) map[string][]string {
|
func calculateChildTargets(reqs map[string][]*reqForNode, opt map[string]Options) map[string][]string {
|
||||||
out := make(map[string][]string)
|
out := make(map[string][]string)
|
||||||
for src := range opt {
|
for name := range opt {
|
||||||
dps := drivers[src]
|
dps := reqs[name]
|
||||||
for _, dp := range dps {
|
for i, dp := range dps {
|
||||||
so := *dp.so
|
so := reqs[name][i].so
|
||||||
for k, v := range so.FrontendAttrs {
|
for k, v := range so.FrontendAttrs {
|
||||||
if strings.HasPrefix(k, "context:") && strings.HasPrefix(v, "target:") {
|
if strings.HasPrefix(k, "context:") && strings.HasPrefix(v, "target:") {
|
||||||
target := resultKey(dp.driverIndex, strings.TrimPrefix(v, "target:"))
|
target := resultKey(dp.driverIndex, strings.TrimPrefix(v, "target:"))
|
||||||
out[target] = append(out[target], resultKey(dp.driverIndex, src))
|
out[target] = append(out[target], resultKey(dp.driverIndex, name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1655,31 +1477,6 @@ func handleLowercaseDockerfile(dir, p string) string {
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
var nodeIdentifierMu sync.Mutex
|
|
||||||
|
|
||||||
func tryNodeIdentifier(configDir string) (out string) {
|
|
||||||
nodeIdentifierMu.Lock()
|
|
||||||
defer nodeIdentifierMu.Unlock()
|
|
||||||
sessionFile := filepath.Join(configDir, ".buildNodeID")
|
|
||||||
if _, err := os.Lstat(sessionFile); err != nil {
|
|
||||||
if os.IsNotExist(err) { // create a new file with stored randomness
|
|
||||||
b := make([]byte, 8)
|
|
||||||
if _, err := rand.Read(b); err != nil {
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
if err := os.WriteFile(sessionFile, []byte(hex.EncodeToString(b)), 0600); err != nil {
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dt, err := os.ReadFile(sessionFile)
|
|
||||||
if err == nil {
|
|
||||||
return string(dt)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func noPrintFunc(opt map[string]Options) bool {
|
func noPrintFunc(opt map[string]Options) bool {
|
||||||
for _, v := range opt {
|
for _, v := range opt {
|
||||||
if v.PrintFunc != nil {
|
if v.PrintFunc != nil {
|
||||||
|
62
build/dial.go
Normal file
62
build/dial.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
stderrors "errors"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Dial(ctx context.Context, nodes []builder.Node, pw progress.Writer, platform *v1.Platform) (net.Conn, error) {
|
||||||
|
nodes, err := filterAvailableNodes(nodes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(nodes) == 0 {
|
||||||
|
return nil, errors.New("no nodes available")
|
||||||
|
}
|
||||||
|
|
||||||
|
var pls []v1.Platform
|
||||||
|
if platform != nil {
|
||||||
|
pls = []v1.Platform{*platform}
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := map[string]Options{"default": {Platforms: pls}}
|
||||||
|
resolved, err := resolveDrivers(ctx, nodes, opts, pw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var dialError error
|
||||||
|
for _, ls := range resolved {
|
||||||
|
for _, rn := range ls {
|
||||||
|
if platform != nil {
|
||||||
|
p := *platform
|
||||||
|
var found bool
|
||||||
|
for _, pp := range rn.platforms {
|
||||||
|
if platforms.Only(p).Match(pp) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := nodes[rn.driverIndex].Driver.Dial(ctx)
|
||||||
|
if err == nil {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
dialError = stderrors.Join(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.Wrap(dialError, "no nodes available")
|
||||||
|
}
|
312
build/driver.go
Normal file
312
build/driver.go
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/driver"
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
gateway "github.com/moby/buildkit/frontend/gateway/client"
|
||||||
|
"github.com/moby/buildkit/util/flightcontrol"
|
||||||
|
"github.com/moby/buildkit/util/tracing"
|
||||||
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
type resolvedNode struct {
|
||||||
|
resolver *nodeResolver
|
||||||
|
driverIndex int
|
||||||
|
platforms []specs.Platform
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dp resolvedNode) Node() builder.Node {
|
||||||
|
return dp.resolver.nodes[dp.driverIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dp resolvedNode) Client(ctx context.Context) (*client.Client, error) {
|
||||||
|
clients, err := dp.resolver.boot(ctx, []int{dp.driverIndex}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return clients[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dp resolvedNode) BuildOpts(ctx context.Context) (gateway.BuildOpts, error) {
|
||||||
|
opts, err := dp.resolver.opts(ctx, []int{dp.driverIndex}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return gateway.BuildOpts{}, err
|
||||||
|
}
|
||||||
|
return opts[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type matchMaker func(specs.Platform) platforms.MatchComparer
|
||||||
|
|
||||||
|
type nodeResolver struct {
|
||||||
|
nodes []builder.Node
|
||||||
|
clients flightcontrol.Group[*client.Client]
|
||||||
|
opt flightcontrol.Group[gateway.BuildOpts]
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveDrivers(ctx context.Context, nodes []builder.Node, opt map[string]Options, pw progress.Writer) (map[string][]*resolvedNode, error) {
|
||||||
|
driverRes := newDriverResolver(nodes)
|
||||||
|
drivers, err := driverRes.Resolve(ctx, opt, pw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return drivers, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDriverResolver(nodes []builder.Node) *nodeResolver {
|
||||||
|
r := &nodeResolver{
|
||||||
|
nodes: nodes,
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *nodeResolver) Resolve(ctx context.Context, opt map[string]Options, pw progress.Writer) (map[string][]*resolvedNode, error) {
|
||||||
|
if len(r.nodes) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes := map[string][]*resolvedNode{}
|
||||||
|
for k, opt := range opt {
|
||||||
|
node, perfect, err := r.resolve(ctx, opt.Platforms, pw, platforms.OnlyStrict, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !perfect {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
nodes[k] = node
|
||||||
|
}
|
||||||
|
if len(nodes) != len(opt) {
|
||||||
|
// if we didn't get a perfect match, we need to boot all drivers
|
||||||
|
allIndexes := make([]int, len(r.nodes))
|
||||||
|
for i := range allIndexes {
|
||||||
|
allIndexes[i] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
clients, err := r.boot(ctx, allIndexes, pw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
eg, egCtx := errgroup.WithContext(ctx)
|
||||||
|
workers := make([][]specs.Platform, len(clients))
|
||||||
|
for i, c := range clients {
|
||||||
|
i, c := i, c
|
||||||
|
if c == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
eg.Go(func() error {
|
||||||
|
ww, err := c.ListWorkers(egCtx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "listing workers")
|
||||||
|
}
|
||||||
|
|
||||||
|
ps := make(map[string]specs.Platform, len(ww))
|
||||||
|
for _, w := range ww {
|
||||||
|
for _, p := range w.Platforms {
|
||||||
|
pk := platforms.Format(platforms.Normalize(p))
|
||||||
|
ps[pk] = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, p := range ps {
|
||||||
|
workers[i] = append(workers[i], p)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// then we can attempt to match against all the available platforms
|
||||||
|
// (this time we don't care about imperfect matches)
|
||||||
|
nodes = map[string][]*resolvedNode{}
|
||||||
|
for k, opt := range opt {
|
||||||
|
node, _, err := r.resolve(ctx, opt.Platforms, pw, platforms.Only, func(idx int, n builder.Node) []specs.Platform {
|
||||||
|
return workers[idx]
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nodes[k] = node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
idxs := make([]int, 0, len(r.nodes))
|
||||||
|
for _, nodes := range nodes {
|
||||||
|
for _, node := range nodes {
|
||||||
|
idxs = append(idxs, node.driverIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// preload capabilities
|
||||||
|
span, ctx := tracing.StartSpan(ctx, "load buildkit capabilities", trace.WithSpanKind(trace.SpanKindInternal))
|
||||||
|
_, err := r.opts(ctx, idxs, pw)
|
||||||
|
tracing.FinishWithError(span, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *nodeResolver) resolve(ctx context.Context, ps []specs.Platform, pw progress.Writer, matcher matchMaker, additional func(idx int, n builder.Node) []specs.Platform) ([]*resolvedNode, bool, error) {
|
||||||
|
if len(r.nodes) == 0 {
|
||||||
|
return nil, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
perfect := true
|
||||||
|
nodeIdxs := make([]int, 0)
|
||||||
|
for _, p := range ps {
|
||||||
|
idx := r.get(p, matcher, additional)
|
||||||
|
if idx == -1 {
|
||||||
|
idx = 0
|
||||||
|
perfect = false
|
||||||
|
}
|
||||||
|
nodeIdxs = append(nodeIdxs, idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
var nodes []*resolvedNode
|
||||||
|
if len(nodeIdxs) == 0 {
|
||||||
|
nodes = append(nodes, &resolvedNode{
|
||||||
|
resolver: r,
|
||||||
|
driverIndex: 0,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
for i, idx := range nodeIdxs {
|
||||||
|
node := &resolvedNode{
|
||||||
|
resolver: r,
|
||||||
|
driverIndex: idx,
|
||||||
|
}
|
||||||
|
if len(ps) > 0 {
|
||||||
|
node.platforms = []specs.Platform{ps[i]}
|
||||||
|
}
|
||||||
|
nodes = append(nodes, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes = recombineNodes(nodes)
|
||||||
|
if _, err := r.boot(ctx, nodeIdxs, pw); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
return nodes, perfect, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *nodeResolver) get(p specs.Platform, matcher matchMaker, additionalPlatforms func(int, builder.Node) []specs.Platform) int {
|
||||||
|
best := -1
|
||||||
|
bestPlatform := specs.Platform{}
|
||||||
|
for i, node := range r.nodes {
|
||||||
|
platforms := node.Platforms
|
||||||
|
if additionalPlatforms != nil {
|
||||||
|
platforms = append([]specs.Platform{}, platforms...)
|
||||||
|
platforms = append(platforms, additionalPlatforms(i, node)...)
|
||||||
|
}
|
||||||
|
for _, p2 := range platforms {
|
||||||
|
m := matcher(p2)
|
||||||
|
if !m.Match(p) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if best == -1 {
|
||||||
|
best = i
|
||||||
|
bestPlatform = p2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if matcher(p2).Less(p, bestPlatform) {
|
||||||
|
best = i
|
||||||
|
bestPlatform = p2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *nodeResolver) boot(ctx context.Context, idxs []int, pw progress.Writer) ([]*client.Client, error) {
|
||||||
|
clients := make([]*client.Client, len(idxs))
|
||||||
|
|
||||||
|
baseCtx := ctx
|
||||||
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
|
for i, idx := range idxs {
|
||||||
|
i, idx := i, idx
|
||||||
|
eg.Go(func() error {
|
||||||
|
c, err := r.clients.Do(ctx, fmt.Sprint(idx), func(ctx context.Context) (*client.Client, error) {
|
||||||
|
if r.nodes[idx].Driver == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return driver.Boot(ctx, baseCtx, r.nodes[idx].Driver, pw)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
clients[i] = c
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return clients, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *nodeResolver) opts(ctx context.Context, idxs []int, pw progress.Writer) ([]gateway.BuildOpts, error) {
|
||||||
|
clients, err := r.boot(ctx, idxs, pw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bopts := make([]gateway.BuildOpts, len(clients))
|
||||||
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
|
for i, idxs := range idxs {
|
||||||
|
i, idx := i, idxs
|
||||||
|
c := clients[i]
|
||||||
|
if c == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
eg.Go(func() error {
|
||||||
|
opt, err := r.opt.Do(ctx, fmt.Sprint(idx), func(ctx context.Context) (gateway.BuildOpts, error) {
|
||||||
|
opt := gateway.BuildOpts{}
|
||||||
|
_, err := c.Build(ctx, client.SolveOpt{
|
||||||
|
Internal: true,
|
||||||
|
}, "buildx", func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
|
||||||
|
opt = c.BuildOpts()
|
||||||
|
return nil, nil
|
||||||
|
}, nil)
|
||||||
|
return opt, err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bopts[i] = opt
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bopts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// recombineDriverPairs recombines resolved nodes that are on the same driver
|
||||||
|
// back together into a single node.
|
||||||
|
func recombineNodes(nodes []*resolvedNode) []*resolvedNode {
|
||||||
|
result := make([]*resolvedNode, 0, len(nodes))
|
||||||
|
lookup := map[int]int{}
|
||||||
|
for _, node := range nodes {
|
||||||
|
if idx, ok := lookup[node.driverIndex]; ok {
|
||||||
|
result[idx].platforms = append(result[idx].platforms, node.platforms...)
|
||||||
|
} else {
|
||||||
|
lookup[node.driverIndex] = len(result)
|
||||||
|
result = append(result, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
315
build/driver_test.go
Normal file
315
build/driver_test.go
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFindDriverSanity(t *testing.T) {
|
||||||
|
r := makeTestResolver(map[string][]specs.Platform{
|
||||||
|
"aaa": {platforms.DefaultSpec()},
|
||||||
|
})
|
||||||
|
|
||||||
|
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.DefaultSpec()}, nil, platforms.OnlyStrict, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, 0, res[0].driverIndex)
|
||||||
|
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||||
|
require.Equal(t, []specs.Platform{platforms.DefaultSpec()}, res[0].platforms)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindDriverEmpty(t *testing.T) {
|
||||||
|
r := makeTestResolver(nil)
|
||||||
|
|
||||||
|
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.DefaultSpec()}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Nil(t, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindDriverWeirdName(t *testing.T) {
|
||||||
|
r := makeTestResolver(map[string][]specs.Platform{
|
||||||
|
"aaa": {platforms.MustParse("linux/amd64")},
|
||||||
|
"bbb": {platforms.MustParse("linux/foobar")},
|
||||||
|
})
|
||||||
|
|
||||||
|
// find first platform
|
||||||
|
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/foobar")}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, 1, res[0].driverIndex)
|
||||||
|
require.Equal(t, "bbb", res[0].Node().Builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindDriverUnknown(t *testing.T) {
|
||||||
|
r := makeTestResolver(map[string][]specs.Platform{
|
||||||
|
"aaa": {platforms.MustParse("linux/amd64")},
|
||||||
|
})
|
||||||
|
|
||||||
|
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/riscv64")}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, 0, res[0].driverIndex)
|
||||||
|
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectNodeSinglePlatform(t *testing.T) {
|
||||||
|
r := makeTestResolver(map[string][]specs.Platform{
|
||||||
|
"aaa": {platforms.MustParse("linux/amd64")},
|
||||||
|
"bbb": {platforms.MustParse("linux/riscv64")},
|
||||||
|
})
|
||||||
|
|
||||||
|
// find first platform
|
||||||
|
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/amd64")}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, 0, res[0].driverIndex)
|
||||||
|
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||||
|
|
||||||
|
// find second platform
|
||||||
|
res, perfect, err = r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/riscv64")}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, 1, res[0].driverIndex)
|
||||||
|
require.Equal(t, "bbb", res[0].Node().Builder)
|
||||||
|
|
||||||
|
// find an unknown platform, should match the first driver
|
||||||
|
res, perfect, err = r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/s390x")}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, 0, res[0].driverIndex)
|
||||||
|
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectNodeMultiPlatform(t *testing.T) {
|
||||||
|
r := makeTestResolver(map[string][]specs.Platform{
|
||||||
|
"aaa": {platforms.MustParse("linux/amd64"), platforms.MustParse("linux/arm64")},
|
||||||
|
"bbb": {platforms.MustParse("linux/riscv64")},
|
||||||
|
})
|
||||||
|
|
||||||
|
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/amd64")}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, 0, res[0].driverIndex)
|
||||||
|
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||||
|
|
||||||
|
res, perfect, err = r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm64")}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, 0, res[0].driverIndex)
|
||||||
|
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||||
|
|
||||||
|
res, perfect, err = r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/riscv64")}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, 1, res[0].driverIndex)
|
||||||
|
require.Equal(t, "bbb", res[0].Node().Builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectNodeNonStrict(t *testing.T) {
|
||||||
|
r := makeTestResolver(map[string][]specs.Platform{
|
||||||
|
"aaa": {platforms.MustParse("linux/amd64")},
|
||||||
|
"bbb": {platforms.MustParse("linux/arm64")},
|
||||||
|
})
|
||||||
|
|
||||||
|
// arm64 should match itself
|
||||||
|
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm64")}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, "bbb", res[0].Node().Builder)
|
||||||
|
|
||||||
|
// arm64 may support arm/v8
|
||||||
|
res, perfect, err = r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm/v8")}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, "bbb", res[0].Node().Builder)
|
||||||
|
|
||||||
|
// arm64 may support arm/v7
|
||||||
|
res, perfect, err = r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm/v7")}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, "bbb", res[0].Node().Builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectNodeNonStrictARM(t *testing.T) {
|
||||||
|
r := makeTestResolver(map[string][]specs.Platform{
|
||||||
|
"aaa": {platforms.MustParse("linux/amd64")},
|
||||||
|
"bbb": {platforms.MustParse("linux/arm64")},
|
||||||
|
"ccc": {platforms.MustParse("linux/arm/v8")},
|
||||||
|
})
|
||||||
|
|
||||||
|
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm/v8")}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, "ccc", res[0].Node().Builder)
|
||||||
|
|
||||||
|
res, perfect, err = r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm/v7")}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, "ccc", res[0].Node().Builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectNodeNonStrictLower(t *testing.T) {
|
||||||
|
r := makeTestResolver(map[string][]specs.Platform{
|
||||||
|
"aaa": {platforms.MustParse("linux/amd64")},
|
||||||
|
"bbb": {platforms.MustParse("linux/arm/v7")},
|
||||||
|
})
|
||||||
|
|
||||||
|
// v8 can't be built on v7 (so we should select the default)...
|
||||||
|
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm/v8")}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||||
|
|
||||||
|
// ...but v6 can be built on v8
|
||||||
|
res, perfect, err = r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm/v6")}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, "bbb", res[0].Node().Builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectNodePreferStart(t *testing.T) {
|
||||||
|
r := makeTestResolver(map[string][]specs.Platform{
|
||||||
|
"aaa": {platforms.MustParse("linux/amd64")},
|
||||||
|
"bbb": {platforms.MustParse("linux/riscv64")},
|
||||||
|
"ccc": {platforms.MustParse("linux/riscv64")},
|
||||||
|
})
|
||||||
|
|
||||||
|
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/riscv64")}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, "bbb", res[0].Node().Builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectNodePreferExact(t *testing.T) {
|
||||||
|
r := makeTestResolver(map[string][]specs.Platform{
|
||||||
|
"aaa": {platforms.MustParse("linux/arm/v8")},
|
||||||
|
"bbb": {platforms.MustParse("linux/arm/v7")},
|
||||||
|
})
|
||||||
|
|
||||||
|
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm/v7")}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, "bbb", res[0].Node().Builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectNodeNoPlatform(t *testing.T) {
|
||||||
|
r := makeTestResolver(map[string][]specs.Platform{
|
||||||
|
"aaa": {platforms.MustParse("linux/foobar")},
|
||||||
|
"bbb": {platforms.DefaultSpec()},
|
||||||
|
})
|
||||||
|
|
||||||
|
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||||
|
require.Empty(t, res[0].platforms)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectNodeAdditionalPlatforms(t *testing.T) {
|
||||||
|
r := makeTestResolver(map[string][]specs.Platform{
|
||||||
|
"aaa": {platforms.MustParse("linux/amd64")},
|
||||||
|
"bbb": {platforms.MustParse("linux/arm/v8")},
|
||||||
|
})
|
||||||
|
|
||||||
|
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm/v7")}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, "bbb", res[0].Node().Builder)
|
||||||
|
|
||||||
|
res, perfect, err = r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm/v7")}, nil, platforms.Only, func(idx int, n builder.Node) []specs.Platform {
|
||||||
|
if n.Builder == "aaa" {
|
||||||
|
return []specs.Platform{platforms.MustParse("linux/arm/v7")}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitNodeMultiPlatform(t *testing.T) {
|
||||||
|
r := makeTestResolver(map[string][]specs.Platform{
|
||||||
|
"aaa": {platforms.MustParse("linux/amd64"), platforms.MustParse("linux/arm64")},
|
||||||
|
"bbb": {platforms.MustParse("linux/riscv64")},
|
||||||
|
})
|
||||||
|
|
||||||
|
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{
|
||||||
|
platforms.MustParse("linux/amd64"),
|
||||||
|
platforms.MustParse("linux/arm64"),
|
||||||
|
}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 1)
|
||||||
|
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||||
|
|
||||||
|
res, perfect, err = r.resolve(context.TODO(), []specs.Platform{
|
||||||
|
platforms.MustParse("linux/amd64"),
|
||||||
|
platforms.MustParse("linux/riscv64"),
|
||||||
|
}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 2)
|
||||||
|
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||||
|
require.Equal(t, "bbb", res[1].Node().Builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitNodeMultiPlatformNoUnify(t *testing.T) {
|
||||||
|
r := makeTestResolver(map[string][]specs.Platform{
|
||||||
|
"aaa": {platforms.MustParse("linux/amd64")},
|
||||||
|
"bbb": {platforms.MustParse("linux/amd64"), platforms.MustParse("linux/riscv64")},
|
||||||
|
})
|
||||||
|
|
||||||
|
// the "best" choice would be the node with both platforms, but we're using
|
||||||
|
// a naive algorithm that doesn't try to unify the platforms
|
||||||
|
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{
|
||||||
|
platforms.MustParse("linux/amd64"),
|
||||||
|
platforms.MustParse("linux/riscv64"),
|
||||||
|
}, nil, platforms.Only, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, perfect)
|
||||||
|
require.Len(t, res, 2)
|
||||||
|
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||||
|
require.Equal(t, "bbb", res[1].Node().Builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTestResolver(nodes map[string][]specs.Platform) *nodeResolver {
|
||||||
|
var ns []builder.Node
|
||||||
|
for name, platforms := range nodes {
|
||||||
|
ns = append(ns, builder.Node{
|
||||||
|
Builder: name,
|
||||||
|
Platforms: platforms,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
sort.Slice(ns, func(i, j int) bool {
|
||||||
|
return ns[i].Builder < ns[j].Builder
|
||||||
|
})
|
||||||
|
return newDriverResolver(ns)
|
||||||
|
}
|
76
build/git.go
76
build/git.go
@@ -9,16 +9,18 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/buildx/util/gitutil"
|
"github.com/docker/buildx/util/gitutil"
|
||||||
|
"github.com/docker/buildx/util/osutil"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DockerfileLabel = "com.docker.image.source.entrypoint"
|
const DockerfileLabel = "com.docker.image.source.entrypoint"
|
||||||
|
|
||||||
func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath string) (res map[string]string, _ error) {
|
func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath string) (map[string]string, func(*client.SolveOpt), error) {
|
||||||
res = make(map[string]string)
|
res := make(map[string]string)
|
||||||
if contextPath == "" {
|
if contextPath == "" {
|
||||||
return
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
setGitLabels := false
|
setGitLabels := false
|
||||||
@@ -37,7 +39,7 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !setGitLabels && !setGitInfo {
|
if !setGitLabels && !setGitInfo {
|
||||||
return
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// figure out in which directory the git command needs to run in
|
// figure out in which directory the git command needs to run in
|
||||||
@@ -45,27 +47,32 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st
|
|||||||
if filepath.IsAbs(contextPath) {
|
if filepath.IsAbs(contextPath) {
|
||||||
wd = contextPath
|
wd = contextPath
|
||||||
} else {
|
} else {
|
||||||
cwd, _ := os.Getwd()
|
wd, _ = filepath.Abs(filepath.Join(osutil.GetWd(), contextPath))
|
||||||
wd, _ = filepath.Abs(filepath.Join(cwd, contextPath))
|
|
||||||
}
|
}
|
||||||
|
wd = osutil.SanitizePath(wd)
|
||||||
|
|
||||||
gitc, err := gitutil.New(gitutil.WithContext(ctx), gitutil.WithWorkingDir(wd))
|
gitc, err := gitutil.New(gitutil.WithContext(ctx), gitutil.WithWorkingDir(wd))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if st, err1 := os.Stat(path.Join(wd, ".git")); err1 == nil && st.IsDir() {
|
if st, err1 := os.Stat(path.Join(wd, ".git")); err1 == nil && st.IsDir() {
|
||||||
return res, errors.Wrap(err, "git was not found in the system")
|
return res, nil, errors.Wrap(err, "git was not found in the system")
|
||||||
}
|
}
|
||||||
return
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !gitc.IsInsideWorkTree() {
|
if !gitc.IsInsideWorkTree() {
|
||||||
if st, err := os.Stat(path.Join(wd, ".git")); err == nil && st.IsDir() {
|
if st, err := os.Stat(path.Join(wd, ".git")); err == nil && st.IsDir() {
|
||||||
return res, errors.New("failed to read current commit information with git rev-parse --is-inside-work-tree")
|
return res, nil, errors.New("failed to read current commit information with git rev-parse --is-inside-work-tree")
|
||||||
}
|
}
|
||||||
return res, nil
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
root, err := gitc.RootDir()
|
||||||
|
if err != nil {
|
||||||
|
return res, nil, errors.Wrap(err, "failed to get git root dir")
|
||||||
}
|
}
|
||||||
|
|
||||||
if sha, err := gitc.FullCommit(); err != nil && !gitutil.IsUnknownRevision(err) {
|
if sha, err := gitc.FullCommit(); err != nil && !gitutil.IsUnknownRevision(err) {
|
||||||
return res, errors.Wrap(err, "failed to get git commit")
|
return res, nil, errors.Wrap(err, "failed to get git commit")
|
||||||
} else if sha != "" {
|
} else if sha != "" {
|
||||||
checkDirty := false
|
checkDirty := false
|
||||||
if v, ok := os.LookupEnv("BUILDX_GIT_CHECK_DIRTY"); ok {
|
if v, ok := os.LookupEnv("BUILDX_GIT_CHECK_DIRTY"); ok {
|
||||||
@@ -93,23 +100,38 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if setGitLabels {
|
if setGitLabels && root != "" {
|
||||||
if root, err := gitc.RootDir(); err != nil {
|
if dockerfilePath == "" {
|
||||||
return res, errors.Wrap(err, "failed to get git root dir")
|
dockerfilePath = filepath.Join(wd, "Dockerfile")
|
||||||
} else if root != "" {
|
}
|
||||||
if dockerfilePath == "" {
|
if !filepath.IsAbs(dockerfilePath) {
|
||||||
dockerfilePath = filepath.Join(wd, "Dockerfile")
|
dockerfilePath = filepath.Join(osutil.GetWd(), dockerfilePath)
|
||||||
}
|
}
|
||||||
if !filepath.IsAbs(dockerfilePath) {
|
if r, err := filepath.Rel(root, dockerfilePath); err == nil && !strings.HasPrefix(r, "..") {
|
||||||
cwd, _ := os.Getwd()
|
res["label:"+DockerfileLabel] = r
|
||||||
dockerfilePath = filepath.Join(cwd, dockerfilePath)
|
|
||||||
}
|
|
||||||
dockerfilePath, _ = filepath.Rel(root, dockerfilePath)
|
|
||||||
if !strings.HasPrefix(dockerfilePath, "..") {
|
|
||||||
res["label:"+DockerfileLabel] = dockerfilePath
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return res, func(so *client.SolveOpt) {
|
||||||
|
if !setGitInfo || root == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for k, dir := range so.LocalDirs {
|
||||||
|
dir, err = filepath.EvalSymlinks(dir)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dir, err = filepath.Abs(dir)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if lp, err := osutil.GetLongPathName(dir); err == nil {
|
||||||
|
dir = lp
|
||||||
|
}
|
||||||
|
dir = osutil.SanitizePath(dir)
|
||||||
|
if r, err := filepath.Rel(root, dir); err == nil && !strings.HasPrefix(r, "..") {
|
||||||
|
so.FrontendAttrs["vcs:localdir:"+k] = r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/buildx/util/gitutil"
|
"github.com/docker/buildx/util/gitutil"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -30,7 +31,7 @@ func setupTest(tb testing.TB) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetGitAttributesNotGitRepo(t *testing.T) {
|
func TestGetGitAttributesNotGitRepo(t *testing.T) {
|
||||||
_, err := getGitAttributes(context.Background(), t.TempDir(), "Dockerfile")
|
_, _, err := getGitAttributes(context.Background(), t.TempDir(), "Dockerfile")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,14 +39,14 @@ func TestGetGitAttributesBadGitRepo(t *testing.T) {
|
|||||||
tmp := t.TempDir()
|
tmp := t.TempDir()
|
||||||
require.NoError(t, os.MkdirAll(path.Join(tmp, ".git"), 0755))
|
require.NoError(t, os.MkdirAll(path.Join(tmp, ".git"), 0755))
|
||||||
|
|
||||||
_, err := getGitAttributes(context.Background(), tmp, "Dockerfile")
|
_, _, err := getGitAttributes(context.Background(), tmp, "Dockerfile")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetGitAttributesNoContext(t *testing.T) {
|
func TestGetGitAttributesNoContext(t *testing.T) {
|
||||||
setupTest(t)
|
setupTest(t)
|
||||||
|
|
||||||
gitattrs, err := getGitAttributes(context.Background(), "", "Dockerfile")
|
gitattrs, _, err := getGitAttributes(context.Background(), "", "Dockerfile")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Empty(t, gitattrs)
|
assert.Empty(t, gitattrs)
|
||||||
}
|
}
|
||||||
@@ -114,7 +115,7 @@ func TestGetGitAttributes(t *testing.T) {
|
|||||||
if tt.envGitInfo != "" {
|
if tt.envGitInfo != "" {
|
||||||
t.Setenv("BUILDX_GIT_INFO", tt.envGitInfo)
|
t.Setenv("BUILDX_GIT_INFO", tt.envGitInfo)
|
||||||
}
|
}
|
||||||
gitattrs, err := getGitAttributes(context.Background(), ".", "Dockerfile")
|
gitattrs, _, err := getGitAttributes(context.Background(), ".", "Dockerfile")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
for _, e := range tt.expected {
|
for _, e := range tt.expected {
|
||||||
assert.Contains(t, gitattrs, e)
|
assert.Contains(t, gitattrs, e)
|
||||||
@@ -139,7 +140,7 @@ func TestGetGitAttributesDirty(t *testing.T) {
|
|||||||
require.NoError(t, os.WriteFile(filepath.Join("dir", "Dockerfile"), df, 0644))
|
require.NoError(t, os.WriteFile(filepath.Join("dir", "Dockerfile"), df, 0644))
|
||||||
|
|
||||||
t.Setenv("BUILDX_GIT_LABELS", "true")
|
t.Setenv("BUILDX_GIT_LABELS", "true")
|
||||||
gitattrs, _ := getGitAttributes(context.Background(), ".", "Dockerfile")
|
gitattrs, _, _ := getGitAttributes(context.Background(), ".", "Dockerfile")
|
||||||
assert.Equal(t, 5, len(gitattrs))
|
assert.Equal(t, 5, len(gitattrs))
|
||||||
|
|
||||||
assert.Contains(t, gitattrs, "label:"+DockerfileLabel)
|
assert.Contains(t, gitattrs, "label:"+DockerfileLabel)
|
||||||
@@ -154,3 +155,59 @@ func TestGetGitAttributesDirty(t *testing.T) {
|
|||||||
assert.Contains(t, gitattrs, "vcs:revision")
|
assert.Contains(t, gitattrs, "vcs:revision")
|
||||||
assert.True(t, strings.HasSuffix(gitattrs["vcs:revision"], "-dirty"))
|
assert.True(t, strings.HasSuffix(gitattrs["vcs:revision"], "-dirty"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLocalDirs(t *testing.T) {
|
||||||
|
setupTest(t)
|
||||||
|
|
||||||
|
so := &client.SolveOpt{
|
||||||
|
FrontendAttrs: map[string]string{},
|
||||||
|
LocalDirs: map[string]string{
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": ".",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, addVCSLocalDir, err := getGitAttributes(context.Background(), ".", "Dockerfile")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, addVCSLocalDir)
|
||||||
|
|
||||||
|
addVCSLocalDir(so)
|
||||||
|
require.Contains(t, so.FrontendAttrs, "vcs:localdir:context")
|
||||||
|
assert.Equal(t, ".", so.FrontendAttrs["vcs:localdir:context"])
|
||||||
|
require.Contains(t, so.FrontendAttrs, "vcs:localdir:dockerfile")
|
||||||
|
assert.Equal(t, ".", so.FrontendAttrs["vcs:localdir:dockerfile"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocalDirsSub(t *testing.T) {
|
||||||
|
gitutil.Mktmp(t)
|
||||||
|
|
||||||
|
c, err := gitutil.New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
gitutil.GitInit(c, t)
|
||||||
|
|
||||||
|
df := []byte("FROM alpine:latest\n")
|
||||||
|
assert.NoError(t, os.MkdirAll("app", 0755))
|
||||||
|
assert.NoError(t, os.WriteFile("app/Dockerfile", df, 0644))
|
||||||
|
|
||||||
|
gitutil.GitAdd(c, t, "app/Dockerfile")
|
||||||
|
gitutil.GitCommit(c, t, "initial commit")
|
||||||
|
gitutil.GitSetRemote(c, t, "origin", "git@github.com:docker/buildx.git")
|
||||||
|
|
||||||
|
so := &client.SolveOpt{
|
||||||
|
FrontendAttrs: map[string]string{},
|
||||||
|
LocalDirs: map[string]string{
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "app",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, addVCSLocalDir, err := getGitAttributes(context.Background(), ".", "app/Dockerfile")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, addVCSLocalDir)
|
||||||
|
|
||||||
|
addVCSLocalDir(so)
|
||||||
|
require.Contains(t, so.FrontendAttrs, "vcs:localdir:context")
|
||||||
|
assert.Equal(t, ".", so.FrontendAttrs["vcs:localdir:context"])
|
||||||
|
require.Contains(t, so.FrontendAttrs, "vcs:localdir:dockerfile")
|
||||||
|
assert.Equal(t, "app", so.FrontendAttrs["vcs:localdir:dockerfile"])
|
||||||
|
}
|
||||||
|
@@ -5,7 +5,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
@@ -34,11 +33,6 @@ func IsRemoteURL(c string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func isLocalDir(c string) bool {
|
|
||||||
st, err := os.Stat(c)
|
|
||||||
return err == nil && st.IsDir()
|
|
||||||
}
|
|
||||||
|
|
||||||
func isArchive(header []byte) bool {
|
func isArchive(header []byte) bool {
|
||||||
for _, m := range [][]byte{
|
for _, m := range [][]byte{
|
||||||
{0x42, 0x5A, 0x68}, // bzip2
|
{0x42, 0x5A, 0x68}, // bzip2
|
||||||
@@ -65,7 +59,10 @@ func toBuildkitExtraHosts(ctx context.Context, inp []string, nodeDriver *driver.
|
|||||||
}
|
}
|
||||||
hosts := make([]string, 0, len(inp))
|
hosts := make([]string, 0, len(inp))
|
||||||
for _, h := range inp {
|
for _, h := range inp {
|
||||||
host, ip, ok := strings.Cut(h, ":")
|
host, ip, ok := strings.Cut(h, "=")
|
||||||
|
if !ok {
|
||||||
|
host, ip, ok = strings.Cut(h, ":")
|
||||||
|
}
|
||||||
if !ok || host == "" || ip == "" {
|
if !ok || host == "" || ip == "" {
|
||||||
return "", errors.Errorf("invalid host %s", h)
|
return "", errors.Errorf("invalid host %s", h)
|
||||||
}
|
}
|
||||||
@@ -77,8 +74,16 @@ func toBuildkitExtraHosts(ctx context.Context, inp []string, nodeDriver *driver.
|
|||||||
return "", errors.Wrap(err, "unable to derive the IP value for host-gateway")
|
return "", errors.Wrap(err, "unable to derive the IP value for host-gateway")
|
||||||
}
|
}
|
||||||
ip = hgip.String()
|
ip = hgip.String()
|
||||||
} else if net.ParseIP(ip) == nil {
|
} else {
|
||||||
return "", errors.Errorf("invalid host %s", h)
|
// If the address is enclosed in square brackets, extract it (for IPv6, but
|
||||||
|
// permit it for IPv4 as well; we don't know the address family here, but it's
|
||||||
|
// unambiguous).
|
||||||
|
if len(ip) > 2 && ip[0] == '[' && ip[len(ip)-1] == ']' {
|
||||||
|
ip = ip[1 : len(ip)-1]
|
||||||
|
}
|
||||||
|
if net.ParseIP(ip) == nil {
|
||||||
|
return "", errors.Errorf("invalid host %s", h)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
hosts = append(hosts, host+"="+ip)
|
hosts = append(hosts, host+"="+ip)
|
||||||
}
|
}
|
||||||
|
148
build/utils_test.go
Normal file
148
build/utils_test.go
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestToBuildkitExtraHosts(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
doc string
|
||||||
|
input []string
|
||||||
|
expectedOut string // Expect output==input if not set.
|
||||||
|
expectedErr string // Expect success if not set.
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
doc: "IPv4, colon sep",
|
||||||
|
input: []string{`myhost:192.168.0.1`},
|
||||||
|
expectedOut: `myhost=192.168.0.1`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "IPv4, eq sep",
|
||||||
|
input: []string{`myhost=192.168.0.1`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "Weird but permitted, IPv4 with brackets",
|
||||||
|
input: []string{`myhost=[192.168.0.1]`},
|
||||||
|
expectedOut: `myhost=192.168.0.1`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "Host and domain",
|
||||||
|
input: []string{`host.and.domain.invalid:10.0.2.1`},
|
||||||
|
expectedOut: `host.and.domain.invalid=10.0.2.1`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "IPv6, colon sep",
|
||||||
|
input: []string{`anipv6host:2003:ab34:e::1`},
|
||||||
|
expectedOut: `anipv6host=2003:ab34:e::1`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "IPv6, colon sep, brackets",
|
||||||
|
input: []string{`anipv6host:[2003:ab34:e::1]`},
|
||||||
|
expectedOut: `anipv6host=2003:ab34:e::1`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "IPv6, eq sep, brackets",
|
||||||
|
input: []string{`anipv6host=[2003:ab34:e::1]`},
|
||||||
|
expectedOut: `anipv6host=2003:ab34:e::1`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "IPv6 localhost, colon sep",
|
||||||
|
input: []string{`ipv6local:::1`},
|
||||||
|
expectedOut: `ipv6local=::1`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "IPv6 localhost, eq sep",
|
||||||
|
input: []string{`ipv6local=::1`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "IPv6 localhost, eq sep, brackets",
|
||||||
|
input: []string{`ipv6local=[::1]`},
|
||||||
|
expectedOut: `ipv6local=::1`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "IPv6 localhost, non-canonical, colon sep",
|
||||||
|
input: []string{`ipv6local:0:0:0:0:0:0:0:1`},
|
||||||
|
expectedOut: `ipv6local=0:0:0:0:0:0:0:1`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "IPv6 localhost, non-canonical, eq sep",
|
||||||
|
input: []string{`ipv6local=0:0:0:0:0:0:0:1`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "IPv6 localhost, non-canonical, eq sep, brackets",
|
||||||
|
input: []string{`ipv6local=[0:0:0:0:0:0:0:1]`},
|
||||||
|
expectedOut: `ipv6local=0:0:0:0:0:0:0:1`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "Bad address, colon sep",
|
||||||
|
input: []string{`myhost:192.notanipaddress.1`},
|
||||||
|
expectedErr: `invalid IP address in add-host: "192.notanipaddress.1"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "Bad address, eq sep",
|
||||||
|
input: []string{`myhost=192.notanipaddress.1`},
|
||||||
|
expectedErr: `invalid IP address in add-host: "192.notanipaddress.1"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "No sep",
|
||||||
|
input: []string{`thathost-nosemicolon10.0.0.1`},
|
||||||
|
expectedErr: `bad format for add-host: "thathost-nosemicolon10.0.0.1"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "Bad IPv6",
|
||||||
|
input: []string{`anipv6host:::::1`},
|
||||||
|
expectedErr: `invalid IP address in add-host: "::::1"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "Bad IPv6, trailing colons",
|
||||||
|
input: []string{`ipv6local:::0::`},
|
||||||
|
expectedErr: `invalid IP address in add-host: "::0::"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "Bad IPv6, missing close bracket",
|
||||||
|
input: []string{`ipv6addr=[::1`},
|
||||||
|
expectedErr: `invalid IP address in add-host: "[::1"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "Bad IPv6, missing open bracket",
|
||||||
|
input: []string{`ipv6addr=::1]`},
|
||||||
|
expectedErr: `invalid IP address in add-host: "::1]"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "Missing address, colon sep",
|
||||||
|
input: []string{`myhost.invalid:`},
|
||||||
|
expectedErr: `invalid IP address in add-host: ""`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "Missing address, eq sep",
|
||||||
|
input: []string{`myhost.invalid=`},
|
||||||
|
expectedErr: `invalid IP address in add-host: ""`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "No input",
|
||||||
|
input: []string{``},
|
||||||
|
expectedErr: `bad format for add-host: ""`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
tc := tc
|
||||||
|
if tc.expectedOut == "" {
|
||||||
|
tc.expectedOut = strings.Join(tc.input, ",")
|
||||||
|
}
|
||||||
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
|
actualOut, actualErr := toBuildkitExtraHosts(context.TODO(), tc.input, nil)
|
||||||
|
if tc.expectedErr == "" {
|
||||||
|
require.Equal(t, tc.expectedOut, actualOut)
|
||||||
|
require.Nil(t, actualErr)
|
||||||
|
} else {
|
||||||
|
require.Zero(t, actualOut)
|
||||||
|
require.Error(t, actualErr, tc.expectedErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -2,19 +2,31 @@ package builder
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/csv"
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
|
k8sutil "github.com/docker/buildx/driver/kubernetes/util"
|
||||||
|
remoteutil "github.com/docker/buildx/driver/remote/util"
|
||||||
|
"github.com/docker/buildx/localstate"
|
||||||
"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/confutil"
|
||||||
"github.com/docker/buildx/util/dockerutil"
|
"github.com/docker/buildx/util/dockerutil"
|
||||||
"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"
|
||||||
|
dopts "github.com/docker/cli/opts"
|
||||||
|
"github.com/google/shlex"
|
||||||
"github.com/moby/buildkit/util/progress/progressui"
|
"github.com/moby/buildkit/util/progress/progressui"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -247,6 +259,28 @@ func (b *Builder) Factory(ctx context.Context, dialMeta map[string][]string) (_
|
|||||||
return b.driverFactory.Factory, err
|
return b.driverFactory.Factory, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Builder) MarshalJSON() ([]byte, error) {
|
||||||
|
var berr string
|
||||||
|
if b.err != nil {
|
||||||
|
berr = strings.TrimSpace(b.err.Error())
|
||||||
|
}
|
||||||
|
return json.Marshal(struct {
|
||||||
|
Name string
|
||||||
|
Driver string
|
||||||
|
LastActivity time.Time `json:",omitempty"`
|
||||||
|
Dynamic bool
|
||||||
|
Nodes []Node
|
||||||
|
Err string `json:",omitempty"`
|
||||||
|
}{
|
||||||
|
Name: b.Name,
|
||||||
|
Driver: b.Driver,
|
||||||
|
LastActivity: b.LastActivity,
|
||||||
|
Dynamic: b.Dynamic,
|
||||||
|
Nodes: b.nodes,
|
||||||
|
Err: berr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// GetBuilders returns all builders
|
// GetBuilders returns all builders
|
||||||
func GetBuilders(dockerCli command.Cli, txn *store.Txn) ([]*Builder, error) {
|
func GetBuilders(dockerCli command.Cli, txn *store.Txn) ([]*Builder, error) {
|
||||||
storeng, err := txn.List()
|
storeng, err := txn.List()
|
||||||
@@ -297,3 +331,347 @@ func GetBuilders(dockerCli command.Cli, txn *store.Txn) ([]*Builder, error) {
|
|||||||
|
|
||||||
return builders, nil
|
return builders, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateOpts struct {
|
||||||
|
Name string
|
||||||
|
Driver string
|
||||||
|
NodeName string
|
||||||
|
Platforms []string
|
||||||
|
BuildkitdFlags string
|
||||||
|
BuildkitdConfigFile string
|
||||||
|
DriverOpts []string
|
||||||
|
Use bool
|
||||||
|
Endpoint string
|
||||||
|
Append bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func Create(ctx context.Context, txn *store.Txn, dockerCli command.Cli, opts CreateOpts) (*Builder, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if opts.Name == "default" {
|
||||||
|
return nil, errors.Errorf("default is a reserved name and cannot be used to identify builder instance")
|
||||||
|
} else if opts.Append && opts.Name == "" {
|
||||||
|
return nil, errors.Errorf("append requires a builder name")
|
||||||
|
}
|
||||||
|
|
||||||
|
name := opts.Name
|
||||||
|
if name == "" {
|
||||||
|
name, err = store.GenerateName(txn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.Append {
|
||||||
|
contexts, err := dockerCli.ContextStore().List()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, c := range contexts {
|
||||||
|
if c.Name == name {
|
||||||
|
return nil, errors.Errorf("instance name %q already exists as context builder", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ng, err := txn.NodeGroupByName(name)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(errors.Cause(err)) {
|
||||||
|
if opts.Append && opts.Name != "" {
|
||||||
|
return nil, errors.Errorf("failed to find instance %q for append", opts.Name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildkitHost := os.Getenv("BUILDKIT_HOST")
|
||||||
|
|
||||||
|
driverName := opts.Driver
|
||||||
|
if driverName == "" {
|
||||||
|
if ng != nil {
|
||||||
|
driverName = ng.Driver
|
||||||
|
} else if opts.Endpoint == "" && buildkitHost != "" {
|
||||||
|
driverName = "remote"
|
||||||
|
} else {
|
||||||
|
f, err := driver.GetDefaultFactory(ctx, opts.Endpoint, dockerCli.Client(), true, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if f == nil {
|
||||||
|
return nil, errors.Errorf("no valid drivers found")
|
||||||
|
}
|
||||||
|
driverName = f.Name()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ng != nil {
|
||||||
|
if opts.NodeName == "" && !opts.Append {
|
||||||
|
return nil, errors.Errorf("existing instance for %q but no append mode, specify the node name to make changes for existing instances", name)
|
||||||
|
}
|
||||||
|
if driverName != ng.Driver {
|
||||||
|
return nil, errors.Errorf("existing instance for %q but has mismatched driver %q", name, ng.Driver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := driver.GetFactory(driverName, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOriginal := ng
|
||||||
|
if ngOriginal != nil {
|
||||||
|
ngOriginal = ngOriginal.Copy()
|
||||||
|
}
|
||||||
|
|
||||||
|
if ng == nil {
|
||||||
|
ng = &store.NodeGroup{
|
||||||
|
Name: name,
|
||||||
|
Driver: driverName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
driverOpts, err := csvToMap(opts.DriverOpts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buildkitdFlags, err := parseBuildkitdFlags(opts.BuildkitdFlags, driverName, driverOpts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ep string
|
||||||
|
var setEp bool
|
||||||
|
switch {
|
||||||
|
case driverName == "kubernetes":
|
||||||
|
if opts.Endpoint != "" {
|
||||||
|
return nil, errors.Errorf("kubernetes driver does not support endpoint args %q", opts.Endpoint)
|
||||||
|
}
|
||||||
|
// generate node name if not provided to avoid duplicated endpoint
|
||||||
|
// error: https://github.com/docker/setup-buildx-action/issues/215
|
||||||
|
nodeName := opts.NodeName
|
||||||
|
if nodeName == "" {
|
||||||
|
nodeName, err = k8sutil.GenerateNodeName(name, txn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// naming endpoint to make append works
|
||||||
|
ep = (&url.URL{
|
||||||
|
Scheme: driverName,
|
||||||
|
Path: "/" + name,
|
||||||
|
RawQuery: (&url.Values{
|
||||||
|
"deployment": {nodeName},
|
||||||
|
"kubeconfig": {os.Getenv("KUBECONFIG")},
|
||||||
|
}).Encode(),
|
||||||
|
}).String()
|
||||||
|
setEp = false
|
||||||
|
case driverName == "remote":
|
||||||
|
if opts.Endpoint != "" {
|
||||||
|
ep = opts.Endpoint
|
||||||
|
} else if buildkitHost != "" {
|
||||||
|
ep = buildkitHost
|
||||||
|
} else {
|
||||||
|
return nil, errors.Errorf("no remote endpoint provided")
|
||||||
|
}
|
||||||
|
ep, err = validateBuildkitEndpoint(ep)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
setEp = true
|
||||||
|
case opts.Endpoint != "":
|
||||||
|
ep, err = validateEndpoint(dockerCli, opts.Endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
setEp = true
|
||||||
|
default:
|
||||||
|
if dockerCli.CurrentContext() == "default" && dockerCli.DockerEndpoint().TLSData != nil {
|
||||||
|
return nil, 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 context set to <context-name>")
|
||||||
|
}
|
||||||
|
ep, err = dockerutil.GetCurrentEndpoint(dockerCli)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
setEp = false
|
||||||
|
}
|
||||||
|
|
||||||
|
buildkitdConfigFile := opts.BuildkitdConfigFile
|
||||||
|
if buildkitdConfigFile == "" {
|
||||||
|
// if buildkit daemon config is not provided, check if the default one
|
||||||
|
// is available and use it
|
||||||
|
if f, ok := confutil.DefaultConfigFile(dockerCli); ok {
|
||||||
|
buildkitdConfigFile = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ng.Update(opts.NodeName, ep, opts.Platforms, setEp, opts.Append, buildkitdFlags, buildkitdConfigFile, driverOpts); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := txn.Save(ng); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := New(dockerCli,
|
||||||
|
WithName(ng.Name),
|
||||||
|
WithStore(txn),
|
||||||
|
WithSkippedValidation(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
nodes, err := b.LoadNodes(timeoutCtx, WithData())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, node := range nodes {
|
||||||
|
if err := node.Err; err != nil {
|
||||||
|
err := errors.Errorf("failed to initialize builder %s (%s): %s", ng.Name, node.Name, err)
|
||||||
|
var err2 error
|
||||||
|
if ngOriginal == nil {
|
||||||
|
err2 = txn.Remove(ng.Name)
|
||||||
|
} else {
|
||||||
|
err2 = txn.Save(ngOriginal)
|
||||||
|
}
|
||||||
|
if err2 != nil {
|
||||||
|
return nil, errors.Errorf("could not rollback to previous state: %s", err2)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Use && ep != "" {
|
||||||
|
current, err := dockerutil.GetCurrentEndpoint(dockerCli)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := txn.SetCurrent(current, ng.Name, false, false); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type LeaveOpts struct {
|
||||||
|
Name string
|
||||||
|
NodeName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Leave(ctx context.Context, txn *store.Txn, dockerCli command.Cli, opts LeaveOpts) error {
|
||||||
|
if opts.Name == "" {
|
||||||
|
return errors.Errorf("leave requires instance name")
|
||||||
|
}
|
||||||
|
if opts.NodeName == "" {
|
||||||
|
return errors.Errorf("leave requires node name")
|
||||||
|
}
|
||||||
|
|
||||||
|
ng, err := txn.NodeGroupByName(opts.Name)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(errors.Cause(err)) {
|
||||||
|
return errors.Errorf("failed to find instance %q for leave", opts.Name)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ng.Leave(opts.NodeName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ls, err := localstate.New(confutil.ConfigDir(dockerCli))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ls.RemoveBuilderNode(ng.Name, opts.NodeName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return txn.Save(ng)
|
||||||
|
}
|
||||||
|
|
||||||
|
func csvToMap(in []string) (map[string]string, error) {
|
||||||
|
if len(in) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
m := make(map[string]string, len(in))
|
||||||
|
for _, s := range in {
|
||||||
|
csvReader := csv.NewReader(strings.NewReader(s))
|
||||||
|
fields, err := csvReader.Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, v := range fields {
|
||||||
|
p := strings.SplitN(v, "=", 2)
|
||||||
|
if len(p) != 2 {
|
||||||
|
return nil, errors.Errorf("invalid value %q, expecting k=v", v)
|
||||||
|
}
|
||||||
|
m[p[0]] = p[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseBuildkitdFlags parses buildkit flags
|
||||||
|
func parseBuildkitdFlags(inp string, driver string, driverOpts map[string]string) (res []string, err error) {
|
||||||
|
if inp != "" {
|
||||||
|
res, err = shlex.Split(inp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to parse buildkit flags")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var allowInsecureEntitlements []string
|
||||||
|
flags := pflag.NewFlagSet("buildkitd", pflag.ContinueOnError)
|
||||||
|
flags.Usage = func() {}
|
||||||
|
flags.StringArrayVar(&allowInsecureEntitlements, "allow-insecure-entitlement", nil, "")
|
||||||
|
_ = flags.Parse(res)
|
||||||
|
|
||||||
|
var hasNetworkHostEntitlement bool
|
||||||
|
for _, e := range allowInsecureEntitlements {
|
||||||
|
if e == "network.host" {
|
||||||
|
hasNetworkHostEntitlement = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := driverOpts["network"]; ok && v == "host" && !hasNetworkHostEntitlement && driver == "docker-container" {
|
||||||
|
// always set network.host entitlement if user has set network=host
|
||||||
|
res = append(res, "--allow-insecure-entitlement=network.host")
|
||||||
|
} else if len(allowInsecureEntitlements) == 0 && (driver == "kubernetes" || driver == "docker-container") {
|
||||||
|
// set network.host entitlement if user does not provide any as
|
||||||
|
// network is isolated for container drivers.
|
||||||
|
res = append(res, "--allow-insecure-entitlement=network.host")
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
139
builder/builder_test.go
Normal file
139
builder/builder_test.go
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCsvToMap(t *testing.T) {
|
||||||
|
d := []string{
|
||||||
|
"\"tolerations=key=foo,value=bar;key=foo2,value=bar2\",replicas=1",
|
||||||
|
"namespace=default",
|
||||||
|
}
|
||||||
|
r, err := csvToMap(d)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Contains(t, r, "tolerations")
|
||||||
|
require.Equal(t, r["tolerations"], "key=foo,value=bar;key=foo2,value=bar2")
|
||||||
|
|
||||||
|
require.Contains(t, r, "replicas")
|
||||||
|
require.Equal(t, r["replicas"], "1")
|
||||||
|
|
||||||
|
require.Contains(t, r, "namespace")
|
||||||
|
require.Equal(t, r["namespace"], "default")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseBuildkitdFlags(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
flags string
|
||||||
|
driver string
|
||||||
|
driverOpts map[string]string
|
||||||
|
expected []string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"docker-container no flags",
|
||||||
|
"",
|
||||||
|
"docker-container",
|
||||||
|
nil,
|
||||||
|
[]string{
|
||||||
|
"--allow-insecure-entitlement=network.host",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kubernetes no flags",
|
||||||
|
"",
|
||||||
|
"kubernetes",
|
||||||
|
nil,
|
||||||
|
[]string{
|
||||||
|
"--allow-insecure-entitlement=network.host",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"remote no flags",
|
||||||
|
"",
|
||||||
|
"remote",
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"docker-container with insecure flag",
|
||||||
|
"--allow-insecure-entitlement=security.insecure",
|
||||||
|
"docker-container",
|
||||||
|
nil,
|
||||||
|
[]string{
|
||||||
|
"--allow-insecure-entitlement=security.insecure",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"docker-container with insecure and host flag",
|
||||||
|
"--allow-insecure-entitlement=network.host --allow-insecure-entitlement=security.insecure",
|
||||||
|
"docker-container",
|
||||||
|
nil,
|
||||||
|
[]string{
|
||||||
|
"--allow-insecure-entitlement=network.host",
|
||||||
|
"--allow-insecure-entitlement=security.insecure",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"docker-container with network host opt",
|
||||||
|
"",
|
||||||
|
"docker-container",
|
||||||
|
map[string]string{"network": "host"},
|
||||||
|
[]string{
|
||||||
|
"--allow-insecure-entitlement=network.host",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"docker-container with host flag and network host opt",
|
||||||
|
"--allow-insecure-entitlement=network.host",
|
||||||
|
"docker-container",
|
||||||
|
map[string]string{"network": "host"},
|
||||||
|
[]string{
|
||||||
|
"--allow-insecure-entitlement=network.host",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"docker-container with insecure, host flag and network host opt",
|
||||||
|
"--allow-insecure-entitlement=network.host --allow-insecure-entitlement=security.insecure",
|
||||||
|
"docker-container",
|
||||||
|
map[string]string{"network": "host"},
|
||||||
|
[]string{
|
||||||
|
"--allow-insecure-entitlement=network.host",
|
||||||
|
"--allow-insecure-entitlement=security.insecure",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"error parsing flags",
|
||||||
|
"foo'",
|
||||||
|
"docker-container",
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range testCases {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
flags, err := parseBuildkitdFlags(tt.flags, tt.driver, tt.driverOpts)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expected, flags)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -2,8 +2,11 @@ package builder
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
ctxkube "github.com/docker/buildx/driver/kubernetes/context"
|
ctxkube "github.com/docker/buildx/driver/kubernetes/context"
|
||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/store"
|
||||||
@@ -139,7 +142,7 @@ func (b *Builder) LoadNodes(ctx context.Context, opts ...LoadNodesOption) (_ []N
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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, lno.dialMeta)
|
d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, factory, n.Endpoint, dockerapi, imageopt.Auth, kcc, n.BuildkitdFlags, n.Files, n.DriverOpts, n.Platforms, b.opts.contextPathHash, lno.dialMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
node.Err = err
|
node.Err = err
|
||||||
return nil
|
return nil
|
||||||
@@ -199,6 +202,51 @@ func (b *Builder) LoadNodes(ctx context.Context, opts ...LoadNodesOption) (_ []N
|
|||||||
return b.nodes, nil
|
return b.nodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *Node) MarshalJSON() ([]byte, error) {
|
||||||
|
var status string
|
||||||
|
if n.DriverInfo != nil {
|
||||||
|
status = n.DriverInfo.Status.String()
|
||||||
|
}
|
||||||
|
var nerr string
|
||||||
|
if n.Err != nil {
|
||||||
|
status = "error"
|
||||||
|
nerr = strings.TrimSpace(n.Err.Error())
|
||||||
|
}
|
||||||
|
var pp []string
|
||||||
|
for _, p := range n.Platforms {
|
||||||
|
pp = append(pp, platforms.Format(p))
|
||||||
|
}
|
||||||
|
return json.Marshal(struct {
|
||||||
|
Name string
|
||||||
|
Endpoint string
|
||||||
|
BuildkitdFlags []string `json:"Flags,omitempty"`
|
||||||
|
DriverOpts map[string]string `json:",omitempty"`
|
||||||
|
Files map[string][]byte `json:",omitempty"`
|
||||||
|
Status string `json:",omitempty"`
|
||||||
|
ProxyConfig map[string]string `json:",omitempty"`
|
||||||
|
Version string `json:",omitempty"`
|
||||||
|
Err string `json:",omitempty"`
|
||||||
|
IDs []string `json:",omitempty"`
|
||||||
|
Platforms []string `json:",omitempty"`
|
||||||
|
GCPolicy []client.PruneInfo `json:",omitempty"`
|
||||||
|
Labels map[string]string `json:",omitempty"`
|
||||||
|
}{
|
||||||
|
Name: n.Name,
|
||||||
|
Endpoint: n.Endpoint,
|
||||||
|
BuildkitdFlags: n.BuildkitdFlags,
|
||||||
|
DriverOpts: n.DriverOpts,
|
||||||
|
Files: n.Files,
|
||||||
|
Status: status,
|
||||||
|
ProxyConfig: n.ProxyConfig,
|
||||||
|
Version: n.Version,
|
||||||
|
Err: nerr,
|
||||||
|
IDs: n.IDs,
|
||||||
|
Platforms: pp,
|
||||||
|
GCPolicy: n.GCPolicy,
|
||||||
|
Labels: n.Labels,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (n *Node) loadData(ctx context.Context) error {
|
func (n *Node) loadData(ctx context.Context) error {
|
||||||
if n.Driver == nil {
|
if n.Driver == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@@ -23,7 +23,6 @@ import (
|
|||||||
"github.com/docker/buildx/util/tracing"
|
"github.com/docker/buildx/util/tracing"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/moby/buildkit/identity"
|
"github.com/moby/buildkit/identity"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
|
||||||
"github.com/moby/buildkit/util/progress/progressui"
|
"github.com/moby/buildkit/util/progress/progressui"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -42,9 +41,7 @@ type bakeOptions struct {
|
|||||||
exportLoad bool
|
exportLoad bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags commonFlags) (err error) {
|
func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in bakeOptions, cFlags commonFlags) (err error) {
|
||||||
ctx := appcontext.Context()
|
|
||||||
|
|
||||||
ctx, end, err := tracing.TraceCurrentCommand(ctx, "bake")
|
ctx, end, err := tracing.TraceCurrentCommand(ctx, "bake")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -141,7 +138,7 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
err = err1
|
err = err1
|
||||||
}
|
}
|
||||||
if err == nil && progressMode != progressui.QuietMode {
|
if err == nil && progressMode != progressui.QuietMode && progressMode != progressui.RawJSONMode {
|
||||||
desktop.PrintBuildDetails(os.Stderr, printer.BuildRefs(), term)
|
desktop.PrintBuildDetails(os.Stderr, printer.BuildRefs(), term)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -266,7 +263,7 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
options.builder = rootOpts.builder
|
options.builder = rootOpts.builder
|
||||||
options.metadataFile = cFlags.metadataFile
|
options.metadataFile = cFlags.metadataFile
|
||||||
// Other common flags (noCache, pull and progress) are processed in runBake function.
|
// Other common flags (noCache, pull and progress) are processed in runBake function.
|
||||||
return runBake(dockerCli, args, options, cFlags)
|
return runBake(cmd.Context(), dockerCli, args, options, cFlags)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.BakeTargets(options.files),
|
ValidArgsFunction: completion.BakeTargets(options.files),
|
||||||
}
|
}
|
||||||
@@ -295,13 +292,17 @@ func saveLocalStateGroup(dockerCli command.Cli, ref string, lsg localstate.State
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readBakeFiles(ctx context.Context, nodes []builder.Node, url string, names []string, stdin io.Reader, pw progress.Writer) (files []bake.File, inp *bake.Input, err error) {
|
func readBakeFiles(ctx context.Context, nodes []builder.Node, url string, names []string, stdin io.Reader, pw progress.Writer) (files []bake.File, inp *bake.Input, err error) {
|
||||||
var lnames []string
|
var lnames []string // local
|
||||||
var rnames []string
|
var rnames []string // remote
|
||||||
|
var anames []string // both
|
||||||
for _, v := range names {
|
for _, v := range names {
|
||||||
if strings.HasPrefix(v, "cwd://") {
|
if strings.HasPrefix(v, "cwd://") {
|
||||||
lnames = append(lnames, strings.TrimPrefix(v, "cwd://"))
|
tname := strings.TrimPrefix(v, "cwd://")
|
||||||
|
lnames = append(lnames, tname)
|
||||||
|
anames = append(anames, tname)
|
||||||
} else {
|
} else {
|
||||||
rnames = append(rnames, v)
|
rnames = append(rnames, v)
|
||||||
|
anames = append(anames, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,7 +321,7 @@ func readBakeFiles(ctx context.Context, nodes []builder.Node, url string, names
|
|||||||
if url != "" {
|
if url != "" {
|
||||||
lfiles, err = bake.ReadLocalFiles(lnames, stdin, sub)
|
lfiles, err = bake.ReadLocalFiles(lnames, stdin, sub)
|
||||||
} else {
|
} else {
|
||||||
lfiles, err = bake.ReadLocalFiles(append(lnames, rnames...), stdin, sub)
|
lfiles, err = bake.ReadLocalFiles(anames, stdin, sub)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
@@ -3,8 +3,10 @@ package commands
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -13,6 +15,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/containerd/console"
|
"github.com/containerd/console"
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/build"
|
||||||
@@ -27,8 +31,12 @@ import (
|
|||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/store"
|
||||||
"github.com/docker/buildx/store/storeutil"
|
"github.com/docker/buildx/store/storeutil"
|
||||||
"github.com/docker/buildx/util/buildflags"
|
"github.com/docker/buildx/util/buildflags"
|
||||||
|
"github.com/docker/buildx/util/cobrautil"
|
||||||
|
"github.com/docker/buildx/util/confutil"
|
||||||
"github.com/docker/buildx/util/desktop"
|
"github.com/docker/buildx/util/desktop"
|
||||||
"github.com/docker/buildx/util/ioset"
|
"github.com/docker/buildx/util/ioset"
|
||||||
|
"github.com/docker/buildx/util/metricutil"
|
||||||
|
"github.com/docker/buildx/util/osutil"
|
||||||
"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-docs-tool/annotation"
|
"github.com/docker/cli-docs-tool/annotation"
|
||||||
@@ -43,7 +51,6 @@ import (
|
|||||||
"github.com/moby/buildkit/frontend/subrequests/outline"
|
"github.com/moby/buildkit/frontend/subrequests/outline"
|
||||||
"github.com/moby/buildkit/frontend/subrequests/targets"
|
"github.com/moby/buildkit/frontend/subrequests/targets"
|
||||||
"github.com/moby/buildkit/solver/errdefs"
|
"github.com/moby/buildkit/solver/errdefs"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
|
||||||
"github.com/moby/buildkit/util/grpcerrors"
|
"github.com/moby/buildkit/util/grpcerrors"
|
||||||
"github.com/moby/buildkit/util/progress/progressui"
|
"github.com/moby/buildkit/util/progress/progressui"
|
||||||
"github.com/morikuni/aec"
|
"github.com/morikuni/aec"
|
||||||
@@ -51,6 +58,8 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/metric"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -210,8 +219,59 @@ func (o *buildOptions) toDisplayMode() (progressui.DisplayMode, error) {
|
|||||||
return progress, nil
|
return progress, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
|
func buildMetricAttributes(dockerCli command.Cli, b *builder.Builder, options *buildOptions) attribute.Set {
|
||||||
ctx := appcontext.Context()
|
return attribute.NewSet(
|
||||||
|
attribute.String("command.name", "build"),
|
||||||
|
attribute.Stringer("command.options.hash", &buildOptionsHash{
|
||||||
|
buildOptions: options,
|
||||||
|
configDir: confutil.ConfigDir(dockerCli),
|
||||||
|
}),
|
||||||
|
attribute.String("driver.name", options.builder),
|
||||||
|
attribute.String("driver.type", b.Driver),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildOptionsHash computes a hash for the buildOptions when the String method is invoked.
|
||||||
|
// This is done so we can delay the computation of the hash until needed by OTEL using
|
||||||
|
// the fmt.Stringer interface.
|
||||||
|
type buildOptionsHash struct {
|
||||||
|
*buildOptions
|
||||||
|
configDir string
|
||||||
|
result string
|
||||||
|
resultOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *buildOptionsHash) String() string {
|
||||||
|
o.resultOnce.Do(func() {
|
||||||
|
target := o.target
|
||||||
|
contextPath := o.contextPath
|
||||||
|
dockerfile := o.dockerfileName
|
||||||
|
if dockerfile == "" {
|
||||||
|
dockerfile = "Dockerfile"
|
||||||
|
}
|
||||||
|
|
||||||
|
if contextPath != "-" && osutil.IsLocalDir(contextPath) {
|
||||||
|
contextPath = osutil.ToAbs(contextPath)
|
||||||
|
}
|
||||||
|
salt := confutil.TryNodeIdentifier(o.configDir)
|
||||||
|
|
||||||
|
h := sha256.New()
|
||||||
|
for _, s := range []string{target, contextPath, dockerfile, salt} {
|
||||||
|
_, _ = io.WriteString(h, s)
|
||||||
|
h.Write([]byte{0})
|
||||||
|
}
|
||||||
|
o.result = hex.EncodeToString(h.Sum(nil))
|
||||||
|
})
|
||||||
|
return o.result
|
||||||
|
}
|
||||||
|
|
||||||
|
func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) (err error) {
|
||||||
|
mp, err := metricutil.NewMeterProvider(ctx, dockerCli)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer mp.Report(context.Background())
|
||||||
|
|
||||||
ctx, end, err := tracing.TraceCurrentCommand(ctx, "build")
|
ctx, end, err := tracing.TraceCurrentCommand(ctx, "build")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -252,6 +312,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
|
|||||||
if _, err := console.ConsoleFromFile(os.Stderr); err == nil {
|
if _, err := console.ConsoleFromFile(os.Stderr); err == nil {
|
||||||
term = true
|
term = true
|
||||||
}
|
}
|
||||||
|
attributes := buildMetricAttributes(dockerCli, b, &options)
|
||||||
|
|
||||||
ctx2, cancel := context.WithCancel(context.TODO())
|
ctx2, cancel := context.WithCancel(context.TODO())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -265,6 +326,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
|
|||||||
fmt.Sprintf("building with %q instance using %s driver", b.Name, b.Driver),
|
fmt.Sprintf("building with %q instance using %s driver", b.Name, b.Driver),
|
||||||
fmt.Sprintf("%s:%s", b.Driver, b.Name),
|
fmt.Sprintf("%s:%s", b.Driver, b.Name),
|
||||||
),
|
),
|
||||||
|
progress.WithMetrics(mp, attributes),
|
||||||
progress.WithOnClose(func() {
|
progress.WithOnClose(func() {
|
||||||
printWarnings(os.Stderr, printer.Warnings(), progressMode)
|
printWarnings(os.Stderr, printer.Warnings(), progressMode)
|
||||||
}),
|
}),
|
||||||
@@ -273,6 +335,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
done := timeBuildCommand(mp, attributes)
|
||||||
var resp *client.SolveResponse
|
var resp *client.SolveResponse
|
||||||
var retErr error
|
var retErr error
|
||||||
if isExperimental() {
|
if isExperimental() {
|
||||||
@@ -284,14 +347,19 @@ func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
|
|||||||
if err := printer.Wait(); retErr == nil {
|
if err := printer.Wait(); retErr == nil {
|
||||||
retErr = err
|
retErr = err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
done(retErr)
|
||||||
if retErr != nil {
|
if retErr != nil {
|
||||||
return retErr
|
return retErr
|
||||||
}
|
}
|
||||||
|
|
||||||
if progressMode != progressui.QuietMode {
|
switch progressMode {
|
||||||
desktop.PrintBuildDetails(os.Stderr, printer.BuildRefs(), term)
|
case progressui.RawJSONMode:
|
||||||
} else {
|
// no additional display
|
||||||
|
case progressui.QuietMode:
|
||||||
fmt.Println(getImageID(resp.ExporterResponse))
|
fmt.Println(getImageID(resp.ExporterResponse))
|
||||||
|
default:
|
||||||
|
desktop.PrintBuildDetails(os.Stderr, printer.BuildRefs(), term)
|
||||||
}
|
}
|
||||||
if options.imageIDFile != "" {
|
if options.imageIDFile != "" {
|
||||||
if err := os.WriteFile(options.imageIDFile, []byte(getImageID(resp.ExporterResponse)), 0644); err != nil {
|
if err := os.WriteFile(options.imageIDFile, []byte(getImageID(resp.ExporterResponse)), 0644); err != nil {
|
||||||
@@ -472,7 +540,7 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugConfig *debug.D
|
|||||||
options.invokeConfig = iConfig
|
options.invokeConfig = iConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
return runBuild(dockerCli, *options)
|
return runBuild(cmd.Context(), dockerCli, *options)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
return nil, cobra.ShellCompDirectiveFilterDirs
|
return nil, cobra.ShellCompDirectiveFilterDirs
|
||||||
@@ -523,7 +591,7 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugConfig *debug.D
|
|||||||
|
|
||||||
if isExperimental() {
|
if isExperimental() {
|
||||||
flags.StringVar(&options.printFunc, "print", "", "Print result of information request (e.g., outline, targets)")
|
flags.StringVar(&options.printFunc, "print", "", "Print result of information request (e.g., outline, targets)")
|
||||||
flags.SetAnnotation("print", "experimentalCLI", nil)
|
cobrautil.MarkFlagsExperimental(flags, "print")
|
||||||
}
|
}
|
||||||
|
|
||||||
flags.BoolVar(&options.exportPush, "push", false, `Shorthand for "--output=type=registry"`)
|
flags.BoolVar(&options.exportPush, "push", false, `Shorthand for "--output=type=registry"`)
|
||||||
@@ -532,7 +600,7 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugConfig *debug.D
|
|||||||
|
|
||||||
flags.StringArrayVar(&options.secrets, "secret", []string{}, `Secret to expose to the build (format: "id=mysecret[,src=/local/secret]")`)
|
flags.StringArrayVar(&options.secrets, "secret", []string{}, `Secret to expose to the build (format: "id=mysecret[,src=/local/secret]")`)
|
||||||
|
|
||||||
flags.Var(&options.shmSize, "shm-size", `Size of "/dev/shm"`)
|
flags.Var(&options.shmSize, "shm-size", `Shared memory size for build containers`)
|
||||||
|
|
||||||
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>]]")`)
|
||||||
|
|
||||||
@@ -552,11 +620,9 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugConfig *debug.D
|
|||||||
if isExperimental() {
|
if isExperimental() {
|
||||||
// TODO: move this to debug command if needed
|
// TODO: move this to debug command if needed
|
||||||
flags.StringVar(&options.Root, "root", "", "Specify root directory of server to connect")
|
flags.StringVar(&options.Root, "root", "", "Specify root directory of server to connect")
|
||||||
flags.SetAnnotation("root", "experimentalCLI", nil)
|
|
||||||
flags.BoolVar(&options.Detach, "detach", false, "Detach buildx server (supported only on linux)")
|
flags.BoolVar(&options.Detach, "detach", false, "Detach buildx server (supported only on linux)")
|
||||||
flags.SetAnnotation("detach", "experimentalCLI", nil)
|
|
||||||
flags.StringVar(&options.ServerConfig, "server-config", "", "Specify buildx server config file (used only when launching new server)")
|
flags.StringVar(&options.ServerConfig, "server-config", "", "Specify buildx server config file (used only when launching new server)")
|
||||||
flags.SetAnnotation("server-config", "experimentalCLI", nil)
|
cobrautil.MarkFlagsExperimental(flags, "root", "detach", "server-config")
|
||||||
}
|
}
|
||||||
|
|
||||||
// hidden flags
|
// hidden flags
|
||||||
@@ -579,7 +645,7 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugConfig *debug.D
|
|||||||
flags.BoolVar(&ignoreBool, "squash", false, "Squash newly built layers into a single new layer")
|
flags.BoolVar(&ignoreBool, "squash", false, "Squash newly built layers into a single new layer")
|
||||||
flags.MarkHidden("squash")
|
flags.MarkHidden("squash")
|
||||||
flags.SetAnnotation("squash", "flag-warn", []string{"experimental flag squash is removed with BuildKit. You should squash inside build using a multi-stage Dockerfile for efficiency."})
|
flags.SetAnnotation("squash", "flag-warn", []string{"experimental flag squash is removed with BuildKit. You should squash inside build using a multi-stage Dockerfile for efficiency."})
|
||||||
flags.SetAnnotation("squash", "experimentalCLI", nil)
|
cobrautil.MarkFlagsExperimental(flags, "squash")
|
||||||
|
|
||||||
flags.StringVarP(&ignore, "memory", "m", "", "Memory limit")
|
flags.StringVarP(&ignore, "memory", "m", "", "Memory limit")
|
||||||
flags.MarkHidden("memory")
|
flags.MarkHidden("memory")
|
||||||
@@ -749,7 +815,7 @@ func dockerUlimitToControllerUlimit(u *dockeropts.UlimitOpt) *controllerapi.Ulim
|
|||||||
}
|
}
|
||||||
|
|
||||||
func printWarnings(w io.Writer, warnings []client.VertexWarning, mode progressui.DisplayMode) {
|
func printWarnings(w io.Writer, warnings []client.VertexWarning, mode progressui.DisplayMode) {
|
||||||
if len(warnings) == 0 || mode == progressui.QuietMode {
|
if len(warnings) == 0 || mode == progressui.QuietMode || mode == progressui.RawJSONMode {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "\n ")
|
fmt.Fprintf(w, "\n ")
|
||||||
@@ -926,3 +992,38 @@ func maybeJSONArray(v string) []string {
|
|||||||
}
|
}
|
||||||
return []string{v}
|
return []string{v}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// timeBuildCommand will start a timer for timing the build command. It records the time when the returned
|
||||||
|
// function is invoked into a metric.
|
||||||
|
func timeBuildCommand(mp metric.MeterProvider, attrs attribute.Set) func(err error) {
|
||||||
|
meter := metricutil.Meter(mp)
|
||||||
|
counter, _ := meter.Float64Counter("command.time",
|
||||||
|
metric.WithDescription("Measures the duration of the build command."),
|
||||||
|
metric.WithUnit("ms"),
|
||||||
|
)
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
return func(err error) {
|
||||||
|
dur := float64(time.Since(start)) / float64(time.Millisecond)
|
||||||
|
extraAttrs := attribute.NewSet()
|
||||||
|
if err != nil {
|
||||||
|
extraAttrs = attribute.NewSet(
|
||||||
|
attribute.String("error.type", otelErrorType(err)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
counter.Add(context.Background(), dur,
|
||||||
|
metric.WithAttributeSet(attrs),
|
||||||
|
metric.WithAttributeSet(extraAttrs),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// otelErrorType returns an attribute for the error type based on the error category.
|
||||||
|
// If nil, this function returns an invalid attribute.
|
||||||
|
func otelErrorType(err error) string {
|
||||||
|
name := "generic"
|
||||||
|
if errors.Is(err, context.Canceled) {
|
||||||
|
name = "canceled"
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
@@ -3,71 +3,34 @@ package commands
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/csv"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/buildx/builder"
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
k8sutil "github.com/docker/buildx/driver/kubernetes/util"
|
|
||||||
remoteutil "github.com/docker/buildx/driver/remote/util"
|
|
||||||
"github.com/docker/buildx/localstate"
|
|
||||||
"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/cobrautil/completion"
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
"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/moby/buildkit/util/appcontext"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
type createOptions struct {
|
type createOptions struct {
|
||||||
name string
|
name string
|
||||||
driver string
|
driver string
|
||||||
nodeName string
|
nodeName string
|
||||||
platform []string
|
platform []string
|
||||||
actionAppend bool
|
actionAppend bool
|
||||||
actionLeave bool
|
actionLeave bool
|
||||||
use bool
|
use bool
|
||||||
flags string
|
driverOpts []string
|
||||||
configFile string
|
buildkitdFlags string
|
||||||
driverOpts []string
|
buildkitdConfigFile string
|
||||||
bootstrap bool
|
bootstrap bool
|
||||||
// upgrade bool // perform upgrade of the driver
|
// upgrade bool // perform upgrade of the driver
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
func runCreate(ctx context.Context, dockerCli command.Cli, in createOptions, args []string) error {
|
||||||
ctx := appcontext.Context()
|
|
||||||
|
|
||||||
if in.name == "default" {
|
|
||||||
return errors.Errorf("default is a reserved name and cannot be used to identify builder instance")
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.actionLeave {
|
|
||||||
if in.name == "" {
|
|
||||||
return errors.Errorf("leave requires instance name")
|
|
||||||
}
|
|
||||||
if in.nodeName == "" {
|
|
||||||
return errors.Errorf("leave requires node name but --node not set")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.actionAppend {
|
|
||||||
if in.name == "" {
|
|
||||||
logrus.Warnf("append used without name, creating a new instance instead")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
txn, release, err := storeutil.GetStore(dockerCli)
|
txn, release, err := storeutil.GetStore(dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -75,232 +38,34 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
// Ensure the file lock gets released no matter what happens.
|
// Ensure the file lock gets released no matter what happens.
|
||||||
defer release()
|
defer release()
|
||||||
|
|
||||||
name := in.name
|
if in.actionLeave {
|
||||||
if name == "" {
|
return builder.Leave(ctx, txn, dockerCli, builder.LeaveOpts{
|
||||||
name, err = store.GenerateName(txn)
|
Name: in.name,
|
||||||
if err != nil {
|
NodeName: in.nodeName,
|
||||||
return err
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !in.actionLeave && !in.actionAppend {
|
|
||||||
contexts, err := dockerCli.ContextStore().List()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, c := range contexts {
|
|
||||||
if c.Name == name {
|
|
||||||
logrus.Warnf("instance name %q already exists as context builder", name)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ng, err := txn.NodeGroupByName(name)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(errors.Cause(err)) {
|
|
||||||
if in.actionAppend && in.name != "" {
|
|
||||||
logrus.Warnf("failed to find %q for append, creating a new instance instead", in.name)
|
|
||||||
}
|
|
||||||
if in.actionLeave {
|
|
||||||
return errors.Errorf("failed to find instance %q for leave", in.name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildkitHost := os.Getenv("BUILDKIT_HOST")
|
|
||||||
|
|
||||||
driverName := in.driver
|
|
||||||
if driverName == "" {
|
|
||||||
if ng != nil {
|
|
||||||
driverName = ng.Driver
|
|
||||||
} else if len(args) == 0 && buildkitHost != "" {
|
|
||||||
driverName = "remote"
|
|
||||||
} else {
|
|
||||||
var arg string
|
|
||||||
if len(args) > 0 {
|
|
||||||
arg = args[0]
|
|
||||||
}
|
|
||||||
f, err := driver.GetDefaultFactory(ctx, arg, dockerCli.Client(), true, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if f == nil {
|
|
||||||
return errors.Errorf("no valid drivers found")
|
|
||||||
}
|
|
||||||
driverName = f.Name()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ng != nil {
|
|
||||||
if in.nodeName == "" && !in.actionAppend {
|
|
||||||
return errors.Errorf("existing instance for %q but no append mode, specify --node to make changes for existing instances", name)
|
|
||||||
}
|
|
||||||
if driverName != ng.Driver {
|
|
||||||
return errors.Errorf("existing instance for %q but has mismatched driver %q", name, ng.Driver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := driver.GetFactory(driverName, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOriginal := ng
|
|
||||||
if ngOriginal != nil {
|
|
||||||
ngOriginal = ngOriginal.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
if ng == nil {
|
|
||||||
ng = &store.NodeGroup{
|
|
||||||
Name: name,
|
|
||||||
Driver: driverName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var flags []string
|
|
||||||
if in.flags != "" {
|
|
||||||
flags, err = shlex.Split(in.flags)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to parse buildkit flags")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var ep string
|
var ep string
|
||||||
var setEp bool
|
if len(args) > 0 {
|
||||||
if in.actionLeave {
|
ep = args[0]
|
||||||
if err := ng.Leave(in.nodeName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ls, err := localstate.New(confutil.ConfigDir(dockerCli))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ls.RemoveBuilderNode(ng.Name, in.nodeName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch {
|
|
||||||
case driverName == "kubernetes":
|
|
||||||
if len(args) > 0 {
|
|
||||||
logrus.Warnf("kubernetes driver does not support endpoint args %q", args[0])
|
|
||||||
}
|
|
||||||
// generate node name if not provided to avoid duplicated endpoint
|
|
||||||
// error: https://github.com/docker/setup-buildx-action/issues/215
|
|
||||||
nodeName := in.nodeName
|
|
||||||
if nodeName == "" {
|
|
||||||
nodeName, err = k8sutil.GenerateNodeName(name, txn)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// naming endpoint to make --append works
|
|
||||||
ep = (&url.URL{
|
|
||||||
Scheme: driverName,
|
|
||||||
Path: "/" + name,
|
|
||||||
RawQuery: (&url.Values{
|
|
||||||
"deployment": {nodeName},
|
|
||||||
"kubeconfig": {os.Getenv("KUBECONFIG")},
|
|
||||||
}).Encode(),
|
|
||||||
}).String()
|
|
||||||
setEp = false
|
|
||||||
case driverName == "remote":
|
|
||||||
if len(args) > 0 {
|
|
||||||
ep = args[0]
|
|
||||||
} else if buildkitHost != "" {
|
|
||||||
ep = buildkitHost
|
|
||||||
} else {
|
|
||||||
return errors.Errorf("no remote endpoint provided")
|
|
||||||
}
|
|
||||||
ep, err = validateBuildkitEndpoint(ep)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
setEp = true
|
|
||||||
case len(args) > 0:
|
|
||||||
ep, err = validateEndpoint(dockerCli, args[0])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
setEp = true
|
|
||||||
default:
|
|
||||||
if dockerCli.CurrentContext() == "default" && dockerCli.DockerEndpoint().TLSData != nil {
|
|
||||||
return errors.Errorf("could not create a builder instance with TLS data loaded from environment. Please use `docker context create <context-name>` to create a context for current environment and then create a builder instance with `docker buildx create <context-name>`")
|
|
||||||
}
|
|
||||||
ep, err = dockerutil.GetCurrentEndpoint(dockerCli)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
setEp = false
|
|
||||||
}
|
|
||||||
|
|
||||||
m, err := csvToMap(in.driverOpts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.configFile == "" {
|
|
||||||
// if buildkit config is not provided, check if the default one is
|
|
||||||
// available and use it
|
|
||||||
if f, ok := confutil.DefaultConfigFile(dockerCli); ok {
|
|
||||||
logrus.Warnf("Using default BuildKit config in %s", f)
|
|
||||||
in.configFile = f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ng.Update(in.nodeName, ep, in.platform, setEp, in.actionAppend, flags, in.configFile, m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := txn.Save(ng); err != nil {
|
b, err := builder.Create(ctx, txn, dockerCli, builder.CreateOpts{
|
||||||
return err
|
Name: in.name,
|
||||||
}
|
Driver: in.driver,
|
||||||
|
NodeName: in.nodeName,
|
||||||
b, err := builder.New(dockerCli,
|
Platforms: in.platform,
|
||||||
builder.WithName(ng.Name),
|
DriverOpts: in.driverOpts,
|
||||||
builder.WithStore(txn),
|
BuildkitdFlags: in.buildkitdFlags,
|
||||||
builder.WithSkippedValidation(),
|
BuildkitdConfigFile: in.buildkitdConfigFile,
|
||||||
)
|
Use: in.use,
|
||||||
|
Endpoint: ep,
|
||||||
|
Append: in.actionAppend,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
nodes, err := b.LoadNodes(timeoutCtx, builder.WithData())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, node := range nodes {
|
|
||||||
if err := node.Err; err != nil {
|
|
||||||
err := errors.Errorf("failed to initialize builder %s (%s): %s", ng.Name, node.Name, err)
|
|
||||||
var err2 error
|
|
||||||
if ngOriginal == nil {
|
|
||||||
err2 = txn.Remove(ng.Name)
|
|
||||||
} else {
|
|
||||||
err2 = txn.Save(ngOriginal)
|
|
||||||
}
|
|
||||||
if err2 != nil {
|
|
||||||
logrus.Warnf("Could not rollback to previous state: %s", err2)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.use && ep != "" {
|
|
||||||
current, err := dockerutil.GetCurrentEndpoint(dockerCli)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := txn.SetCurrent(current, ng.Name, false, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The store is no longer used from this point.
|
// The store is no longer used from this point.
|
||||||
// Release it so we aren't holding the file lock during the boot.
|
// Release it so we aren't holding the file lock during the boot.
|
||||||
release()
|
release()
|
||||||
@@ -311,7 +76,7 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s\n", ng.Name)
|
fmt.Printf("%s\n", b.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,7 +96,7 @@ func createCmd(dockerCli command.Cli) *cobra.Command {
|
|||||||
Short: "Create a new builder instance",
|
Short: "Create a new builder instance",
|
||||||
Args: cli.RequiresMaxArgs(1),
|
Args: cli.RequiresMaxArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runCreate(dockerCli, options, args)
|
return runCreate(cmd.Context(), dockerCli, options, args)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.Disable,
|
ValidArgsFunction: completion.Disable,
|
||||||
}
|
}
|
||||||
@@ -341,12 +106,16 @@ func createCmd(dockerCli command.Cli) *cobra.Command {
|
|||||||
flags.StringVar(&options.name, "name", "", "Builder instance name")
|
flags.StringVar(&options.name, "name", "", "Builder instance name")
|
||||||
flags.StringVar(&options.driver, "driver", "", fmt.Sprintf("Driver to use (available: %s)", drivers.String()))
|
flags.StringVar(&options.driver, "driver", "", fmt.Sprintf("Driver to use (available: %s)", drivers.String()))
|
||||||
flags.StringVar(&options.nodeName, "node", "", "Create/modify node with given name")
|
flags.StringVar(&options.nodeName, "node", "", "Create/modify node with given name")
|
||||||
flags.StringVar(&options.flags, "buildkitd-flags", "", "Flags for buildkitd daemon")
|
|
||||||
flags.StringVar(&options.configFile, "config", "", "BuildKit config file")
|
|
||||||
flags.StringArrayVar(&options.platform, "platform", []string{}, "Fixed platforms for current node")
|
flags.StringArrayVar(&options.platform, "platform", []string{}, "Fixed platforms for current node")
|
||||||
flags.StringArrayVar(&options.driverOpts, "driver-opt", []string{}, "Options for the driver")
|
flags.StringArrayVar(&options.driverOpts, "driver-opt", []string{}, "Options for the driver")
|
||||||
flags.BoolVar(&options.bootstrap, "bootstrap", false, "Boot builder after creation")
|
flags.StringVar(&options.buildkitdFlags, "buildkitd-flags", "", "BuildKit daemon flags")
|
||||||
|
|
||||||
|
// we allow for both "--config" and "--buildkitd-config", although the latter is the recommended way to avoid ambiguity.
|
||||||
|
flags.StringVar(&options.buildkitdConfigFile, "buildkitd-config", "", "BuildKit daemon config file")
|
||||||
|
flags.StringVar(&options.buildkitdConfigFile, "config", "", "BuildKit daemon config file")
|
||||||
|
flags.MarkHidden("config")
|
||||||
|
|
||||||
|
flags.BoolVar(&options.bootstrap, "bootstrap", false, "Boot builder after creation")
|
||||||
flags.BoolVar(&options.actionAppend, "append", false, "Append a node to builder instead of changing it")
|
flags.BoolVar(&options.actionAppend, "append", false, "Append a node to builder instead of changing it")
|
||||||
flags.BoolVar(&options.actionLeave, "leave", false, "Remove a node from builder instead of changing it")
|
flags.BoolVar(&options.actionLeave, "leave", false, "Remove a node from builder instead of changing it")
|
||||||
flags.BoolVar(&options.use, "use", false, "Set the current builder instance")
|
flags.BoolVar(&options.use, "use", false, "Set the current builder instance")
|
||||||
@@ -356,49 +125,3 @@ func createCmd(dockerCli command.Cli) *cobra.Command {
|
|||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func csvToMap(in []string) (map[string]string, error) {
|
|
||||||
if len(in) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
m := make(map[string]string, len(in))
|
|
||||||
for _, s := range in {
|
|
||||||
csvReader := csv.NewReader(strings.NewReader(s))
|
|
||||||
fields, err := csvReader.Read()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, v := range fields {
|
|
||||||
p := strings.SplitN(v, "=", 2)
|
|
||||||
if len(p) != 2 {
|
|
||||||
return nil, errors.Errorf("invalid value %q, expecting k=v", v)
|
|
||||||
}
|
|
||||||
m[p[0]] = p[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateEndpoint validates that endpoint is either a context or a docker host
|
|
||||||
func validateEndpoint(dockerCli command.Cli, ep string) (string, error) {
|
|
||||||
dem, err := dockerutil.GetDockerEndpoint(dockerCli, ep)
|
|
||||||
if err == nil && dem != nil {
|
|
||||||
if ep == "default" {
|
|
||||||
return dem.Host, nil
|
|
||||||
}
|
|
||||||
return ep, nil
|
|
||||||
}
|
|
||||||
h, err := dopts.ParseHost(true, ep)
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.Wrapf(err, "failed to parse endpoint %s", ep)
|
|
||||||
}
|
|
||||||
return h, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateBuildkitEndpoint validates that endpoint is a valid buildkit host
|
|
||||||
func validateBuildkitEndpoint(ep string) (string, error) {
|
|
||||||
if err := remoteutil.IsValidEndpoint(ep); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return ep, nil
|
|
||||||
}
|
|
||||||
|
@@ -1,26 +0,0 @@
|
|||||||
package commands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCsvToMap(t *testing.T) {
|
|
||||||
d := []string{
|
|
||||||
"\"tolerations=key=foo,value=bar;key=foo2,value=bar2\",replicas=1",
|
|
||||||
"namespace=default",
|
|
||||||
}
|
|
||||||
r, err := csvToMap(d)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Contains(t, r, "tolerations")
|
|
||||||
require.Equal(t, r["tolerations"], "key=foo,value=bar;key=foo2,value=bar2")
|
|
||||||
|
|
||||||
require.Contains(t, r, "replicas")
|
|
||||||
require.Equal(t, r["replicas"], "1")
|
|
||||||
|
|
||||||
require.Contains(t, r, "namespace")
|
|
||||||
require.Equal(t, r["namespace"], "default")
|
|
||||||
}
|
|
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/docker/buildx/controller/control"
|
"github.com/docker/buildx/controller/control"
|
||||||
controllerapi "github.com/docker/buildx/controller/pb"
|
controllerapi "github.com/docker/buildx/controller/pb"
|
||||||
"github.com/docker/buildx/monitor"
|
"github.com/docker/buildx/monitor"
|
||||||
|
"github.com/docker/buildx/util/cobrautil"
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/moby/buildkit/util/progress/progressui"
|
"github.com/moby/buildkit/util/progress/progressui"
|
||||||
@@ -42,9 +43,6 @@ func RootCmd(dockerCli command.Cli, children ...DebuggableCmd) *cobra.Command {
|
|||||||
Use: "debug",
|
Use: "debug",
|
||||||
Short: "Start debugger",
|
Short: "Start debugger",
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Annotations: map[string]string{
|
|
||||||
"experimentalCLI": "",
|
|
||||||
},
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
printer, err := progress.NewPrinter(context.TODO(), os.Stderr, progressui.DisplayMode(progressMode))
|
printer, err := progress.NewPrinter(context.TODO(), os.Stderr, progressui.DisplayMode(progressMode))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -73,21 +71,19 @@ func RootCmd(dockerCli command.Cli, children ...DebuggableCmd) *cobra.Command {
|
|||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
cobrautil.MarkCommandExperimental(cmd)
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.StringVar(&options.InvokeFlag, "invoke", "", "Launch a monitor with executing specified command")
|
flags.StringVar(&options.InvokeFlag, "invoke", "", "Launch a monitor with executing specified command")
|
||||||
flags.SetAnnotation("invoke", "experimentalCLI", nil)
|
|
||||||
flags.StringVar(&options.OnFlag, "on", "error", "When to launch the monitor ([always, error])")
|
flags.StringVar(&options.OnFlag, "on", "error", "When to launch the monitor ([always, error])")
|
||||||
flags.SetAnnotation("on", "experimentalCLI", nil)
|
|
||||||
|
|
||||||
flags.StringVar(&controlOptions.Root, "root", "", "Specify root directory of server to connect for the monitor")
|
flags.StringVar(&controlOptions.Root, "root", "", "Specify root directory of server to connect for the monitor")
|
||||||
flags.SetAnnotation("root", "experimentalCLI", nil)
|
|
||||||
flags.BoolVar(&controlOptions.Detach, "detach", runtime.GOOS == "linux", "Detach buildx server for the monitor (supported only on linux)")
|
flags.BoolVar(&controlOptions.Detach, "detach", runtime.GOOS == "linux", "Detach buildx server for the monitor (supported only on linux)")
|
||||||
flags.SetAnnotation("detach", "experimentalCLI", nil)
|
|
||||||
flags.StringVar(&controlOptions.ServerConfig, "server-config", "", "Specify buildx server config file for the monitor (used only when launching new server)")
|
flags.StringVar(&controlOptions.ServerConfig, "server-config", "", "Specify buildx server config file for the monitor (used only when launching new server)")
|
||||||
flags.SetAnnotation("server-config", "experimentalCLI", nil)
|
|
||||||
flags.StringVar(&progressMode, "progress", "auto", `Set type of progress output ("auto", "plain", "tty") for the monitor. Use plain to show container output`)
|
flags.StringVar(&progressMode, "progress", "auto", `Set type of progress output ("auto", "plain", "tty") for the monitor. Use plain to show container output`)
|
||||||
|
|
||||||
|
cobrautil.MarkFlagsExperimental(flags, "invoke", "on", "root", "detach", "server-config")
|
||||||
|
|
||||||
for _, c := range children {
|
for _, c := range children {
|
||||||
cmd.AddCommand(c.NewDebugger(&options))
|
cmd.AddCommand(c.NewDebugger(&options))
|
||||||
}
|
}
|
||||||
|
132
commands/dial_stdio.go
Normal file
132
commands/dial_stdio.go
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
|
"github.com/docker/buildx/build"
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/moby/buildkit/util/appcontext"
|
||||||
|
"github.com/moby/buildkit/util/progress/progressui"
|
||||||
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stdioOptions struct {
|
||||||
|
builder string
|
||||||
|
platform string
|
||||||
|
progress string
|
||||||
|
}
|
||||||
|
|
||||||
|
func runDialStdio(dockerCli command.Cli, opts stdioOptions) error {
|
||||||
|
ctx := appcontext.Context()
|
||||||
|
|
||||||
|
contextPathHash, _ := os.Getwd()
|
||||||
|
b, err := builder.New(dockerCli,
|
||||||
|
builder.WithName(opts.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)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
printer, err := progress.NewPrinter(ctx, os.Stderr, progressui.DisplayMode(opts.progress), progress.WithPhase("dial-stdio"), progress.WithDesc("builder: "+b.Name, "builder:"+b.Name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var p *v1.Platform
|
||||||
|
if opts.platform != "" {
|
||||||
|
pp, err := platforms.Parse(opts.platform)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "invalid platform %q", opts.platform)
|
||||||
|
}
|
||||||
|
p = &pp
|
||||||
|
}
|
||||||
|
|
||||||
|
defer printer.Wait()
|
||||||
|
|
||||||
|
return progress.Wrap("Proxying to builder", printer.Write, func(sub progress.SubLogger) error {
|
||||||
|
var conn net.Conn
|
||||||
|
|
||||||
|
err := sub.Wrap("Dialing builder", func() error {
|
||||||
|
conn, err = build.Dial(ctx, nodes, printer, p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
closeWrite(conn)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var eg errgroup.Group
|
||||||
|
|
||||||
|
eg.Go(func() error {
|
||||||
|
_, err := io.Copy(conn, os.Stdin)
|
||||||
|
closeWrite(conn)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
eg.Go(func() error {
|
||||||
|
_, err := io.Copy(os.Stdout, conn)
|
||||||
|
closeRead(conn)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return eg.Wait()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeRead(conn net.Conn) error {
|
||||||
|
if c, ok := conn.(interface{ CloseRead() error }); ok {
|
||||||
|
return c.CloseRead()
|
||||||
|
}
|
||||||
|
return conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeWrite(conn net.Conn) error {
|
||||||
|
if c, ok := conn.(interface{ CloseWrite() error }); ok {
|
||||||
|
return c.CloseWrite()
|
||||||
|
}
|
||||||
|
return conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func dialStdioCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||||
|
opts := stdioOptions{}
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "dial-stdio",
|
||||||
|
Short: "Proxy current stdio streams to builder instance",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
opts.builder = rootOpts.builder
|
||||||
|
return runDialStdio(dockerCli, opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
cmd.Flags()
|
||||||
|
flags.StringVar(&opts.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Target platform: this is used for node selection")
|
||||||
|
flags.StringVar(&opts.progress, "progress", "quiet", "Set type of progress output (auto, plain, tty).")
|
||||||
|
return cmd
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@@ -15,7 +16,6 @@ import (
|
|||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
@@ -26,9 +26,7 @@ type duOptions struct {
|
|||||||
verbose bool
|
verbose bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDiskUsage(dockerCli command.Cli, opts duOptions) error {
|
func runDiskUsage(ctx context.Context, dockerCli command.Cli, opts duOptions) error {
|
||||||
ctx := appcontext.Context()
|
|
||||||
|
|
||||||
pi, err := toBuildkitPruneInfo(opts.filter.Value())
|
pi, err := toBuildkitPruneInfo(opts.filter.Value())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -114,7 +112,7 @@ func duCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
options.builder = rootOpts.builder
|
options.builder = rootOpts.builder
|
||||||
return runDiskUsage(dockerCli, options)
|
return runDiskUsage(cmd.Context(), dockerCli, options)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.Disable,
|
ValidArgsFunction: completion.Disable,
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,6 @@ import (
|
|||||||
"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"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
|
||||||
"github.com/moby/buildkit/util/progress/progressui"
|
"github.com/moby/buildkit/util/progress/progressui"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
@@ -32,7 +31,7 @@ type createOptions struct {
|
|||||||
progress string
|
progress string
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
func runCreate(ctx context.Context, dockerCli command.Cli, in createOptions, args []string) error {
|
||||||
if len(args) == 0 && len(in.files) == 0 {
|
if len(args) == 0 && len(in.files) == 0 {
|
||||||
return errors.Errorf("no sources specified")
|
return errors.Errorf("no sources specified")
|
||||||
}
|
}
|
||||||
@@ -113,8 +112,6 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := appcontext.Context()
|
|
||||||
|
|
||||||
b, err := builder.New(dockerCli, builder.WithName(in.builder))
|
b, err := builder.New(dockerCli, builder.WithName(in.builder))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -274,7 +271,7 @@ func createCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command {
|
|||||||
Short: "Create a new image based on source images",
|
Short: "Create a new image based on source images",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
options.builder = *opts.Builder
|
options.builder = *opts.Builder
|
||||||
return runCreate(dockerCli, options, args)
|
return runCreate(cmd.Context(), dockerCli, options, args)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.Disable,
|
ValidArgsFunction: completion.Disable,
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,14 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/docker/buildx/builder"
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/util/cobrautil/completion"
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
"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"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -18,9 +19,7 @@ type inspectOptions struct {
|
|||||||
raw bool
|
raw bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func runInspect(dockerCli command.Cli, in inspectOptions, name string) error {
|
func runInspect(ctx context.Context, dockerCli command.Cli, in inspectOptions, name string) error {
|
||||||
ctx := appcontext.Context()
|
|
||||||
|
|
||||||
if in.format != "" && in.raw {
|
if in.format != "" && in.raw {
|
||||||
return errors.Errorf("format and raw cannot be used together")
|
return errors.Errorf("format and raw cannot be used together")
|
||||||
}
|
}
|
||||||
@@ -51,7 +50,7 @@ func inspectCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
|||||||
Args: cli.ExactArgs(1),
|
Args: cli.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
options.builder = *rootOpts.Builder
|
options.builder = *rootOpts.Builder
|
||||||
return runInspect(dockerCli, options, args[0])
|
return runInspect(cmd.Context(), dockerCli, options, args[0])
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.Disable,
|
ValidArgsFunction: completion.Disable,
|
||||||
}
|
}
|
||||||
|
@@ -17,7 +17,6 @@ import (
|
|||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/debug"
|
"github.com/docker/cli/cli/debug"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,9 +25,7 @@ type inspectOptions struct {
|
|||||||
builder string
|
builder string
|
||||||
}
|
}
|
||||||
|
|
||||||
func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
func runInspect(ctx context.Context, dockerCli command.Cli, in inspectOptions) error {
|
||||||
ctx := appcontext.Context()
|
|
||||||
|
|
||||||
b, err := builder.New(dockerCli,
|
b, err := builder.New(dockerCli,
|
||||||
builder.WithName(in.builder),
|
builder.WithName(in.builder),
|
||||||
builder.WithSkippedValidation(),
|
builder.WithSkippedValidation(),
|
||||||
@@ -87,11 +84,11 @@ func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
|||||||
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(w, "Status:\t%s\n", nodes[i].DriverInfo.Status)
|
fmt.Fprintf(w, "Status:\t%s\n", nodes[i].DriverInfo.Status)
|
||||||
if len(n.Flags) > 0 {
|
if len(n.BuildkitdFlags) > 0 {
|
||||||
fmt.Fprintf(w, "Flags:\t%s\n", strings.Join(n.Flags, " "))
|
fmt.Fprintf(w, "BuildKit daemon flags:\t%s\n", strings.Join(n.BuildkitdFlags, " "))
|
||||||
}
|
}
|
||||||
if nodes[i].Version != "" {
|
if nodes[i].Version != "" {
|
||||||
fmt.Fprintf(w, "Buildkit:\t%s\n", nodes[i].Version)
|
fmt.Fprintf(w, "BuildKit version:\t%s\n", nodes[i].Version)
|
||||||
}
|
}
|
||||||
platforms := platformutil.FormatInGroups(n.Node.Platforms, n.Platforms)
|
platforms := platformutil.FormatInGroups(n.Node.Platforms, n.Platforms)
|
||||||
if len(platforms) > 0 {
|
if len(platforms) > 0 {
|
||||||
@@ -150,7 +147,7 @@ func inspectCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
options.builder = args[0]
|
options.builder = args[0]
|
||||||
}
|
}
|
||||||
return runInspect(dockerCli, options)
|
return runInspect(cmd.Context(), dockerCli, options)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.BuilderNames(dockerCli),
|
ValidArgsFunction: completion.BuilderNames(dockerCli),
|
||||||
}
|
}
|
||||||
|
233
commands/ls.go
233
commands/ls.go
@@ -2,30 +2,43 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/buildx/builder"
|
"github.com/docker/buildx/builder"
|
||||||
|
"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/cobrautil/completion"
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
"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"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
lsNameNodeHeader = "NAME/NODE"
|
||||||
|
lsDriverEndpointHeader = "DRIVER/ENDPOINT"
|
||||||
|
lsStatusHeader = "STATUS"
|
||||||
|
lsLastActivityHeader = "LAST ACTIVITY"
|
||||||
|
lsBuildkitHeader = "BUILDKIT"
|
||||||
|
lsPlatformsHeader = "PLATFORMS"
|
||||||
|
|
||||||
|
lsIndent = ` \_ `
|
||||||
|
|
||||||
|
lsDefaultTableFormat = "table {{.Name}}\t{{.DriverEndpoint}}\t{{.Status}}\t{{.Buildkit}}\t{{.Platforms}}"
|
||||||
|
)
|
||||||
|
|
||||||
type lsOptions struct {
|
type lsOptions struct {
|
||||||
|
format string
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLs(dockerCli command.Cli, in lsOptions) error {
|
func runLs(ctx context.Context, dockerCli command.Cli, in lsOptions) error {
|
||||||
ctx := appcontext.Context()
|
|
||||||
|
|
||||||
txn, release, err := storeutil.GetStore(dockerCli)
|
txn, release, err := storeutil.GetStore(dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -59,22 +72,9 @@ func runLs(dockerCli command.Cli, in lsOptions) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
w := tabwriter.NewWriter(dockerCli.Out(), 0, 0, 1, ' ', 0)
|
if hasErrors, err := lsPrint(dockerCli, current, builders, in.format); err != nil {
|
||||||
fmt.Fprintf(w, "NAME/NODE\tDRIVER/ENDPOINT\tSTATUS\tBUILDKIT\tPLATFORMS\n")
|
return err
|
||||||
|
} else if hasErrors {
|
||||||
printErr := false
|
|
||||||
for _, b := range builders {
|
|
||||||
if current.Name == b.Name {
|
|
||||||
b.Name += " *"
|
|
||||||
}
|
|
||||||
if ok := printBuilder(w, b); !ok {
|
|
||||||
printErr = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Flush()
|
|
||||||
|
|
||||||
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 {
|
||||||
@@ -92,31 +92,6 @@ func runLs(dockerCli command.Cli, in lsOptions) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func printBuilder(w io.Writer, b *builder.Builder) (ok bool) {
|
|
||||||
ok = true
|
|
||||||
var err string
|
|
||||||
if b.Err() != nil {
|
|
||||||
ok = false
|
|
||||||
err = "error"
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, "%s\t%s\t%s\t\t\n", b.Name, b.Driver, err)
|
|
||||||
if b.Err() == nil {
|
|
||||||
for _, n := range b.Nodes() {
|
|
||||||
var status string
|
|
||||||
if n.DriverInfo != nil {
|
|
||||||
status = n.DriverInfo.Status.String()
|
|
||||||
}
|
|
||||||
if n.Err != nil {
|
|
||||||
ok = false
|
|
||||||
fmt.Fprintf(w, " %s\t%s\t%s\t\t\n", n.Name, n.Endpoint, "error")
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(w, " %s\t%s\t%s\t%s\t%s\n", n.Name, n.Endpoint, status, n.Version, strings.Join(platformutil.FormatInGroups(n.Node.Platforms, n.Platforms), ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func lsCmd(dockerCli command.Cli) *cobra.Command {
|
func lsCmd(dockerCli command.Cli) *cobra.Command {
|
||||||
var options lsOptions
|
var options lsOptions
|
||||||
|
|
||||||
@@ -125,13 +100,175 @@ func lsCmd(dockerCli command.Cli) *cobra.Command {
|
|||||||
Short: "List builder instances",
|
Short: "List builder instances",
|
||||||
Args: cli.ExactArgs(0),
|
Args: cli.ExactArgs(0),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runLs(dockerCli, options)
|
return runLs(cmd.Context(), dockerCli, options)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.Disable,
|
ValidArgsFunction: completion.Disable,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.StringVar(&options.format, "format", formatter.TableFormatKey, "Format the output")
|
||||||
|
|
||||||
// hide builder persistent flag for this command
|
// hide builder persistent flag for this command
|
||||||
cobrautil.HideInheritedFlags(cmd, "builder")
|
cobrautil.HideInheritedFlags(cmd, "builder")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func lsPrint(dockerCli command.Cli, current *store.NodeGroup, builders []*builder.Builder, format string) (hasErrors bool, _ error) {
|
||||||
|
if format == formatter.TableFormatKey {
|
||||||
|
format = lsDefaultTableFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := formatter.Context{
|
||||||
|
Output: dockerCli.Out(),
|
||||||
|
Format: formatter.Format(format),
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.SliceStable(builders, func(i, j int) bool {
|
||||||
|
ierr := builders[i].Err() != nil
|
||||||
|
jerr := builders[j].Err() != nil
|
||||||
|
if ierr && !jerr {
|
||||||
|
return false
|
||||||
|
} else if !ierr && jerr {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return i < j
|
||||||
|
})
|
||||||
|
|
||||||
|
render := func(format func(subContext formatter.SubContext) error) error {
|
||||||
|
for _, b := range builders {
|
||||||
|
if err := format(&lsContext{
|
||||||
|
Builder: &lsBuilder{
|
||||||
|
Builder: b,
|
||||||
|
Current: b.Name == current.Name,
|
||||||
|
},
|
||||||
|
format: ctx.Format,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if b.Err() != nil {
|
||||||
|
if ctx.Format.IsTable() {
|
||||||
|
hasErrors = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, n := range b.Nodes() {
|
||||||
|
if n.Err != nil {
|
||||||
|
if ctx.Format.IsTable() {
|
||||||
|
hasErrors = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := format(&lsContext{
|
||||||
|
format: ctx.Format,
|
||||||
|
Builder: &lsBuilder{
|
||||||
|
Builder: b,
|
||||||
|
Current: b.Name == current.Name,
|
||||||
|
},
|
||||||
|
node: n,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lsCtx := lsContext{}
|
||||||
|
lsCtx.Header = formatter.SubHeaderContext{
|
||||||
|
"Name": lsNameNodeHeader,
|
||||||
|
"DriverEndpoint": lsDriverEndpointHeader,
|
||||||
|
"LastActivity": lsLastActivityHeader,
|
||||||
|
"Status": lsStatusHeader,
|
||||||
|
"Buildkit": lsBuildkitHeader,
|
||||||
|
"Platforms": lsPlatformsHeader,
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasErrors, ctx.Write(&lsCtx, render)
|
||||||
|
}
|
||||||
|
|
||||||
|
type lsBuilder struct {
|
||||||
|
*builder.Builder
|
||||||
|
Current bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type lsContext struct {
|
||||||
|
formatter.HeaderContext
|
||||||
|
Builder *lsBuilder
|
||||||
|
|
||||||
|
format formatter.Format
|
||||||
|
node builder.Node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(c.Builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) Name() string {
|
||||||
|
if c.node.Name == "" {
|
||||||
|
name := c.Builder.Name
|
||||||
|
if c.Builder.Current && c.format.IsTable() {
|
||||||
|
name += "*"
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
if c.format.IsTable() {
|
||||||
|
return lsIndent + c.node.Name
|
||||||
|
}
|
||||||
|
return c.node.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) DriverEndpoint() string {
|
||||||
|
if c.node.Name == "" {
|
||||||
|
return c.Builder.Driver
|
||||||
|
}
|
||||||
|
if c.format.IsTable() {
|
||||||
|
return lsIndent + c.node.Endpoint
|
||||||
|
}
|
||||||
|
return c.node.Endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) LastActivity() string {
|
||||||
|
if c.node.Name != "" || c.Builder.LastActivity.IsZero() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return c.Builder.LastActivity.UTC().Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) Status() string {
|
||||||
|
if c.node.Name == "" {
|
||||||
|
if c.Builder.Err() != nil {
|
||||||
|
return "error"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if c.node.Err != nil {
|
||||||
|
return "error"
|
||||||
|
}
|
||||||
|
if c.node.DriverInfo != nil {
|
||||||
|
return c.node.DriverInfo.Status.String()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) Buildkit() string {
|
||||||
|
if c.node.Name == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return c.node.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) Platforms() string {
|
||||||
|
if c.node.Name == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.Join(platformutil.FormatInGroups(c.node.Node.Platforms, c.node.Platforms), ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) Error() string {
|
||||||
|
if c.node.Name != "" && c.node.Err != nil {
|
||||||
|
return c.node.Err.Error()
|
||||||
|
} else if err := c.Builder.Err(); err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -15,7 +16,6 @@ import (
|
|||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
@@ -35,9 +35,7 @@ const (
|
|||||||
allCacheWarning = `WARNING! This will remove all build cache. Are you sure you want to continue?`
|
allCacheWarning = `WARNING! This will remove all build cache. Are you sure you want to continue?`
|
||||||
)
|
)
|
||||||
|
|
||||||
func runPrune(dockerCli command.Cli, opts pruneOptions) error {
|
func runPrune(ctx context.Context, dockerCli command.Cli, opts pruneOptions) error {
|
||||||
ctx := appcontext.Context()
|
|
||||||
|
|
||||||
pruneFilters := opts.filter.Value()
|
pruneFilters := opts.filter.Value()
|
||||||
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
|
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
|
||||||
|
|
||||||
@@ -51,8 +49,12 @@ func runPrune(dockerCli command.Cli, opts pruneOptions) error {
|
|||||||
warning = allCacheWarning
|
warning = allCacheWarning
|
||||||
}
|
}
|
||||||
|
|
||||||
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
if !opts.force {
|
||||||
return nil
|
if ok, err := prompt(ctx, dockerCli.In(), dockerCli.Out(), warning); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := builder.New(dockerCli, builder.WithName(opts.builder))
|
b, err := builder.New(dockerCli, builder.WithName(opts.builder))
|
||||||
@@ -138,7 +140,7 @@ func pruneCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
options.builder = rootOpts.builder
|
options.builder = rootOpts.builder
|
||||||
return runPrune(dockerCli, options)
|
return runPrune(cmd.Context(), dockerCli, options)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.Disable,
|
ValidArgsFunction: completion.Disable,
|
||||||
}
|
}
|
||||||
|
@@ -9,16 +9,14 @@ import (
|
|||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/store"
|
||||||
"github.com/docker/buildx/store/storeutil"
|
"github.com/docker/buildx/store/storeutil"
|
||||||
"github.com/docker/buildx/util/cobrautil/completion"
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
"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/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
type rmOptions struct {
|
type rmOptions struct {
|
||||||
builder string
|
builders []string
|
||||||
keepState bool
|
keepState bool
|
||||||
keepDaemon bool
|
keepDaemon bool
|
||||||
allInactive bool
|
allInactive bool
|
||||||
@@ -29,11 +27,13 @@ const (
|
|||||||
rmInactiveWarning = `WARNING! This will remove all builders that are not in running state. Are you sure you want to continue?`
|
rmInactiveWarning = `WARNING! This will remove all builders that are not in running state. Are you sure you want to continue?`
|
||||||
)
|
)
|
||||||
|
|
||||||
func runRm(dockerCli command.Cli, in rmOptions) error {
|
func runRm(ctx context.Context, dockerCli command.Cli, in rmOptions) error {
|
||||||
ctx := appcontext.Context()
|
if in.allInactive && !in.force {
|
||||||
|
if ok, err := prompt(ctx, dockerCli.In(), dockerCli.Out(), rmInactiveWarning); err != nil {
|
||||||
if in.allInactive && !in.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), rmInactiveWarning) {
|
return err
|
||||||
return nil
|
} else if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
txn, release, err := storeutil.GetStore(dockerCli)
|
txn, release, err := storeutil.GetStore(dockerCli)
|
||||||
@@ -46,33 +46,52 @@ func runRm(dockerCli command.Cli, in rmOptions) error {
|
|||||||
return rmAllInactive(ctx, txn, dockerCli, in)
|
return rmAllInactive(ctx, txn, dockerCli, in)
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := builder.New(dockerCli,
|
eg, _ := errgroup.WithContext(ctx)
|
||||||
builder.WithName(in.builder),
|
for _, name := range in.builders {
|
||||||
builder.WithStore(txn),
|
func(name string) {
|
||||||
builder.WithSkippedValidation(),
|
eg.Go(func() (err error) {
|
||||||
)
|
defer func() {
|
||||||
if err != nil {
|
if err == nil {
|
||||||
return err
|
_, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", name)
|
||||||
|
} else {
|
||||||
|
_, _ = fmt.Fprintf(dockerCli.Err(), "failed to remove %s: %v\n", name, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
b, err := builder.New(dockerCli,
|
||||||
|
builder.WithName(name),
|
||||||
|
builder.WithStore(txn),
|
||||||
|
builder.WithSkippedValidation(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes, err := b.LoadNodes(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cb := b.ContextName(); cb != "" {
|
||||||
|
return errors.Errorf("context builder cannot be removed, run `docker context rm %s` to remove this context", cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
err1 := rm(ctx, nodes, in)
|
||||||
|
if err := txn.Remove(b.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err1 != nil {
|
||||||
|
return err1
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes, err := b.LoadNodes(ctx)
|
if err := eg.Wait(); err != nil {
|
||||||
if err != nil {
|
return errors.New("failed to remove one or more builders")
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cb := b.ContextName(); cb != "" {
|
|
||||||
return errors.Errorf("context builder cannot be removed, run `docker context rm %s` to remove this context", cb)
|
|
||||||
}
|
|
||||||
|
|
||||||
err1 := rm(ctx, nodes, in)
|
|
||||||
if err := txn.Remove(b.Name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err1 != nil {
|
|
||||||
return err1
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", b.Name)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,25 +99,24 @@ func rmCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
var options rmOptions
|
var options rmOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "rm [NAME]",
|
Use: "rm [OPTIONS] [NAME] [NAME...]",
|
||||||
Short: "Remove a builder instance",
|
Short: "Remove one or more builder instances",
|
||||||
Args: cli.RequiresMaxArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
options.builder = rootOpts.builder
|
options.builders = []string{rootOpts.builder}
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
if options.allInactive {
|
if options.allInactive {
|
||||||
return errors.New("cannot specify builder name when --all-inactive is set")
|
return errors.New("cannot specify builder name when --all-inactive is set")
|
||||||
}
|
}
|
||||||
options.builder = args[0]
|
options.builders = args
|
||||||
}
|
}
|
||||||
return runRm(dockerCli, options)
|
return runRm(cmd.Context(), dockerCli, options)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.BuilderNames(dockerCli),
|
ValidArgsFunction: completion.BuilderNames(dockerCli),
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVar(&options.keepState, "keep-state", false, "Keep BuildKit state")
|
flags.BoolVar(&options.keepState, "keep-state", false, "Keep BuildKit state")
|
||||||
flags.BoolVar(&options.keepDaemon, "keep-daemon", false, "Keep the buildkitd daemon running")
|
flags.BoolVar(&options.keepDaemon, "keep-daemon", false, "Keep the BuildKit daemon running")
|
||||||
flags.BoolVar(&options.allInactive, "all-inactive", false, "Remove all inactive builders")
|
flags.BoolVar(&options.allInactive, "all-inactive", false, "Remove all inactive builders")
|
||||||
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
|
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
|
||||||
|
|
||||||
|
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/docker/cli/cli-plugins/plugin"
|
"github.com/docker/cli/cli-plugins/plugin"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/debug"
|
"github.com/docker/cli/cli/debug"
|
||||||
|
"github.com/moby/buildkit/util/appcontext"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
@@ -29,12 +30,15 @@ func NewRootCmd(name string, isPlugin bool, dockerCli command.Cli) *cobra.Comman
|
|||||||
CompletionOptions: cobra.CompletionOptions{
|
CompletionOptions: cobra.CompletionOptions{
|
||||||
HiddenDefaultCmd: true,
|
HiddenDefaultCmd: true,
|
||||||
},
|
},
|
||||||
}
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if isPlugin {
|
cmd.SetContext(appcontext.Context())
|
||||||
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
if !isPlugin {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return plugin.PersistentPreRunE(cmd, args)
|
return plugin.PersistentPreRunE(cmd, args)
|
||||||
}
|
},
|
||||||
} else {
|
}
|
||||||
|
if !isPlugin {
|
||||||
// match plugin behavior for standalone mode
|
// match plugin behavior for standalone mode
|
||||||
// https://github.com/docker/cli/blob/6c9eb708fa6d17765d71965f90e1c59cea686ee9/cli-plugins/plugin/plugin.go#L117-L127
|
// https://github.com/docker/cli/blob/6c9eb708fa6d17765d71965f90e1c59cea686ee9/cli-plugins/plugin/plugin.go#L117-L127
|
||||||
cmd.SilenceUsage = true
|
cmd.SilenceUsage = true
|
||||||
@@ -59,6 +63,10 @@ func NewRootCmd(name string, isPlugin bool, dockerCli command.Cli) *cobra.Comman
|
|||||||
"using default config store",
|
"using default config store",
|
||||||
))
|
))
|
||||||
|
|
||||||
|
if !isExperimental() {
|
||||||
|
cmd.SetHelpTemplate(cmd.HelpTemplate() + "\nExperimental commands and flags are hidden. Set BUILDX_EXPERIMENTAL=1 to show them.\n")
|
||||||
|
}
|
||||||
|
|
||||||
addCommands(cmd, dockerCli)
|
addCommands(cmd, dockerCli)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@@ -75,6 +83,7 @@ func addCommands(cmd *cobra.Command, dockerCli command.Cli) {
|
|||||||
buildCmd(dockerCli, opts, nil),
|
buildCmd(dockerCli, opts, nil),
|
||||||
bakeCmd(dockerCli, opts),
|
bakeCmd(dockerCli, opts),
|
||||||
createCmd(dockerCli),
|
createCmd(dockerCli),
|
||||||
|
dialStdioCmd(dockerCli, opts),
|
||||||
rmCmd(dockerCli, opts),
|
rmCmd(dockerCli, opts),
|
||||||
lsCmd(dockerCli),
|
lsCmd(dockerCli),
|
||||||
useCmd(dockerCli, opts),
|
useCmd(dockerCli, opts),
|
||||||
|
@@ -7,7 +7,6 @@ import (
|
|||||||
"github.com/docker/buildx/util/cobrautil/completion"
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
"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/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,9 +14,7 @@ type stopOptions struct {
|
|||||||
builder string
|
builder string
|
||||||
}
|
}
|
||||||
|
|
||||||
func runStop(dockerCli command.Cli, in stopOptions) error {
|
func runStop(ctx context.Context, dockerCli command.Cli, in stopOptions) error {
|
||||||
ctx := appcontext.Context()
|
|
||||||
|
|
||||||
b, err := builder.New(dockerCli,
|
b, err := builder.New(dockerCli,
|
||||||
builder.WithName(in.builder),
|
builder.WithName(in.builder),
|
||||||
builder.WithSkippedValidation(),
|
builder.WithSkippedValidation(),
|
||||||
@@ -45,7 +42,7 @@ func stopCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
options.builder = args[0]
|
options.builder = args[0]
|
||||||
}
|
}
|
||||||
return runStop(dockerCli, options)
|
return runStop(cmd.Context(), dockerCli, options)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.BuilderNames(dockerCli),
|
ValidArgsFunction: completion.BuilderNames(dockerCli),
|
||||||
}
|
}
|
||||||
|
23
commands/util.go
Normal file
23
commands/util.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
)
|
||||||
|
|
||||||
|
func prompt(ctx context.Context, ins io.Reader, out io.Writer, msg string) (bool, error) {
|
||||||
|
done := make(chan struct{})
|
||||||
|
var ok bool
|
||||||
|
go func() {
|
||||||
|
ok = command.PromptForConfirmation(ins, out, msg)
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return false, context.Cause(ctx)
|
||||||
|
case <-done:
|
||||||
|
return ok, nil
|
||||||
|
}
|
||||||
|
}
|
@@ -15,6 +15,7 @@ func CreateExports(entries []*ExportEntry) ([]client.ExportEntry, error) {
|
|||||||
if len(entries) == 0 {
|
if len(entries) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
var stdoutUsed bool
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
if entry.Type == "" {
|
if entry.Type == "" {
|
||||||
return nil, errors.Errorf("type is required for output")
|
return nil, errors.Errorf("type is required for output")
|
||||||
@@ -68,10 +69,14 @@ func CreateExports(entries []*ExportEntry) ([]client.ExportEntry, error) {
|
|||||||
entry.Destination = "-"
|
entry.Destination = "-"
|
||||||
}
|
}
|
||||||
if entry.Destination == "-" {
|
if entry.Destination == "-" {
|
||||||
|
if stdoutUsed {
|
||||||
|
return nil, errors.Errorf("multiple outputs configured to write to stdout")
|
||||||
|
}
|
||||||
if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
|
if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
|
||||||
return nil, errors.Errorf("dest file is required for %s exporter. refusing to write to console", out.Type)
|
return nil, errors.Errorf("dest file is required for %s exporter. refusing to write to console", out.Type)
|
||||||
}
|
}
|
||||||
out.Output = wrapWriteCloser(os.Stdout)
|
out.Output = wrapWriteCloser(os.Stdout)
|
||||||
|
stdoutUsed = true
|
||||||
} else if entry.Destination != "" {
|
} else if entry.Destination != "" {
|
||||||
fi, err := os.Stat(entry.Destination)
|
fi, err := os.Stat(entry.Destination)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
@@ -7,6 +7,9 @@ variable "DOCS_FORMATS" {
|
|||||||
variable "DESTDIR" {
|
variable "DESTDIR" {
|
||||||
default = "./bin"
|
default = "./bin"
|
||||||
}
|
}
|
||||||
|
variable "GOLANGCI_LINT_MULTIPLATFORM" {
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
|
||||||
# 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" {
|
||||||
@@ -32,6 +35,17 @@ target "lint" {
|
|||||||
inherits = ["_common"]
|
inherits = ["_common"]
|
||||||
dockerfile = "./hack/dockerfiles/lint.Dockerfile"
|
dockerfile = "./hack/dockerfiles/lint.Dockerfile"
|
||||||
output = ["type=cacheonly"]
|
output = ["type=cacheonly"]
|
||||||
|
platforms = GOLANGCI_LINT_MULTIPLATFORM != null ? [
|
||||||
|
"darwin/amd64",
|
||||||
|
"darwin/arm64",
|
||||||
|
"linux/amd64",
|
||||||
|
"linux/arm64",
|
||||||
|
"linux/s390x",
|
||||||
|
"linux/ppc64le",
|
||||||
|
"linux/riscv64",
|
||||||
|
"windows/amd64",
|
||||||
|
"windows/arm64"
|
||||||
|
] : []
|
||||||
}
|
}
|
||||||
|
|
||||||
target "validate-vendor" {
|
target "validate-vendor" {
|
||||||
|
@@ -213,7 +213,7 @@ target "webapp" {
|
|||||||
The following table shows the complete list of attributes that you can assign to a target:
|
The following table shows the complete list of attributes that you can assign to a target:
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
| ----------------------------------------------- | ------- | -------------------------------------------------------------------- |
|
|-------------------------------------------------|---------|----------------------------------------------------------------------|
|
||||||
| [`args`](#targetargs) | Map | Build arguments |
|
| [`args`](#targetargs) | Map | Build arguments |
|
||||||
| [`annotations`](#targetannotations) | List | Exporter annotations |
|
| [`annotations`](#targetannotations) | List | Exporter annotations |
|
||||||
| [`attest`](#targetattest) | List | Build attestations |
|
| [`attest`](#targetattest) | List | Build attestations |
|
||||||
@@ -233,9 +233,11 @@ The following table shows the complete list of attributes that you can assign to
|
|||||||
| [`platforms`](#targetplatforms) | List | Target platforms |
|
| [`platforms`](#targetplatforms) | List | Target platforms |
|
||||||
| [`pull`](#targetpull) | Boolean | Always pull images |
|
| [`pull`](#targetpull) | Boolean | Always pull images |
|
||||||
| [`secret`](#targetsecret) | List | Secrets to expose to the build |
|
| [`secret`](#targetsecret) | List | Secrets to expose to the build |
|
||||||
|
| [`shm-size`](#targetshm-size) | List | Size of `/dev/shm` |
|
||||||
| [`ssh`](#targetssh) | List | SSH agent sockets or keys 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 |
|
| [`tags`](#targettags) | List | Image names and tags |
|
||||||
| [`target`](#targettarget) | String | Target build stage |
|
| [`target`](#targettarget) | String | Target build stage |
|
||||||
|
| [`ulimits`](#targetulimits) | List | Ulimit options |
|
||||||
|
|
||||||
### `target.args`
|
### `target.args`
|
||||||
|
|
||||||
@@ -832,6 +834,29 @@ RUN --mount=type=secret,id=KUBECONFIG \
|
|||||||
KUBECONFIG=$(cat /run/secrets/KUBECONFIG) helm upgrade --install
|
KUBECONFIG=$(cat /run/secrets/KUBECONFIG) helm upgrade --install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `target.shm-size`
|
||||||
|
|
||||||
|
Sets the size of the shared memory allocated for build containers when using
|
||||||
|
`RUN` instructions.
|
||||||
|
|
||||||
|
The format is `<number><unit>`. `number` must be greater than `0`. Unit is
|
||||||
|
optional and can be `b` (bytes), `k` (kilobytes), `m` (megabytes), or `g`
|
||||||
|
(gigabytes). If you omit the unit, the system uses bytes.
|
||||||
|
|
||||||
|
This is the same as the `--shm-size` flag for `docker build`.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "default" {
|
||||||
|
shm-size = "128m"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> In most cases, it is recommended to let the builder automatically determine
|
||||||
|
> the appropriate configurations. Manual adjustments should only be considered
|
||||||
|
> when specific performance tuning is required for complex build scenarios.
|
||||||
|
|
||||||
### `target.ssh`
|
### `target.ssh`
|
||||||
|
|
||||||
Defines SSH agent sockets or keys to expose to the build.
|
Defines SSH agent sockets or keys to expose to the build.
|
||||||
@@ -878,6 +903,32 @@ target "default" {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `target.ulimits`
|
||||||
|
|
||||||
|
Ulimits overrides the default ulimits of build's containers when using `RUN`
|
||||||
|
instructions and are specified with a soft and hard limit as such:
|
||||||
|
`<type>=<soft limit>[:<hard limit>]`, for example:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "app" {
|
||||||
|
ulimits = [
|
||||||
|
"nofile=1024:1024"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> If you do not provide a `hard limit`, the `soft limit` is used
|
||||||
|
> for both values. If no `ulimits` are set, they are inherited from
|
||||||
|
> the default `ulimits` set on the daemon.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> In most cases, it is recommended to let the builder automatically determine
|
||||||
|
> the appropriate configurations. Manual adjustments should only be considered
|
||||||
|
> when specific performance tuning is required for complex build scenarios.
|
||||||
|
|
||||||
## Group
|
## Group
|
||||||
|
|
||||||
Groups allow you to invoke multiple builds (targets) at once.
|
Groups allow you to invoke multiple builds (targets) at once.
|
||||||
|
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/buildx/commands"
|
"github.com/docker/buildx/commands"
|
||||||
clidocstool "github.com/docker/cli-docs-tool"
|
clidocstool "github.com/docker/cli-docs-tool"
|
||||||
@@ -26,6 +27,28 @@ type options struct {
|
|||||||
formats []string
|
formats []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fixUpExperimentalCLI trims the " (EXPERIMENTAL)" suffix from the CLI output,
|
||||||
|
// as docs.docker.com already displays "experimental (CLI)",
|
||||||
|
//
|
||||||
|
// https://github.com/docker/buildx/pull/2188#issuecomment-1889487022
|
||||||
|
func fixUpExperimentalCLI(cmd *cobra.Command) {
|
||||||
|
const (
|
||||||
|
annotationExperimentalCLI = "experimentalCLI"
|
||||||
|
suffixExperimental = " (EXPERIMENTAL)"
|
||||||
|
)
|
||||||
|
if _, ok := cmd.Annotations[annotationExperimentalCLI]; ok {
|
||||||
|
cmd.Short = strings.TrimSuffix(cmd.Short, suffixExperimental)
|
||||||
|
}
|
||||||
|
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||||
|
if _, ok := f.Annotations[annotationExperimentalCLI]; ok {
|
||||||
|
f.Usage = strings.TrimSuffix(f.Usage, suffixExperimental)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
for _, c := range cmd.Commands() {
|
||||||
|
fixUpExperimentalCLI(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func gen(opts *options) error {
|
func gen(opts *options) error {
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
|
|
||||||
@@ -57,6 +80,8 @@ func gen(opts *options) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "yaml":
|
case "yaml":
|
||||||
|
// fix up is needed only for yaml (used for generating docs.docker.com contents)
|
||||||
|
fixUpExperimentalCLI(cmd)
|
||||||
if err = c.GenYamlTree(cmd); err != nil {
|
if err = c.GenYamlTree(cmd); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -9,21 +9,22 @@ 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 |
|
||||||
| [`debug`](buildx_debug.md) | Start debugger |
|
| [`debug`](buildx_debug.md) | Start debugger (EXPERIMENTAL) |
|
||||||
| [`du`](buildx_du.md) | Disk usage |
|
| [`dial-stdio`](buildx_dial-stdio.md) | Proxy current stdio streams to builder instance |
|
||||||
| [`imagetools`](buildx_imagetools.md) | Commands to work on images in registry |
|
| [`du`](buildx_du.md) | Disk usage |
|
||||||
| [`inspect`](buildx_inspect.md) | Inspect current builder instance |
|
| [`imagetools`](buildx_imagetools.md) | Commands to work on images in registry |
|
||||||
| [`ls`](buildx_ls.md) | List builder instances |
|
| [`inspect`](buildx_inspect.md) | Inspect current builder instance |
|
||||||
| [`prune`](buildx_prune.md) | Remove build cache |
|
| [`ls`](buildx_ls.md) | List builder instances |
|
||||||
| [`rm`](buildx_rm.md) | Remove a builder instance |
|
| [`prune`](buildx_prune.md) | Remove build cache |
|
||||||
| [`stop`](buildx_stop.md) | Stop builder instance |
|
| [`rm`](buildx_rm.md) | Remove one or more builder instances |
|
||||||
| [`use`](buildx_use.md) | Set the current builder instance |
|
| [`stop`](buildx_stop.md) | Stop builder instance |
|
||||||
| [`version`](buildx_version.md) | Show buildx version information |
|
| [`use`](buildx_use.md) | Set the current builder instance |
|
||||||
|
| [`version`](buildx_version.md) | Show buildx version information |
|
||||||
|
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
@@ -25,7 +25,7 @@ Start a build
|
|||||||
| [`--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/reference/cli/docker/image/build/#cgroup-parent) | `string` | | Set the parent cgroup for the `RUN` instructions during build |
|
| [`--cgroup-parent`](https://docs.docker.com/reference/cli/docker/image/build/#cgroup-parent) | `string` | | Set the parent cgroup for the `RUN` instructions during build |
|
||||||
| `--detach` | | | Detach buildx server (supported only on linux) |
|
| `--detach` | | | Detach buildx server (supported only on linux) (EXPERIMENTAL) |
|
||||||
| [`-f`](https://docs.docker.com/reference/cli/docker/image/build/#file), [`--file`](https://docs.docker.com/reference/cli/docker/image/build/#file) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
|
| [`-f`](https://docs.docker.com/reference/cli/docker/image/build/#file), [`--file`](https://docs.docker.com/reference/cli/docker/image/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 |
|
||||||
| `--label` | `stringArray` | | Set metadata for an image |
|
| `--label` | `stringArray` | | Set metadata for an image |
|
||||||
@@ -33,20 +33,20 @@ Start a build
|
|||||||
| [`--metadata-file`](#metadata-file) | `string` | | Write build result metadata to the file |
|
| [`--metadata-file`](#metadata-file) | `string` | | Write build result metadata to the file |
|
||||||
| `--network` | `string` | `default` | Set the networking mode for the `RUN` instructions during build |
|
| `--network` | `string` | `default` | Set the networking mode for the `RUN` instructions during build |
|
||||||
| `--no-cache` | | | Do not use cache when building the image |
|
| `--no-cache` | | | Do not use cache when building the image |
|
||||||
| `--no-cache-filter` | `stringArray` | | Do not cache specified stages |
|
| [`--no-cache-filter`](#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) |
|
| `--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` | | Shorthand for `--attest=type=provenance` |
|
| [`--provenance`](#provenance) | `string` | | Shorthand 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 |
|
||||||
| `--root` | `string` | | Specify root directory of server to connect |
|
| `--root` | `string` | | Specify root directory of server to connect (EXPERIMENTAL) |
|
||||||
| [`--sbom`](#sbom) | `string` | | Shorthand for `--attest=type=sbom` |
|
| [`--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]`) |
|
||||||
| `--server-config` | `string` | | Specify buildx server config file (used only when launching new server) |
|
| `--server-config` | `string` | | Specify buildx server config file (used only when launching new server) (EXPERIMENTAL) |
|
||||||
| [`--shm-size`](#shm-size) | `bytes` | `0` | Size of `/dev/shm` |
|
| [`--shm-size`](#shm-size) | `bytes` | `0` | Shared memory size for build containers |
|
||||||
| [`--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/reference/cli/docker/image/build/#tag), [`--tag`](https://docs.docker.com/reference/cli/docker/image/build/#tag) | `stringArray` | | Name and optionally a tag (format: `name:tag`) |
|
| [`-t`](https://docs.docker.com/reference/cli/docker/image/build/#tag), [`--tag`](https://docs.docker.com/reference/cli/docker/image/build/#tag) | `stringArray` | | Name and optionally a tag (format: `name:tag`) |
|
||||||
| [`--target`](https://docs.docker.com/reference/cli/docker/image/build/#target) | `string` | | Set the target build stage to build |
|
| [`--target`](https://docs.docker.com/reference/cli/docker/image/build/#target) | `string` | | Set the target build stage to build |
|
||||||
@@ -154,7 +154,7 @@ Allow extra privileged entitlement. List of entitlements:
|
|||||||
- `security.insecure` - Allows executions without sandbox. See
|
- `security.insecure` - Allows executions without sandbox. See
|
||||||
[related Dockerfile extensions](https://docs.docker.com/reference/dockerfile/#run---securitysandbox).
|
[related Dockerfile extensions](https://docs.docker.com/reference/dockerfile/#run---securitysandbox).
|
||||||
|
|
||||||
For entitlements to be enabled, the `buildkitd` daemon also needs to allow them
|
For entitlements to be enabled, the BuildKit daemon also needs to allow them
|
||||||
with `--allow-insecure-entitlement` (see [`create --buildkitd-flags`](buildx_create.md#buildkitd-flags)).
|
with `--allow-insecure-entitlement` (see [`create --buildkitd-flags`](buildx_create.md#buildkitd-flags)).
|
||||||
|
|
||||||
```console
|
```console
|
||||||
@@ -327,6 +327,7 @@ $ cat metadata.json
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"buildx.build.ref": "mybuilder/mybuilder0/0fjb6ubs52xx3vygf6fgdl611",
|
||||||
"containerimage.config.digest": "sha256:2937f66a9722f7f4a2df583de2f8cb97fc9196059a410e7f00072fc918930e66",
|
"containerimage.config.digest": "sha256:2937f66a9722f7f4a2df583de2f8cb97fc9196059a410e7f00072fc918930e66",
|
||||||
"containerimage.descriptor": {
|
"containerimage.descriptor": {
|
||||||
"annotations": {
|
"annotations": {
|
||||||
@@ -341,6 +342,61 @@ $ cat metadata.json
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### <a name="no-cache-filter"></a> Ignore build cache for specific stages (--no-cache-filter)
|
||||||
|
|
||||||
|
The `--no-cache-filter` lets you specify one or more stages of a multi-stage
|
||||||
|
Dockerfile for which build cache should be ignored. To specify multiple stages,
|
||||||
|
use a comma-separated syntax:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build --no-cache-filter stage1,stage2,stage3 .
|
||||||
|
```
|
||||||
|
|
||||||
|
For example, the following Dockerfile contains four stages:
|
||||||
|
|
||||||
|
- `base`
|
||||||
|
- `install`
|
||||||
|
- `test`
|
||||||
|
- `release`
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
FROM oven/bun:1 as base
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
FROM base AS install
|
||||||
|
WORKDIR /temp/dev
|
||||||
|
RUN --mount=type=bind,source=package.json,target=package.json \
|
||||||
|
--mount=type=bind,source=bun.lockb,target=bun.lockb \
|
||||||
|
bun install --frozen-lockfile
|
||||||
|
|
||||||
|
FROM base AS test
|
||||||
|
COPY --from=install /temp/dev/node_modules node_modules
|
||||||
|
COPY . .
|
||||||
|
RUN bun test
|
||||||
|
|
||||||
|
FROM base AS release
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
COPY --from=install /temp/dev/node_modules node_modules
|
||||||
|
COPY . .
|
||||||
|
ENTRYPOINT ["bun", "run", "index.js"]
|
||||||
|
```
|
||||||
|
|
||||||
|
To ignore the cache for the `install` stage:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build --no-cache-filter install .
|
||||||
|
```
|
||||||
|
|
||||||
|
To ignore the cache the `install` and `release` stages:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build --no-cache-filter install,release .
|
||||||
|
```
|
||||||
|
|
||||||
|
The arguments for the `--no-cache-filter` flag must be names of stages.
|
||||||
|
|
||||||
### <a name="output"></a> Set the export action for the build result (-o, --output)
|
### <a name="output"></a> Set the export action for the build result (-o, --output)
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -368,6 +424,10 @@ $ docker buildx build -o type=docker,dest=- . > myimage.tar
|
|||||||
$ docker buildx build -t tonistiigi/foo -o type=registry
|
$ docker buildx build -t tonistiigi/foo -o type=registry
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **Note **
|
||||||
|
>
|
||||||
|
> Since BuildKit v0.13.0 multiple outputs can be specified by repeating the flag.
|
||||||
|
|
||||||
Supported exported types are:
|
Supported exported types are:
|
||||||
|
|
||||||
#### `local`
|
#### `local`
|
||||||
@@ -598,12 +658,21 @@ RUN --mount=type=bind,target=. \
|
|||||||
$ SECRET_TOKEN=token docker buildx build --secret id=SECRET_TOKEN .
|
$ SECRET_TOKEN=token docker buildx build --secret id=SECRET_TOKEN .
|
||||||
```
|
```
|
||||||
|
|
||||||
### <a name="shm-size"></a> Size of /dev/shm (--shm-size)
|
### <a name="shm-size"></a> Shared memory size for build containers (--shm-size)
|
||||||
|
|
||||||
|
Sets the size of the shared memory allocated for build containers when using
|
||||||
|
`RUN` instructions.
|
||||||
|
|
||||||
The format is `<number><unit>`. `number` must be greater than `0`. Unit is
|
The format is `<number><unit>`. `number` must be greater than `0`. Unit is
|
||||||
optional and can be `b` (bytes), `k` (kilobytes), `m` (megabytes), or `g`
|
optional and can be `b` (bytes), `k` (kilobytes), `m` (megabytes), or `g`
|
||||||
(gigabytes). If you omit the unit, the system uses bytes.
|
(gigabytes). If you omit the unit, the system uses bytes.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> In most cases, it is recommended to let the builder automatically determine
|
||||||
|
> the appropriate configurations. Manual adjustments should only be considered
|
||||||
|
> when specific performance tuning is required for complex build scenarios.
|
||||||
|
|
||||||
### <a name="ssh"></a> SSH agent socket or keys to expose to the build (--ssh)
|
### <a name="ssh"></a> SSH agent socket or keys to expose to the build (--ssh)
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -637,7 +706,8 @@ $ docker buildx build --ssh default=$SSH_AUTH_SOCK .
|
|||||||
|
|
||||||
### <a name="ulimit"></a> Set ulimits (--ulimit)
|
### <a name="ulimit"></a> Set ulimits (--ulimit)
|
||||||
|
|
||||||
`--ulimit` is specified with a soft and hard limit as such:
|
`--ulimit` overrides the default ulimits of build's containers when using `RUN`
|
||||||
|
instructions and are specified with a soft and hard limit as such:
|
||||||
`<type>=<soft limit>[:<hard limit>]`, for example:
|
`<type>=<soft limit>[:<hard limit>]`, for example:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
@@ -649,3 +719,9 @@ $ docker buildx build --ulimit nofile=1024:1024 .
|
|||||||
> If you don't provide a `hard limit`, the `soft limit` is used
|
> If you don't provide a `hard limit`, the `soft limit` is used
|
||||||
> for both values. If no `ulimits` are set, they're inherited from
|
> for both values. If no `ulimits` are set, they're inherited from
|
||||||
> the default `ulimits` set on the daemon.
|
> the default `ulimits` set on the daemon.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> In most cases, it is recommended to let the builder automatically determine
|
||||||
|
> the appropriate configurations. Manual adjustments should only be considered
|
||||||
|
> when specific performance tuning is required for complex build scenarios.
|
||||||
|
@@ -9,19 +9,19 @@ 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-config`](#buildkitd-config) | `string` | | BuildKit daemon config file |
|
||||||
| [`--config`](#config) | `string` | | BuildKit config file |
|
| [`--buildkitd-flags`](#buildkitd-flags) | `string` | | BuildKit daemon flags |
|
||||||
| [`--driver`](#driver) | `string` | | Driver to use (available: `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 |
|
||||||
| [`--node`](#node) | `string` | | Create/modify node with given name |
|
| [`--node`](#node) | `string` | | Create/modify node with given name |
|
||||||
| [`--platform`](#platform) | `stringArray` | | Fixed platforms for current node |
|
| [`--platform`](#platform) | `stringArray` | | Fixed platforms for current node |
|
||||||
| [`--use`](#use) | | | Set the current builder instance |
|
| [`--use`](#use) | | | Set the current builder instance |
|
||||||
|
|
||||||
|
|
||||||
<!---MARKER_GEN_END-->
|
<!---MARKER_GEN_END-->
|
||||||
@@ -55,29 +55,15 @@ $ docker buildx create --name eager_beaver --append mycontext2
|
|||||||
eager_beaver
|
eager_beaver
|
||||||
```
|
```
|
||||||
|
|
||||||
### <a name="buildkitd-flags"></a> Specify options for the buildkitd daemon (--buildkitd-flags)
|
### <a name="buildkitd-config"></a> Specify a configuration file for the BuildKit daemon (--buildkitd-config)
|
||||||
|
|
||||||
```text
|
```text
|
||||||
--buildkitd-flags FLAGS
|
--buildkitd-config FILE
|
||||||
```
|
```
|
||||||
|
|
||||||
Adds flags when starting the buildkitd daemon. They take precedence over the
|
Specifies the configuration file for the BuildKit daemon to use. The
|
||||||
configuration file specified by [`--config`](#config). See `buildkitd --help`
|
configuration can be overridden by [`--buildkitd-flags`](#buildkitd-flags).
|
||||||
for the available flags.
|
See an [example BuildKit daemon configuration file](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md).
|
||||||
|
|
||||||
```text
|
|
||||||
--buildkitd-flags '--debug --debugaddr 0.0.0.0:6666'
|
|
||||||
```
|
|
||||||
|
|
||||||
### <a name="config"></a> Specify a configuration file for the buildkitd daemon (--config)
|
|
||||||
|
|
||||||
```text
|
|
||||||
--config FILE
|
|
||||||
```
|
|
||||||
|
|
||||||
Specifies the configuration file for the buildkitd daemon to use. The configuration
|
|
||||||
can be overridden by [`--buildkitd-flags`](#buildkitd-flags).
|
|
||||||
See an [example buildkitd configuration file](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md).
|
|
||||||
|
|
||||||
If you don't specify a configuration file, Buildx looks for one by default in:
|
If you don't specify a configuration file, Buildx looks for one by default in:
|
||||||
|
|
||||||
@@ -90,6 +76,36 @@ certificates for registries in the `buildkitd.toml` configuration, the files
|
|||||||
will be copied into the container under `/etc/buildkit/certs` and configuration
|
will be copied into the container under `/etc/buildkit/certs` and configuration
|
||||||
will be updated to reflect that.
|
will be updated to reflect that.
|
||||||
|
|
||||||
|
### <a name="buildkitd-flags"></a> Specify options for the BuildKit daemon (--buildkitd-flags)
|
||||||
|
|
||||||
|
```text
|
||||||
|
--buildkitd-flags FLAGS
|
||||||
|
```
|
||||||
|
|
||||||
|
Adds flags when starting the BuildKit daemon. They take precedence over the
|
||||||
|
configuration file specified by [`--buildkitd-config`](#buildkitd-config). See
|
||||||
|
`buildkitd --help` for the available flags.
|
||||||
|
|
||||||
|
```text
|
||||||
|
--buildkitd-flags '--debug --debugaddr 0.0.0.0:6666'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### BuildKit daemon network mode
|
||||||
|
|
||||||
|
You can specify the network mode for the BuildKit daemon with either the
|
||||||
|
configuration file specified by [`--buildkitd-config`](#buildkitd-config) using the
|
||||||
|
`worker.oci.networkMode` option or `--oci-worker-net` flag here. The default
|
||||||
|
value is `auto` and can be one of `bridge`, `cni`, `host`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
--buildkitd-flags '--oci-worker-net bridge'
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> Network mode "bridge" is supported since BuildKit v0.13 and will become the
|
||||||
|
> default in next v0.14.
|
||||||
|
|
||||||
### <a name="driver"></a> Set the builder driver to use (--driver)
|
### <a name="driver"></a> Set the builder driver to use (--driver)
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -133,8 +149,8 @@ to achieve that.
|
|||||||
|
|
||||||
#### `remote` driver
|
#### `remote` driver
|
||||||
|
|
||||||
Uses a remote instance of buildkitd over an arbitrary connection. With this
|
Uses a remote instance of BuildKit daemon over an arbitrary connection. With
|
||||||
driver, you manually create and manage instances of buildkit yourself, and
|
this driver, you manually create and manage instances of buildkit yourself, and
|
||||||
configure buildx to point at it.
|
configure buildx to point at it.
|
||||||
|
|
||||||
Unlike `docker` driver, built images will not automatically appear in
|
Unlike `docker` driver, built images will not automatically appear in
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
# docker buildx debug
|
# docker buildx debug
|
||||||
|
|
||||||
<!---MARKER_GEN_START-->
|
<!---MARKER_GEN_START-->
|
||||||
Start debugger
|
Start debugger (EXPERIMENTAL)
|
||||||
|
|
||||||
### Subcommands
|
### Subcommands
|
||||||
|
|
||||||
@@ -15,12 +15,12 @@ Start debugger
|
|||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|:------------------|:---------|:--------|:---------------------------------------------------------------------------------------------------------|
|
|:------------------|:---------|:--------|:---------------------------------------------------------------------------------------------------------|
|
||||||
| `--builder` | `string` | | Override the configured builder instance |
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
| `--detach` | `bool` | `true` | Detach buildx server for the monitor (supported only on linux) |
|
| `--detach` | `bool` | `true` | Detach buildx server for the monitor (supported only on linux) (EXPERIMENTAL) |
|
||||||
| `--invoke` | `string` | | Launch a monitor with executing specified command |
|
| `--invoke` | `string` | | Launch a monitor with executing specified command (EXPERIMENTAL) |
|
||||||
| `--on` | `string` | `error` | When to launch the monitor ([always, error]) |
|
| `--on` | `string` | `error` | When to launch the monitor ([always, error]) (EXPERIMENTAL) |
|
||||||
| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`) for the monitor. Use plain to show container output |
|
| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`) for the monitor. Use plain to show container output |
|
||||||
| `--root` | `string` | | Specify root directory of server to connect for the monitor |
|
| `--root` | `string` | | Specify root directory of server to connect for the monitor (EXPERIMENTAL) |
|
||||||
| `--server-config` | `string` | | Specify buildx server config file for the monitor (used only when launching new server) |
|
| `--server-config` | `string` | | Specify buildx server config file for the monitor (used only when launching new server) (EXPERIMENTAL) |
|
||||||
|
|
||||||
|
|
||||||
<!---MARKER_GEN_END-->
|
<!---MARKER_GEN_END-->
|
||||||
|
@@ -21,7 +21,7 @@ Start a build
|
|||||||
| `--cache-from` | `stringArray` | | External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`) |
|
| `--cache-from` | `stringArray` | | External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`) |
|
||||||
| `--cache-to` | `stringArray` | | Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`) |
|
| `--cache-to` | `stringArray` | | Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`) |
|
||||||
| [`--cgroup-parent`](https://docs.docker.com/reference/cli/docker/image/build/#cgroup-parent) | `string` | | Set the parent cgroup for the `RUN` instructions during build |
|
| [`--cgroup-parent`](https://docs.docker.com/reference/cli/docker/image/build/#cgroup-parent) | `string` | | Set the parent cgroup for the `RUN` instructions during build |
|
||||||
| `--detach` | | | Detach buildx server (supported only on linux) |
|
| `--detach` | | | Detach buildx server (supported only on linux) (EXPERIMENTAL) |
|
||||||
| [`-f`](https://docs.docker.com/reference/cli/docker/image/build/#file), [`--file`](https://docs.docker.com/reference/cli/docker/image/build/#file) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
|
| [`-f`](https://docs.docker.com/reference/cli/docker/image/build/#file), [`--file`](https://docs.docker.com/reference/cli/docker/image/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 |
|
||||||
| `--label` | `stringArray` | | Set metadata for an image |
|
| `--label` | `stringArray` | | Set metadata for an image |
|
||||||
@@ -32,17 +32,17 @@ Start a build
|
|||||||
| `--no-cache-filter` | `stringArray` | | Do not cache specified stages |
|
| `--no-cache-filter` | `stringArray` | | Do not cache specified stages |
|
||||||
| `-o`, `--output` | `stringArray` | | Output destination (format: `type=local,dest=path`) |
|
| `-o`, `--output` | `stringArray` | | Output destination (format: `type=local,dest=path`) |
|
||||||
| `--platform` | `stringArray` | | Set target platform for build |
|
| `--platform` | `stringArray` | | Set target platform for build |
|
||||||
| `--print` | `string` | | Print result of information request (e.g., outline, targets) |
|
| `--print` | `string` | | Print result of information request (e.g., outline, targets) (EXPERIMENTAL) |
|
||||||
| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output |
|
| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output |
|
||||||
| `--provenance` | `string` | | Shorthand for `--attest=type=provenance` |
|
| `--provenance` | `string` | | Shorthand for `--attest=type=provenance` |
|
||||||
| `--pull` | | | Always attempt to pull all referenced images |
|
| `--pull` | | | Always attempt to pull all referenced images |
|
||||||
| `--push` | | | Shorthand for `--output=type=registry` |
|
| `--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 |
|
||||||
| `--root` | `string` | | Specify root directory of server to connect |
|
| `--root` | `string` | | Specify root directory of server to connect (EXPERIMENTAL) |
|
||||||
| `--sbom` | `string` | | Shorthand for `--attest=type=sbom` |
|
| `--sbom` | `string` | | Shorthand for `--attest=type=sbom` |
|
||||||
| `--secret` | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
| `--secret` | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
||||||
| `--server-config` | `string` | | Specify buildx server config file (used only when launching new server) |
|
| `--server-config` | `string` | | Specify buildx server config file (used only when launching new server) (EXPERIMENTAL) |
|
||||||
| `--shm-size` | `bytes` | `0` | Size of `/dev/shm` |
|
| `--shm-size` | `bytes` | `0` | Shared memory size for build containers |
|
||||||
| `--ssh` | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|<id>[=<socket>\|<key>[,<key>]]`) |
|
| `--ssh` | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|<id>[=<socket>\|<key>[,<key>]]`) |
|
||||||
| [`-t`](https://docs.docker.com/reference/cli/docker/image/build/#tag), [`--tag`](https://docs.docker.com/reference/cli/docker/image/build/#tag) | `stringArray` | | Name and optionally a tag (format: `name:tag`) |
|
| [`-t`](https://docs.docker.com/reference/cli/docker/image/build/#tag), [`--tag`](https://docs.docker.com/reference/cli/docker/image/build/#tag) | `stringArray` | | Name and optionally a tag (format: `name:tag`) |
|
||||||
| [`--target`](https://docs.docker.com/reference/cli/docker/image/build/#target) | `string` | | Set the target build stage to build |
|
| [`--target`](https://docs.docker.com/reference/cli/docker/image/build/#target) | `string` | | Set the target build stage to build |
|
||||||
|
47
docs/reference/buildx_dial-stdio.md
Normal file
47
docs/reference/buildx_dial-stdio.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# docker buildx dial-stdio
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Proxy current stdio streams to builder instance
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:-------------|:---------|:--------|:-------------------------------------------------|
|
||||||
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
|
| `--platform` | `string` | | Target platform: this is used for node selection |
|
||||||
|
| `--progress` | `string` | `quiet` | Set type of progress output (auto, plain, tty). |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
dial-stdio uses the stdin and stdout streams of the command to proxy to the configured builder instance.
|
||||||
|
It is not intended to be used by humans, but rather by other tools that want to interact with the builder instance via BuildKit API.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Example go program that uses the dial-stdio command wire up a buildkit client.
|
||||||
|
This is for example use only and may not be suitable for production use.
|
||||||
|
|
||||||
|
```go
|
||||||
|
client.New(ctx, "", client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
||||||
|
c1, c2 := net.Pipe()
|
||||||
|
cmd := exec.Command("docker", "buildx", "dial-stdio")
|
||||||
|
cmd.Stdin = c1
|
||||||
|
cmd.Stdout = c1
|
||||||
|
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
c1.Close()
|
||||||
|
c2.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
cmd.Wait()
|
||||||
|
c2.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return c2
|
||||||
|
}))
|
||||||
|
```
|
@@ -13,13 +13,106 @@ Disk usage
|
|||||||
|:------------------------|:---------|:--------|:-----------------------------------------|
|
|:------------------------|:---------|:--------|:-----------------------------------------|
|
||||||
| [`--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`](#verbose) | | | Provide a more verbose output |
|
||||||
|
|
||||||
|
|
||||||
<!---MARKER_GEN_END-->
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
### Show disk usage
|
||||||
|
|
||||||
|
The `docker buildx du` command shows the disk usage for the currently selected
|
||||||
|
builder.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx du
|
||||||
|
ID RECLAIMABLE SIZE LAST ACCESSED
|
||||||
|
12wgll9os87pazzft8lt0yztp* true 1.704GB 13 days ago
|
||||||
|
iupsv3it5ubh92aweb7c1wojc* true 1.297GB 36 minutes ago
|
||||||
|
ek4ve8h4obyv5kld6vicmtqyn true 811.7MB 13 days ago
|
||||||
|
isovrfnmkelzhtdx942w9vjcb* true 811.7MB 13 days ago
|
||||||
|
0jty7mjrndi1yo7xkv1baralh true 810.5MB 13 days ago
|
||||||
|
jyzkefmsysqiaakgwmjgxjpcz* true 810.5MB 13 days ago
|
||||||
|
z8w1y95jn93gvj92jtaj6uhwk true 318MB 2 weeks ago
|
||||||
|
rz2zgfcwlfxsxd7d41w2sz2tt true 8.224kB* 43 hours ago
|
||||||
|
n5bkzpewmk2eiu6hn9tzx18jd true 8.224kB* 43 hours ago
|
||||||
|
ao94g6vtbzdl6k5zgdmrmnwpt true 8.224kB* 43 hours ago
|
||||||
|
2pyjep7njm0wh39vcingxb97i true 8.224kB* 43 hours ago
|
||||||
|
Shared: 115.5MB
|
||||||
|
Private: 10.25GB
|
||||||
|
Reclaimable: 10.36GB
|
||||||
|
Total: 10.36GB
|
||||||
|
```
|
||||||
|
|
||||||
|
If `RECLAIMABLE` is false, the `docker buildx du prune` command won't delete
|
||||||
|
the record, even if you use `--all`. That's because the record is actively in
|
||||||
|
use by some component of the builder.
|
||||||
|
|
||||||
|
The asterisks (\*) in the default output indicate the following:
|
||||||
|
|
||||||
|
- An asterisk next to an ID (`zu7m6evdpebh5h8kfkpw9dlf2*`) indicates that the record
|
||||||
|
is mutable. The size of the record may change, or another build can take ownership of
|
||||||
|
it and change or commit to it. If you run the `du` command again, this item may
|
||||||
|
not be there anymore, or the size might be different.
|
||||||
|
- An asterisk next to a size (`8.288kB*`) indicates that the record is shared.
|
||||||
|
Storage of the record is shared with some other resource, typically an image.
|
||||||
|
If you prune such a record then you will lose build cache but only metadata
|
||||||
|
will be deleted as the image still needs to actual storage layers.
|
||||||
|
|
||||||
|
### <a name="verbose"></a> Use verbose output (--verbose)
|
||||||
|
|
||||||
|
The verbose output of the `docker buildx du` command is useful for inspecting
|
||||||
|
the disk usage records in more detail. The verbose output shows the mutable and
|
||||||
|
shared states more clearly, as well as additional information about the
|
||||||
|
corresponding layer.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx du --verbose
|
||||||
|
...
|
||||||
|
Last used: 2 days ago
|
||||||
|
Type: regular
|
||||||
|
|
||||||
|
ID: 05d0elirb4mmvpmnzbrp3ssrg
|
||||||
|
Parent: e8sfdn4mygrg7msi9ak1dy6op
|
||||||
|
Created at: 2023-11-20 09:53:30.881558721 +0000 UTC
|
||||||
|
Mutable: false
|
||||||
|
Reclaimable: true
|
||||||
|
Shared: false
|
||||||
|
Size: 0B
|
||||||
|
Description: [gobase 3/3] WORKDIR /src
|
||||||
|
Usage count: 3
|
||||||
|
Last used: 24 hours ago
|
||||||
|
Type: regular
|
||||||
|
|
||||||
|
Reclaimable: 4.453GB
|
||||||
|
Total: 4.453GB
|
||||||
|
```
|
||||||
|
|
||||||
### <a name="builder"></a> Override the configured builder instance (--builder)
|
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||||
|
|
||||||
Same as [`buildx --builder`](buildx.md#builder).
|
Use the `--builder` flag to inspect the disk usage of a particular builder.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx du --builder youthful_shtern
|
||||||
|
ID RECLAIMABLE SIZE LAST ACCESSED
|
||||||
|
g41agepgdczekxg2mtw0dujsv* true 1.312GB 47 hours ago
|
||||||
|
e6ycrsa0bn9akigqgzu0sc6kr true 318MB 47 hours ago
|
||||||
|
our9zg4ndly65ze1ccczdksiz true 204.9MB 45 hours ago
|
||||||
|
b7xv3xpxnwupc81tc9ya3mgq6* true 120.6MB 47 hours ago
|
||||||
|
zihgye15ss6vum3wmck9egdoy* true 79.81MB 2 days ago
|
||||||
|
aaydharssv1ug98yhuwclkfrh* true 79.81MB 2 days ago
|
||||||
|
ta1r4vmnjug5dhub76as4kkol* true 74.51MB 47 hours ago
|
||||||
|
murma9f83j9h8miifbq68udjf* true 74.51MB 47 hours ago
|
||||||
|
47f961866a49g5y8myz80ixw1* true 74.51MB 47 hours ago
|
||||||
|
tzh99xtzlaf6txllh3cobag8t true 74.49MB 47 hours ago
|
||||||
|
ld6laoeuo1kwapysu6afwqybl* true 59.89MB 47 hours ago
|
||||||
|
yitxizi5kaplpyomqpos2cryp* true 59.83MB 47 hours ago
|
||||||
|
iy8aa4b7qjn0qmy9wiga9cj8w true 33.65MB 47 hours ago
|
||||||
|
mci7okeijyp8aqqk16j80dy09 true 19.86MB 47 hours ago
|
||||||
|
lqvj091he652slxdla4wom3pz true 14.08MB 47 hours ago
|
||||||
|
fkt31oiv793nd26h42llsjcw7* true 11.87MB 2 days ago
|
||||||
|
uj802yxtvkcjysnjb4kgwvn2v true 11.68MB 45 hours ago
|
||||||
|
Reclaimable: 2.627GB
|
||||||
|
Total: 2.627GB
|
||||||
|
```
|
||||||
|
@@ -7,23 +7,78 @@ docker buildx ls
|
|||||||
<!---MARKER_GEN_START-->
|
<!---MARKER_GEN_START-->
|
||||||
List builder instances
|
List builder instances
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:----------------------|:---------|:--------|:------------------|
|
||||||
|
| [`--format`](#format) | `string` | `table` | Format the output |
|
||||||
|
|
||||||
|
|
||||||
<!---MARKER_GEN_END-->
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
Lists all builder instances and the nodes for each instance
|
Lists all builder instances and the nodes for each instance.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker buildx ls
|
$ docker buildx ls
|
||||||
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
|
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
|
||||||
elated_tesla * docker-container
|
elated_tesla* docker-container
|
||||||
elated_tesla0 unix:///var/run/docker.sock running v0.10.3 linux/amd64
|
\_ elated_tesla0 \_ unix:///var/run/docker.sock running v0.10.3 linux/amd64
|
||||||
elated_tesla1 ssh://ubuntu@1.2.3.4 running v0.10.3 linux/arm64*, linux/arm/v7, linux/arm/v6
|
\_ elated_tesla1 \_ ssh://ubuntu@1.2.3.4 running v0.10.3 linux/arm64*, linux/arm/v7, linux/arm/v6
|
||||||
default docker
|
default docker
|
||||||
default default running v0.8.2 linux/amd64
|
\_ default \_ default running v0.8.2 linux/amd64
|
||||||
```
|
```
|
||||||
|
|
||||||
Each builder has one or more nodes associated with it. The current builder's
|
Each builder has one or more nodes associated with it. The current builder's
|
||||||
name is marked with a `*` in `NAME/NODE` and explicit node to build against for
|
name is marked with a `*` in `NAME/NODE` and explicit node to build against for
|
||||||
the target platform marked with a `*` in the `PLATFORMS` column.
|
the target platform marked with a `*` in the `PLATFORMS` column.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### <a name="format"></a> Format the output (--format)
|
||||||
|
|
||||||
|
The formatting options (`--format`) pretty-prints builder instances output
|
||||||
|
using a Go template.
|
||||||
|
|
||||||
|
Valid placeholders for the Go template are listed below:
|
||||||
|
|
||||||
|
| Placeholder | Description |
|
||||||
|
|-------------------|---------------------------------------------|
|
||||||
|
| `.Name` | Builder or node name |
|
||||||
|
| `.DriverEndpoint` | Driver (for builder) or Endpoint (for node) |
|
||||||
|
| `.LastActivity` | Builder last activity |
|
||||||
|
| `.Status` | Builder or node status |
|
||||||
|
| `.Buildkit` | BuildKit version of the node |
|
||||||
|
| `.Platforms` | Available node's platforms |
|
||||||
|
| `.Error` | Error |
|
||||||
|
| `.Builder` | Builder object |
|
||||||
|
|
||||||
|
When using the `--format` option, the `ls` command will either output the data
|
||||||
|
exactly as the template declares or, when using the `table` directive, includes
|
||||||
|
column headers as well.
|
||||||
|
|
||||||
|
The following example uses a template without headers and outputs the
|
||||||
|
`Name` and `DriverEndpoint` entries separated by a colon (`:`):
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx ls --format "{{.Name}}: {{.DriverEndpoint}}"
|
||||||
|
elated_tesla: docker-container
|
||||||
|
elated_tesla0: unix:///var/run/docker.sock
|
||||||
|
elated_tesla1: ssh://ubuntu@1.2.3.4
|
||||||
|
default: docker
|
||||||
|
default: default
|
||||||
|
```
|
||||||
|
|
||||||
|
The `Builder` placeholder can be used to access the builder object and its
|
||||||
|
fields. For example, the following template outputs the builder's and
|
||||||
|
nodes' names with their respective endpoints:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx ls --format "{{.Builder.Name}}: {{range .Builder.Nodes}}\n {{.Name}}: {{.Endpoint}}{{end}}"
|
||||||
|
elated_tesla:
|
||||||
|
elated_tesla0: unix:///var/run/docker.sock
|
||||||
|
elated_tesla1: ssh://ubuntu@1.2.3.4
|
||||||
|
default: docker
|
||||||
|
default: default
|
||||||
|
```
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
# buildx rm
|
# buildx rm
|
||||||
|
|
||||||
```text
|
```text
|
||||||
docker buildx rm [NAME]
|
docker buildx rm [OPTIONS] [NAME] [NAME...]
|
||||||
```
|
```
|
||||||
|
|
||||||
<!---MARKER_GEN_START-->
|
<!---MARKER_GEN_START-->
|
||||||
Remove a builder instance
|
Remove one or more builder instances
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ Remove a builder instance
|
|||||||
| [`--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 |
|
||||||
| [`--keep-daemon`](#keep-daemon) | | | Keep the buildkitd daemon running |
|
| [`--keep-daemon`](#keep-daemon) | | | Keep the BuildKit daemon running |
|
||||||
| [`--keep-state`](#keep-state) | | | Keep BuildKit state |
|
| [`--keep-state`](#keep-state) | | | Keep BuildKit state |
|
||||||
|
|
||||||
|
|
||||||
@@ -48,10 +48,10 @@ Do not prompt for confirmation before removing inactive builders.
|
|||||||
$ docker buildx rm --all-inactive --force
|
$ docker buildx rm --all-inactive --force
|
||||||
```
|
```
|
||||||
|
|
||||||
### <a name="keep-daemon"></a> Keep the buildkitd daemon running (--keep-daemon)
|
### <a name="keep-daemon"></a> Keep the BuildKit daemon running (--keep-daemon)
|
||||||
|
|
||||||
Keep the BuildKit daemon running after the buildx context is removed. This is
|
Keep the BuildKit daemon running after the buildx context is removed. This is
|
||||||
useful when you manage buildkitd daemons and buildx contexts independently.
|
useful when you manage BuildKit daemons and buildx contexts independently.
|
||||||
Only supported by the
|
Only supported by the
|
||||||
[`docker-container`](https://docs.docker.com/build/drivers/docker-container/)
|
[`docker-container`](https://docs.docker.com/build/drivers/docker-container/)
|
||||||
and [`kubernetes`](https://docs.docker.com/build/drivers/kubernetes/) drivers.
|
and [`kubernetes`](https://docs.docker.com/build/drivers/kubernetes/) drivers.
|
||||||
|
@@ -22,6 +22,7 @@ import (
|
|||||||
"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"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
|
"github.com/docker/docker/api/types/system"
|
||||||
dockerclient "github.com/docker/docker/client"
|
dockerclient "github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
dockerarchive "github.com/docker/docker/pkg/archive"
|
dockerarchive "github.com/docker/docker/pkg/archive"
|
||||||
@@ -43,17 +44,18 @@ type Driver struct {
|
|||||||
|
|
||||||
// if you add fields, remember to update docs:
|
// if you add fields, remember to update docs:
|
||||||
// https://github.com/docker/docs/blob/main/content/build/drivers/docker-container.md
|
// https://github.com/docker/docs/blob/main/content/build/drivers/docker-container.md
|
||||||
netMode string
|
netMode string
|
||||||
image string
|
image string
|
||||||
memory opts.MemBytes
|
memory opts.MemBytes
|
||||||
memorySwap opts.MemSwapBytes
|
memorySwap opts.MemSwapBytes
|
||||||
cpuQuota int64
|
cpuQuota int64
|
||||||
cpuPeriod int64
|
cpuPeriod int64
|
||||||
cpuShares int64
|
cpuShares int64
|
||||||
cpusetCpus string
|
cpusetCpus string
|
||||||
cpusetMems string
|
cpusetMems string
|
||||||
cgroupParent string
|
cgroupParent string
|
||||||
env []string
|
restartPolicy container.RestartPolicy
|
||||||
|
env []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) IsMobyDriver() bool {
|
func (d *Driver) IsMobyDriver() bool {
|
||||||
@@ -120,7 +122,8 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
|
|||||||
useInit := true // let it cleanup exited processes created by BuildKit's container API
|
useInit := true // let it cleanup exited processes created by BuildKit's container API
|
||||||
return l.Wrap("creating container "+d.Name, func() error {
|
return l.Wrap("creating container "+d.Name, func() error {
|
||||||
hc := &container.HostConfig{
|
hc := &container.HostConfig{
|
||||||
Privileged: true,
|
Privileged: true,
|
||||||
|
RestartPolicy: d.restartPolicy,
|
||||||
Mounts: []mount.Mount{
|
Mounts: []mount.Mount{
|
||||||
{
|
{
|
||||||
Type: mount.TypeVolume,
|
Type: mount.TypeVolume,
|
||||||
@@ -164,7 +167,7 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
secOpts, err := dockertypes.DecodeSecurityOptions(info.SecurityOptions)
|
secOpts, err := system.DecodeSecurityOptions(info.SecurityOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -223,7 +226,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, dockertypes.ContainerLogsOptions{
|
rc, err := d.DockerAPI.ContainerLogs(ctx, d.Name, container.LogsOptions{
|
||||||
ShowStdout: true, ShowStderr: true,
|
ShowStdout: true, ShowStderr: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -301,7 +304,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, dockertypes.ContainerStartOptions{})
|
return d.DockerAPI.ContainerStart(ctx, d.Name, container.StartOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
|
func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
|
||||||
@@ -359,18 +362,18 @@ func (d *Driver) Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if info.Status != driver.Inactive {
|
if info.Status != driver.Inactive {
|
||||||
container, err := d.DockerAPI.ContainerInspect(ctx, d.Name)
|
ctr, err := d.DockerAPI.ContainerInspect(ctx, d.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if rmDaemon {
|
if rmDaemon {
|
||||||
if err := d.DockerAPI.ContainerRemove(ctx, d.Name, dockertypes.ContainerRemoveOptions{
|
if err := d.DockerAPI.ContainerRemove(ctx, d.Name, container.RemoveOptions{
|
||||||
RemoveVolumes: true,
|
RemoveVolumes: true,
|
||||||
Force: force,
|
Force: force,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, v := range container.Mounts {
|
for _, v := range ctr.Mounts {
|
||||||
if v.Name != d.Name+volumeStateSuffix {
|
if v.Name != d.Name+volumeStateSuffix {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -383,15 +386,22 @@ func (d *Driver) Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
func (d *Driver) Dial(ctx context.Context) (net.Conn, error) {
|
||||||
_, conn, err := d.exec(ctx, []string{"buildctl", "dial-stdio"})
|
_, conn, err := d.exec(ctx, []string{"buildctl", "dial-stdio"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
conn = demuxConn(conn)
|
conn = demuxConn(conn)
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
exp, err := detect.Exporter()
|
func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
||||||
|
conn, err := d.Dial(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
exp, _, err := detect.Exporter()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -484,7 +494,7 @@ func writeConfigFiles(m map[string][]byte) (_ string, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getBuildkitFlags(initConfig driver.InitConfig) []string {
|
func getBuildkitFlags(initConfig driver.InitConfig) []string {
|
||||||
flags := initConfig.BuildkitFlags
|
flags := initConfig.BuildkitdFlags
|
||||||
if _, ok := initConfig.Files[buildkitdConfigFile]; ok {
|
if _, ok := initConfig.Files[buildkitdConfigFile]; ok {
|
||||||
// There's no way for us to determine the appropriate default configuration
|
// There's no way for us to determine the appropriate default configuration
|
||||||
// path and the default path can vary depending on if the image is normal
|
// path and the default path can vary depending on if the image is normal
|
||||||
|
@@ -7,12 +7,14 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
|
dockeropts "github.com/docker/cli/opts"
|
||||||
dockerclient "github.com/docker/docker/client"
|
dockerclient "github.com/docker/docker/client"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const prioritySupported = 30
|
const prioritySupported = 30
|
||||||
const priorityUnsupported = 70
|
const priorityUnsupported = 70
|
||||||
|
const defaultRestartPolicy = "unless-stopped"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
driver.Register(&factory{})
|
driver.Register(&factory{})
|
||||||
@@ -40,14 +42,19 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
|
|||||||
if cfg.DockerAPI == nil {
|
if cfg.DockerAPI == nil {
|
||||||
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}
|
rp, err := dockeropts.ParseRestartPolicy(defaultRestartPolicy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
d := &Driver{
|
||||||
|
factory: f,
|
||||||
|
InitConfig: cfg,
|
||||||
|
restartPolicy: rp,
|
||||||
|
}
|
||||||
for k, v := range cfg.DriverOpts {
|
for k, v := range cfg.DriverOpts {
|
||||||
switch {
|
switch {
|
||||||
case k == "network":
|
case k == "network":
|
||||||
d.netMode = v
|
d.netMode = v
|
||||||
if v == "host" {
|
|
||||||
d.InitConfig.BuildkitFlags = append(d.InitConfig.BuildkitFlags, "--allow-insecure-entitlement=network.host")
|
|
||||||
}
|
|
||||||
case k == "image":
|
case k == "image":
|
||||||
d.image = v
|
d.image = v
|
||||||
case k == "memory":
|
case k == "memory":
|
||||||
@@ -82,6 +89,11 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
|
|||||||
d.cpusetMems = v
|
d.cpusetMems = v
|
||||||
case k == "cgroup-parent":
|
case k == "cgroup-parent":
|
||||||
d.cgroupParent = v
|
d.cgroupParent = v
|
||||||
|
case k == "restart-policy":
|
||||||
|
d.restartPolicy, err = dockeropts.ParseRestartPolicy(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
case strings.HasPrefix(k, "env."):
|
case strings.HasPrefix(k, "env."):
|
||||||
envName := strings.TrimPrefix(k, "env.")
|
envName := strings.TrimPrefix(k, "env.")
|
||||||
if envName == "" {
|
if envName == "" {
|
||||||
|
@@ -57,16 +57,20 @@ func (d *Driver) Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Driver) Dial(ctx context.Context) (net.Conn, error) {
|
||||||
|
return d.DockerAPI.DialHijack(ctx, "/grpc", "h2c", d.DialMeta)
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
||||||
opts := []client.ClientOpt{
|
opts := []client.ClientOpt{
|
||||||
client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
||||||
return d.DockerAPI.DialHijack(ctx, "/grpc", "h2c", d.DialMeta)
|
return d.Dial(ctx)
|
||||||
}), client.WithSessionDialer(func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
|
}), client.WithSessionDialer(func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
|
||||||
return d.DockerAPI.DialHijack(ctx, "/session", proto, meta)
|
return d.DockerAPI.DialHijack(ctx, "/session", proto, meta)
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
exp, err := detect.Exporter()
|
exp, _, err := detect.Exporter()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -59,6 +59,7 @@ type Driver interface {
|
|||||||
Version(context.Context) (string, error)
|
Version(context.Context) (string, error)
|
||||||
Stop(ctx context.Context, force bool) error
|
Stop(ctx context.Context, force bool) error
|
||||||
Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error
|
Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error
|
||||||
|
Dial(ctx context.Context) (net.Conn, error)
|
||||||
Client(ctx context.Context) (*client.Client, error)
|
Client(ctx context.Context) (*client.Client, error)
|
||||||
Features(ctx context.Context) map[Feature]bool
|
Features(ctx context.Context) map[Feature]bool
|
||||||
HostGatewayIP(ctx context.Context) (net.IP, error)
|
HostGatewayIP(ctx context.Context) (net.IP, error)
|
||||||
|
@@ -189,7 +189,7 @@ func (d *Driver) Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
func (d *Driver) Dial(ctx context.Context) (net.Conn, error) {
|
||||||
restClient := d.clientset.CoreV1().RESTClient()
|
restClient := d.clientset.CoreV1().RESTClient()
|
||||||
restClientConfig, err := d.KubeClientConfig.ClientConfig()
|
restClientConfig, err := d.KubeClientConfig.ClientConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -208,15 +208,18 @@ func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
exp, err := detect.Exporter()
|
func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
||||||
|
exp, _, err := detect.Exporter()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var opts []client.ClientOpt
|
var opts []client.ClientOpt
|
||||||
opts = append(opts, client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
opts = append(opts, client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
||||||
return conn, nil
|
return d.Dial(ctx)
|
||||||
}))
|
}))
|
||||||
if td, ok := exp.(client.TracerDelegate); ok {
|
if td, ok := exp.(client.TracerDelegate); ok {
|
||||||
opts = append(opts, client.WithTracerDelegate(td))
|
opts = append(opts, client.WithTracerDelegate(td))
|
||||||
|
@@ -105,7 +105,7 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg
|
|||||||
Name: deploymentName,
|
Name: deploymentName,
|
||||||
Image: bkimage.DefaultImage,
|
Image: bkimage.DefaultImage,
|
||||||
Replicas: 1,
|
Replicas: 1,
|
||||||
BuildkitFlags: cfg.BuildkitFlags,
|
BuildkitFlags: cfg.BuildkitdFlags,
|
||||||
Rootless: false,
|
Rootless: false,
|
||||||
Platforms: cfg.Platforms,
|
Platforms: cfg.Platforms,
|
||||||
ConfigFiles: cfg.Files,
|
ConfigFiles: cfg.Files,
|
||||||
|
@@ -52,7 +52,7 @@ type InitConfig struct {
|
|||||||
EndpointAddr string
|
EndpointAddr string
|
||||||
DockerAPI dockerclient.APIClient
|
DockerAPI dockerclient.APIClient
|
||||||
KubeClientConfig KubeClientConfig
|
KubeClientConfig KubeClientConfig
|
||||||
BuildkitFlags []string
|
BuildkitdFlags []string
|
||||||
Files map[string][]byte
|
Files map[string][]byte
|
||||||
DriverOpts map[string]string
|
DriverOpts map[string]string
|
||||||
Auth Auth
|
Auth Auth
|
||||||
@@ -103,13 +103,13 @@ func GetFactory(name string, instanceRequired bool) (Factory, error) {
|
|||||||
return nil, errors.Errorf("failed to find driver %q", name)
|
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, dialMeta map[string][]string) (*DriverHandle, error) {
|
func GetDriver(ctx context.Context, name string, f Factory, endpointAddr string, api dockerclient.APIClient, auth Auth, kcc KubeClientConfig, buildkitdFlags []string, files map[string][]byte, do map[string]string, platforms []specs.Platform, contextPathHash string, dialMeta map[string][]string) (*DriverHandle, error) {
|
||||||
ic := InitConfig{
|
ic := InitConfig{
|
||||||
EndpointAddr: endpointAddr,
|
EndpointAddr: endpointAddr,
|
||||||
DockerAPI: api,
|
DockerAPI: api,
|
||||||
KubeClientConfig: kcc,
|
KubeClientConfig: kcc,
|
||||||
Name: name,
|
Name: name,
|
||||||
BuildkitFlags: flags,
|
BuildkitdFlags: buildkitdFlags,
|
||||||
DriverOpts: do,
|
DriverOpts: do,
|
||||||
Auth: auth,
|
Auth: auth,
|
||||||
Platforms: platforms,
|
Platforms: platforms,
|
||||||
|
@@ -2,13 +2,19 @@ package remote
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
|
util "github.com/docker/buildx/driver/remote/util"
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
"github.com/moby/buildkit/util/tracing/detect"
|
"github.com/moby/buildkit/util/tracing/detect"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Driver struct {
|
type Driver struct {
|
||||||
@@ -32,7 +38,11 @@ func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return c.Wait(ctx)
|
return progress.Wrap("[internal] waiting for connection", l, func(_ progress.SubLogger) error {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
return c.Wait(ctx)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
|
func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
|
||||||
@@ -69,7 +79,7 @@ func (d *Driver) Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error {
|
|||||||
func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
||||||
opts := []client.ClientOpt{}
|
opts := []client.ClientOpt{}
|
||||||
|
|
||||||
exp, err := detect.Exporter()
|
exp, _, err := detect.Exporter()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -77,14 +87,60 @@ func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
|||||||
opts = append(opts, client.WithTracerDelegate(td))
|
opts = append(opts, client.WithTracerDelegate(td))
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.tlsOpts != nil {
|
opts = append(opts, client.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) {
|
||||||
opts = append(opts, []client.ClientOpt{
|
return d.Dial(ctx)
|
||||||
client.WithServerConfig(d.tlsOpts.serverName, d.tlsOpts.caCert),
|
}))
|
||||||
client.WithCredentials(d.tlsOpts.cert, d.tlsOpts.key),
|
|
||||||
}...)
|
return client.New(ctx, "", opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Driver) Dial(ctx context.Context) (net.Conn, error) {
|
||||||
|
network, addr, ok := strings.Cut(d.InitConfig.EndpointAddr, "://")
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.Errorf("invalid endpoint address: %s", d.InitConfig.EndpointAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return client.New(ctx, d.InitConfig.EndpointAddr, opts...)
|
conn, err := util.DialContext(ctx, network, addr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.tlsOpts != nil {
|
||||||
|
cfg, err := loadTLS(d.tlsOpts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error loading tls config")
|
||||||
|
}
|
||||||
|
conn = tls.Client(conn, cfg)
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadTLS(opts *tlsOpts) (*tls.Config, error) {
|
||||||
|
cfg := &tls.Config{
|
||||||
|
ServerName: opts.serverName,
|
||||||
|
RootCAs: x509.NewCertPool(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.caCert != "" {
|
||||||
|
ca, err := os.ReadFile(opts.caCert)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "could not read ca certificate")
|
||||||
|
}
|
||||||
|
if ok := cfg.RootCAs.AppendCertsFromPEM(ca); !ok {
|
||||||
|
return nil, errors.New("failed to append ca certs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.cert != "" || opts.key != "" {
|
||||||
|
cert, err := tls.LoadX509KeyPair(opts.cert, opts.key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "could not read certificate/key")
|
||||||
|
}
|
||||||
|
cfg.Certificates = append(cfg.Certificates, cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Features(ctx context.Context) map[driver.Feature]bool {
|
func (d *Driver) Features(ctx context.Context) map[driver.Feature]bool {
|
||||||
|
@@ -46,7 +46,7 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
|
|||||||
if len(cfg.Files) > 0 {
|
if len(cfg.Files) > 0 {
|
||||||
return nil, errors.Errorf("setting config file is not supported for remote driver")
|
return nil, errors.Errorf("setting config file is not supported for remote driver")
|
||||||
}
|
}
|
||||||
if len(cfg.BuildkitFlags) > 0 {
|
if len(cfg.BuildkitdFlags) > 0 {
|
||||||
return nil, errors.Errorf("setting buildkit flags is not supported for remote driver")
|
return nil, errors.Errorf("setting buildkit flags is not supported for remote driver")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
17
driver/remote/util/dialer_unix.go
Normal file
17
driver/remote/util/dialer_unix.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package remote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DialContext(ctx context.Context, network string, addr string) (net.Conn, error) {
|
||||||
|
dialer := &net.Dialer{}
|
||||||
|
|
||||||
|
conn, err := dialer.DialContext(ctx, network, addr)
|
||||||
|
|
||||||
|
return conn, err
|
||||||
|
}
|
23
driver/remote/util/dialer_windows.go
Normal file
23
driver/remote/util/dialer_windows.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package remote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DialContext(ctx context.Context, network string, addr string) (net.Conn, error) {
|
||||||
|
var conn net.Conn
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// dial context doesn't support named pipes
|
||||||
|
if network == "npipe" {
|
||||||
|
conn, err = winio.DialPipeContext(ctx, addr)
|
||||||
|
} else {
|
||||||
|
dialer := &net.Dialer{}
|
||||||
|
conn, err = dialer.DialContext(ctx, network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, err
|
||||||
|
}
|
@@ -12,6 +12,7 @@ var schemes = map[string]struct{}{
|
|||||||
"ssh": {},
|
"ssh": {},
|
||||||
"docker-container": {},
|
"docker-container": {},
|
||||||
"kube-pod": {},
|
"kube-pod": {},
|
||||||
|
"npipe": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsValidEndpoint(ep string) error {
|
func IsValidEndpoint(ep string) error {
|
||||||
|
174
go.mod
174
go.mod
@@ -1,164 +1,178 @@
|
|||||||
module github.com/docker/buildx
|
module github.com/docker/buildx
|
||||||
|
|
||||||
go 1.20
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/semver/v3 v3.2.1
|
github.com/Masterminds/semver/v3 v3.2.1
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.18.16
|
github.com/Microsoft/go-winio v0.6.1
|
||||||
github.com/compose-spec/compose-go v1.20.0
|
github.com/aws/aws-sdk-go-v2/config v1.26.6
|
||||||
github.com/containerd/console v1.0.3
|
github.com/compose-spec/compose-go/v2 v2.0.0-rc.8
|
||||||
github.com/containerd/containerd v1.7.7
|
github.com/containerd/console v1.0.4
|
||||||
github.com/containerd/continuity v0.4.2
|
github.com/containerd/containerd v1.7.13
|
||||||
|
github.com/containerd/continuity v0.4.3
|
||||||
github.com/containerd/log v0.1.0
|
github.com/containerd/log v0.1.0
|
||||||
github.com/containerd/typeurl/v2 v2.1.1
|
github.com/containerd/typeurl/v2 v2.1.1
|
||||||
github.com/creack/pty v1.1.18
|
github.com/creack/pty v1.1.18
|
||||||
github.com/distribution/reference v0.5.0
|
github.com/distribution/reference v0.5.0
|
||||||
github.com/docker/cli v24.0.5+incompatible
|
github.com/docker/cli v25.0.3+incompatible
|
||||||
github.com/docker/cli-docs-tool v0.7.0
|
github.com/docker/cli-docs-tool v0.7.0
|
||||||
github.com/docker/docker v24.0.5+incompatible
|
github.com/docker/docker v25.0.3+incompatible
|
||||||
github.com/docker/go-units v0.5.0
|
github.com/docker/go-units v0.5.0
|
||||||
github.com/gofrs/flock v0.8.1
|
github.com/gofrs/flock v0.8.1
|
||||||
github.com/gogo/protobuf v1.3.2
|
github.com/gogo/protobuf v1.3.2
|
||||||
github.com/golang/protobuf v1.5.3
|
github.com/golang/protobuf v1.5.3
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/hashicorp/go-cty-funcs v0.0.0-20230405223818-a090f58aa992
|
github.com/hashicorp/go-cty-funcs v0.0.0-20230405223818-a090f58aa992
|
||||||
github.com/hashicorp/hcl/v2 v2.19.1
|
github.com/hashicorp/hcl/v2 v2.19.1
|
||||||
github.com/moby/buildkit v0.13.0-beta1.0.20231023114302-d5c1d785b042
|
github.com/moby/buildkit v0.13.0-rc3
|
||||||
github.com/moby/sys/mountinfo v0.6.2
|
github.com/moby/sys/mountinfo v0.7.1
|
||||||
github.com/moby/sys/signal v0.7.0
|
github.com/moby/sys/signal v0.7.0
|
||||||
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.1.0-rc5
|
github.com/opencontainers/image-spec v1.1.0-rc5
|
||||||
github.com/pelletier/go-toml v1.9.5
|
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-20200727003509-22c0c7ab6b1b
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/spf13/cobra v1.7.0
|
github.com/spf13/cobra v1.8.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
github.com/zclconf/go-cty v1.14.1
|
github.com/zclconf/go-cty v1.14.1
|
||||||
go.opentelemetry.io/otel v1.14.0
|
go.opentelemetry.io/otel v1.21.0
|
||||||
go.opentelemetry.io/otel/trace v1.14.0
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.42.0
|
||||||
golang.org/x/mod v0.11.0
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.42.0
|
||||||
golang.org/x/sync v0.3.0
|
go.opentelemetry.io/otel/metric v1.21.0
|
||||||
golang.org/x/term v0.13.0
|
go.opentelemetry.io/otel/sdk v1.21.0
|
||||||
google.golang.org/grpc v1.53.0
|
go.opentelemetry.io/otel/sdk/metric v1.21.0
|
||||||
|
go.opentelemetry.io/otel/trace v1.21.0
|
||||||
|
golang.org/x/mod v0.14.0
|
||||||
|
golang.org/x/sync v0.6.0
|
||||||
|
golang.org/x/sys v0.16.0
|
||||||
|
golang.org/x/term v0.16.0
|
||||||
|
google.golang.org/grpc v1.59.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
k8s.io/api v0.26.7
|
k8s.io/api v0.29.2
|
||||||
k8s.io/apimachinery v0.26.7
|
k8s.io/apimachinery v0.29.2
|
||||||
k8s.io/apiserver v0.26.7
|
k8s.io/apiserver v0.29.2
|
||||||
k8s.io/client-go v0.26.7
|
k8s.io/client-go v0.29.2
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // 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/Microsoft/go-winio v0.6.1 // indirect
|
github.com/Microsoft/hcsshim v0.11.4 // indirect
|
||||||
github.com/Microsoft/hcsshim v0.11.1 // indirect
|
|
||||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
|
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
|
||||||
github.com/agext/levenshtein v1.2.3 // indirect
|
github.com/agext/levenshtein v1.2.3 // indirect
|
||||||
github.com/apparentlymart/go-cidr v1.0.1 // indirect
|
github.com/apparentlymart/go-cidr v1.0.1 // indirect
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2 v1.17.6 // indirect
|
github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.16 // indirect
|
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.24 // indirect
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.30 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.24 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.12.5 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.5 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.18.6 // indirect
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
|
||||||
github.com/aws/smithy-go v1.13.5 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
|
||||||
|
github.com/aws/smithy-go v1.19.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/containerd/ttrpc v1.2.2 // indirect
|
github.com/containerd/ttrpc v1.2.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
github.com/docker/distribution v2.8.2+incompatible // indirect
|
||||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
github.com/docker/docker-credential-helpers v0.8.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.5.0 // indirect
|
||||||
github.com/docker/go-metrics v0.0.1 // indirect
|
github.com/docker/go-metrics v0.0.1 // indirect
|
||||||
github.com/emicklei/go-restful/v3 v3.10.1 // indirect
|
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/fvbommel/sortorder v1.0.1 // indirect
|
github.com/fvbommel/sortorder v1.0.1 // indirect
|
||||||
github.com/go-logr/logr v1.2.3 // indirect
|
github.com/go-logr/logr v1.3.0 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||||
github.com/go-openapi/swag v0.19.14 // indirect
|
github.com/go-openapi/swag v0.22.3 // indirect
|
||||||
github.com/gogo/googleapis v1.4.1 // indirect
|
github.com/gogo/googleapis v1.4.1 // indirect
|
||||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
github.com/google/gnostic-models v0.6.8 // indirect
|
||||||
github.com/google/go-cmp v0.5.9 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/google/gofuzz v1.2.0 // indirect
|
github.com/google/gofuzz v1.2.0 // indirect
|
||||||
github.com/gorilla/mux v1.8.0 // indirect
|
github.com/gorilla/mux v1.8.0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/imdario/mergo v0.3.16 // indirect
|
github.com/imdario/mergo v0.3.16 // indirect
|
||||||
github.com/in-toto/in-toto-golang v0.5.0 // indirect
|
github.com/in-toto/in-toto-golang v0.5.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.16.3 // indirect
|
github.com/klauspost/compress v1.17.4 // indirect
|
||||||
github.com/mailru/easyjson v0.7.6 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 // 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.4 // 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/copystructure v1.2.0 // 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/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
github.com/moby/locker v1.0.1 // indirect
|
github.com/moby/locker v1.0.1 // indirect
|
||||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||||
github.com/moby/spdystream v0.2.0 // indirect
|
github.com/moby/spdystream v0.2.0 // indirect
|
||||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||||
|
github.com/moby/sys/user v0.1.0 // indirect
|
||||||
github.com/moby/term v0.5.0 // indirect
|
github.com/moby/term v0.5.0 // 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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/opencontainers/runc v1.1.9 // indirect
|
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // 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.14.0 // indirect
|
github.com/prometheus/client_golang v1.17.0 // indirect
|
||||||
github.com/prometheus/client_model v0.3.0 // indirect
|
github.com/prometheus/client_model v0.5.0 // indirect
|
||||||
github.com/prometheus/common v0.42.0 // indirect
|
github.com/prometheus/common v0.44.0 // indirect
|
||||||
github.com/prometheus/procfs v0.9.0 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
|
github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
|
||||||
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
||||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||||
github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb // indirect
|
github.com/tonistiigi/fsutil v0.0.0-20240223190444-7a889f53dbf6 // 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-20230623042737-f9a4f7ef6531 // indirect
|
github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531 // indirect
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.40.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.40.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.14.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v0.37.0 // indirect
|
go.opentelemetry.io/otel/exporters/prometheus v0.42.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk v1.14.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
golang.org/x/crypto v0.18.0 // indirect
|
||||||
golang.org/x/crypto v0.14.0 // indirect
|
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect
|
golang.org/x/net v0.20.0 // indirect
|
||||||
golang.org/x/net v0.17.0 // indirect
|
golang.org/x/oauth2 v0.11.0 // indirect
|
||||||
golang.org/x/oauth2 v0.5.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
golang.org/x/sys v0.13.0 // indirect
|
|
||||||
golang.org/x/text v0.13.0 // indirect
|
|
||||||
golang.org/x/time v0.3.0 // indirect
|
golang.org/x/time v0.3.0 // indirect
|
||||||
golang.org/x/tools v0.7.0 // indirect
|
golang.org/x/tools v0.17.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-20230306155012-7f2fa6fef1f4 // indirect
|
google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect
|
||||||
google.golang.org/protobuf v1.30.0 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
|
||||||
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
k8s.io/klog/v2 v2.90.1 // indirect
|
k8s.io/klog/v2 v2.110.1 // indirect
|
||||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
|
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
|
||||||
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect
|
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
|
||||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
ARG GO_VERSION=1.21.6
|
ARG GO_VERSION=1.21
|
||||||
ARG FORMATS=md,yaml
|
ARG FORMATS=md,yaml
|
||||||
|
|
||||||
FROM golang:${GO_VERSION}-alpine AS docsgen
|
FROM golang:${GO_VERSION}-alpine AS docsgen
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
# Copyright The Buildx Authors.
|
# Copyright The Buildx Authors.
|
||||||
# Licensed under the Apache License, Version 2.0
|
# Licensed under the Apache License, Version 2.0
|
||||||
|
|
||||||
ARG GO_VERSION="1.21.6"
|
ARG GO_VERSION="1.21"
|
||||||
ARG PROTOC_VERSION="3.11.4"
|
ARG PROTOC_VERSION="3.11.4"
|
||||||
|
|
||||||
# protoc is dynamically linked to glibc so can't use alpine base
|
# protoc is dynamically linked to glibc so can't use alpine base
|
||||||
|
@@ -1,14 +1,19 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
ARG GO_VERSION=1.21.6
|
ARG GO_VERSION=1.21
|
||||||
|
ARG XX_VERSION=1.3.0
|
||||||
ARG GOLANGCI_LINT_VERSION=1.54.2
|
ARG GOLANGCI_LINT_VERSION=1.54.2
|
||||||
|
|
||||||
FROM golang:${GO_VERSION}-alpine
|
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||||
|
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine
|
||||||
RUN apk add --no-cache git gcc musl-dev
|
RUN apk add --no-cache git gcc musl-dev
|
||||||
ENV GOFLAGS="-buildvcs=false"
|
ENV GOFLAGS="-buildvcs=false"
|
||||||
ARG GOLANGCI_LINT_VERSION
|
ARG GOLANGCI_LINT_VERSION
|
||||||
RUN wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v${GOLANGCI_LINT_VERSION}
|
RUN wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v${GOLANGCI_LINT_VERSION}
|
||||||
|
COPY --link --from=xx / /
|
||||||
WORKDIR /go/src/github.com/docker/buildx
|
WORKDIR /go/src/github.com/docker/buildx
|
||||||
|
ARG TARGETPLATFORM
|
||||||
RUN --mount=target=/go/src/github.com/docker/buildx \
|
RUN --mount=target=/go/src/github.com/docker/buildx \
|
||||||
--mount=target=/root/.cache,type=cache \
|
--mount=target=/root/.cache,type=cache,id=lint-cache-$TARGETPLATFORM \
|
||||||
|
xx-go --wrap && \
|
||||||
golangci-lint run
|
golangci-lint run
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
ARG GO_VERSION=1.21.6
|
ARG GO_VERSION=1.21
|
||||||
ARG MODOUTDATED_VERSION=v0.8.0
|
ARG MODOUTDATED_VERSION=v0.8.0
|
||||||
|
|
||||||
FROM golang:${GO_VERSION}-alpine AS base
|
FROM golang:${GO_VERSION}-alpine AS base
|
||||||
|
@@ -49,7 +49,7 @@ if [ "$DRIVER" != "docker" ]; then
|
|||||||
for platform in ${PLATFORMS//,/ }; do
|
for platform in ${PLATFORMS//,/ }; do
|
||||||
createFlags=""
|
createFlags=""
|
||||||
if [ -f "$BUILDKIT_CFG" ]; then
|
if [ -f "$BUILDKIT_CFG" ]; then
|
||||||
createFlags="$createFlags --config=${BUILDKIT_CFG}"
|
createFlags="$createFlags --buildkitd-config=${BUILDKIT_CFG}"
|
||||||
fi
|
fi
|
||||||
if [ "$firstNode" = "0" ]; then
|
if [ "$firstNode" = "0" ]; then
|
||||||
createFlags="$createFlags --append"
|
createFlags="$createFlags --append"
|
||||||
@@ -71,7 +71,7 @@ if [ "$DRIVER" != "docker" ]; then
|
|||||||
else
|
else
|
||||||
createFlags=""
|
createFlags=""
|
||||||
if [ -f "$BUILDKIT_CFG" ]; then
|
if [ -f "$BUILDKIT_CFG" ]; then
|
||||||
createFlags="$createFlags --config=${BUILDKIT_CFG}"
|
createFlags="$createFlags --buildkitd-config=${BUILDKIT_CFG}"
|
||||||
fi
|
fi
|
||||||
buildxCmd create ${createFlags} \
|
buildxCmd create ${createFlags} \
|
||||||
--bootstrap \
|
--bootstrap \
|
||||||
|
@@ -24,11 +24,11 @@ type NodeGroup struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
Name string
|
Name string
|
||||||
Endpoint string
|
Endpoint string
|
||||||
Platforms []specs.Platform
|
Platforms []specs.Platform
|
||||||
Flags []string
|
DriverOpts map[string]string
|
||||||
DriverOpts map[string]string
|
BuildkitdFlags []string `json:"Flags"` // keep the field name for backward compatibility
|
||||||
|
|
||||||
Files map[string][]byte
|
Files map[string][]byte
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,7 @@ func (ng *NodeGroup) Leave(name string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ng *NodeGroup) Update(name, endpoint string, platforms []string, endpointsSet bool, actionAppend bool, flags []string, configFile string, do map[string]string) error {
|
func (ng *NodeGroup) Update(name, endpoint string, platforms []string, endpointsSet bool, actionAppend bool, buildkitdFlags []string, buildkitdConfigFile string, do map[string]string) error {
|
||||||
if ng.Dynamic {
|
if ng.Dynamic {
|
||||||
return errors.New("dynamic node group does not support Update")
|
return errors.New("dynamic node group does not support Update")
|
||||||
}
|
}
|
||||||
@@ -66,8 +66,8 @@ func (ng *NodeGroup) Update(name, endpoint string, platforms []string, endpoints
|
|||||||
}
|
}
|
||||||
|
|
||||||
var files map[string][]byte
|
var files map[string][]byte
|
||||||
if configFile != "" {
|
if buildkitdConfigFile != "" {
|
||||||
files, err = confutil.LoadConfigFiles(configFile)
|
files, err = confutil.LoadConfigFiles(buildkitdConfigFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -83,15 +83,15 @@ func (ng *NodeGroup) Update(name, endpoint string, platforms []string, endpoints
|
|||||||
if len(platforms) > 0 {
|
if len(platforms) > 0 {
|
||||||
n.Platforms = pp
|
n.Platforms = pp
|
||||||
}
|
}
|
||||||
if flags != nil {
|
if buildkitdFlags != nil {
|
||||||
n.Flags = flags
|
n.BuildkitdFlags = buildkitdFlags
|
||||||
needsRestart = true
|
needsRestart = true
|
||||||
}
|
}
|
||||||
if do != nil {
|
if do != nil {
|
||||||
n.DriverOpts = do
|
n.DriverOpts = do
|
||||||
needsRestart = true
|
needsRestart = true
|
||||||
}
|
}
|
||||||
if configFile != "" {
|
if buildkitdConfigFile != "" {
|
||||||
for k, v := range files {
|
for k, v := range files {
|
||||||
n.Files[k] = v
|
n.Files[k] = v
|
||||||
}
|
}
|
||||||
@@ -115,12 +115,12 @@ func (ng *NodeGroup) Update(name, endpoint string, platforms []string, endpoints
|
|||||||
}
|
}
|
||||||
|
|
||||||
n := Node{
|
n := Node{
|
||||||
Name: name,
|
Name: name,
|
||||||
Endpoint: endpoint,
|
Endpoint: endpoint,
|
||||||
Platforms: pp,
|
Platforms: pp,
|
||||||
Flags: flags,
|
DriverOpts: do,
|
||||||
DriverOpts: do,
|
BuildkitdFlags: buildkitdFlags,
|
||||||
Files: files,
|
Files: files,
|
||||||
}
|
}
|
||||||
|
|
||||||
ng.Nodes = append(ng.Nodes, n)
|
ng.Nodes = append(ng.Nodes, n)
|
||||||
@@ -143,8 +143,8 @@ func (ng *NodeGroup) Copy() *NodeGroup {
|
|||||||
func (n *Node) Copy() *Node {
|
func (n *Node) Copy() *Node {
|
||||||
platforms := []specs.Platform{}
|
platforms := []specs.Platform{}
|
||||||
copy(platforms, n.Platforms)
|
copy(platforms, n.Platforms)
|
||||||
flags := []string{}
|
buildkitdFlags := []string{}
|
||||||
copy(flags, n.Flags)
|
copy(buildkitdFlags, n.BuildkitdFlags)
|
||||||
driverOpts := map[string]string{}
|
driverOpts := map[string]string{}
|
||||||
for k, v := range n.DriverOpts {
|
for k, v := range n.DriverOpts {
|
||||||
driverOpts[k] = v
|
driverOpts[k] = v
|
||||||
@@ -156,12 +156,12 @@ func (n *Node) Copy() *Node {
|
|||||||
files[k] = vv
|
files[k] = vv
|
||||||
}
|
}
|
||||||
return &Node{
|
return &Node{
|
||||||
Name: n.Name,
|
Name: n.Name,
|
||||||
Endpoint: n.Endpoint,
|
Endpoint: n.Endpoint,
|
||||||
Platforms: platforms,
|
Platforms: platforms,
|
||||||
Flags: flags,
|
BuildkitdFlags: buildkitdFlags,
|
||||||
DriverOpts: driverOpts,
|
DriverOpts: driverOpts,
|
||||||
Files: files,
|
Files: files,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -28,8 +28,8 @@ func TestNodeGroupUpdate(t *testing.T) {
|
|||||||
require.Equal(t, []string{"linux/arm64"}, platformutil.Format(ng.Nodes[1].Platforms))
|
require.Equal(t, []string{"linux/arm64"}, platformutil.Format(ng.Nodes[1].Platforms))
|
||||||
|
|
||||||
require.Equal(t, "foo2", ng.Nodes[0].Endpoint)
|
require.Equal(t, "foo2", ng.Nodes[0].Endpoint)
|
||||||
require.Equal(t, []string{"--debug"}, ng.Nodes[0].Flags)
|
require.Equal(t, []string{"--debug"}, ng.Nodes[0].BuildkitdFlags)
|
||||||
require.Equal(t, []string(nil), ng.Nodes[1].Flags)
|
require.Equal(t, []string(nil), ng.Nodes[1].BuildkitdFlags)
|
||||||
|
|
||||||
// duplicate endpoint
|
// duplicate endpoint
|
||||||
err = ng.Update("foo1", "foo2", nil, true, false, nil, "", nil)
|
err = ng.Update("foo1", "foo2", nil, true, false, nil, "", nil)
|
||||||
|
149
tests/bake.go
149
tests/bake.go
@@ -1,6 +1,7 @@
|
|||||||
package tests
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -24,6 +25,7 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){
|
|||||||
testBakeRemote,
|
testBakeRemote,
|
||||||
testBakeRemoteCmdContext,
|
testBakeRemoteCmdContext,
|
||||||
testBakeRemoteLocalOverride,
|
testBakeRemoteLocalOverride,
|
||||||
|
testBakeLocalCwdOverride,
|
||||||
testBakeRemoteCmdContextOverride,
|
testBakeRemoteCmdContextOverride,
|
||||||
testBakeRemoteContextSubdir,
|
testBakeRemoteContextSubdir,
|
||||||
testBakeRemoteCmdContextEscapeRoot,
|
testBakeRemoteCmdContextEscapeRoot,
|
||||||
@@ -31,6 +33,9 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){
|
|||||||
testBakeRemoteDockerfileCwd,
|
testBakeRemoteDockerfileCwd,
|
||||||
testBakeRemoteLocalContextRemoteDockerfile,
|
testBakeRemoteLocalContextRemoteDockerfile,
|
||||||
testBakeEmpty,
|
testBakeEmpty,
|
||||||
|
testBakeShmSize,
|
||||||
|
testBakeUlimits,
|
||||||
|
testBakeRefs,
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBakeLocal(t *testing.T, sb integration.Sandbox) {
|
func testBakeLocal(t *testing.T, sb integration.Sandbox) {
|
||||||
@@ -53,7 +58,7 @@ target "default" {
|
|||||||
|
|
||||||
cmd := buildxCmd(sb, withDir(dir), withArgs("bake", "--progress=plain", "--set", "*.output=type=local,dest="+dirDest))
|
cmd := buildxCmd(sb, withDir(dir), withArgs("bake", "--progress=plain", "--set", "*.output=type=local,dest="+dirDest))
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
require.NoError(t, err, out)
|
require.NoError(t, err, string(out))
|
||||||
require.Contains(t, string(out), `#1 [internal] load local bake definitions`)
|
require.Contains(t, string(out), `#1 [internal] load local bake definitions`)
|
||||||
require.Contains(t, string(out), `#1 reading docker-bake.hcl`)
|
require.Contains(t, string(out), `#1 reading docker-bake.hcl`)
|
||||||
|
|
||||||
@@ -173,6 +178,41 @@ EOT
|
|||||||
require.FileExists(t, filepath.Join(dirDest, "bar"))
|
require.FileExists(t, filepath.Join(dirDest, "bar"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testBakeLocalCwdOverride(t *testing.T, sb integration.Sandbox) {
|
||||||
|
bakeFile := []byte(`
|
||||||
|
target "default" {
|
||||||
|
dockerfile-inline = <<EOT
|
||||||
|
FROM scratch
|
||||||
|
COPY foo /foo
|
||||||
|
EOT
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
cwdBakefile := []byte(`
|
||||||
|
target "default" {
|
||||||
|
dockerfile-inline = <<EOT
|
||||||
|
FROM scratch
|
||||||
|
COPY bar /bar
|
||||||
|
EOT
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
dir := tmpdir(
|
||||||
|
t,
|
||||||
|
fstest.CreateFile("docker-bake.hcl", bakeFile, 0600),
|
||||||
|
fstest.CreateFile("docker-bake-cwd.hcl", cwdBakefile, 0600),
|
||||||
|
fstest.CreateFile("bar", []byte("bar"), 0600),
|
||||||
|
)
|
||||||
|
dirDest := t.TempDir()
|
||||||
|
|
||||||
|
cmd := buildxCmd(sb, withDir(dir), withArgs("bake", "--file", "docker-bake.hcl", "--file", "cwd://docker-bake-cwd.hcl", "--progress=plain", "--set", "*.output=type=local,dest="+dirDest))
|
||||||
|
dt, err := cmd.CombinedOutput()
|
||||||
|
require.NoError(t, err, string(dt))
|
||||||
|
require.Contains(t, string(dt), `#1 [internal] load local bake definitions`)
|
||||||
|
require.Contains(t, string(dt), `#1 reading docker-bake.hcl`)
|
||||||
|
require.Contains(t, string(dt), `#1 reading docker-bake-cwd.hcl`)
|
||||||
|
require.FileExists(t, filepath.Join(dirDest, "bar"))
|
||||||
|
}
|
||||||
|
|
||||||
func testBakeRemoteCmdContext(t *testing.T, sb integration.Sandbox) {
|
func testBakeRemoteCmdContext(t *testing.T, sb integration.Sandbox) {
|
||||||
bakefile := []byte(`
|
bakefile := []byte(`
|
||||||
target "default" {
|
target "default" {
|
||||||
@@ -484,3 +524,110 @@ func testBakeEmpty(t *testing.T, sb integration.Sandbox) {
|
|||||||
require.Error(t, err, out)
|
require.Error(t, err, out)
|
||||||
require.Contains(t, out, "couldn't find a bake definition")
|
require.Contains(t, out, "couldn't find a bake definition")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testBakeShmSize(t *testing.T, sb integration.Sandbox) {
|
||||||
|
dockerfile := []byte(`
|
||||||
|
FROM busybox AS build
|
||||||
|
RUN mount | grep /dev/shm > /shmsize
|
||||||
|
FROM scratch
|
||||||
|
COPY --from=build /shmsize /
|
||||||
|
`)
|
||||||
|
bakefile := []byte(`
|
||||||
|
target "default" {
|
||||||
|
shm-size = "128m"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
dir := tmpdir(
|
||||||
|
t,
|
||||||
|
fstest.CreateFile("docker-bake.hcl", bakefile, 0600),
|
||||||
|
fstest.CreateFile("Dockerfile", dockerfile, 0600),
|
||||||
|
)
|
||||||
|
|
||||||
|
dirDest := t.TempDir()
|
||||||
|
|
||||||
|
out, err := bakeCmd(
|
||||||
|
sb,
|
||||||
|
withDir(dir),
|
||||||
|
withArgs("--set", "*.output=type=local,dest="+dirDest),
|
||||||
|
)
|
||||||
|
require.NoError(t, err, out)
|
||||||
|
|
||||||
|
dt, err := os.ReadFile(filepath.Join(dirDest, "shmsize"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Contains(t, string(dt), `size=131072k`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBakeUlimits(t *testing.T, sb integration.Sandbox) {
|
||||||
|
dockerfile := []byte(`
|
||||||
|
FROM busybox AS build
|
||||||
|
RUN ulimit -n > first > /ulimit
|
||||||
|
FROM scratch
|
||||||
|
COPY --from=build /ulimit /
|
||||||
|
`)
|
||||||
|
bakefile := []byte(`
|
||||||
|
target "default" {
|
||||||
|
ulimits = ["nofile=1024:1024"]
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
dir := tmpdir(
|
||||||
|
t,
|
||||||
|
fstest.CreateFile("docker-bake.hcl", bakefile, 0600),
|
||||||
|
fstest.CreateFile("Dockerfile", dockerfile, 0600),
|
||||||
|
)
|
||||||
|
|
||||||
|
dirDest := t.TempDir()
|
||||||
|
|
||||||
|
out, err := bakeCmd(
|
||||||
|
sb,
|
||||||
|
withDir(dir),
|
||||||
|
withArgs("--set", "*.output=type=local,dest="+dirDest),
|
||||||
|
)
|
||||||
|
require.NoError(t, err, out)
|
||||||
|
|
||||||
|
dt, err := os.ReadFile(filepath.Join(dirDest, "ulimit"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Contains(t, string(dt), `1024`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBakeRefs(t *testing.T, sb integration.Sandbox) {
|
||||||
|
dockerfile := []byte(`
|
||||||
|
FROM scratch
|
||||||
|
COPY foo /foo
|
||||||
|
`)
|
||||||
|
bakefile := []byte(`
|
||||||
|
target "default" {
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
dir := tmpdir(
|
||||||
|
t,
|
||||||
|
fstest.CreateFile("docker-bake.hcl", bakefile, 0600),
|
||||||
|
fstest.CreateFile("Dockerfile", dockerfile, 0600),
|
||||||
|
fstest.CreateFile("foo", []byte("foo"), 0600),
|
||||||
|
)
|
||||||
|
|
||||||
|
dirDest := t.TempDir()
|
||||||
|
|
||||||
|
outFlag := "default.output=type=docker"
|
||||||
|
if sb.DockerAddress() == "" {
|
||||||
|
// there is no Docker atm to load the image
|
||||||
|
outFlag += ",dest=" + dirDest + "/image.tar"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := buildxCmd(sb, withDir(dir), withArgs("bake", "--metadata-file", filepath.Join(dirDest, "md.json"), "--set", outFlag))
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
require.NoError(t, err, out)
|
||||||
|
|
||||||
|
dt, err := os.ReadFile(filepath.Join(dirDest, "md.json"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
type mdT struct {
|
||||||
|
Default struct {
|
||||||
|
BuildRef string `json:"buildx.build.ref"`
|
||||||
|
} `json:"default"`
|
||||||
|
}
|
||||||
|
var md mdT
|
||||||
|
err = json.Unmarshal(dt, &md)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.NotEmpty(t, md.Default.BuildRef)
|
||||||
|
}
|
||||||
|
162
tests/build.go
162
tests/build.go
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -15,6 +16,7 @@ import (
|
|||||||
"github.com/containerd/containerd/platforms"
|
"github.com/containerd/containerd/platforms"
|
||||||
"github.com/containerd/continuity/fs/fstest"
|
"github.com/containerd/continuity/fs/fstest"
|
||||||
"github.com/creack/pty"
|
"github.com/creack/pty"
|
||||||
|
"github.com/moby/buildkit/util/appdefaults"
|
||||||
"github.com/moby/buildkit/util/contentutil"
|
"github.com/moby/buildkit/util/contentutil"
|
||||||
"github.com/moby/buildkit/util/testutil"
|
"github.com/moby/buildkit/util/testutil"
|
||||||
"github.com/moby/buildkit/util/testutil/integration"
|
"github.com/moby/buildkit/util/testutil/integration"
|
||||||
@@ -48,6 +50,10 @@ var buildTests = []func(t *testing.T, sb integration.Sandbox){
|
|||||||
testBuildOCIExportNotSupported,
|
testBuildOCIExportNotSupported,
|
||||||
testBuildMultiPlatformNotSupported,
|
testBuildMultiPlatformNotSupported,
|
||||||
testDockerHostGateway,
|
testDockerHostGateway,
|
||||||
|
testBuildNetworkModeBridge,
|
||||||
|
testBuildShmSize,
|
||||||
|
testBuildUlimit,
|
||||||
|
testBuildRef,
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBuild(t *testing.T, sb integration.Sandbox) {
|
func testBuild(t *testing.T, sb integration.Sandbox) {
|
||||||
@@ -118,7 +124,7 @@ func testBuildRegistryExportAttestations(t *testing.T, sb integration.Sandbox) {
|
|||||||
out, err := buildCmd(sb, withArgs(fmt.Sprintf("--output=type=image,name=%s,push=true", target), "--provenance=true", dir))
|
out, err := buildCmd(sb, withArgs(fmt.Sprintf("--output=type=image,name=%s,push=true", target), "--provenance=true", dir))
|
||||||
if sb.Name() == "docker" {
|
if sb.Name() == "docker" {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Contains(t, out, "attestations are not supported")
|
require.Contains(t, out, "Attestation is not supported")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
require.NoError(t, err, string(out))
|
require.NoError(t, err, string(out))
|
||||||
@@ -279,23 +285,6 @@ RUN exit 1`)
|
|||||||
require.True(t, buildDetailsPattern.MatchString(string(out)), fmt.Sprintf("expected build details link in output, got %q", out))
|
require.True(t, buildDetailsPattern.MatchString(string(out)), fmt.Sprintf("expected build details link in output, got %q", out))
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTestProject(t *testing.T) string {
|
|
||||||
dockerfile := []byte(`
|
|
||||||
FROM busybox:latest AS base
|
|
||||||
COPY foo /etc/foo
|
|
||||||
RUN cp /etc/foo /etc/bar
|
|
||||||
|
|
||||||
FROM scratch
|
|
||||||
COPY --from=base /etc/bar /bar
|
|
||||||
`)
|
|
||||||
dir := tmpdir(
|
|
||||||
t,
|
|
||||||
fstest.CreateFile("Dockerfile", dockerfile, 0600),
|
|
||||||
fstest.CreateFile("foo", []byte("foo"), 0600),
|
|
||||||
)
|
|
||||||
return dir
|
|
||||||
}
|
|
||||||
|
|
||||||
func testBuildProgress(t *testing.T, sb integration.Sandbox) {
|
func testBuildProgress(t *testing.T, sb integration.Sandbox) {
|
||||||
dir := createTestProject(t)
|
dir := createTestProject(t)
|
||||||
driver, _, _ := strings.Cut(sb.Name(), "+")
|
driver, _, _ := strings.Cut(sb.Name(), "+")
|
||||||
@@ -432,3 +421,140 @@ RUN ping -c 1 buildx.host-gateway-ip.local
|
|||||||
require.NoError(t, err, string(out))
|
require.NoError(t, err, string(out))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testBuildNetworkModeBridge(t *testing.T, sb integration.Sandbox) {
|
||||||
|
if sb.Name() != "docker" {
|
||||||
|
t.Skip("skipping test for non-docker workers")
|
||||||
|
}
|
||||||
|
|
||||||
|
var builderName string
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if builderName == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out, err := rmCmd(sb, withArgs(builderName))
|
||||||
|
require.NoError(t, err, out)
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: use stable buildkit image when v0.13.0 released
|
||||||
|
out, err := createCmd(sb, withArgs("--driver", "docker-container", "--buildkitd-flags=--oci-worker-net=bridge --allow-insecure-entitlement=network.host", "--driver-opt", "image=moby/buildkit:master"))
|
||||||
|
require.NoError(t, err, out)
|
||||||
|
builderName = strings.TrimSpace(out)
|
||||||
|
|
||||||
|
dockerfile := []byte(`
|
||||||
|
FROM busybox AS build
|
||||||
|
RUN ip a show eth0 | awk '/inet / {split($2, a, "/"); print a[1]}' > /ip-bridge.txt
|
||||||
|
RUN --network=host ip a show eth0 | awk '/inet / {split($2, a, "/"); print a[1]}' > /ip-host.txt
|
||||||
|
FROM scratch
|
||||||
|
COPY --from=build /ip*.txt /`)
|
||||||
|
dir := tmpdir(t, fstest.CreateFile("Dockerfile", dockerfile, 0600))
|
||||||
|
|
||||||
|
cmd := buildxCmd(sb, withArgs("build", "--allow=network.host", fmt.Sprintf("--output=type=local,dest=%s", dir), dir))
|
||||||
|
cmd.Env = append(cmd.Env, "BUILDX_BUILDER="+builderName)
|
||||||
|
outb, err := cmd.CombinedOutput()
|
||||||
|
require.NoError(t, err, string(outb))
|
||||||
|
|
||||||
|
dt, err := os.ReadFile(filepath.Join(dir, "ip-bridge.txt"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ipBridge := net.ParseIP(strings.TrimSpace(string(dt)))
|
||||||
|
require.NotNil(t, ipBridge)
|
||||||
|
|
||||||
|
_, subnet, err := net.ParseCIDR(appdefaults.BridgeSubnet)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, subnet.Contains(ipBridge))
|
||||||
|
|
||||||
|
dt, err = os.ReadFile(filepath.Join(dir, "ip-host.txt"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ip := net.ParseIP(strings.TrimSpace(string(dt)))
|
||||||
|
require.NotNil(t, ip)
|
||||||
|
|
||||||
|
require.NotEqual(t, ip, ipBridge)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBuildShmSize(t *testing.T, sb integration.Sandbox) {
|
||||||
|
dockerfile := []byte(`
|
||||||
|
FROM busybox AS build
|
||||||
|
RUN mount | grep /dev/shm > /shmsize
|
||||||
|
FROM scratch
|
||||||
|
COPY --from=build /shmsize /
|
||||||
|
`)
|
||||||
|
dir := tmpdir(
|
||||||
|
t,
|
||||||
|
fstest.CreateFile("Dockerfile", dockerfile, 0600),
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd := buildxCmd(sb, withArgs("build", "--shm-size=128m", fmt.Sprintf("--output=type=local,dest=%s", dir), dir))
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
require.NoError(t, err, string(out))
|
||||||
|
|
||||||
|
dt, err := os.ReadFile(filepath.Join(dir, "shmsize"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Contains(t, string(dt), `size=131072k`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBuildUlimit(t *testing.T, sb integration.Sandbox) {
|
||||||
|
dockerfile := []byte(`
|
||||||
|
FROM busybox AS build
|
||||||
|
RUN ulimit -n > first > /ulimit
|
||||||
|
FROM scratch
|
||||||
|
COPY --from=build /ulimit /
|
||||||
|
`)
|
||||||
|
dir := tmpdir(
|
||||||
|
t,
|
||||||
|
fstest.CreateFile("Dockerfile", dockerfile, 0600),
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd := buildxCmd(sb, withArgs("build", "--ulimit=nofile=1024:1024", fmt.Sprintf("--output=type=local,dest=%s", dir), dir))
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
require.NoError(t, err, string(out))
|
||||||
|
|
||||||
|
dt, err := os.ReadFile(filepath.Join(dir, "ulimit"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Contains(t, string(dt), `1024`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBuildRef(t *testing.T, sb integration.Sandbox) {
|
||||||
|
dir := createTestProject(t)
|
||||||
|
dirDest := t.TempDir()
|
||||||
|
|
||||||
|
outFlag := "--output=type=docker"
|
||||||
|
if sb.DockerAddress() == "" {
|
||||||
|
// there is no Docker atm to load the image
|
||||||
|
outFlag += ",dest=" + dirDest + "/image.tar"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := buildxCmd(sb, withArgs("build", outFlag, "--metadata-file", filepath.Join(dirDest, "md.json"), dir))
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
require.NoError(t, err, string(out))
|
||||||
|
|
||||||
|
dt, err := os.ReadFile(filepath.Join(dirDest, "md.json"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
type mdT struct {
|
||||||
|
BuildRef string `json:"buildx.build.ref"`
|
||||||
|
}
|
||||||
|
var md mdT
|
||||||
|
err = json.Unmarshal(dt, &md)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.NotEmpty(t, md.BuildRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTestProject(t *testing.T) string {
|
||||||
|
dockerfile := []byte(`
|
||||||
|
FROM busybox:latest AS base
|
||||||
|
COPY foo /etc/foo
|
||||||
|
RUN cp /etc/foo /etc/bar
|
||||||
|
|
||||||
|
FROM scratch
|
||||||
|
COPY --from=base /etc/bar /bar
|
||||||
|
`)
|
||||||
|
dir := tmpdir(
|
||||||
|
t,
|
||||||
|
fstest.CreateFile("Dockerfile", dockerfile, 0600),
|
||||||
|
fstest.CreateFile("foo", []byte("foo"), 0600),
|
||||||
|
)
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
@@ -17,6 +17,7 @@ func createCmd(sb integration.Sandbox, opts ...cmdOpt) (string, error) {
|
|||||||
|
|
||||||
var createTests = []func(t *testing.T, sb integration.Sandbox){
|
var createTests = []func(t *testing.T, sb integration.Sandbox){
|
||||||
testCreateMemoryLimit,
|
testCreateMemoryLimit,
|
||||||
|
testCreateRestartAlways,
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCreateMemoryLimit(t *testing.T, sb integration.Sandbox) {
|
func testCreateMemoryLimit(t *testing.T, sb integration.Sandbox) {
|
||||||
@@ -37,3 +38,22 @@ func testCreateMemoryLimit(t *testing.T, sb integration.Sandbox) {
|
|||||||
require.NoError(t, err, out)
|
require.NoError(t, err, out)
|
||||||
builderName = strings.TrimSpace(out)
|
builderName = strings.TrimSpace(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testCreateRestartAlways(t *testing.T, sb integration.Sandbox) {
|
||||||
|
if sb.Name() != "docker-container" {
|
||||||
|
t.Skip("only testing for docker-container driver")
|
||||||
|
}
|
||||||
|
|
||||||
|
var builderName string
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if builderName == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out, err := rmCmd(sb, withArgs(builderName))
|
||||||
|
require.NoError(t, err, out)
|
||||||
|
})
|
||||||
|
|
||||||
|
out, err := createCmd(sb, withArgs("--driver", "docker-container", "--driver-opt", "restart-policy=always"))
|
||||||
|
require.NoError(t, err, out)
|
||||||
|
builderName = strings.TrimSpace(out)
|
||||||
|
}
|
||||||
|
124
tests/dialstdio.go
Normal file
124
tests/dialstdio.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/moby/buildkit/client/llb"
|
||||||
|
gwclient "github.com/moby/buildkit/frontend/gateway/client"
|
||||||
|
"github.com/moby/buildkit/util/progress/progressui"
|
||||||
|
"github.com/moby/buildkit/util/testutil/integration"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dialstdioTests = []func(t *testing.T, sb integration.Sandbox){
|
||||||
|
testDialStdio,
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDialStdio(t *testing.T, sb integration.Sandbox) {
|
||||||
|
do := func(t *testing.T, pipe func(t *testing.T, cmd *exec.Cmd) net.Conn) {
|
||||||
|
errBuf := bytes.NewBuffer(nil)
|
||||||
|
defer func() {
|
||||||
|
if t.Failed() {
|
||||||
|
t.Log(errBuf.String())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
c, err := client.New(sb.Context(), "", client.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) {
|
||||||
|
cmd = buildxCmd(sb, withArgs("dial-stdio", "--progress", "auto"))
|
||||||
|
conn := pipe(t, cmd)
|
||||||
|
cmd.Stderr = errBuf
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return nil, errors.Wrap(err, errBuf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
c.Close()
|
||||||
|
// Since the client is closed (and as such the connection shutdown), the buildx command should exit cleanly.
|
||||||
|
chErr := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
chErr <- cmd.Wait()
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-time.After(10 * time.Second):
|
||||||
|
t.Error("timeout waiting for buildx command to exit")
|
||||||
|
case <-chErr:
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, err = c.Info(sb.Context())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Contains(t, errBuf.String(), "builder: "+sb.Address())
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
f, err := os.CreateTemp(dir, "log")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if t.Failed() {
|
||||||
|
dt, _ := os.ReadFile(f.Name())
|
||||||
|
t.Log(string(dt))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
p, err := progress.NewPrinter(sb.Context(), f, progressui.AutoMode)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ch, chDone := progress.NewChannel(p)
|
||||||
|
done := func() {
|
||||||
|
select {
|
||||||
|
case <-sb.Context().Done():
|
||||||
|
case <-chDone:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = c.Build(sb.Context(), client.SolveOpt{
|
||||||
|
Exports: []client.ExportEntry{
|
||||||
|
{Type: "local", OutputDir: dir},
|
||||||
|
},
|
||||||
|
}, "", func(ctx context.Context, gwc gwclient.Client) (*gwclient.Result, error) {
|
||||||
|
def, err := llb.Scratch().File(llb.Mkfile("hello", 0o600, []byte("world"))).Marshal(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gwc.Solve(ctx, gwclient.SolveRequest{
|
||||||
|
Definition: def.ToPB(),
|
||||||
|
})
|
||||||
|
}, ch)
|
||||||
|
done()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
dt, err := os.ReadFile(filepath.Join(dir, "hello"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "world", string(dt))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("conn=netpipe", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
do(t, func(t *testing.T, cmd *exec.Cmd) net.Conn {
|
||||||
|
c1, c2 := net.Pipe()
|
||||||
|
cmd.Stdin = c1
|
||||||
|
cmd.Stdout = c1
|
||||||
|
return c2
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
@@ -17,6 +17,8 @@ func inspectCmd(sb integration.Sandbox, opts ...cmdOpt) (string, error) {
|
|||||||
|
|
||||||
var inspectTests = []func(t *testing.T, sb integration.Sandbox){
|
var inspectTests = []func(t *testing.T, sb integration.Sandbox){
|
||||||
testInspect,
|
testInspect,
|
||||||
|
testInspectBuildkitdFlags,
|
||||||
|
testInspectNetworkHostEntitlement,
|
||||||
}
|
}
|
||||||
|
|
||||||
func testInspect(t *testing.T, sb integration.Sandbox) {
|
func testInspect(t *testing.T, sb integration.Sandbox) {
|
||||||
@@ -47,3 +49,63 @@ func testInspect(t *testing.T, sb integration.Sandbox) {
|
|||||||
require.Empty(t, hostGatewayIP, "host-gateway-ip worker label should not be set with non-docker driver")
|
require.Empty(t, hostGatewayIP, "host-gateway-ip worker label should not be set with non-docker driver")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testInspectBuildkitdFlags(t *testing.T, sb integration.Sandbox) {
|
||||||
|
if sb.Name() != "docker-container" {
|
||||||
|
t.Skip("only testing for docker-container driver")
|
||||||
|
}
|
||||||
|
|
||||||
|
var builderName string
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if builderName == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out, err := rmCmd(sb, withArgs(builderName))
|
||||||
|
require.NoError(t, err, out)
|
||||||
|
})
|
||||||
|
|
||||||
|
out, err := createCmd(sb, withArgs("--driver", "docker-container", "--buildkitd-flags=--oci-worker-net=bridge"))
|
||||||
|
require.NoError(t, err, out)
|
||||||
|
builderName = strings.TrimSpace(out)
|
||||||
|
|
||||||
|
out, err = inspectCmd(sb, withArgs(builderName))
|
||||||
|
require.NoError(t, err, out)
|
||||||
|
|
||||||
|
for _, line := range strings.Split(out, "\n") {
|
||||||
|
if v, ok := strings.CutPrefix(line, "BuildKit daemon flags:"); ok {
|
||||||
|
require.Contains(t, v, "--oci-worker-net=bridge")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.Fail(t, "--oci-worker-net=bridge not found in inspect output")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInspectNetworkHostEntitlement(t *testing.T, sb integration.Sandbox) {
|
||||||
|
if sb.Name() != "docker-container" {
|
||||||
|
t.Skip("only testing for docker-container driver")
|
||||||
|
}
|
||||||
|
|
||||||
|
var builderName string
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if builderName == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out, err := rmCmd(sb, withArgs(builderName))
|
||||||
|
require.NoError(t, err, out)
|
||||||
|
})
|
||||||
|
|
||||||
|
out, err := createCmd(sb, withArgs("--driver", "docker-container"))
|
||||||
|
require.NoError(t, err, out)
|
||||||
|
builderName = strings.TrimSpace(out)
|
||||||
|
|
||||||
|
out, err = inspectCmd(sb, withArgs(builderName))
|
||||||
|
require.NoError(t, err, out)
|
||||||
|
|
||||||
|
for _, line := range strings.Split(out, "\n") {
|
||||||
|
if v, ok := strings.CutPrefix(line, "BuildKit daemon flags:"); ok {
|
||||||
|
require.Contains(t, v, "--allow-insecure-entitlement=network.host")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.Fail(t, "network.host insecure entitlement not found in inspect output")
|
||||||
|
}
|
||||||
|
@@ -47,8 +47,10 @@ func buildxCmd(sb integration.Sandbox, opts ...cmdOpt) *exec.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if builder := sb.Address(); builder != "" {
|
if builder := sb.Address(); builder != "" {
|
||||||
cmd.Args = append(cmd.Args, "--builder="+builder)
|
cmd.Env = append(cmd.Env,
|
||||||
cmd.Env = append(cmd.Env, "BUILDX_CONFIG=/tmp/buildx-"+builder)
|
"BUILDX_CONFIG=/tmp/buildx-"+builder,
|
||||||
|
"BUILDX_BUILDER="+builder,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if context := sb.DockerAddress(); context != "" {
|
if context := sb.DockerAddress(); context != "" {
|
||||||
cmd.Env = append(cmd.Env, "DOCKER_CONTEXT="+context)
|
cmd.Env = append(cmd.Env, "DOCKER_CONTEXT="+context)
|
||||||
|
@@ -28,6 +28,8 @@ func TestIntegration(t *testing.T) {
|
|||||||
tests = append(tests, imagetoolsTests...)
|
tests = append(tests, imagetoolsTests...)
|
||||||
tests = append(tests, versionTests...)
|
tests = append(tests, versionTests...)
|
||||||
tests = append(tests, createTests...)
|
tests = append(tests, createTests...)
|
||||||
|
tests = append(tests, rmTests...)
|
||||||
|
tests = append(tests, dialstdioTests...)
|
||||||
testIntegration(t, tests...)
|
testIntegration(t, tests...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
38
tests/ls.go
38
tests/ls.go
@@ -20,15 +20,37 @@ var lsTests = []func(t *testing.T, sb integration.Sandbox){
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testLs(t *testing.T, sb integration.Sandbox) {
|
func testLs(t *testing.T, sb integration.Sandbox) {
|
||||||
out, err := lsCmd(sb)
|
tests := []struct {
|
||||||
require.NoError(t, err, string(out))
|
name string
|
||||||
|
args []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no args",
|
||||||
|
args: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format",
|
||||||
|
args: []string{"--format", "{{.Name}}: {{.DriverEndpoint}}"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
sbDriver, _, _ := strings.Cut(sb.Name(), "+")
|
sbDriver, _, _ := strings.Cut(sb.Name(), "+")
|
||||||
for _, line := range strings.Split(out, "\n") {
|
for _, tt := range tests {
|
||||||
if strings.Contains(line, sb.Address()) {
|
tt := tt
|
||||||
require.Contains(t, line, sbDriver)
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
return
|
out, err := lsCmd(sb, withArgs(tt.args...))
|
||||||
}
|
require.NoError(t, err, out)
|
||||||
|
found := false
|
||||||
|
for _, line := range strings.Split(out, "\n") {
|
||||||
|
if strings.Contains(line, sb.Address()) {
|
||||||
|
found = true
|
||||||
|
require.Contains(t, line, sbDriver)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
require.Fail(t, out)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
require.Fail(t, out)
|
|
||||||
}
|
}
|
||||||
|
48
tests/rm.go
48
tests/rm.go
@@ -1,7 +1,11 @@
|
|||||||
package tests
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/moby/buildkit/util/testutil/integration"
|
"github.com/moby/buildkit/util/testutil/integration"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func rmCmd(sb integration.Sandbox, opts ...cmdOpt) (string, error) {
|
func rmCmd(sb integration.Sandbox, opts ...cmdOpt) (string, error) {
|
||||||
@@ -10,3 +14,47 @@ func rmCmd(sb integration.Sandbox, opts ...cmdOpt) (string, error) {
|
|||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
return string(out), err
|
return string(out), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rmTests = []func(t *testing.T, sb integration.Sandbox){
|
||||||
|
testRm,
|
||||||
|
testRmMulti,
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRm(t *testing.T, sb integration.Sandbox) {
|
||||||
|
if sb.Name() != "docker-container" {
|
||||||
|
t.Skip("only testing for docker-container driver")
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := rmCmd(sb, withArgs("default"))
|
||||||
|
require.Error(t, err, out) // can't remove a docker builder
|
||||||
|
|
||||||
|
out, err = createCmd(sb, withArgs("--driver", "docker-container"))
|
||||||
|
require.NoError(t, err, out)
|
||||||
|
builderName := strings.TrimSpace(out)
|
||||||
|
|
||||||
|
out, err = inspectCmd(sb, withArgs(builderName, "--bootstrap"))
|
||||||
|
require.NoError(t, err, out)
|
||||||
|
|
||||||
|
out, err = rmCmd(sb, withArgs(builderName))
|
||||||
|
require.NoError(t, err, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRmMulti(t *testing.T, sb integration.Sandbox) {
|
||||||
|
if sb.Name() != "docker-container" {
|
||||||
|
t.Skip("only testing for docker-container driver")
|
||||||
|
}
|
||||||
|
|
||||||
|
var builderNames []string
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
out, err := createCmd(sb, withArgs("--driver", "docker-container"))
|
||||||
|
require.NoError(t, err, out)
|
||||||
|
builderName := strings.TrimSpace(out)
|
||||||
|
|
||||||
|
out, err = inspectCmd(sb, withArgs(builderName, "--bootstrap"))
|
||||||
|
require.NoError(t, err, out)
|
||||||
|
builderNames = append(builderNames, builderName)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := rmCmd(sb, withArgs(builderNames...))
|
||||||
|
require.NoError(t, err, out)
|
||||||
|
}
|
||||||
|
@@ -35,6 +35,10 @@ func (s *backend) Rootless() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *backend) NetNSDetached() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (s backend) Supports(feature string) bool {
|
func (s backend) Supports(feature string) bool {
|
||||||
if enabledFeatures := os.Getenv("BUILDKIT_TEST_ENABLE_FEATURES"); enabledFeatures != "" {
|
if enabledFeatures := os.Getenv("BUILDKIT_TEST_ENABLE_FEATURES"); enabledFeatures != "" {
|
||||||
for _, enabledFeature := range strings.Split(enabledFeatures, ",") {
|
for _, enabledFeature := range strings.Split(enabledFeatures, ",") {
|
||||||
|
@@ -37,6 +37,10 @@ func (w *containerWorker) Rootless() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *containerWorker) NetNSDetached() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (w *containerWorker) New(ctx context.Context, cfg *integration.BackendConfig) (integration.Backend, func() error, error) {
|
func (w *containerWorker) New(ctx context.Context, cfg *integration.BackendConfig) (integration.Backend, func() error, error) {
|
||||||
w.dockerOnce.Do(func() {
|
w.dockerOnce.Do(func() {
|
||||||
w.docker, w.dockerClose, w.dockerErr = dockerWorker{id: w.id}.New(ctx, cfg)
|
w.docker, w.dockerClose, w.dockerErr = dockerWorker{id: w.id}.New(ctx, cfg)
|
||||||
@@ -54,7 +58,7 @@ func (w *containerWorker) New(ctx context.Context, cfg *integration.BackendConfi
|
|||||||
cmd := exec.Command("buildx", "create",
|
cmd := exec.Command("buildx", "create",
|
||||||
"--bootstrap",
|
"--bootstrap",
|
||||||
"--name="+name,
|
"--name="+name,
|
||||||
"--config="+cfgfile,
|
"--buildkitd-config="+cfgfile,
|
||||||
"--driver=docker-container",
|
"--driver=docker-container",
|
||||||
"--driver-opt=network=host",
|
"--driver-opt=network=host",
|
||||||
)
|
)
|
||||||
|
@@ -35,6 +35,10 @@ func (c dockerWorker) Rootless() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *dockerWorker) NetNSDetached() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (c dockerWorker) New(ctx context.Context, cfg *integration.BackendConfig) (b integration.Backend, cl func() error, err error) {
|
func (c dockerWorker) New(ctx context.Context, cfg *integration.BackendConfig) (b integration.Backend, cl func() error, err error) {
|
||||||
moby := bkworkers.Moby{
|
moby := bkworkers.Moby{
|
||||||
ID: c.id,
|
ID: c.id,
|
||||||
|
@@ -30,6 +30,10 @@ func (w remoteWorker) Rootless() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w remoteWorker) NetNSDetached() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (w remoteWorker) New(ctx context.Context, cfg *integration.BackendConfig) (b integration.Backend, cl func() error, err error) {
|
func (w remoteWorker) New(ctx context.Context, cfg *integration.BackendConfig) (b integration.Backend, cl func() error, err error) {
|
||||||
oci := bkworkers.OCI{ID: w.id}
|
oci := bkworkers.OCI{ID: w.id}
|
||||||
bk, bkclose, err := oci.New(ctx, cfg)
|
bk, bkclose, err := oci.New(ctx, cfg)
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
package cobrautil
|
package cobrautil
|
||||||
|
|
||||||
import "github.com/spf13/cobra"
|
import (
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
// HideInheritedFlags hides inherited flags
|
// HideInheritedFlags hides inherited flags
|
||||||
func HideInheritedFlags(cmd *cobra.Command, hidden ...string) {
|
func HideInheritedFlags(cmd *cobra.Command, hidden ...string) {
|
||||||
@@ -12,3 +16,38 @@ func HideInheritedFlags(cmd *cobra.Command, hidden ...string) {
|
|||||||
_ = cmd.Flags().MarkHidden(h)
|
_ = cmd.Flags().MarkHidden(h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const annotationExperimentalCLI = "experimentalCLI"
|
||||||
|
|
||||||
|
func MarkFlagExperimental(f *pflag.Flag) {
|
||||||
|
if _, ok := f.Annotations[annotationExperimentalCLI]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if f.Annotations == nil {
|
||||||
|
f.Annotations = make(map[string][]string)
|
||||||
|
}
|
||||||
|
f.Annotations[annotationExperimentalCLI] = nil
|
||||||
|
f.Usage += " (EXPERIMENTAL)"
|
||||||
|
}
|
||||||
|
|
||||||
|
func MarkFlagsExperimental(fs *pflag.FlagSet, names ...string) {
|
||||||
|
for _, name := range names {
|
||||||
|
f := fs.Lookup(name)
|
||||||
|
if f == nil {
|
||||||
|
logrus.Warningf("Unknown flag name %q", name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
MarkFlagExperimental(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MarkCommandExperimental(c *cobra.Command) {
|
||||||
|
if _, ok := c.Annotations[annotationExperimentalCLI]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c.Annotations == nil {
|
||||||
|
c.Annotations = make(map[string]string)
|
||||||
|
}
|
||||||
|
c.Annotations[annotationExperimentalCLI] = ""
|
||||||
|
c.Short += " (EXPERIMENTAL)"
|
||||||
|
}
|
||||||
|
15
util/confutil/exp.go
Normal file
15
util/confutil/exp.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package confutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsExperimental checks if the experimental flag has been configured.
|
||||||
|
func IsExperimental() bool {
|
||||||
|
if v, ok := os.LookupEnv("BUILDX_EXPERIMENTAL"); ok {
|
||||||
|
vv, _ := strconv.ParseBool(v)
|
||||||
|
return vv
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
34
util/confutil/node.go
Normal file
34
util/confutil/node.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package confutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var nodeIdentifierMu sync.Mutex
|
||||||
|
|
||||||
|
func TryNodeIdentifier(configDir string) (out string) {
|
||||||
|
nodeIdentifierMu.Lock()
|
||||||
|
defer nodeIdentifierMu.Unlock()
|
||||||
|
sessionFile := filepath.Join(configDir, ".buildNodeID")
|
||||||
|
if _, err := os.Lstat(sessionFile); err != nil {
|
||||||
|
if os.IsNotExist(err) { // create a new file with stored randomness
|
||||||
|
b := make([]byte, 8)
|
||||||
|
if _, err := rand.Read(b); err != nil {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(sessionFile, []byte(hex.EncodeToString(b)), 0600); err != nil {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dt, err := os.ReadFile(sessionFile)
|
||||||
|
if err == nil {
|
||||||
|
return string(dt)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
@@ -9,6 +9,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/osutil"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -66,7 +67,11 @@ func (c *Git) IsDirty() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Git) RootDir() (string, error) {
|
func (c *Git) RootDir() (string, error) {
|
||||||
return c.clean(c.run("rev-parse", "--show-toplevel"))
|
root, err := c.clean(c.run("rev-parse", "--show-toplevel"))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return osutil.SanitizePath(root), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Git) GitDir() (string, error) {
|
func (c *Git) GitDir() (string, error) {
|
||||||
@@ -78,7 +83,13 @@ func (c *Git) GitDir() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Git) RemoteURL() (string, error) {
|
func (c *Git) RemoteURL() (string, error) {
|
||||||
// Try to get the remote URL from the origin remote first
|
// Try default remote based on remote tracking branch
|
||||||
|
if remote, err := c.currentRemote(); err == nil && remote != "" {
|
||||||
|
if ru, err := c.clean(c.run("remote", "get-url", remote)); err == nil && ru != "" {
|
||||||
|
return stripCredentials(ru), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Next try to get the remote URL from the origin remote first
|
||||||
if ru, err := c.clean(c.run("remote", "get-url", "origin")); err == nil && ru != "" {
|
if ru, err := c.clean(c.run("remote", "get-url", "origin")); err == nil && ru != "" {
|
||||||
return stripCredentials(ru), nil
|
return stripCredentials(ru), nil
|
||||||
}
|
}
|
||||||
@@ -149,6 +160,22 @@ func (c *Git) clean(out string, err error) (string, error) {
|
|||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Git) currentRemote() (string, error) {
|
||||||
|
symref, err := c.clean(c.run("symbolic-ref", "-q", "HEAD"))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if symref == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
// git for-each-ref --format='%(upstream:remotename)'
|
||||||
|
remote, err := c.clean(c.run("for-each-ref", "--format=%(upstream:remotename)", symref))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return remote, nil
|
||||||
|
}
|
||||||
|
|
||||||
func IsUnknownRevision(err error) bool {
|
func IsUnknownRevision(err error) bool {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return false
|
return false
|
||||||
|
@@ -106,8 +106,9 @@ func TestGitDescribeTags(t *testing.T) {
|
|||||||
|
|
||||||
func TestGitRemoteURL(t *testing.T) {
|
func TestGitRemoteURL(t *testing.T) {
|
||||||
type remote struct {
|
type remote struct {
|
||||||
name string
|
name string
|
||||||
url string
|
url string
|
||||||
|
tracking string
|
||||||
}
|
}
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
@@ -165,6 +166,36 @@ func TestGitRemoteURL(t *testing.T) {
|
|||||||
},
|
},
|
||||||
fail: true,
|
fail: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "single tracking branch",
|
||||||
|
remotes: []remote{
|
||||||
|
{
|
||||||
|
name: "foo",
|
||||||
|
url: "https://github.com/tonistiigi/buildx.git",
|
||||||
|
tracking: "master",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "https://github.com/tonistiigi/buildx.git",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fork tracking branch",
|
||||||
|
remotes: []remote{
|
||||||
|
{
|
||||||
|
name: "origin",
|
||||||
|
url: "git@github.com:crazy-max/buildx.git",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "foobranch",
|
||||||
|
url: "https://github.com/tonistiigi/buildx.git",
|
||||||
|
tracking: "master",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "crazymax",
|
||||||
|
url: "git@github.com:crazy-max/buildx.git",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "https://github.com/tonistiigi/buildx.git",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range cases {
|
for _, tt := range cases {
|
||||||
tt := tt
|
tt := tt
|
||||||
@@ -177,6 +208,9 @@ func TestGitRemoteURL(t *testing.T) {
|
|||||||
GitCommit(c, t, "initial commit")
|
GitCommit(c, t, "initial commit")
|
||||||
for _, r := range tt.remotes {
|
for _, r := range tt.remotes {
|
||||||
GitSetRemote(c, t, r.name, r.url)
|
GitSetRemote(c, t, r.name, r.url)
|
||||||
|
if r.tracking != "" {
|
||||||
|
GitSetMainUpstream(c, t, r.name, r.tracking)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ru, err := c.RemoteURL()
|
ru, err := c.RemoteURL()
|
||||||
|
20
util/gitutil/path_unix_test.go
Normal file
20
util/gitutil/path_unix_test.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package gitutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/osutil"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSanitizePathUnix(t *testing.T) {
|
||||||
|
assert.Equal(t, "/home/foobar", osutil.SanitizePath("/home/foobar"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSanitizePathWSL(t *testing.T) {
|
||||||
|
t.Setenv("WSL_DISTRO_NAME", "Ubuntu")
|
||||||
|
assert.Equal(t, "/mnt/c/Users/foobar", osutil.SanitizePath("C:\\Users\\foobar"))
|
||||||
|
}
|
29
util/gitutil/path_windows_test.go
Normal file
29
util/gitutil/path_windows_test.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package gitutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/osutil"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSanitizePathWindows(t *testing.T) {
|
||||||
|
expected := "C:\\Users\\foobar"
|
||||||
|
if isGitBash() {
|
||||||
|
expected = "C:/Users/foobar"
|
||||||
|
}
|
||||||
|
assert.Equal(t, expected, osutil.SanitizePath("C:/Users/foobar"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGitBash() bool {
|
||||||
|
// The MSYSTEM environment variable is used in MSYS2 environments,
|
||||||
|
// including Git Bash, to select the active environment. This variable
|
||||||
|
// dictates the environment in which the shell operates, influencing
|
||||||
|
// factors like the path prefixes, default compilers, and system libraries
|
||||||
|
// used: https://www.msys2.org/docs/environments/
|
||||||
|
if _, ok := os.LookupEnv("MSYSTEM"); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
@@ -52,6 +52,15 @@ func GitSetRemote(c *Git, tb testing.TB, name string, url string) {
|
|||||||
require.NoError(tb, err)
|
require.NoError(tb, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GitSetMainUpstream(c *Git, tb testing.TB, remote, target string) {
|
||||||
|
tb.Helper()
|
||||||
|
_, err := fakeGit(c, "fetch", "--depth", "1", remote, target)
|
||||||
|
require.NoError(tb, err)
|
||||||
|
|
||||||
|
_, err = fakeGit(c, "branch", "--set-upstream-to", remote+"/"+target, "main")
|
||||||
|
require.NoError(tb, err)
|
||||||
|
}
|
||||||
|
|
||||||
func Mktmp(tb testing.TB) string {
|
func Mktmp(tb testing.TB) string {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
folder := tb.TempDir()
|
folder := tb.TempDir()
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"maps"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -230,13 +231,17 @@ func (r *Resolver) Copy(ctx context.Context, src *Source, dest reference.Named)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
source, repo := u.Hostname(), strings.TrimPrefix(u.Path, "/")
|
|
||||||
if src.Desc.Annotations == nil {
|
|
||||||
src.Desc.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
src.Desc.Annotations["containerd.io/distribution.source."+source] = repo
|
|
||||||
|
|
||||||
err = contentutil.CopyChain(ctx, contentutil.FromPusher(p), contentutil.FromFetcher(f), src.Desc)
|
desc := src.Desc
|
||||||
|
desc.Annotations = maps.Clone(desc.Annotations)
|
||||||
|
if desc.Annotations == nil {
|
||||||
|
desc.Annotations = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
source, repo := u.Hostname(), strings.TrimPrefix(u.Path, "/")
|
||||||
|
desc.Annotations["containerd.io/distribution.source."+source] = repo
|
||||||
|
|
||||||
|
err = contentutil.CopyChain(ctx, contentutil.FromPusher(p), contentutil.FromFetcher(f), desc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,9 @@ package imagetools
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -21,6 +23,12 @@ import (
|
|||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
inTotoGenericMime = "application/vnd.in-toto+json"
|
||||||
|
inTotoSPDXDSSEMime = "application/vnd.in-toto.spdx+dsse"
|
||||||
|
inTotoProvenanceDSSEMime = "application/vnd.in-toto.provenance+dsse"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
annotationReferences = []string{
|
annotationReferences = []string{
|
||||||
"com.docker.reference.digest",
|
"com.docker.reference.digest",
|
||||||
@@ -274,7 +282,7 @@ type sbomStub struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *loader) scanSBOM(ctx context.Context, fetcher remotes.Fetcher, r *result, refs []digest.Digest, as *asset) error {
|
func (l *loader) scanSBOM(ctx context.Context, fetcher remotes.Fetcher, r *result, refs []digest.Digest, as *asset) error {
|
||||||
ctx = remotes.WithMediaTypeKeyPrefix(ctx, "application/vnd.in-toto+json", "intoto")
|
ctx = withIntotoMediaTypes(ctx)
|
||||||
as.deferredSbom = func() (*sbomStub, error) {
|
as.deferredSbom = func() (*sbomStub, error) {
|
||||||
var sbom *sbomStub
|
var sbom *sbomStub
|
||||||
for _, dgst := range refs {
|
for _, dgst := range refs {
|
||||||
@@ -283,7 +291,8 @@ func (l *loader) scanSBOM(ctx context.Context, fetcher remotes.Fetcher, r *resul
|
|||||||
return nil, errors.Errorf("referenced image %s not found", dgst)
|
return nil, errors.Errorf("referenced image %s not found", dgst)
|
||||||
}
|
}
|
||||||
for _, layer := range mfst.manifest.Layers {
|
for _, layer := range mfst.manifest.Layers {
|
||||||
if layer.MediaType == "application/vnd.in-toto+json" && layer.Annotations["in-toto.io/predicate-type"] == "https://spdx.dev/Document" {
|
if (layer.MediaType == inTotoGenericMime || isInTotoDSSE(layer.MediaType)) &&
|
||||||
|
layer.Annotations["in-toto.io/predicate-type"] == "https://spdx.dev/Document" {
|
||||||
_, err := remotes.FetchHandler(l.cache, fetcher)(ctx, layer)
|
_, err := remotes.FetchHandler(l.cache, fetcher)(ctx, layer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -292,6 +301,12 @@ func (l *loader) scanSBOM(ctx context.Context, fetcher remotes.Fetcher, r *resul
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dt, err = decodeDSSE(dt, layer.MediaType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var spdx struct {
|
var spdx struct {
|
||||||
Predicate interface{} `json:"predicate"`
|
Predicate interface{} `json:"predicate"`
|
||||||
}
|
}
|
||||||
@@ -318,7 +333,7 @@ type provenanceStub struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *loader) scanProvenance(ctx context.Context, fetcher remotes.Fetcher, r *result, refs []digest.Digest, as *asset) error {
|
func (l *loader) scanProvenance(ctx context.Context, fetcher remotes.Fetcher, r *result, refs []digest.Digest, as *asset) error {
|
||||||
ctx = remotes.WithMediaTypeKeyPrefix(ctx, "application/vnd.in-toto+json", "intoto")
|
ctx = withIntotoMediaTypes(ctx)
|
||||||
as.deferredProvenance = func() (*provenanceStub, error) {
|
as.deferredProvenance = func() (*provenanceStub, error) {
|
||||||
var provenance *provenanceStub
|
var provenance *provenanceStub
|
||||||
for _, dgst := range refs {
|
for _, dgst := range refs {
|
||||||
@@ -327,7 +342,8 @@ func (l *loader) scanProvenance(ctx context.Context, fetcher remotes.Fetcher, r
|
|||||||
return nil, errors.Errorf("referenced image %s not found", dgst)
|
return nil, errors.Errorf("referenced image %s not found", dgst)
|
||||||
}
|
}
|
||||||
for _, layer := range mfst.manifest.Layers {
|
for _, layer := range mfst.manifest.Layers {
|
||||||
if layer.MediaType == "application/vnd.in-toto+json" && strings.HasPrefix(layer.Annotations["in-toto.io/predicate-type"], "https://slsa.dev/provenance/") {
|
if (layer.MediaType == inTotoGenericMime || isInTotoDSSE(layer.MediaType)) &&
|
||||||
|
strings.HasPrefix(layer.Annotations["in-toto.io/predicate-type"], "https://slsa.dev/provenance/") {
|
||||||
_, err := remotes.FetchHandler(l.cache, fetcher)(ctx, layer)
|
_, err := remotes.FetchHandler(l.cache, fetcher)(ctx, layer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -336,6 +352,12 @@ func (l *loader) scanProvenance(ctx context.Context, fetcher remotes.Fetcher, r
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dt, err = decodeDSSE(dt, layer.MediaType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var slsa struct {
|
var slsa struct {
|
||||||
Predicate interface{} `json:"predicate"`
|
Predicate interface{} `json:"predicate"`
|
||||||
}
|
}
|
||||||
@@ -415,3 +437,36 @@ func (r *result) SBOM() (map[string]sbomStub, error) {
|
|||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isInTotoDSSE(mime string) bool {
|
||||||
|
isDSSE, _ := regexp.MatchString("application/vnd\\.in-toto\\..*\\+dsse", mime)
|
||||||
|
|
||||||
|
return isDSSE
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeDSSE(dt []byte, mime string) ([]byte, error) {
|
||||||
|
if isInTotoDSSE(mime) {
|
||||||
|
var dsse struct {
|
||||||
|
Payload string `json:"payload"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(dt, &dsse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(dsse.Payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dt = decoded
|
||||||
|
}
|
||||||
|
|
||||||
|
return dt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func withIntotoMediaTypes(ctx context.Context) context.Context {
|
||||||
|
for _, mime := range []string{inTotoGenericMime, inTotoSPDXDSSEMime, inTotoProvenanceDSSEMime} {
|
||||||
|
ctx = remotes.WithMediaTypeKeyPrefix(ctx, mime, "intoto")
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
219
util/metricutil/metric.go
Normal file
219
util/metricutil/metric.go
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
package metricutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/confutil"
|
||||||
|
"github.com/docker/buildx/version"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
|
||||||
|
"go.opentelemetry.io/otel/metric"
|
||||||
|
"go.opentelemetry.io/otel/metric/noop"
|
||||||
|
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
|
||||||
|
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
otelConfigFieldName = "otel"
|
||||||
|
reportTimeout = 2 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// MeterProvider holds a MeterProvider for metric generation and the configured
|
||||||
|
// exporters for reporting metrics from the CLI.
|
||||||
|
type MeterProvider struct {
|
||||||
|
metric.MeterProvider
|
||||||
|
reader *sdkmetric.ManualReader
|
||||||
|
exporters []sdkmetric.Exporter
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMeterProvider configures a MeterProvider from the CLI context.
|
||||||
|
func NewMeterProvider(ctx context.Context, cli command.Cli) (*MeterProvider, error) {
|
||||||
|
var exps []sdkmetric.Exporter
|
||||||
|
|
||||||
|
if exp, err := dockerOtelExporter(cli); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if exp != nil {
|
||||||
|
exps = append(exps, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if confutil.IsExperimental() {
|
||||||
|
// Expose the user-facing metric exporter only if the experimental flag is set.
|
||||||
|
if exp, err := detectOtlpExporter(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if exp != nil {
|
||||||
|
exps = append(exps, exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(exps) == 0 {
|
||||||
|
// No exporters are configured so use a noop provider.
|
||||||
|
return &MeterProvider{
|
||||||
|
MeterProvider: noop.NewMeterProvider(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := sdkmetric.NewManualReader(
|
||||||
|
sdkmetric.WithTemporalitySelector(deltaTemporality),
|
||||||
|
)
|
||||||
|
mp := sdkmetric.NewMeterProvider(
|
||||||
|
sdkmetric.WithResource(Resource()),
|
||||||
|
sdkmetric.WithReader(reader),
|
||||||
|
)
|
||||||
|
return &MeterProvider{
|
||||||
|
MeterProvider: mp,
|
||||||
|
reader: reader,
|
||||||
|
exporters: exps,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report exports metrics to the configured exporter. This should be done before the CLI
|
||||||
|
// exits.
|
||||||
|
func (m *MeterProvider) Report(ctx context.Context) {
|
||||||
|
if m.reader == nil {
|
||||||
|
// Not configured.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, reportTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var rm metricdata.ResourceMetrics
|
||||||
|
if err := m.reader.Collect(ctx, &rm); err != nil {
|
||||||
|
// Error when collecting metrics. Do not send any.
|
||||||
|
otel.Handle(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var eg errgroup.Group
|
||||||
|
for _, exp := range m.exporters {
|
||||||
|
exp := exp
|
||||||
|
eg.Go(func() error {
|
||||||
|
if err := exp.Export(ctx, &rm); err != nil {
|
||||||
|
otel.Handle(err)
|
||||||
|
}
|
||||||
|
_ = exp.Shutdown(ctx)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't report an error because we don't allow it to.
|
||||||
|
_ = eg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// dockerOtelExporter reads the CLI metadata to determine an OTLP exporter
|
||||||
|
// endpoint for docker metrics to be sent.
|
||||||
|
//
|
||||||
|
// This location, configuration, and usage is hard-coded as part of
|
||||||
|
// sending usage statistics so this metric reporting is not meant to be
|
||||||
|
// user facing.
|
||||||
|
func dockerOtelExporter(cli command.Cli) (sdkmetric.Exporter, error) {
|
||||||
|
endpoint, err := otelExporterOtlpEndpoint(cli)
|
||||||
|
if endpoint == "" || err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the endpoint. The docker config expects the endpoint to be
|
||||||
|
// in the form of a URL to match the environment variable, but this
|
||||||
|
// option doesn't correspond directly to WithEndpoint.
|
||||||
|
//
|
||||||
|
// We pretend we're the same as the environment reader.
|
||||||
|
u, err := url.Parse(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Errorf("docker otel endpoint is invalid: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var opts []otlpmetricgrpc.Option
|
||||||
|
switch u.Scheme {
|
||||||
|
case "unix":
|
||||||
|
// Unix sockets are a bit weird. OTEL seems to imply they
|
||||||
|
// can be used as an environment variable and are handled properly,
|
||||||
|
// but they don't seem to be as the behavior of the environment variable
|
||||||
|
// is to strip the scheme from the endpoint, but the underlying implementation
|
||||||
|
// needs the scheme to use the correct resolver.
|
||||||
|
//
|
||||||
|
// We'll just handle this in a special way and add the unix:// back to the endpoint.
|
||||||
|
opts = []otlpmetricgrpc.Option{
|
||||||
|
otlpmetricgrpc.WithEndpoint(fmt.Sprintf("unix://%s", path.Join(u.Host, u.Path))),
|
||||||
|
otlpmetricgrpc.WithInsecure(),
|
||||||
|
}
|
||||||
|
case "http":
|
||||||
|
opts = []otlpmetricgrpc.Option{
|
||||||
|
// Omit the scheme when using http or https.
|
||||||
|
otlpmetricgrpc.WithEndpoint(path.Join(u.Host, u.Path)),
|
||||||
|
otlpmetricgrpc.WithInsecure(),
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
opts = []otlpmetricgrpc.Option{
|
||||||
|
// Omit the scheme when using http or https.
|
||||||
|
otlpmetricgrpc.WithEndpoint(path.Join(u.Host, u.Path)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hardcoded endpoint from the endpoint.
|
||||||
|
exp, err := otlpmetricgrpc.New(context.Background(), opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return exp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// otelExporterOtlpEndpoint retrieves the OTLP endpoint used for the docker reporter
|
||||||
|
// from the current context.
|
||||||
|
func otelExporterOtlpEndpoint(cli command.Cli) (string, error) {
|
||||||
|
meta, err := cli.ContextStore().GetMetadata(cli.CurrentContext())
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var otelCfg interface{}
|
||||||
|
switch m := meta.Metadata.(type) {
|
||||||
|
case command.DockerContext:
|
||||||
|
otelCfg = m.AdditionalFields[otelConfigFieldName]
|
||||||
|
case map[string]interface{}:
|
||||||
|
otelCfg = m[otelConfigFieldName]
|
||||||
|
}
|
||||||
|
|
||||||
|
if otelCfg == nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
otelMap, ok := otelCfg.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return "", errors.Errorf(
|
||||||
|
"unexpected type for field %q: %T (expected: %T)",
|
||||||
|
otelConfigFieldName,
|
||||||
|
otelCfg,
|
||||||
|
otelMap,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// keys from https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/
|
||||||
|
endpoint, _ := otelMap["OTEL_EXPORTER_OTLP_ENDPOINT"].(string)
|
||||||
|
return endpoint, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deltaTemporality sets the Temporality of every instrument to delta.
|
||||||
|
//
|
||||||
|
// This isn't really needed since we create a unique resource on each invocation,
|
||||||
|
// but it can help with cardinality concerns for downstream processors since they can
|
||||||
|
// perform aggregation for a time interval and then discard the data once that time
|
||||||
|
// period has passed. Cumulative temporality would imply to the downstream processor
|
||||||
|
// that they might receive a successive point and they may unnecessarily keep state
|
||||||
|
// they really shouldn't.
|
||||||
|
func deltaTemporality(_ sdkmetric.InstrumentKind) metricdata.Temporality {
|
||||||
|
return metricdata.DeltaTemporality
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meter returns a Meter from the MetricProvider that indicates the measurement
|
||||||
|
// comes from buildx with the appropriate version.
|
||||||
|
func Meter(mp metric.MeterProvider) metric.Meter {
|
||||||
|
return mp.Meter(version.Package,
|
||||||
|
metric.WithInstrumentationVersion(version.Version))
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user