mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-08-15 08:15:55 +08:00
Compare commits
234 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
780fad46f2 | ||
![]() |
2ca5ffa06a | ||
![]() |
33e3ca524e | ||
![]() |
ea1a71dc07 | ||
![]() |
ae820293a2 | ||
![]() |
f68f42cb11 | ||
![]() |
7f58ad45fa | ||
![]() |
aa7c17989b | ||
![]() |
6b6afc4077 | ||
![]() |
69a1419ab1 | ||
![]() |
080e9981c7 | ||
![]() |
8cc00ab486 | ||
![]() |
40fad4bbb5 | ||
![]() |
232af9aa0d | ||
![]() |
5bf2ff98c9 | ||
![]() |
570e733a51 | ||
![]() |
cffcd57edb | ||
![]() |
1496ac9b55 | ||
![]() |
290e25917c | ||
![]() |
0360668cc1 | ||
![]() |
343a4753c7 | ||
![]() |
d827f42d38 | ||
![]() |
5843e67a90 | ||
![]() |
517df133e3 | ||
![]() |
621114fbe1 | ||
![]() |
2066051d3a | ||
![]() |
d94cbd870c | ||
![]() |
48f15dcf3d | ||
![]() |
35a60b8e04 | ||
![]() |
4b3df09155 | ||
![]() |
b1215c2ce2 | ||
![]() |
99ac03f9f3 | ||
![]() |
a0aa45a4a7 | ||
![]() |
aab3a92890 | ||
![]() |
37020dc8da | ||
![]() |
d66d3a2d09 | ||
![]() |
f057195a4f | ||
![]() |
378bf70d4b | ||
![]() |
1ccf0bd7d8 | ||
![]() |
ddbfddce88 | ||
![]() |
ea19cf9d8d | ||
![]() |
3b69482a2f | ||
![]() |
778fbb4669 | ||
![]() |
13533e359a | ||
![]() |
3c2d0aa667 | ||
![]() |
5551de4b8a | ||
![]() |
fa51b90094 | ||
![]() |
bfd1ea3877 | ||
![]() |
abfb2c064d | ||
![]() |
4f7517115c | ||
![]() |
1621b9bad0 | ||
![]() |
d2bf42f8b4 | ||
![]() |
d1a46faf84 | ||
![]() |
39f1d99dcc | ||
![]() |
1f04ec9575 | ||
![]() |
ac2e081528 | ||
![]() |
95ac9ebb8a | ||
![]() |
c41b006be1 | ||
![]() |
92fb995505 | ||
![]() |
3c94621142 | ||
![]() |
2d720a1e0b | ||
![]() |
0269388aa7 | ||
![]() |
4b2aab09b5 | ||
![]() |
1c7434a8f0 | ||
![]() |
20f8f67928 | ||
![]() |
159535261d | ||
![]() |
a840e891fe | ||
![]() |
a7c704c39d | ||
![]() |
e1c0eb2187 | ||
![]() |
aa8ab9fcca | ||
![]() |
a746959fc1 | ||
![]() |
ee34eb2180 | ||
![]() |
844b901005 | ||
![]() |
83ebc13a37 | ||
![]() |
82c8f2d8f0 | ||
![]() |
a11cc8840e | ||
![]() |
35ee6ce62d | ||
![]() |
37861cb99f | ||
![]() |
a178d05023 | ||
![]() |
ee9ccfe2e3 | ||
![]() |
c6c4b4a871 | ||
![]() |
273c6a75a2 | ||
![]() |
1384bf02f9 | ||
![]() |
fb7b670b76 | ||
![]() |
9ac5b075cf | ||
![]() |
0124b6b9c9 | ||
![]() |
691a1479fc | ||
![]() |
c9d69b082b | ||
![]() |
e24e04be57 | ||
![]() |
38f9241316 | ||
![]() |
3862ff269b | ||
![]() |
9e5321eab8 | ||
![]() |
f2cf7cf281 | ||
![]() |
8961d3573e | ||
![]() |
26570d05c1 | ||
![]() |
8627f668f2 | ||
![]() |
d8ca46066d | ||
![]() |
c00c5a89e5 | ||
![]() |
14b7936c3b | ||
![]() |
40b41ac6e4 | ||
![]() |
fd6de6b6ae | ||
![]() |
f3111bcbef | ||
![]() |
e6be472831 | ||
![]() |
e5217f26e2 | ||
![]() |
7f7acf7837 | ||
![]() |
baae4b2e71 | ||
![]() |
42448c5f37 | ||
![]() |
fc7875675c | ||
![]() |
355261e49e | ||
![]() |
44c840b31d | ||
![]() |
1bc068a583 | ||
![]() |
340686a383 | ||
![]() |
1ad87c6ba6 | ||
![]() |
eadf5eddbc | ||
![]() |
f4f58003fb | ||
![]() |
bda4882a65 | ||
![]() |
77ddee9314 | ||
![]() |
c9676c79d1 | ||
![]() |
18095ee87b | ||
![]() |
c4d07f67e3 | ||
![]() |
205165bec5 | ||
![]() |
870b38837b | ||
![]() |
10d4b7a878 | ||
![]() |
abed97cf33 | ||
![]() |
f10d8dab5e | ||
![]() |
5185d534bc | ||
![]() |
a520de447e | ||
![]() |
4121ae50b5 | ||
![]() |
87c4bf1df9 | ||
![]() |
09339bf500 | ||
![]() |
af9edb6ba4 | ||
![]() |
b2ec1d331c | ||
![]() |
213d3af3b0 | ||
![]() |
4804824c78 | ||
![]() |
d89e3f3014 | ||
![]() |
9ab9b852c2 | ||
![]() |
2a257a8252 | ||
![]() |
0e1f0e3c73 | ||
![]() |
078b65905a | ||
![]() |
417f52e001 | ||
![]() |
2bca8fa677 | ||
![]() |
721b63f3a0 | ||
![]() |
14e65ff3b4 | ||
![]() |
3282dae09b | ||
![]() |
7b297eb895 | ||
![]() |
bae6b1cec8 | ||
![]() |
f4ac640252 | ||
![]() |
7c627da986 | ||
![]() |
d52f5db6ba | ||
![]() |
66672b4052 | ||
![]() |
ed6be92de4 | ||
![]() |
2def02ea74 | ||
![]() |
52b0ea328f | ||
![]() |
960107d00f | ||
![]() |
bbc902b4d6 | ||
![]() |
54549235da | ||
![]() |
231f983600 | ||
![]() |
891d355679 | ||
![]() |
87fbc406f5 | ||
![]() |
08b5a52ccd | ||
![]() |
14a28d7fc3 | ||
![]() |
5a79b401b0 | ||
![]() |
5e4444823c | ||
![]() |
5ff7635447 | ||
![]() |
709ef36b4f | ||
![]() |
7f0b59dc37 | ||
![]() |
9e8c532e61 | ||
![]() |
f2be09f4e4 | ||
![]() |
3ff9abca3a | ||
![]() |
3d630c6f7f | ||
![]() |
9f4f945d4f | ||
![]() |
a0490a8720 | ||
![]() |
aba962c12c | ||
![]() |
aa21e3c731 | ||
![]() |
5b9d88b3ad | ||
![]() |
8bce430f4d | ||
![]() |
c6f8de90aa | ||
![]() |
6b65b0c982 | ||
![]() |
f5c2673878 | ||
![]() |
8e92bfc8f0 | ||
![]() |
d7adb9ef6e | ||
![]() |
6634f1e75c | ||
![]() |
6aba19193a | ||
![]() |
eb1aabe9e3 | ||
![]() |
714f181d81 | ||
![]() |
fd44accc79 | ||
![]() |
43edd6b77e | ||
![]() |
427c19d65c | ||
![]() |
6db68d0295 | ||
![]() |
abe8ba769e | ||
![]() |
96fb17b711 | ||
![]() |
63e5633d62 | ||
![]() |
299d41660b | ||
![]() |
1ec87b7beb | ||
![]() |
0475107882 | ||
![]() |
75f8d7ebb5 | ||
![]() |
7c97854b6f | ||
![]() |
5f4d4a87f7 | ||
![]() |
c1ce7300d5 | ||
![]() |
e118c4d8e9 | ||
![]() |
5fe779703d | ||
![]() |
15a5a42eb1 | ||
![]() |
5b974158f9 | ||
![]() |
1c0a7f14e8 | ||
![]() |
7ec8912591 | ||
![]() |
83da6a3378 | ||
![]() |
cad02a4681 | ||
![]() |
c967f1d570 | ||
![]() |
be3efc979b | ||
![]() |
5c5f54c6d6 | ||
![]() |
6f8f04e1f8 | ||
![]() |
afd821010d | ||
![]() |
bcc882cbf1 | ||
![]() |
75b80c277f | ||
![]() |
096d1befc9 | ||
![]() |
2bf6187a88 | ||
![]() |
8ed8795268 | ||
![]() |
6e32ea3418 | ||
![]() |
8b2171f78a | ||
![]() |
92f1234aaa | ||
![]() |
73645c8348 | ||
![]() |
662c0768cb | ||
![]() |
43150ef849 | ||
![]() |
3f18b659a0 | ||
![]() |
6b81b0bed6 | ||
![]() |
f0af89a204 | ||
![]() |
550c2b9042 | ||
![]() |
c8cda08209 | ||
![]() |
2b03339235 | ||
![]() |
6e1fd0eab6 | ||
![]() |
5336e74bd4 | ||
![]() |
afeaed790f | ||
![]() |
aed531a8a9 | ||
![]() |
eee78c6c10 |
185
.github/workflows/build.yml
vendored
Normal file
185
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
|
||||
env:
|
||||
REPO_SLUG_ORIGIN: "moby/buildkit:master"
|
||||
CACHEKEY_BINARIES: "binaries"
|
||||
PLATFORMS: "linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/s390x,linux/ppc64le"
|
||||
|
||||
jobs:
|
||||
base:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Cache ${{ env.CACHEKEY_BINARIES }}
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
key: ${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
driver-opts: image=${{ env.REPO_SLUG_ORIGIN }}
|
||||
-
|
||||
name: Build ${{ env.CACHEKEY_BINARIES }}
|
||||
run: |
|
||||
./hack/build_ci_first_pass binaries
|
||||
env:
|
||||
CACHEDIR_FROM: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
CACHEDIR_TO: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}-new
|
||||
-
|
||||
# FIXME: Temp fix for https://github.com/moby/buildkit/issues/1850
|
||||
name: Move cache
|
||||
run: |
|
||||
rm -rf /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
mv /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}-new /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [base]
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Cache ${{ env.CACHEKEY_BINARIES }}
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
key: ${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
driver-opts: image=${{ env.REPO_SLUG_ORIGIN }}
|
||||
-
|
||||
name: Test
|
||||
run: |
|
||||
make test
|
||||
env:
|
||||
TEST_COVERAGE: 1
|
||||
TESTFLAGS: -v --parallel=6 --timeout=20m
|
||||
CACHEDIR_FROM: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
-
|
||||
name: Send to Codecov
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
file: ./coverage/coverage.txt
|
||||
|
||||
cross:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [base]
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Cache ${{ env.CACHEKEY_BINARIES }}
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
key: ${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
driver-opts: image=${{ env.REPO_SLUG_ORIGIN }}
|
||||
-
|
||||
name: Cross
|
||||
run: |
|
||||
make cross
|
||||
env:
|
||||
TARGETPLATFORM: ${{ env.PLATFORMS }},darwin/amd64,windows/amd64
|
||||
CACHEDIR_FROM: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
|
||||
binaries:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test, cross]
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Prepare
|
||||
id: prep
|
||||
run: |
|
||||
TAG=pr
|
||||
if [[ $GITHUB_REF == refs/tags/v* ]]; then
|
||||
TAG=${GITHUB_REF#refs/tags/}
|
||||
elif [[ $GITHUB_REF == refs/heads/* ]]; then
|
||||
TAG=$(echo ${GITHUB_REF#refs/heads/} | sed -r 's#/+#-#g')
|
||||
fi
|
||||
echo ::set-output name=tag::${TAG}
|
||||
-
|
||||
name: Cache ${{ env.CACHEKEY_BINARIES }}
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
key: ${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
driver-opts: image=${{ env.REPO_SLUG_ORIGIN }}
|
||||
-
|
||||
name: Build ${{ steps.prep.outputs.tag }}
|
||||
run: |
|
||||
./hack/release "${{ steps.prep.outputs.tag }}" release-out
|
||||
env:
|
||||
PLATFORMS: ${{ env.PLATFORMS }},darwin/amd64,windows/amd64
|
||||
CACHEDIR_FROM: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
||||
-
|
||||
name: Move artifacts
|
||||
run: |
|
||||
mv ./release-out/**/* ./release-out/
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: buildx
|
||||
path: ./release-out/*
|
||||
if-no-files-found: error
|
||||
-
|
||||
name: GitHub Release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: softprops/action-gh-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
draft: true
|
||||
files: ./release-out/*
|
||||
name: ${{ steps.prep.outputs.tag }}
|
25
.github/workflows/godev.yml
vendored
Normal file
25
.github/workflows/godev.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# Workflow used to make a request to proxy.golang.org to refresh cache on https://pkg.go.dev/github.com/docker/buildx
|
||||
# when a released of buildx is produced
|
||||
name: godev
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.13
|
||||
-
|
||||
name: Call pkg.go.dev
|
||||
run: |
|
||||
go get github.com/${GITHUB_REPOSITORY}@${GITHUB_REF#refs/tags/}
|
||||
env:
|
||||
GO111MODULE: on
|
||||
GOPROXY: https://proxy.golang.org
|
38
.github/workflows/validate.yml
vendored
Normal file
38
.github/workflows/validate.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: validate
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
|
||||
env:
|
||||
REPO_SLUG_ORIGIN: "moby/buildkit:master"
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- lint
|
||||
- validate-vendor
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
driver-opts: image=${{ env.REPO_SLUG_ORIGIN }}
|
||||
-
|
||||
name: Run
|
||||
run: |
|
||||
make ${{ matrix.target }}
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
bin
|
||||
cross-out
|
||||
coverage
|
||||
cross-out
|
||||
|
35
.travis.yml
35
.travis.yml
@@ -1,35 +0,0 @@
|
||||
dist: trusty
|
||||
sudo: required
|
||||
|
||||
install:
|
||||
- docker run --name buildkit --rm -d --privileged -p 1234:1234 $REPO_SLUG_ORIGIN --addr tcp://0.0.0.0:1234
|
||||
- sudo docker cp buildkit:/usr/bin/buildctl /usr/bin/
|
||||
- export BUILDKIT_HOST=tcp://0.0.0.0:1234
|
||||
|
||||
env:
|
||||
global:
|
||||
- PLATFORMS="linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/s390x,linux/ppc64le"
|
||||
- CROSS_PLATFORMS="${PLATFORMS},darwin/amd64,windows/amd64"
|
||||
- PREFER_BUILDCTL="1"
|
||||
|
||||
script:
|
||||
- make binaries validate-all && TARGETPLATFORM="${CROSS_PLATFORMS}" ./hack/cross
|
||||
|
||||
|
||||
deploy:
|
||||
- provider: script
|
||||
script: PLATFORMS="${CROSS_PLATFORMS}" ./hack/release $TRAVIS_TAG release-out
|
||||
on:
|
||||
repo: docker/buildx
|
||||
tags: true
|
||||
condition: $TRAVIS_TAG =~ ^v[0-9]
|
||||
- provider: releases
|
||||
api_key:
|
||||
secure: "VKVL+tyS3BfqjM4VMGHoHJbcKY4mqq4AGrclVEvBnt0gm1LkGeKxSheCZgF1EC4oSV8rCy6dkoRWL0PLkl895MIl20Z4v53o1NOQ4Fn0A+eptnrld8jYUkL5PcD+kdEqv2GkBn7vO6E/fwYY/wH9FYlE+fXUa0c/YQGqNGS+XVDtgkftqBV+F2EzaIwk+D+QClFBRmKvIbXrUQASi1K6K2eT3gvzR4zh679TSdI2nbnTKtE06xG1PBFVmb1Ux3/Jz4yHFvf2d3M1mOyqIBsozKoyxisiFQxnm3FjhPrdlZJ9oy/nsQM3ahQKJ3DF8hiLI1LxcxRa6wo//t3uu2eJSYl/c5nu0T7gVw4sChQNy52fUhEGoDTDwYoAxsLSDXcpj1jevRsKvxt/dh2e2De1a9HYj5oM+z2O+pcyiY98cKDbhe2miUqUdiYMBy24xUunB46zVcJF3pIqCYtw5ts8ES6Ixn3u+4OGV/hMDrVdiG2bOZtNVkdbKMEkOEBGa3parPJ69jh6og639kdAD3DFxyZn3YKYuJlcNShn3tj6iPokBYhlLwwf8vuEV7gK7G0rDS9yxuF03jgkwpBBF2wy+u1AbJv241T7v2ZB8H8VlYyHA0E5pnoWbw+lIOTy4IAc8gIesMvDuFFi4r1okhiAt/24U0p4aAohjh1nPuU3spY="
|
||||
file: release-out/**/*
|
||||
skip_cleanup: true
|
||||
file_glob: true
|
||||
on:
|
||||
repo: docker/buildx
|
||||
tags: true
|
||||
condition: $TRAVIS_TAG =~ ^v[0-9]
|
13
.yamllint.yml
Normal file
13
.yamllint.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
ignore: |
|
||||
/vendor
|
||||
|
||||
extends: default
|
||||
|
||||
yaml-files:
|
||||
- '*.yaml'
|
||||
- '*.yml'
|
||||
|
||||
rules:
|
||||
truthy: disable
|
||||
line-length: disable
|
||||
document-start: disable
|
@@ -8,7 +8,7 @@ FROM docker:$DOCKERD_VERSION AS dockerd-release
|
||||
# xgo is a helper for golang cross-compilation
|
||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:golang@sha256:6f7d999551dd471b58f70716754290495690efa8421e0a1fcf18eb11d0c0a537 AS xgo
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:1.12-alpine AS gobase
|
||||
FROM --platform=$BUILDPLATFORM golang:1.13-alpine AS gobase
|
||||
COPY --from=xgo / /
|
||||
RUN apk add --no-cache file git
|
||||
ENV GOFLAGS=-mod=vendor
|
||||
|
3
Makefile
3
Makefile
@@ -7,6 +7,9 @@ binaries:
|
||||
binaries-cross:
|
||||
EXPORT_LOCAL=cross-out ./hack/cross
|
||||
|
||||
cross:
|
||||
./hack/cross
|
||||
|
||||
install: binaries
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
cp bin/buildx ~/.docker/cli-plugins/docker-buildx
|
||||
|
362
README.md
362
README.md
@@ -1,9 +1,13 @@
|
||||
# buildx
|
||||
### Docker CLI plugin for extended build capabilities with BuildKit
|
||||
|
||||
_buildx is Tech Preview_
|
||||
[](https://pkg.go.dev/github.com/docker/buildx)
|
||||
[](https://github.com/docker/buildx/actions?query=workflow%3Abuild)
|
||||
[](https://goreportcard.com/report/github.com/docker/buildx)
|
||||
[](https://codecov.io/gh/docker/buildx)
|
||||
|
||||
### TL;DR
|
||||
`buildx` is a Docker CLI plugin for extended build capabilities with [BuildKit](https://github.com/moby/buildkit).
|
||||
|
||||
Key features:
|
||||
|
||||
- Familiar UI from `docker build`
|
||||
- Full BuildKit capabilities with container driver
|
||||
@@ -11,7 +15,7 @@ _buildx is Tech Preview_
|
||||
- Multi-node builds for cross-platform images
|
||||
- Compose build support
|
||||
- WIP: High-level build constructs (`bake`)
|
||||
- TODO: In-container driver support
|
||||
- In-container driver support (both Docker and Kubernetes)
|
||||
|
||||
# Table of Contents
|
||||
|
||||
@@ -41,32 +45,21 @@ _buildx is Tech Preview_
|
||||
|
||||
# Installing
|
||||
|
||||
Using `buildx` as a docker CLI plugin requires using Docker 19.03.0 beta. A limited set of functionality works with older versions of Docker when invoking the binary directly.
|
||||
Using `buildx` as a docker CLI plugin requires using Docker 19.03. A limited set of functionality works with older versions of Docker when invoking the binary directly.
|
||||
|
||||
### Docker Desktop (Edge)
|
||||
### Docker CE
|
||||
|
||||
`buildx` is included with Docker Desktop Edge builds since 19.03.0-beta3.
|
||||
|
||||
For more information see https://docs.docker.com/docker-for-mac/edge-release-notes/
|
||||
|
||||
### Docker CE nightly builds
|
||||
|
||||
`buildx` comes bundled with the Docker CE nightly builds.
|
||||
- Mac: https://download.docker.com/mac/static/nightly/
|
||||
- Linux:
|
||||
```
|
||||
$ # uncomment next line to uninstall previous Docker CE installation if present
|
||||
$ # apt purge docker-ce docker-ce-cli
|
||||
$ curl -fsSL https://get.docker.com/ -o docker-install.sh
|
||||
$ CHANNEL=nightly sh docker-install.sh
|
||||
```
|
||||
`buildx` comes bundled with Docker CE starting with 19.03, but requires experimental mode to be enabled on the Docker CLI.
|
||||
To enable it, `"experimental": "enabled"` can be added to the CLI configuration file `~/.docker/config.json`. An alternative is to set the `DOCKER_CLI_EXPERIMENTAL=enabled` environment variable.
|
||||
|
||||
### Binary release
|
||||
|
||||
Download the latest binary release from https://github.com/docker/buildx/releases/latest and copy it to `~/.docker/cli-plugins` folder with name `docker-buildx`.
|
||||
|
||||
|
||||
After installing you can run `docker buildx` to see the new commands.
|
||||
Change the permission to execute:
|
||||
```sh
|
||||
chmod a+x ~/.docker/cli-plugins/docker-buildx
|
||||
```
|
||||
|
||||
# Building
|
||||
|
||||
@@ -80,6 +73,7 @@ $ make install
|
||||
```
|
||||
$ export DOCKER_BUILDKIT=1
|
||||
$ docker build --platform=local -o . git://github.com/docker/buildx
|
||||
$ mkdir -p ~/.docker/cli-plugins
|
||||
$ mv buildx ~/.docker/cli-plugins/docker-buildx
|
||||
```
|
||||
|
||||
@@ -89,7 +83,7 @@ $ mv buildx ~/.docker/cli-plugins/docker-buildx
|
||||
|
||||
Buildx is a Docker CLI plugin that extends the `docker build` command with the full support of the features provided by [Moby BuildKit](https://github.com/moby/buildkit) builder toolkit. It provides the same user experience as `docker build` with many new features like creating scoped builder instances and building against multiple nodes concurrently.
|
||||
|
||||
After installation, buildx can be accessed through the `docker buildx` command. `docker buildx build` is the command for starting a new build.
|
||||
After installation, buildx can be accessed through the `docker buildx` command with Docker 19.03. `docker buildx build` is the command for starting a new build. With Docker versions older than 19.03 buildx binary can be called directly to access the `docker buildx` subcommands.
|
||||
|
||||
```
|
||||
$ docker buildx build .
|
||||
@@ -127,7 +121,11 @@ When invoking a build, the `--platform` flag can be used to specify the target p
|
||||
|
||||
Multi-platform images can be built by mainly three different strategies that are all supported by buildx and Dockerfiles. You can use the QEMU emulation support in the kernel, build on multiple native nodes using the same builder instance or use a stage in Dockerfile to cross-compile to different architectures.
|
||||
|
||||
QEMU is the easiest way to get started if your node already supports it (e.g. if you are using Docker Desktop). It requires no changes to your Dockerfile and BuildKit will automatically detect the secondary architectures that are available. When BuildKit needs to run a binary for a different architecture it will automatically load it through a binary registered in the binfmt_misc handler.
|
||||
QEMU is the easiest way to get started if your node already supports it (e.g. if you are using Docker Desktop). It requires no changes to your Dockerfile and BuildKit will automatically detect the secondary architectures that are available. When BuildKit needs to run a binary for a different architecture it will automatically load it through a binary registered in the binfmt_misc handler. For QEMU binaries registered with binfmt_misc on the host OS to work transparently inside containers they must be registed with the fix_binary flag. This requires a kernel >= 4.8 and binfmt-support >= 2.1.7. You can check for proper registration by checking if `F` is among the flags in `/proc/sys/fs/binfmt_misc/qemu-*`. While Docker Desktop comes preconfigured with binfmt_misc support for additional platforms, for other installations it likely needs to be installed using [`tonistiigi/binfmt`](https://github.com/tonistiigi/binfmt) image.
|
||||
|
||||
```
|
||||
$ docker run --privileged --rm tonistiigi/binfmt --install all
|
||||
```
|
||||
|
||||
Using multiple native nodes provides better support for more complicated cases not handled by QEMU and generally have better performance. Additional nodes can be added to the builder instance with `--append` flag.
|
||||
|
||||
@@ -142,7 +140,7 @@ $ docker buildx build --platform linux/amd64,linux/arm64 .
|
||||
Finally, depending on your project, the language that you use may have good support for cross-compilation. In that case, multi-stage builds in Dockerfiles can be effectively used to build binaries for the platform specified with `--platform` using the native architecture of the build node. List of build arguments like `BUILDPLATFORM` and `TARGETPLATFORM` are available automatically inside your Dockerfile and can be leveraged by the processes running as part of your build.
|
||||
|
||||
```
|
||||
FROM --platform $BUILDPLATFORM golang:alpine AS build
|
||||
FROM --platform=$BUILDPLATFORM golang:alpine AS build
|
||||
ARG TARGETPLATFORM
|
||||
ARG BUILDPLATFORM
|
||||
RUN echo "I am running on $BUILDPLATFORM, building for $TARGETPLATFORM" > /log
|
||||
@@ -174,6 +172,7 @@ Options:
|
||||
| Flag | Description |
|
||||
| --- | --- |
|
||||
| --add-host [] | Add a custom host-to-IP mapping (host:ip)
|
||||
| --allow [] | Allow extra privileged entitlement, e.g. network.host, security.insecure
|
||||
| --build-arg [] | Set build-time variables
|
||||
| --cache-from [] | External cache sources (eg. user/app:cache, type=local,src=path/to/dir)
|
||||
| --cache-to [] | Cache export destinations (eg. user/app:cache, type=local,dest=path/to/dir)
|
||||
@@ -189,7 +188,7 @@ Options:
|
||||
| --pull | Always attempt to pull a newer version of the image
|
||||
| --push | Shorthand for --output=type=registry
|
||||
| --secret [] | Secret file to expose to the build: id=mysecret,src=/local/secret
|
||||
| --ssh [] | SSH agent socket or keys to expose to the build (format: default|<id>[=<socket>|<key>[,<key>]])
|
||||
| --ssh [] | SSH agent socket or keys to expose to the build (format: default\|<id>[=<socket>\|<key>[,<key>]])
|
||||
| --tag [] | Name and optionally a tag in the 'name:tag' format
|
||||
| --target string | Set the target build stage to build.
|
||||
|
||||
@@ -218,6 +217,21 @@ docker buildx build --platform=darwin .
|
||||
|
||||
Sets the export action for the build result. In `docker build` all builds finish by creating a container image and exporting it to `docker images`. `buildx` makes this step configurable allowing results to be exported directly to the client, oci image tarballs, registry etc.
|
||||
|
||||
Buildx with `docker` driver currently only supports local, tarball exporter and image exporter. `docker-container` driver supports all the exporters.
|
||||
|
||||
If just the path is specified as a value, `buildx` will use the local exporter with this path as the destination. If the value is “-”, `buildx` will use `tar` exporter and write to `stdout`.
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
docker buildx build -o . .
|
||||
docker buildx build -o outdir .
|
||||
docker buildx build -o - - > out.tar
|
||||
docker buildx build -o type=docker .
|
||||
docker buildx build -o type=docker,dest=- . > myimage.tar
|
||||
docker buildx build -t tonistiigi/foo -o type=registry
|
||||
````
|
||||
|
||||
Supported exported types are:
|
||||
|
||||
##### `local`
|
||||
@@ -269,23 +283,7 @@ Attribute keys:
|
||||
The `registry` exporter is a shortcut for `type=image,push=true`.
|
||||
|
||||
|
||||
|
||||
Buildx with `docker` driver currently only supports local, tarball exporter and image exporter. `docker-container` driver supports all the exporters.
|
||||
|
||||
If just the path is specified as a value, `buildx` will use the local exporter with this path as the destination. If the value is “-”, `buildx` will use `tar` exporter and write to `stdout`.
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
docker buildx build -o . .
|
||||
docker buildx build -o outdir .
|
||||
docker buildx build -o - - > out.tar
|
||||
docker buildx build -o type=docker .
|
||||
docker buildx build -o type=docker,dest=- . > myimage.tar
|
||||
docker buildx build -t tonistiigi/foo -o type=registry
|
||||
````
|
||||
|
||||
#### `--push`
|
||||
#### `--push`
|
||||
|
||||
Shorthand for [`--output=type=registry`](#registry). Will automatically push the build result to registry.
|
||||
|
||||
@@ -295,7 +293,7 @@ Shorthand for [`--output=type=docker`](#docker). Will automatically load the sin
|
||||
|
||||
#### `--cache-from=[NAME|type=TYPE[,KEY=VALUE]]`
|
||||
|
||||
Use an external cache source for a build. Supported types are `registry` and `local`. The `registry` source can import cache from a cache manifest or (special) image configuration on the registry. The `local` source can export cache from local files previously exported with `--cache-to`.
|
||||
Use an external cache source for a build. Supported types are `registry` and `local`. The `registry` source can import cache from a cache manifest or (special) image configuration on the registry. The `local` source can import cache from local files previously exported with `--cache-to`.
|
||||
|
||||
If no type is specified, `registry` exporter is used with a specified reference.
|
||||
|
||||
@@ -327,6 +325,20 @@ docker buildx build --cache-to=type=registry,ref=user/app .
|
||||
docker buildx build --cache-to=type=local,dest=path/to/cache .
|
||||
```
|
||||
|
||||
#### `--allow=ENTITLEMENT`
|
||||
|
||||
Allow extra privileged entitlement. List of entitlements:
|
||||
|
||||
- `network.host` - Allows executions with host networking.
|
||||
- `security.insecure` - Allows executions without sandbox. See [related Dockerfile extensions](https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md#run---securityinsecuresandbox).
|
||||
|
||||
For entitlements to be enabled, the `buildkitd` daemon also needs to allow them with `--allow-insecure-entitlement` (see [`create --buildkitd-flags`](#--buildkitd-flags-flags))
|
||||
|
||||
Example:
|
||||
```
|
||||
$ docker buildx create --use --name insecure-builder --buildkitd-flags '--allow-insecure-entitlement security.insecure'
|
||||
$ docker buildx build --allow security.insecure .
|
||||
```
|
||||
|
||||
### `buildx create [OPTIONS] [CONTEXT|ENDPOINT]`
|
||||
|
||||
@@ -340,21 +352,16 @@ Options:
|
||||
|
||||
| Flag | Description |
|
||||
| --- | --- |
|
||||
| --append | Append a node to builder instead of changing it
|
||||
| --driver string | Driver to use (eg. docker-container)
|
||||
| --leave | Remove a node from builder instead of changing it
|
||||
| --name string | Builder instance name
|
||||
| --node string | Create/modify node with given name
|
||||
| --platform stringArray | Fixed platforms for current node
|
||||
| --use | Set the current builder instance
|
||||
|
||||
#### `--driver DRIVER`
|
||||
|
||||
Sets the builder driver to be used. There are two available drivers, each have their own specificities.
|
||||
|
||||
- `docker` - Uses the builder that is built into the docker daemon. With this driver, the [`--load`](#--load) flag is implied by default on `buildx build`. However, building multi-platform images or exporting cache is not currently supported.
|
||||
|
||||
- `docker-container` - Uses a buildkit container that will be spawned via docker. With this driver, both building multi-platform images and exporting cache are supported. However, images built will not automatically appear in `docker images` (see [`build --load`](#--load)).
|
||||
| --append | Append a node to builder instead of changing it
|
||||
| --buildkitd-flags string | Flags for buildkitd daemon
|
||||
| --config string | BuildKit config file
|
||||
| --driver string | Driver to use (eg. docker-container)
|
||||
| --driver-opt stringArray | Options for the driver
|
||||
| --leave | Remove a node from builder instead of changing it
|
||||
| --name string | Builder instance name
|
||||
| --node string | Create/modify node with given name
|
||||
| --platform stringArray | Fixed platforms for current node
|
||||
| --use | Set the current builder instance
|
||||
|
||||
#### `--append`
|
||||
|
||||
@@ -368,6 +375,50 @@ $ docker buildx create --name eager_beaver --append mycontext2
|
||||
eager_beaver
|
||||
```
|
||||
|
||||
#### `--buildkitd-flags FLAGS`
|
||||
|
||||
Adds flags when starting the buildkitd daemon. They take precedence over the configuration file specified by [`--config`](#--config-file). See `buildkitd --help` for the available flags.
|
||||
|
||||
Example:
|
||||
```
|
||||
--buildkitd-flags '--debug --debugaddr 0.0.0.0:6666'
|
||||
```
|
||||
|
||||
#### `--config FILE`
|
||||
|
||||
Specifies the configuration file for the buildkitd daemon to use. The configuration can be overridden by [`--buildkitd-flags`](#--buildkitd-flags-flags). See an [example buildkitd configuration file](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md).
|
||||
|
||||
#### `--driver DRIVER`
|
||||
|
||||
Sets the builder driver to be used. There are two available drivers, each have their own specificities.
|
||||
|
||||
- `docker` - Uses the builder that is built into the docker daemon. With this driver, the [`--load`](#--load) flag is implied by default on `buildx build`. However, building multi-platform images or exporting cache is not currently supported.
|
||||
|
||||
- `docker-container` - Uses a buildkit container that will be spawned via docker. With this driver, both building multi-platform images and exporting cache are supported. However, images built will not automatically appear in `docker images` (see [`build --load`](#--load)).
|
||||
|
||||
- `kubernetes` - Uses a kubernetes pods. With this driver , you can spin up pods with defined buildkit container image to build your images.
|
||||
|
||||
|
||||
#### `--driver-opt OPTIONS`
|
||||
|
||||
Passes additional driver-specific options. Details for each driver:
|
||||
|
||||
- `docker` - No driver options
|
||||
- `docker-container`
|
||||
- `image=IMAGE` - Sets the container image to be used for running buildkit.
|
||||
- `network=NETMODE` - Sets the network mode for running the buildkit container.
|
||||
- Example:
|
||||
```
|
||||
--driver docker-container --driver-opt image=moby/buildkit:master,network=host
|
||||
```
|
||||
- `kubernetes`
|
||||
- `image=IMAGE` - Sets the container image to be used for running buildkit.
|
||||
- `namespace=NS` - Sets the Kubernetes namespace. Defaults to the current namespace.
|
||||
- `replicas=N` - Sets the number of `Pod` replicas. Defaults to 1.
|
||||
- `nodeselector="label1=value1,label2=value2"` - Sets the kv of `Pod` nodeSelector. No Defaults. Example `nodeselector=kubernetes.io/arch=arm64`
|
||||
- `rootless=(true|false)` - Run the container as a non-root user without `securityContext.privileged`. [Using Ubuntu host kernel is recommended](https://github.com/moby/buildkit/blob/master/docs/rootless.md). Defaults to false.
|
||||
- `loadbalance=(sticky|random)` - Load-balancing strategy. If set to "sticky", the pod is chosen using the hash of the context path. Defaults to "sticky"
|
||||
|
||||
#### `--leave`
|
||||
|
||||
Changes the action of the command to removes a node from a builder. The builder needs to be specified with `--name` and node that is removed is set with `--node`.
|
||||
@@ -465,11 +516,13 @@ Options:
|
||||
| Flag | Description |
|
||||
| --- | --- |
|
||||
| -f, --file stringArray | Build definition file
|
||||
| --load | Shorthand for --set=*.output=type=docker
|
||||
| --no-cache | Do not use cache when building the image
|
||||
| --print | Print the options without building
|
||||
| --progress string | Set type of progress output (auto, plain, tty). Use plain to show container output (default "auto")
|
||||
| --pull | Always attempt to pull a newer version of the image
|
||||
| --set stringArray | Override target value (eg: target.key=value)
|
||||
| --push | Shorthand for --set=*.output=type=registry
|
||||
| --set stringArray | Override target value (eg: targetpattern.key=value)
|
||||
|
||||
#### `-f, --file FILE`
|
||||
|
||||
@@ -512,16 +565,22 @@ Same as `build --progress`. Set type of progress output (auto, plain, tty). Use
|
||||
|
||||
Same as `build --pull`.
|
||||
|
||||
#### `--set target.key[.subkey]=value`
|
||||
#### `--set targetpattern.key[.subkey]=value`
|
||||
|
||||
Override target configurations from command line.
|
||||
Override target configurations from command line. The pattern matching syntax is defined in https://golang.org/pkg/path/#Match.
|
||||
|
||||
Example:
|
||||
```
|
||||
docker buildx bake --set target.args.mybuildarg=value
|
||||
docker buildx bake --set target.platform=linux/arm64
|
||||
docker buildx bake --set foo*.args.mybuildarg=value # overrides build arg for all targets starting with 'foo'
|
||||
docker buildx bake --set *.platform=linux/arm64 # overrides platform for all targets
|
||||
docker buildx bake --set foo*.no-cache # bypass caching only for targets starting with 'foo'
|
||||
```
|
||||
|
||||
Complete list of overridable fields:
|
||||
args, cache-from, cache-to, context, dockerfile, labels, no-cache, output, platform, pull, secrets, ssh, tags, target
|
||||
|
||||
#### File definition
|
||||
|
||||
In addition to compose files, bake supports a JSON and an equivalent HCL file format for defining build groups and targets.
|
||||
@@ -541,23 +600,184 @@ Note: Design of bake command is work in progress, the user experience may change
|
||||
Example HCL defintion:
|
||||
|
||||
```
|
||||
group “default” {
|
||||
targets = [“db”, “webapp-dev”]
|
||||
group "default" {
|
||||
targets = ["db", "webapp-dev"]
|
||||
}
|
||||
|
||||
target “webapp-dev” {
|
||||
target "webapp-dev" {
|
||||
dockerfile = "Dockerfile.webapp"
|
||||
tags = ["docker.io/username/webapp"]
|
||||
}
|
||||
|
||||
target “webapp-release” {
|
||||
inherits = [“webapp-dev”]
|
||||
platforms = [“linux/amd64”, “linux/arm64”]
|
||||
target "webapp-release" {
|
||||
inherits = ["webapp-dev"]
|
||||
platforms = ["linux/amd64", "linux/arm64"]
|
||||
}
|
||||
|
||||
target “db” {
|
||||
target "db" {
|
||||
dockerfile = "Dockerfile.db"
|
||||
tags = [“docker.io/username/db”]
|
||||
tags = ["docker.io/username/db"]
|
||||
}
|
||||
```
|
||||
|
||||
Complete list of valid target fields:
|
||||
args, cache-from, cache-to, context, dockerfile, inherits, labels, no-cache, output, platform, pull, secrets, ssh, tags, target
|
||||
|
||||
#### HCL variables and functions
|
||||
|
||||
Similar to how Terraform provides a way to [define variables](https://www.terraform.io/docs/configuration/variables.html#declaring-an-input-variable), the HCL file format also supports variable block definitions. These can be used to define variables with values provided by the current environment or a default value when unset.
|
||||
|
||||
|
||||
|
||||
Example of using interpolation to tag an image with the git sha:
|
||||
|
||||
```
|
||||
$ cat <<'EOF' > docker-bake.hcl
|
||||
variable "TAG" {
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
group "default" {
|
||||
targets = ["webapp"]
|
||||
}
|
||||
|
||||
target "webapp" {
|
||||
tags = ["docker.io/username/webapp:${TAG}"]
|
||||
}
|
||||
EOF
|
||||
|
||||
$ docker buildx bake --print webapp
|
||||
{
|
||||
"target": {
|
||||
"webapp": {
|
||||
"context": ".",
|
||||
"dockerfile": "Dockerfile",
|
||||
"tags": [
|
||||
"docker.io/username/webapp:latest"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ TAG=$(git rev-parse --short HEAD) docker buildx bake --print webapp
|
||||
{
|
||||
"target": {
|
||||
"webapp": {
|
||||
"context": ".",
|
||||
"dockerfile": "Dockerfile",
|
||||
"tags": [
|
||||
"docker.io/username/webapp:985e9e9"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
A [set of generally useful functions](https://github.com/docker/buildx/blob/master/bake/hcl.go#L19-L65) provided by [go-cty](https://github.com/zclconf/go-cty/tree/master/cty/function/stdlib) are avaialble for use in HCL files. In addition, [user defined functions](https://github.com/hashicorp/hcl/tree/hcl2/ext/userfunc) are also supported.
|
||||
|
||||
|
||||
|
||||
Example of using the `add` function:
|
||||
|
||||
```
|
||||
$ cat <<'EOF' > docker-bake.hcl
|
||||
variable "TAG" {
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
group "default" {
|
||||
targets = ["webapp"]
|
||||
}
|
||||
|
||||
target "webapp" {
|
||||
args = {
|
||||
buildno = "${add(123, 1)}"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
$ docker buildx bake --print webapp
|
||||
{
|
||||
"target": {
|
||||
"webapp": {
|
||||
"context": ".",
|
||||
"dockerfile": "Dockerfile",
|
||||
"args": {
|
||||
"buildno": "124"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example of defining an `increment` function:
|
||||
|
||||
```
|
||||
$ cat <<'EOF' > docker-bake.hcl
|
||||
function "increment" {
|
||||
params = [number]
|
||||
result = number + 1
|
||||
}
|
||||
|
||||
group "default" {
|
||||
targets = ["webapp"]
|
||||
}
|
||||
|
||||
target "webapp" {
|
||||
args = {
|
||||
buildno = "${increment(123)}"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
$ docker buildx bake --print webapp
|
||||
{
|
||||
"target": {
|
||||
"webapp": {
|
||||
"context": ".",
|
||||
"dockerfile": "Dockerfile",
|
||||
"args": {
|
||||
"buildno": "124"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example of only adding tags if a variable is not empty using an `notequal` function:
|
||||
|
||||
```
|
||||
$ cat <<'EOF' > docker-bake.hcl
|
||||
variable "TAG" {default="" }
|
||||
|
||||
group "default" {
|
||||
targets = [
|
||||
"webapp",
|
||||
]
|
||||
}
|
||||
|
||||
target "webapp" {
|
||||
context="."
|
||||
dockerfile="Dockerfile"
|
||||
tags = [
|
||||
"my-image:latest",
|
||||
notequal("",TAG) ? "my-image:${TAG}": "",
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
$ docker buildx bake --print webapp
|
||||
{
|
||||
"target": {
|
||||
"webapp": {
|
||||
"context": ".",
|
||||
"dockerfile": "Dockerfile",
|
||||
"tags": [
|
||||
"my-image:latest"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
428
bake/bake.go
428
bake/bake.go
@@ -5,60 +5,100 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/util/platformutil"
|
||||
"github.com/docker/docker/pkg/urlutil"
|
||||
hcl "github.com/hashicorp/hcl/v2"
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/session/auth/authprovider"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func ReadTargets(ctx context.Context, files, targets, overrides []string) (map[string]Target, error) {
|
||||
var httpPrefix = regexp.MustCompile(`^https?://`)
|
||||
var gitURLPathWithFragmentSuffix = regexp.MustCompile(`\.git(?:#.+)?$`)
|
||||
|
||||
type File struct {
|
||||
Name string
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func defaultFilenames() []string {
|
||||
return []string{
|
||||
"docker-compose.yml", // support app
|
||||
"docker-compose.yaml", // support app
|
||||
"docker-bake.json",
|
||||
"docker-bake.override.json",
|
||||
"docker-bake.hcl",
|
||||
"docker-bake.override.hcl",
|
||||
}
|
||||
}
|
||||
|
||||
func ReadLocalFiles(names []string) ([]File, error) {
|
||||
isDefault := false
|
||||
if len(names) == 0 {
|
||||
isDefault = true
|
||||
names = defaultFilenames()
|
||||
}
|
||||
out := make([]File, 0, len(names))
|
||||
|
||||
for _, n := range names {
|
||||
dt, err := ioutil.ReadFile(n)
|
||||
if err != nil {
|
||||
if isDefault && errors.Is(err, os.ErrNotExist) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, File{Name: n, Data: dt})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func ReadTargets(ctx context.Context, files []File, targets, overrides []string) (map[string]*Target, error) {
|
||||
var c Config
|
||||
for _, f := range files {
|
||||
cfg, err := ParseFile(f)
|
||||
cfg, err := ParseFile(f.Data, f.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c = mergeConfig(c, *cfg)
|
||||
}
|
||||
if err := c.setOverrides(overrides); err != nil {
|
||||
o, err := c.newOverrides(overrides)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := map[string]Target{}
|
||||
m := map[string]*Target{}
|
||||
for _, n := range targets {
|
||||
for _, n := range c.ResolveGroup(n) {
|
||||
t, err := c.ResolveTarget(n)
|
||||
t, err := c.ResolveTarget(n, o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t != nil {
|
||||
m[n] = *t
|
||||
m[n] = t
|
||||
}
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func ParseFile(fn string) (*Config, error) {
|
||||
dt, err := ioutil.ReadFile(fn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func ParseFile(dt []byte, fn string) (*Config, error) {
|
||||
fnl := strings.ToLower(fn)
|
||||
if strings.HasSuffix(fnl, ".yml") || strings.HasSuffix(fnl, ".yaml") {
|
||||
return ParseCompose(dt)
|
||||
}
|
||||
|
||||
if strings.HasSuffix(fnl, ".json") || strings.HasSuffix(fnl, ".hcl") {
|
||||
return ParseHCL(dt)
|
||||
return ParseHCL(dt, fn)
|
||||
}
|
||||
|
||||
cfg, err := ParseCompose(dt)
|
||||
if err != nil {
|
||||
cfg, err2 := ParseHCL(dt)
|
||||
cfg, err2 := ParseHCL(dt, fn)
|
||||
if err2 != nil {
|
||||
return nil, errors.Errorf("failed to parse %s: parsing yaml: %s, parsing hcl: %s", fn, err.Error(), err2.Error())
|
||||
}
|
||||
@@ -68,92 +108,176 @@ func ParseFile(fn string) (*Config, error) {
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Group map[string]Group
|
||||
Target map[string]Target
|
||||
Variables []*Variable `json:"-" hcl:"variable,block"`
|
||||
Groups []*Group `json:"group" hcl:"group,block"`
|
||||
Targets []*Target `json:"target" hcl:"target,block"`
|
||||
Remain hcl.Body `json:"-" hcl:",remain"`
|
||||
}
|
||||
|
||||
func mergeConfig(c1, c2 Config) Config {
|
||||
for k, g := range c2.Group {
|
||||
if c1.Group == nil {
|
||||
c1.Group = map[string]Group{}
|
||||
}
|
||||
c1.Group[k] = g
|
||||
if c1.Groups == nil {
|
||||
c1.Groups = []*Group{}
|
||||
}
|
||||
|
||||
for k, t := range c2.Target {
|
||||
if c1.Target == nil {
|
||||
c1.Target = map[string]Target{}
|
||||
for _, g2 := range c2.Groups {
|
||||
var g1 *Group
|
||||
for _, g := range c1.Groups {
|
||||
if g2.Name == g.Name {
|
||||
g1 = g
|
||||
break
|
||||
}
|
||||
}
|
||||
if base, ok := c1.Target[k]; ok {
|
||||
t = merge(base, t)
|
||||
if g1 == nil {
|
||||
c1.Groups = append(c1.Groups, g2)
|
||||
continue
|
||||
}
|
||||
c1.Target[k] = t
|
||||
|
||||
nextTarget:
|
||||
for _, t2 := range g2.Targets {
|
||||
for _, t1 := range g1.Targets {
|
||||
if t1 == t2 {
|
||||
continue nextTarget
|
||||
}
|
||||
}
|
||||
g1.Targets = append(g1.Targets, t2)
|
||||
}
|
||||
c1.Groups = append(c1.Groups, g1)
|
||||
}
|
||||
|
||||
if c1.Targets == nil {
|
||||
c1.Targets = []*Target{}
|
||||
}
|
||||
|
||||
for _, t2 := range c2.Targets {
|
||||
var t1 *Target
|
||||
for _, t := range c1.Targets {
|
||||
if t2.Name == t.Name {
|
||||
t1 = t
|
||||
break
|
||||
}
|
||||
}
|
||||
if t1 != nil {
|
||||
t2 = merge(t1, t2)
|
||||
}
|
||||
c1.Targets = append(c1.Targets, t2)
|
||||
}
|
||||
|
||||
return c1
|
||||
}
|
||||
|
||||
func (c Config) setOverrides(v []string) error {
|
||||
for _, v := range v {
|
||||
parts := strings.SplitN(v, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return errors.Errorf("invalid override %s, expected target.name=value", v)
|
||||
func (c Config) expandTargets(pattern string) ([]string, error) {
|
||||
for _, target := range c.Targets {
|
||||
if target.Name == pattern {
|
||||
return []string{pattern}, nil
|
||||
}
|
||||
}
|
||||
|
||||
var names []string
|
||||
for _, target := range c.Targets {
|
||||
ok, err := path.Match(pattern, target.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not match targets with '%s'", pattern)
|
||||
}
|
||||
if ok {
|
||||
names = append(names, target.Name)
|
||||
}
|
||||
}
|
||||
if len(names) == 0 {
|
||||
return nil, errors.Errorf("could not find any target matching '%s'", pattern)
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
func (c Config) newOverrides(v []string) (map[string]*Target, error) {
|
||||
m := map[string]*Target{}
|
||||
for _, v := range v {
|
||||
|
||||
parts := strings.SplitN(v, "=", 2)
|
||||
keys := strings.SplitN(parts[0], ".", 3)
|
||||
if len(keys) < 2 {
|
||||
return errors.Errorf("invalid override key %s, expected target.name", parts[0])
|
||||
return nil, errors.Errorf("invalid override key %s, expected target.name", parts[0])
|
||||
}
|
||||
|
||||
name := keys[0]
|
||||
|
||||
t, ok := c.Target[name]
|
||||
if !ok {
|
||||
return errors.Errorf("unknown target %s", name)
|
||||
pattern := keys[0]
|
||||
if len(parts) != 2 && keys[1] != "args" {
|
||||
return nil, errors.Errorf("invalid override %s, expected target.name=value", v)
|
||||
}
|
||||
|
||||
switch keys[1] {
|
||||
case "context":
|
||||
t.Context = &parts[1]
|
||||
case "dockerfile":
|
||||
t.Dockerfile = &parts[1]
|
||||
case "args":
|
||||
if len(keys) != 3 {
|
||||
return errors.Errorf("invalid key %s, args requires name", parts[0])
|
||||
}
|
||||
if t.Args == nil {
|
||||
t.Args = map[string]string{}
|
||||
}
|
||||
t.Args[keys[2]] = parts[1]
|
||||
case "labels":
|
||||
if len(keys) != 3 {
|
||||
return errors.Errorf("invalid key %s, lanels requires name", parts[0])
|
||||
}
|
||||
if t.Labels == nil {
|
||||
t.Labels = map[string]string{}
|
||||
}
|
||||
t.Labels[keys[2]] = parts[1]
|
||||
case "tags":
|
||||
t.Tags = append(t.Tags, parts[1])
|
||||
case "cache-from":
|
||||
t.CacheFrom = append(t.CacheFrom, parts[1])
|
||||
case "cache-to":
|
||||
t.CacheTo = append(t.CacheTo, parts[1])
|
||||
case "target":
|
||||
s := parts[1]
|
||||
t.Target = &s
|
||||
case "secrets":
|
||||
t.Secrets = append(t.Secrets, parts[1])
|
||||
case "ssh":
|
||||
t.SSH = append(t.SSH, parts[1])
|
||||
case "platform":
|
||||
t.Platforms = append(t.Platforms, parts[1])
|
||||
case "output":
|
||||
t.Outputs = append(t.Outputs, parts[1])
|
||||
default:
|
||||
return errors.Errorf("unknown key: %s", keys[1])
|
||||
names, err := c.expandTargets(pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
t, ok := m[name]
|
||||
if !ok {
|
||||
t = &Target{}
|
||||
}
|
||||
|
||||
switch keys[1] {
|
||||
case "context":
|
||||
t.Context = &parts[1]
|
||||
case "dockerfile":
|
||||
t.Dockerfile = &parts[1]
|
||||
case "args":
|
||||
if len(keys) != 3 {
|
||||
return nil, errors.Errorf("invalid key %s, args requires name", parts[0])
|
||||
}
|
||||
if t.Args == nil {
|
||||
t.Args = map[string]string{}
|
||||
}
|
||||
if len(parts) < 2 {
|
||||
v, ok := os.LookupEnv(keys[2])
|
||||
if ok {
|
||||
t.Args[keys[2]] = v
|
||||
}
|
||||
} else {
|
||||
t.Args[keys[2]] = parts[1]
|
||||
}
|
||||
case "labels":
|
||||
if len(keys) != 3 {
|
||||
return nil, errors.Errorf("invalid key %s, lanels requires name", parts[0])
|
||||
}
|
||||
if t.Labels == nil {
|
||||
t.Labels = map[string]string{}
|
||||
}
|
||||
t.Labels[keys[2]] = parts[1]
|
||||
case "tags":
|
||||
t.Tags = append(t.Tags, parts[1])
|
||||
case "cache-from":
|
||||
t.CacheFrom = append(t.CacheFrom, parts[1])
|
||||
case "cache-to":
|
||||
t.CacheTo = append(t.CacheTo, parts[1])
|
||||
case "target":
|
||||
s := parts[1]
|
||||
t.Target = &s
|
||||
case "secrets":
|
||||
t.Secrets = append(t.Secrets, parts[1])
|
||||
case "ssh":
|
||||
t.SSH = append(t.SSH, parts[1])
|
||||
case "platform":
|
||||
t.Platforms = append(t.Platforms, parts[1])
|
||||
case "output":
|
||||
t.Outputs = append(t.Outputs, parts[1])
|
||||
case "no-cache":
|
||||
noCache, err := strconv.ParseBool(parts[1])
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("invalid value %s for boolean key no-cache", parts[1])
|
||||
}
|
||||
t.NoCache = &noCache
|
||||
case "pull":
|
||||
pull, err := strconv.ParseBool(parts[1])
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("invalid value %s for boolean key pull", parts[1])
|
||||
}
|
||||
t.Pull = &pull
|
||||
default:
|
||||
return nil, errors.Errorf("unknown key: %s", keys[1])
|
||||
}
|
||||
m[name] = t
|
||||
}
|
||||
c.Target[name] = t
|
||||
}
|
||||
return nil
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c Config) ResolveGroup(name string) []string {
|
||||
@@ -164,8 +288,14 @@ func (c Config) group(name string, visited map[string]struct{}) []string {
|
||||
if _, ok := visited[name]; ok {
|
||||
return nil
|
||||
}
|
||||
g, ok := c.Group[name]
|
||||
if !ok {
|
||||
var g *Group
|
||||
for _, group := range c.Groups {
|
||||
if group.Name == name {
|
||||
g = group
|
||||
break
|
||||
}
|
||||
}
|
||||
if g == nil {
|
||||
return []string{name}
|
||||
}
|
||||
visited[name] = struct{}{}
|
||||
@@ -176,8 +306,8 @@ func (c Config) group(name string, visited map[string]struct{}) []string {
|
||||
return targets
|
||||
}
|
||||
|
||||
func (c Config) ResolveTarget(name string) (*Target, error) {
|
||||
t, err := c.target(name, map[string]struct{}{})
|
||||
func (c Config) ResolveTarget(name string, overrides map[string]*Target) (*Target, error) {
|
||||
t, err := c.target(name, map[string]struct{}{}, overrides)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -192,50 +322,74 @@ func (c Config) ResolveTarget(name string) (*Target, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (c Config) target(name string, visited map[string]struct{}) (*Target, error) {
|
||||
func (c Config) target(name string, visited map[string]struct{}, overrides map[string]*Target) (*Target, error) {
|
||||
if _, ok := visited[name]; ok {
|
||||
return nil, nil
|
||||
}
|
||||
visited[name] = struct{}{}
|
||||
t, ok := c.Target[name]
|
||||
if !ok {
|
||||
var t *Target
|
||||
for _, target := range c.Targets {
|
||||
if target.Name == name {
|
||||
t = target
|
||||
break
|
||||
}
|
||||
}
|
||||
if t == nil {
|
||||
return nil, errors.Errorf("failed to find target %s", name)
|
||||
}
|
||||
var tt Target
|
||||
tt := &Target{}
|
||||
for _, name := range t.Inherits {
|
||||
t, err := c.target(name, visited)
|
||||
t, err := c.target(name, visited, overrides)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t != nil {
|
||||
tt = merge(tt, *t)
|
||||
tt = merge(tt, t)
|
||||
}
|
||||
}
|
||||
t.Inherits = nil
|
||||
tt = merge(merge(defaultTarget(), t), tt)
|
||||
tt = merge(merge(defaultTarget(), tt), t)
|
||||
if override, ok := overrides[name]; ok {
|
||||
tt = merge(tt, override)
|
||||
}
|
||||
tt.normalize()
|
||||
return &tt, nil
|
||||
return tt, nil
|
||||
}
|
||||
|
||||
type Variable struct {
|
||||
Name string `json:"-" hcl:"name,label"`
|
||||
Default string `json:"default,omitempty" hcl:"default,optional"`
|
||||
}
|
||||
|
||||
type Group struct {
|
||||
Targets []string
|
||||
Name string `json:"-" hcl:"name,label"`
|
||||
Targets []string `json:"targets" hcl:"targets"`
|
||||
// Target // TODO?
|
||||
}
|
||||
|
||||
type Target struct {
|
||||
Inherits []string `json:"inherits,omitempty" hcl:"inherits,omitempty"`
|
||||
Context *string `json:"context,omitempty" hcl:"context,omitempty"`
|
||||
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,omitempty"`
|
||||
Args map[string]string `json:"args,omitempty" hcl:"args,omitempty"`
|
||||
Labels map[string]string `json:"labels,omitempty" hcl:"labels,omitempty"`
|
||||
Tags []string `json:"tags,omitempty" hcl:"tags,omitempty"`
|
||||
CacheFrom []string `json:"cache-from,omitempty" hcl:"cache-from,omitempty"`
|
||||
CacheTo []string `json:"cache-to,omitempty" hcl:"cache-to,omitempty"`
|
||||
Target *string `json:"target,omitempty" hcl:"target,omitempty"`
|
||||
Secrets []string `json:"secret,omitempty" hcl:"secret,omitempty"`
|
||||
SSH []string `json:"ssh,omitempty" hcl:"ssh,omitempty"`
|
||||
Platforms []string `json:"platforms,omitempty" hcl:"platforms,omitempty"`
|
||||
Outputs []string `json:"output,omitempty" hcl:"output,omitempty"`
|
||||
Name string `json:"-" hcl:"name,label"`
|
||||
|
||||
// Inherits is the only field that cannot be overridden with --set
|
||||
Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional"`
|
||||
|
||||
Context *string `json:"context,omitempty" hcl:"context,optional"`
|
||||
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional"`
|
||||
DockerfileInline *string `json:"dockerfile-inline,omitempty" hcl:"dockerfile-inline,optional"`
|
||||
Args map[string]string `json:"args,omitempty" hcl:"args,optional"`
|
||||
Labels map[string]string `json:"labels,omitempty" hcl:"labels,optional"`
|
||||
Tags []string `json:"tags,omitempty" hcl:"tags,optional"`
|
||||
CacheFrom []string `json:"cache-from,omitempty" hcl:"cache-from,optional"`
|
||||
CacheTo []string `json:"cache-to,omitempty" hcl:"cache-to,optional"`
|
||||
Target *string `json:"target,omitempty" hcl:"target,optional"`
|
||||
Secrets []string `json:"secret,omitempty" hcl:"secret,optional"`
|
||||
SSH []string `json:"ssh,omitempty" hcl:"ssh,optional"`
|
||||
Platforms []string `json:"platforms,omitempty" hcl:"platforms,optional"`
|
||||
Outputs []string `json:"output,omitempty" hcl:"output,optional"`
|
||||
Pull *bool `json:"pull,omitempty" hcl:"pull,optional"`
|
||||
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional"`
|
||||
|
||||
// IMPORTANT: if you add more fields here, do not forget to update newOverrides and README.
|
||||
}
|
||||
|
||||
func (t *Target) normalize() {
|
||||
@@ -248,10 +402,10 @@ func (t *Target) normalize() {
|
||||
t.Outputs = removeDupes(t.Outputs)
|
||||
}
|
||||
|
||||
func TargetsToBuildOpt(m map[string]Target) (map[string]build.Options, error) {
|
||||
func TargetsToBuildOpt(m map[string]*Target, inp *Input) (map[string]build.Options, error) {
|
||||
m2 := make(map[string]build.Options, len(m))
|
||||
for k, v := range m {
|
||||
bo, err := toBuildOpt(v)
|
||||
bo, err := toBuildOpt(v, inp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -260,7 +414,19 @@ func TargetsToBuildOpt(m map[string]Target) (map[string]build.Options, error) {
|
||||
return m2, nil
|
||||
}
|
||||
|
||||
func toBuildOpt(t Target) (*build.Options, error) {
|
||||
func updateContext(t *build.Inputs, inp *Input) {
|
||||
if inp == nil || inp.State == nil {
|
||||
return
|
||||
}
|
||||
if t.ContextPath == "." {
|
||||
t.ContextPath = inp.URL
|
||||
return
|
||||
}
|
||||
st := llb.Scratch().File(llb.Copy(*inp.State, t.ContextPath, "/"), llb.WithCustomNamef("set context to %s", t.ContextPath))
|
||||
t.ContextState = &st
|
||||
}
|
||||
|
||||
func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
||||
if v := t.Context; v != nil && *v == "-" {
|
||||
return nil, errors.Errorf("context from stdin not allowed in bake")
|
||||
}
|
||||
@@ -272,6 +438,7 @@ func toBuildOpt(t Target) (*build.Options, error) {
|
||||
if t.Context != nil {
|
||||
contextPath = *t.Context
|
||||
}
|
||||
contextPath = path.Clean(contextPath)
|
||||
dockerfilePath := "Dockerfile"
|
||||
if t.Dockerfile != nil {
|
||||
dockerfilePath = *t.Dockerfile
|
||||
@@ -281,14 +448,31 @@ func toBuildOpt(t Target) (*build.Options, error) {
|
||||
dockerfilePath = path.Join(contextPath, dockerfilePath)
|
||||
}
|
||||
|
||||
noCache := false
|
||||
if t.NoCache != nil {
|
||||
noCache = *t.NoCache
|
||||
}
|
||||
pull := false
|
||||
if t.Pull != nil {
|
||||
pull = *t.Pull
|
||||
}
|
||||
|
||||
bi := build.Inputs{
|
||||
ContextPath: contextPath,
|
||||
DockerfilePath: dockerfilePath,
|
||||
}
|
||||
if t.DockerfileInline != nil {
|
||||
bi.DockerfileInline = *t.DockerfileInline
|
||||
}
|
||||
updateContext(&bi, inp)
|
||||
|
||||
bo := &build.Options{
|
||||
Inputs: build.Inputs{
|
||||
ContextPath: contextPath,
|
||||
DockerfilePath: dockerfilePath,
|
||||
},
|
||||
Inputs: bi,
|
||||
Tags: t.Tags,
|
||||
BuildArgs: t.Args,
|
||||
Labels: t.Labels,
|
||||
NoCache: noCache,
|
||||
Pull: pull,
|
||||
}
|
||||
|
||||
platforms, err := platformutil.Parse(t.Platforms)
|
||||
@@ -336,17 +520,20 @@ func toBuildOpt(t Target) (*build.Options, error) {
|
||||
return bo, nil
|
||||
}
|
||||
|
||||
func defaultTarget() Target {
|
||||
return Target{}
|
||||
func defaultTarget() *Target {
|
||||
return &Target{}
|
||||
}
|
||||
|
||||
func merge(t1, t2 Target) Target {
|
||||
func merge(t1, t2 *Target) *Target {
|
||||
if t2.Context != nil {
|
||||
t1.Context = t2.Context
|
||||
}
|
||||
if t2.Dockerfile != nil {
|
||||
t1.Dockerfile = t2.Dockerfile
|
||||
}
|
||||
if t2.DockerfileInline != nil {
|
||||
t1.DockerfileInline = t2.DockerfileInline
|
||||
}
|
||||
for k, v := range t2.Args {
|
||||
if t1.Args == nil {
|
||||
t1.Args = map[string]string{}
|
||||
@@ -383,6 +570,12 @@ func merge(t1, t2 Target) Target {
|
||||
if t2.Outputs != nil { // no merge
|
||||
t1.Outputs = t2.Outputs
|
||||
}
|
||||
if t2.Pull != nil {
|
||||
t1.Pull = t2.Pull
|
||||
}
|
||||
if t2.NoCache != nil {
|
||||
t1.NoCache = t2.NoCache
|
||||
}
|
||||
t1.Inherits = append(t1.Inherits, t2.Inherits...)
|
||||
return t1
|
||||
}
|
||||
@@ -394,6 +587,9 @@ func removeDupes(s []string) []string {
|
||||
if _, ok := seen[v]; ok {
|
||||
continue
|
||||
}
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
seen[v] = struct{}{}
|
||||
s[i] = v
|
||||
i++
|
||||
|
@@ -2,9 +2,7 @@ package bake
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -12,40 +10,182 @@ import (
|
||||
|
||||
func TestReadTargets(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmpdir, err := ioutil.TempDir("", "bake")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
fp := filepath.Join(tmpdir, "config.hcl")
|
||||
err = ioutil.WriteFile(fp, []byte(`
|
||||
target "dep" {
|
||||
fp := File{
|
||||
Name: "config.hcl",
|
||||
Data: []byte(`
|
||||
target "webDEP" {
|
||||
args = {
|
||||
VAR_INHERITED = "webDEP"
|
||||
VAR_BOTH = "webDEP"
|
||||
}
|
||||
no-cache = true
|
||||
}
|
||||
|
||||
target "webapp" {
|
||||
dockerfile = "Dockerfile.webapp"
|
||||
inherits = ["dep"]
|
||||
}`), 0600)
|
||||
require.NoError(t, err)
|
||||
args = {
|
||||
VAR_BOTH = "webapp"
|
||||
}
|
||||
inherits = ["webDEP"]
|
||||
}`),
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
m, err := ReadTargets(ctx, []string{fp}, []string{"webapp"}, nil)
|
||||
require.NoError(t, err)
|
||||
t.Run("NoOverrides", func(t *testing.T) {
|
||||
m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(m))
|
||||
|
||||
require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile)
|
||||
require.Equal(t, ".", *m["webapp"].Context)
|
||||
require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile)
|
||||
require.Equal(t, ".", *m["webapp"].Context)
|
||||
require.Equal(t, "webDEP", m["webapp"].Args["VAR_INHERITED"])
|
||||
require.Equal(t, true, *m["webapp"].NoCache)
|
||||
require.Nil(t, m["webapp"].Pull)
|
||||
})
|
||||
|
||||
t.Run("InvalidTargetOverrides", func(t *testing.T) {
|
||||
_, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"nosuchtarget.context=foo"})
|
||||
require.NotNil(t, err)
|
||||
require.Equal(t, err.Error(), "could not find any target matching 'nosuchtarget'")
|
||||
})
|
||||
|
||||
t.Run("ArgsOverrides", func(t *testing.T) {
|
||||
t.Run("leaf", func(t *testing.T) {
|
||||
os.Setenv("VAR_FROMENV"+t.Name(), "fromEnv")
|
||||
defer os.Unsetenv("VAR_FROM_ENV" + t.Name())
|
||||
|
||||
m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{
|
||||
"webapp.args.VAR_UNSET",
|
||||
"webapp.args.VAR_EMPTY=",
|
||||
"webapp.args.VAR_SET=bananas",
|
||||
"webapp.args.VAR_FROMENV" + t.Name(),
|
||||
"webapp.args.VAR_INHERITED=override",
|
||||
// not overriding VAR_BOTH on purpose
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile)
|
||||
require.Equal(t, ".", *m["webapp"].Context)
|
||||
|
||||
_, isSet := m["webapp"].Args["VAR_UNSET"]
|
||||
require.False(t, isSet, m["webapp"].Args["VAR_UNSET"])
|
||||
|
||||
_, isSet = m["webapp"].Args["VAR_EMPTY"]
|
||||
require.True(t, isSet, m["webapp"].Args["VAR_EMPTY"])
|
||||
|
||||
require.Equal(t, m["webapp"].Args["VAR_SET"], "bananas")
|
||||
|
||||
require.Equal(t, m["webapp"].Args["VAR_FROMENV"+t.Name()], "fromEnv")
|
||||
|
||||
require.Equal(t, m["webapp"].Args["VAR_BOTH"], "webapp")
|
||||
require.Equal(t, m["webapp"].Args["VAR_INHERITED"], "override")
|
||||
})
|
||||
|
||||
// building leaf but overriding parent fields
|
||||
t.Run("parent", func(t *testing.T) {
|
||||
m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{
|
||||
"webDEP.args.VAR_INHERITED=override",
|
||||
"webDEP.args.VAR_BOTH=override",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, m["webapp"].Args["VAR_INHERITED"], "override")
|
||||
require.Equal(t, m["webapp"].Args["VAR_BOTH"], "webapp")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("ContextOverride", func(t *testing.T) {
|
||||
_, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.context"})
|
||||
require.NotNil(t, err)
|
||||
|
||||
m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.context=foo"})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "foo", *m["webapp"].Context)
|
||||
})
|
||||
|
||||
t.Run("NoCacheOverride", func(t *testing.T) {
|
||||
m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.no-cache=false"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, *m["webapp"].NoCache)
|
||||
})
|
||||
|
||||
t.Run("PullOverride", func(t *testing.T) {
|
||||
m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.pull=false"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, *m["webapp"].Pull)
|
||||
})
|
||||
|
||||
t.Run("PatternOverride", func(t *testing.T) {
|
||||
// same check for two cases
|
||||
multiTargetCheck := func(t *testing.T, m map[string]*Target, err error) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(m))
|
||||
require.Equal(t, "foo", *m["webapp"].Dockerfile)
|
||||
require.Equal(t, "webDEP", m["webapp"].Args["VAR_INHERITED"])
|
||||
require.Equal(t, "foo", *m["webDEP"].Dockerfile)
|
||||
require.Equal(t, "webDEP", m["webDEP"].Args["VAR_INHERITED"])
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
targets []string
|
||||
overrides []string
|
||||
check func(*testing.T, map[string]*Target, error)
|
||||
}{
|
||||
{
|
||||
name: "multi target single pattern",
|
||||
targets: []string{"webapp", "webDEP"},
|
||||
overrides: []string{"web*.dockerfile=foo"},
|
||||
check: multiTargetCheck,
|
||||
},
|
||||
{
|
||||
name: "multi target multi pattern",
|
||||
targets: []string{"webapp", "webDEP"},
|
||||
overrides: []string{"web*.dockerfile=foo", "*.args.VAR_BOTH=bar"},
|
||||
check: multiTargetCheck,
|
||||
},
|
||||
{
|
||||
name: "single target",
|
||||
targets: []string{"webapp"},
|
||||
overrides: []string{"web*.dockerfile=foo"},
|
||||
check: func(t *testing.T, m map[string]*Target, err error) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(m))
|
||||
require.Equal(t, "foo", *m["webapp"].Dockerfile)
|
||||
require.Equal(t, "webDEP", m["webapp"].Args["VAR_INHERITED"])
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nomatch",
|
||||
targets: []string{"webapp"},
|
||||
overrides: []string{"nomatch*.dockerfile=foo"},
|
||||
check: func(t *testing.T, m map[string]*Target, err error) {
|
||||
// NOTE: I am unsure whether failing to match should always error out
|
||||
// instead of simply skipping that override.
|
||||
// Let's enforce the error and we can relax it later if users complain.
|
||||
require.NotNil(t, err)
|
||||
require.Equal(t, err.Error(), "could not find any target matching 'nomatch*'")
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range cases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
m, err := ReadTargets(ctx, []File{fp}, test.targets, test.overrides)
|
||||
test.check(t, m, err)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestReadTargetsCompose(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmpdir, err := ioutil.TempDir("", "bake")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
fp := filepath.Join(tmpdir, "docker-compose.yml")
|
||||
err = ioutil.WriteFile(fp, []byte(`
|
||||
version: "3"
|
||||
|
||||
fp := File{
|
||||
Name: "docker-compose.yml",
|
||||
Data: []byte(
|
||||
`version: "3"
|
||||
services:
|
||||
db:
|
||||
build: .
|
||||
@@ -56,14 +196,33 @@ services:
|
||||
dockerfile: Dockerfile.webapp
|
||||
args:
|
||||
buildno: 1
|
||||
`), 0600)
|
||||
require.NoError(t, err)
|
||||
`),
|
||||
}
|
||||
|
||||
fp2 := File{
|
||||
Name: "docker-compose2.yml",
|
||||
Data: []byte(
|
||||
`version: "3"
|
||||
services:
|
||||
newservice:
|
||||
build: .
|
||||
webapp:
|
||||
build:
|
||||
args:
|
||||
buildno2: 12
|
||||
`),
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
m, err := ReadTargets(ctx, []string{fp}, []string{"default"}, nil)
|
||||
m, err := ReadTargets(ctx, []File{fp, fp2}, []string{"default"}, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 3, len(m))
|
||||
_, ok := m["newservice"]
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile)
|
||||
require.Equal(t, ".", *m["webapp"].Context)
|
||||
require.Equal(t, "1", m["webapp"].Args["buildno"])
|
||||
require.Equal(t, "12", m["webapp"].Args["buildno2"])
|
||||
}
|
||||
|
@@ -2,7 +2,9 @@ package bake
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/compose/loader"
|
||||
composetypes "github.com/docker/cli/cli/compose/types"
|
||||
@@ -19,9 +21,22 @@ func parseCompose(dt []byte) (*composetypes.Config, error) {
|
||||
Config: parsed,
|
||||
},
|
||||
},
|
||||
Environment: envMap(os.Environ()),
|
||||
})
|
||||
}
|
||||
|
||||
func envMap(env []string) map[string]string {
|
||||
result := make(map[string]string, len(env))
|
||||
for _, s := range env {
|
||||
kv := strings.SplitN(s, "=", 2)
|
||||
if len(kv) != 2 {
|
||||
continue
|
||||
}
|
||||
result[kv[0]] = kv[1]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ParseCompose(dt []byte) (*Config, error) {
|
||||
cfg, err := parseCompose(dt)
|
||||
if err != nil {
|
||||
@@ -31,10 +46,10 @@ func ParseCompose(dt []byte) (*Config, error) {
|
||||
var c Config
|
||||
var zeroBuildConfig composetypes.BuildConfig
|
||||
if len(cfg.Services) > 0 {
|
||||
c.Group = map[string]Group{}
|
||||
c.Target = map[string]Target{}
|
||||
c.Groups = []*Group{}
|
||||
c.Targets = []*Target{}
|
||||
|
||||
var g Group
|
||||
g := &Group{Name: "default"}
|
||||
|
||||
for _, s := range cfg.Services {
|
||||
|
||||
@@ -57,7 +72,8 @@ func ParseCompose(dt []byte) (*Config, error) {
|
||||
dockerfilePathP = &dockerfilePath
|
||||
}
|
||||
g.Targets = append(g.Targets, s.Name)
|
||||
t := Target{
|
||||
t := &Target{
|
||||
Name: s.Name,
|
||||
Context: contextPathP,
|
||||
Dockerfile: dockerfilePathP,
|
||||
Labels: s.Build.Labels,
|
||||
@@ -72,9 +88,9 @@ func ParseCompose(dt []byte) (*Config, error) {
|
||||
if s.Image != "" {
|
||||
t.Tags = []string{s.Image}
|
||||
}
|
||||
c.Target[s.Name] = t
|
||||
c.Targets = append(c.Targets, t)
|
||||
}
|
||||
c.Group["default"] = g
|
||||
c.Groups = append(c.Groups, g)
|
||||
|
||||
}
|
||||
|
||||
@@ -86,6 +102,8 @@ func toMap(in composetypes.MappingWithEquals) map[string]string {
|
||||
for k, v := range in {
|
||||
if v != nil {
|
||||
m[k] = *v
|
||||
} else {
|
||||
m[k] = os.Getenv(k)
|
||||
}
|
||||
}
|
||||
return m
|
||||
|
@@ -27,17 +27,23 @@ services:
|
||||
c, err := ParseCompose(dt)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 1, len(c.Group))
|
||||
sort.Strings(c.Group["default"].Targets)
|
||||
require.Equal(t, []string{"db", "webapp"}, c.Group["default"].Targets)
|
||||
require.Equal(t, 1, len(c.Groups))
|
||||
require.Equal(t, c.Groups[0].Name, "default")
|
||||
sort.Strings(c.Groups[0].Targets)
|
||||
require.Equal(t, []string{"db", "webapp"}, c.Groups[0].Targets)
|
||||
|
||||
require.Equal(t, 2, len(c.Target))
|
||||
require.Equal(t, "./db", *c.Target["db"].Context)
|
||||
require.Equal(t, 2, len(c.Targets))
|
||||
sort.Slice(c.Targets, func(i, j int) bool {
|
||||
return c.Targets[i].Name < c.Targets[j].Name
|
||||
})
|
||||
require.Equal(t, "db", c.Targets[0].Name)
|
||||
require.Equal(t, "./db", *c.Targets[0].Context)
|
||||
|
||||
require.Equal(t, "./dir", *c.Target["webapp"].Context)
|
||||
require.Equal(t, "Dockerfile-alternate", *c.Target["webapp"].Dockerfile)
|
||||
require.Equal(t, 1, len(c.Target["webapp"].Args))
|
||||
require.Equal(t, "123", c.Target["webapp"].Args["buildno"])
|
||||
require.Equal(t, "webapp", c.Targets[1].Name)
|
||||
require.Equal(t, "./dir", *c.Targets[1].Context)
|
||||
require.Equal(t, "Dockerfile-alternate", *c.Targets[1].Dockerfile)
|
||||
require.Equal(t, 1, len(c.Targets[1].Args))
|
||||
require.Equal(t, "123", c.Targets[1].Args["buildno"])
|
||||
}
|
||||
|
||||
func TestNoBuildOutOfTreeService(t *testing.T) {
|
||||
@@ -52,7 +58,7 @@ services:
|
||||
`)
|
||||
c, err := ParseCompose(dt)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(c.Group))
|
||||
require.Equal(t, 1, len(c.Groups))
|
||||
}
|
||||
|
||||
func TestParseComposeTarget(t *testing.T) {
|
||||
@@ -73,8 +79,14 @@ services:
|
||||
c, err := ParseCompose(dt)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "db", *c.Target["db"].Target)
|
||||
require.Equal(t, "webapp", *c.Target["webapp"].Target)
|
||||
require.Equal(t, 2, len(c.Targets))
|
||||
sort.Slice(c.Targets, func(i, j int) bool {
|
||||
return c.Targets[i].Name < c.Targets[j].Name
|
||||
})
|
||||
require.Equal(t, "db", c.Targets[0].Name)
|
||||
require.Equal(t, "db", *c.Targets[0].Target)
|
||||
require.Equal(t, "webapp", c.Targets[1].Name)
|
||||
require.Equal(t, "webapp", *c.Targets[1].Target)
|
||||
}
|
||||
|
||||
func TestComposeBuildWithoutContext(t *testing.T) {
|
||||
@@ -93,8 +105,14 @@ services:
|
||||
|
||||
c, err := ParseCompose(dt)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "db", *c.Target["db"].Target)
|
||||
require.Equal(t, "webapp", *c.Target["webapp"].Target)
|
||||
require.Equal(t, 2, len(c.Targets))
|
||||
sort.Slice(c.Targets, func(i, j int) bool {
|
||||
return c.Targets[i].Name < c.Targets[j].Name
|
||||
})
|
||||
require.Equal(t, c.Targets[0].Name, "db")
|
||||
require.Equal(t, "db", *c.Targets[0].Target)
|
||||
require.Equal(t, c.Targets[1].Name, "webapp")
|
||||
require.Equal(t, "webapp", *c.Targets[1].Target)
|
||||
}
|
||||
|
||||
func TestBogusCompose(t *testing.T) {
|
||||
|
224
bake/hcl.go
224
bake/hcl.go
@@ -1,11 +1,229 @@
|
||||
package bake
|
||||
|
||||
import "github.com/hashicorp/hcl"
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
hcl "github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/ext/userfunc"
|
||||
"github.com/hashicorp/hcl/v2/hclsimple"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/hashicorp/hcl/v2/json"
|
||||
"github.com/moby/buildkit/solver/errdefs"
|
||||
"github.com/moby/buildkit/solver/pb"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
"github.com/zclconf/go-cty/cty/function/stdlib"
|
||||
)
|
||||
|
||||
// Collection of generally useful functions in cty-using applications, which
|
||||
// HCL supports. These functions are available for use in HCL files.
|
||||
var (
|
||||
stdlibFunctions = map[string]function.Function{
|
||||
"absolute": stdlib.AbsoluteFunc,
|
||||
"add": stdlib.AddFunc,
|
||||
"and": stdlib.AndFunc,
|
||||
"byteslen": stdlib.BytesLenFunc,
|
||||
"bytesslice": stdlib.BytesSliceFunc,
|
||||
"chomp": stdlib.ChompFunc,
|
||||
"chunklist": stdlib.ChunklistFunc,
|
||||
"ceil": stdlib.CeilFunc,
|
||||
"csvdecode": stdlib.CSVDecodeFunc,
|
||||
"coalesce": stdlib.CoalesceFunc,
|
||||
"coalescelist": stdlib.CoalesceListFunc,
|
||||
"compact": stdlib.CompactFunc,
|
||||
"concat": stdlib.ConcatFunc,
|
||||
"contains": stdlib.ContainsFunc,
|
||||
"distinct": stdlib.DistinctFunc,
|
||||
"divide": stdlib.DivideFunc,
|
||||
"element": stdlib.ElementFunc,
|
||||
"equal": stdlib.EqualFunc,
|
||||
"flatten": stdlib.FlattenFunc,
|
||||
"floor": stdlib.FloorFunc,
|
||||
"formatdate": stdlib.FormatDateFunc,
|
||||
"format": stdlib.FormatFunc,
|
||||
"formatlist": stdlib.FormatListFunc,
|
||||
"greaterthan": stdlib.GreaterThanFunc,
|
||||
"greaterthanorequalto": stdlib.GreaterThanOrEqualToFunc,
|
||||
"hasindex": stdlib.HasIndexFunc,
|
||||
"indent": stdlib.IndentFunc,
|
||||
"index": stdlib.IndexFunc,
|
||||
"int": stdlib.IntFunc,
|
||||
"jsondecode": stdlib.JSONDecodeFunc,
|
||||
"jsonencode": stdlib.JSONEncodeFunc,
|
||||
"keys": stdlib.KeysFunc,
|
||||
"join": stdlib.JoinFunc,
|
||||
"length": stdlib.LengthFunc,
|
||||
"lessthan": stdlib.LessThanFunc,
|
||||
"lessthanorequalto": stdlib.LessThanOrEqualToFunc,
|
||||
"log": stdlib.LogFunc,
|
||||
"lookup": stdlib.LookupFunc,
|
||||
"lower": stdlib.LowerFunc,
|
||||
"max": stdlib.MaxFunc,
|
||||
"merge": stdlib.MergeFunc,
|
||||
"min": stdlib.MinFunc,
|
||||
"modulo": stdlib.ModuloFunc,
|
||||
"multiply": stdlib.MultiplyFunc,
|
||||
"negate": stdlib.NegateFunc,
|
||||
"notequal": stdlib.NotEqualFunc,
|
||||
"not": stdlib.NotFunc,
|
||||
"or": stdlib.OrFunc,
|
||||
"parseint": stdlib.ParseIntFunc,
|
||||
"pow": stdlib.PowFunc,
|
||||
"range": stdlib.RangeFunc,
|
||||
"regexall": stdlib.RegexAllFunc,
|
||||
"regex": stdlib.RegexFunc,
|
||||
"reverse": stdlib.ReverseFunc,
|
||||
"reverselist": stdlib.ReverseListFunc,
|
||||
"sethaselement": stdlib.SetHasElementFunc,
|
||||
"setintersection": stdlib.SetIntersectionFunc,
|
||||
"setsubtract": stdlib.SetSubtractFunc,
|
||||
"setsymmetricdifference": stdlib.SetSymmetricDifferenceFunc,
|
||||
"setunion": stdlib.SetUnionFunc,
|
||||
"signum": stdlib.SignumFunc,
|
||||
"slice": stdlib.SliceFunc,
|
||||
"sort": stdlib.SortFunc,
|
||||
"split": stdlib.SplitFunc,
|
||||
"strlen": stdlib.StrlenFunc,
|
||||
"substr": stdlib.SubstrFunc,
|
||||
"subtract": stdlib.SubtractFunc,
|
||||
"timeadd": stdlib.TimeAddFunc,
|
||||
"title": stdlib.TitleFunc,
|
||||
"trim": stdlib.TrimFunc,
|
||||
"trimprefix": stdlib.TrimPrefixFunc,
|
||||
"trimspace": stdlib.TrimSpaceFunc,
|
||||
"trimsuffix": stdlib.TrimSuffixFunc,
|
||||
"upper": stdlib.UpperFunc,
|
||||
"values": stdlib.ValuesFunc,
|
||||
"zipmap": stdlib.ZipmapFunc,
|
||||
}
|
||||
)
|
||||
|
||||
// Used in the first pass of decoding instead of the Config struct to disallow
|
||||
// interpolation while parsing variable blocks.
|
||||
type staticConfig struct {
|
||||
Variables []*Variable `hcl:"variable,block"`
|
||||
Remain hcl.Body `hcl:",remain"`
|
||||
}
|
||||
|
||||
func ParseHCL(dt []byte, fn string) (_ *Config, err error) {
|
||||
if strings.HasSuffix(fn, ".json") || strings.HasSuffix(fn, ".hcl") {
|
||||
return parseHCL(dt, fn)
|
||||
}
|
||||
cfg, err := parseHCL(dt, fn+".hcl")
|
||||
if err != nil {
|
||||
cfg2, err2 := parseHCL(dt, fn+".json")
|
||||
if err2 == nil {
|
||||
return cfg2, nil
|
||||
}
|
||||
}
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func parseHCL(dt []byte, fn string) (_ *Config, err error) {
|
||||
defer func() {
|
||||
err = formatHCLError(dt, err)
|
||||
}()
|
||||
|
||||
// Decode user defined functions, first parsing as hcl and falling back to
|
||||
// json, returning errors based on the file suffix.
|
||||
file, hcldiags := hclsyntax.ParseConfig(dt, fn, hcl.Pos{Line: 1, Column: 1})
|
||||
if hcldiags.HasErrors() {
|
||||
var jsondiags hcl.Diagnostics
|
||||
file, jsondiags = json.Parse(dt, fn)
|
||||
if jsondiags.HasErrors() {
|
||||
fnl := strings.ToLower(fn)
|
||||
if strings.HasSuffix(fnl, ".json") {
|
||||
return nil, jsondiags
|
||||
} else {
|
||||
return nil, hcldiags
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
userFunctions, _, diags := userfunc.DecodeUserFunctions(file.Body, "function", func() *hcl.EvalContext {
|
||||
return &hcl.EvalContext{
|
||||
Functions: stdlibFunctions,
|
||||
}
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
var sc staticConfig
|
||||
|
||||
// Decode only variable blocks without interpolation.
|
||||
if err := hclsimple.Decode(fn, dt, nil, &sc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set all variables to their default value if defined.
|
||||
variables := make(map[string]cty.Value)
|
||||
for _, variable := range sc.Variables {
|
||||
variables[variable.Name] = cty.StringVal(variable.Default)
|
||||
}
|
||||
|
||||
// Override default with values from environment.
|
||||
for _, env := range os.Environ() {
|
||||
parts := strings.SplitN(env, "=", 2)
|
||||
name, value := parts[0], parts[1]
|
||||
if _, ok := variables[name]; ok {
|
||||
variables[name] = cty.StringVal(value)
|
||||
}
|
||||
}
|
||||
|
||||
functions := make(map[string]function.Function)
|
||||
for k, v := range stdlibFunctions {
|
||||
functions[k] = v
|
||||
}
|
||||
for k, v := range userFunctions {
|
||||
functions[k] = v
|
||||
}
|
||||
|
||||
ctx := &hcl.EvalContext{
|
||||
Variables: variables,
|
||||
Functions: functions,
|
||||
}
|
||||
|
||||
func ParseHCL(dt []byte) (*Config, error) {
|
||||
var c Config
|
||||
if err := hcl.Unmarshal(dt, &c); err != nil {
|
||||
|
||||
// Decode with variables and functions.
|
||||
if err := hclsimple.Decode(fn, dt, ctx, &c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func formatHCLError(dt []byte, err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
diags, ok := err.(hcl.Diagnostics)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
for _, d := range diags {
|
||||
if d.Severity != hcl.DiagError {
|
||||
continue
|
||||
}
|
||||
if d.Subject != nil {
|
||||
src := errdefs.Source{
|
||||
Info: &pb.SourceInfo{
|
||||
Filename: d.Subject.Filename,
|
||||
Data: dt,
|
||||
},
|
||||
Ranges: []*pb.Range{toErrRange(d.Subject)},
|
||||
}
|
||||
err = errdefs.WithSource(err, src)
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func toErrRange(in *hcl.Range) *pb.Range {
|
||||
return &pb.Range{
|
||||
Start: pb.Position{Line: int32(in.Start.Line), Character: int32(in.Start.Column)},
|
||||
End: pb.Position{Line: int32(in.End.Line), Character: int32(in.End.Column)},
|
||||
}
|
||||
}
|
||||
|
272
bake/hcl_test.go
272
bake/hcl_test.go
@@ -1,57 +1,253 @@
|
||||
package bake
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseHCL(t *testing.T) {
|
||||
var dt = []byte(`
|
||||
group "default" {
|
||||
targets = ["db", "webapp"]
|
||||
}
|
||||
t.Parallel()
|
||||
|
||||
target "db" {
|
||||
context = "./db"
|
||||
tags = ["docker.io/tonistiigi/db"]
|
||||
}
|
||||
|
||||
target "webapp" {
|
||||
context = "./dir"
|
||||
dockerfile = "Dockerfile-alternate"
|
||||
args = {
|
||||
buildno = "123"
|
||||
t.Run("Basic", func(t *testing.T) {
|
||||
dt := []byte(`
|
||||
group "default" {
|
||||
targets = ["db", "webapp"]
|
||||
}
|
||||
}
|
||||
|
||||
target "cross" {
|
||||
platforms = [
|
||||
"linux/amd64",
|
||||
"linux/arm64"
|
||||
]
|
||||
}
|
||||
|
||||
target "webapp-plus" {
|
||||
inherits = ["webapp", "cross"]
|
||||
args = {
|
||||
IAMCROSS = "true"
|
||||
target "db" {
|
||||
context = "./db"
|
||||
tags = ["docker.io/tonistiigi/db"]
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
c, err := ParseHCL(dt)
|
||||
require.NoError(t, err)
|
||||
target "webapp" {
|
||||
context = "./dir"
|
||||
dockerfile = "Dockerfile-alternate"
|
||||
args = {
|
||||
buildno = "123"
|
||||
}
|
||||
}
|
||||
|
||||
require.Equal(t, 1, len(c.Group))
|
||||
require.Equal(t, []string{"db", "webapp"}, c.Group["default"].Targets)
|
||||
target "cross" {
|
||||
platforms = [
|
||||
"linux/amd64",
|
||||
"linux/arm64"
|
||||
]
|
||||
}
|
||||
|
||||
require.Equal(t, 4, len(c.Target))
|
||||
require.Equal(t, "./db", *c.Target["db"].Context)
|
||||
target "webapp-plus" {
|
||||
inherits = ["webapp", "cross"]
|
||||
args = {
|
||||
IAMCROSS = "true"
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
require.Equal(t, 1, len(c.Target["webapp"].Args))
|
||||
require.Equal(t, "123", c.Target["webapp"].Args["buildno"])
|
||||
c, err := ParseHCL(dt, "docker-bake.hcl")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 2, len(c.Target["cross"].Platforms))
|
||||
require.Equal(t, []string{"linux/amd64", "linux/arm64"}, c.Target["cross"].Platforms)
|
||||
require.Equal(t, 1, len(c.Groups))
|
||||
require.Equal(t, "default", c.Groups[0].Name)
|
||||
require.Equal(t, []string{"db", "webapp"}, c.Groups[0].Targets)
|
||||
|
||||
require.Equal(t, 4, len(c.Targets))
|
||||
require.Equal(t, c.Targets[0].Name, "db")
|
||||
require.Equal(t, "./db", *c.Targets[0].Context)
|
||||
|
||||
require.Equal(t, c.Targets[1].Name, "webapp")
|
||||
require.Equal(t, 1, len(c.Targets[1].Args))
|
||||
require.Equal(t, "123", c.Targets[1].Args["buildno"])
|
||||
|
||||
require.Equal(t, c.Targets[2].Name, "cross")
|
||||
require.Equal(t, 2, len(c.Targets[2].Platforms))
|
||||
require.Equal(t, []string{"linux/amd64", "linux/arm64"}, c.Targets[2].Platforms)
|
||||
|
||||
require.Equal(t, c.Targets[3].Name, "webapp-plus")
|
||||
require.Equal(t, 1, len(c.Targets[3].Args))
|
||||
require.Equal(t, map[string]string{"IAMCROSS": "true"}, c.Targets[3].Args)
|
||||
})
|
||||
|
||||
t.Run("BasicInJSON", func(t *testing.T) {
|
||||
dt := []byte(`
|
||||
{
|
||||
"group": {
|
||||
"default": {
|
||||
"targets": ["db", "webapp"]
|
||||
}
|
||||
},
|
||||
"target": {
|
||||
"db": {
|
||||
"context": "./db",
|
||||
"tags": ["docker.io/tonistiigi/db"]
|
||||
},
|
||||
"webapp": {
|
||||
"context": "./dir",
|
||||
"dockerfile": "Dockerfile-alternate",
|
||||
"args": {
|
||||
"buildno": "123"
|
||||
}
|
||||
},
|
||||
"cross": {
|
||||
"platforms": [
|
||||
"linux/amd64",
|
||||
"linux/arm64"
|
||||
]
|
||||
},
|
||||
"webapp-plus": {
|
||||
"inherits": ["webapp", "cross"],
|
||||
"args": {
|
||||
"IAMCROSS": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
c, err := ParseHCL(dt, "docker-bake.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 1, len(c.Groups))
|
||||
require.Equal(t, "default", c.Groups[0].Name)
|
||||
require.Equal(t, []string{"db", "webapp"}, c.Groups[0].Targets)
|
||||
|
||||
require.Equal(t, 4, len(c.Targets))
|
||||
require.Equal(t, c.Targets[0].Name, "db")
|
||||
require.Equal(t, "./db", *c.Targets[0].Context)
|
||||
|
||||
require.Equal(t, c.Targets[1].Name, "webapp")
|
||||
require.Equal(t, 1, len(c.Targets[1].Args))
|
||||
require.Equal(t, "123", c.Targets[1].Args["buildno"])
|
||||
|
||||
require.Equal(t, c.Targets[2].Name, "cross")
|
||||
require.Equal(t, 2, len(c.Targets[2].Platforms))
|
||||
require.Equal(t, []string{"linux/amd64", "linux/arm64"}, c.Targets[2].Platforms)
|
||||
|
||||
require.Equal(t, c.Targets[3].Name, "webapp-plus")
|
||||
require.Equal(t, 1, len(c.Targets[3].Args))
|
||||
require.Equal(t, map[string]string{"IAMCROSS": "true"}, c.Targets[3].Args)
|
||||
})
|
||||
|
||||
t.Run("WithFunctions", func(t *testing.T) {
|
||||
dt := []byte(`
|
||||
group "default" {
|
||||
targets = ["webapp"]
|
||||
}
|
||||
|
||||
target "webapp" {
|
||||
args = {
|
||||
buildno = "${add(123, 1)}"
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
c, err := ParseHCL(dt, "docker-bake.hcl")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 1, len(c.Groups))
|
||||
require.Equal(t, "default", c.Groups[0].Name)
|
||||
require.Equal(t, []string{"webapp"}, c.Groups[0].Targets)
|
||||
|
||||
require.Equal(t, 1, len(c.Targets))
|
||||
require.Equal(t, c.Targets[0].Name, "webapp")
|
||||
require.Equal(t, "124", c.Targets[0].Args["buildno"])
|
||||
})
|
||||
|
||||
t.Run("WithUserDefinedFunctions", func(t *testing.T) {
|
||||
dt := []byte(`
|
||||
function "increment" {
|
||||
params = [number]
|
||||
result = number + 1
|
||||
}
|
||||
|
||||
group "default" {
|
||||
targets = ["webapp"]
|
||||
}
|
||||
|
||||
target "webapp" {
|
||||
args = {
|
||||
buildno = "${increment(123)}"
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
c, err := ParseHCL(dt, "docker-bake.hcl")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 1, len(c.Groups))
|
||||
require.Equal(t, "default", c.Groups[0].Name)
|
||||
require.Equal(t, []string{"webapp"}, c.Groups[0].Targets)
|
||||
|
||||
require.Equal(t, 1, len(c.Targets))
|
||||
require.Equal(t, c.Targets[0].Name, "webapp")
|
||||
require.Equal(t, "124", c.Targets[0].Args["buildno"])
|
||||
})
|
||||
|
||||
t.Run("WithVariables", func(t *testing.T) {
|
||||
dt := []byte(`
|
||||
variable "BUILD_NUMBER" {
|
||||
default = "123"
|
||||
}
|
||||
|
||||
group "default" {
|
||||
targets = ["webapp"]
|
||||
}
|
||||
|
||||
target "webapp" {
|
||||
args = {
|
||||
buildno = "${BUILD_NUMBER}"
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
c, err := ParseHCL(dt, "docker-bake.hcl")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 1, len(c.Groups))
|
||||
require.Equal(t, "default", c.Groups[0].Name)
|
||||
require.Equal(t, []string{"webapp"}, c.Groups[0].Targets)
|
||||
|
||||
require.Equal(t, 1, len(c.Targets))
|
||||
require.Equal(t, c.Targets[0].Name, "webapp")
|
||||
require.Equal(t, "123", c.Targets[0].Args["buildno"])
|
||||
|
||||
os.Setenv("BUILD_NUMBER", "456")
|
||||
|
||||
c, err = ParseHCL(dt, "docker-bake.hcl")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 1, len(c.Groups))
|
||||
require.Equal(t, "default", c.Groups[0].Name)
|
||||
require.Equal(t, []string{"webapp"}, c.Groups[0].Targets)
|
||||
|
||||
require.Equal(t, 1, len(c.Targets))
|
||||
require.Equal(t, c.Targets[0].Name, "webapp")
|
||||
require.Equal(t, "456", c.Targets[0].Args["buildno"])
|
||||
})
|
||||
|
||||
t.Run("WithIncorrectVariables", func(t *testing.T) {
|
||||
dt := []byte(`
|
||||
variable "DEFAULT_BUILD_NUMBER" {
|
||||
default = "1"
|
||||
}
|
||||
|
||||
variable "BUILD_NUMBER" {
|
||||
default = "${DEFAULT_BUILD_NUMBER}"
|
||||
}
|
||||
|
||||
group "default" {
|
||||
targets = ["webapp"]
|
||||
}
|
||||
|
||||
target "webapp" {
|
||||
args = {
|
||||
buildno = "${BUILD_NUMBER}"
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
_, err := ParseHCL(dt, "docker-bake.hcl")
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "docker-bake.hcl:7,17-37: Variables not allowed; Variables may not be used here.")
|
||||
})
|
||||
}
|
||||
|
236
bake/remote.go
Normal file
236
bake/remote.go
Normal file
@@ -0,0 +1,236 @@
|
||||
package bake
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/driver"
|
||||
"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/pkg/errors"
|
||||
)
|
||||
|
||||
type Input struct {
|
||||
State *llb.State
|
||||
URL string
|
||||
}
|
||||
|
||||
func ReadRemoteFiles(ctx context.Context, dis []build.DriverInfo, url string, names []string, pw progress.Writer) ([]File, *Input, error) {
|
||||
st, filename, ok := detectHttpContext(url)
|
||||
if !ok {
|
||||
st, ok = detectGitContext(url)
|
||||
if !ok {
|
||||
return nil, nil, errors.Errorf("not url context")
|
||||
}
|
||||
}
|
||||
|
||||
inp := &Input{State: st, URL: url}
|
||||
var files []File
|
||||
|
||||
var di *build.DriverInfo
|
||||
for _, d := range dis {
|
||||
if d.Err == nil {
|
||||
di = &d
|
||||
continue
|
||||
}
|
||||
}
|
||||
if di == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
c, err := driver.Boot(ctx, di.Driver, pw)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
ch, done := progress.NewChannel(pw)
|
||||
defer func() { <-done }()
|
||||
_, err = c.Build(ctx, client.SolveOpt{}, "buildx", func(ctx context.Context, c gwclient.Client) (*gwclient.Result, error) {
|
||||
def, err := st.Marshal(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := c.Solve(ctx, gwclient.SolveRequest{
|
||||
Definition: def.ToPB(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ref, err := res.SingleRef()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if filename != "" {
|
||||
files, err = filesFromURLRef(ctx, c, ref, inp, filename, names)
|
||||
} else {
|
||||
files, err = filesFromRef(ctx, ref, names)
|
||||
}
|
||||
return nil, err
|
||||
}, ch)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return files, inp, nil
|
||||
}
|
||||
|
||||
func IsRemoteURL(url string) bool {
|
||||
if _, _, ok := detectHttpContext(url); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := detectGitContext(url); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func detectHttpContext(url string) (*llb.State, string, bool) {
|
||||
if httpPrefix.MatchString(url) {
|
||||
httpContext := llb.HTTP(url, llb.Filename("context"), llb.WithCustomName("[internal] load remote build context"))
|
||||
return &httpContext, "context", true
|
||||
}
|
||||
return nil, "", false
|
||||
}
|
||||
|
||||
func detectGitContext(ref string) (*llb.State, bool) {
|
||||
found := false
|
||||
if httpPrefix.MatchString(ref) && gitURLPathWithFragmentSuffix.MatchString(ref) {
|
||||
found = true
|
||||
}
|
||||
|
||||
for _, prefix := range []string{"git://", "github.com/", "git@"} {
|
||||
if strings.HasPrefix(ref, prefix) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
parts := strings.SplitN(ref, "#", 2)
|
||||
branch := ""
|
||||
if len(parts) > 1 {
|
||||
branch = parts[1]
|
||||
}
|
||||
gitOpts := []llb.GitOption{llb.WithCustomName("[internal] load git source " + ref)}
|
||||
|
||||
st := llb.Git(parts[0], branch, gitOpts...)
|
||||
return &st, true
|
||||
}
|
||||
|
||||
func isArchive(header []byte) bool {
|
||||
for _, m := range [][]byte{
|
||||
{0x42, 0x5A, 0x68}, // bzip2
|
||||
{0x1F, 0x8B, 0x08}, // gzip
|
||||
{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}, // xz
|
||||
} {
|
||||
if len(header) < len(m) {
|
||||
continue
|
||||
}
|
||||
if bytes.Equal(m, header[:len(m)]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
r := tar.NewReader(bytes.NewBuffer(header))
|
||||
_, err := r.Next()
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func filesFromURLRef(ctx context.Context, c gwclient.Client, ref gwclient.Reference, inp *Input, filename string, names []string) ([]File, error) {
|
||||
stat, err := ref.StatFile(ctx, gwclient.StatRequest{Path: filename})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dt, err := ref.ReadFile(ctx, gwclient.ReadRequest{
|
||||
Filename: filename,
|
||||
Range: &gwclient.FileRange{
|
||||
Length: 1024,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if isArchive(dt) {
|
||||
bc := llb.Scratch().File(llb.Copy(inp.State, filename, "/", &llb.CopyInfo{
|
||||
AttemptUnpack: true,
|
||||
}))
|
||||
inp.State = &bc
|
||||
inp.URL = ""
|
||||
def, err := bc.Marshal(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := c.Solve(ctx, gwclient.SolveRequest{
|
||||
Definition: def.ToPB(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ref, err := res.SingleRef()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return filesFromRef(ctx, ref, names)
|
||||
}
|
||||
|
||||
inp.State = nil
|
||||
name := inp.URL
|
||||
inp.URL = ""
|
||||
|
||||
if len(dt) > stat.Size() {
|
||||
if stat.Size() > 1024*512 {
|
||||
return nil, errors.Errorf("non-archive definition URL bigger than maximum allowed size")
|
||||
}
|
||||
|
||||
dt, err = ref.ReadFile(ctx, gwclient.ReadRequest{
|
||||
Filename: filename,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return []File{{Name: name, Data: dt}}, nil
|
||||
}
|
||||
|
||||
func filesFromRef(ctx context.Context, ref gwclient.Reference, names []string) ([]File, error) {
|
||||
// TODO: auto-remove parent dir in needed
|
||||
var files []File
|
||||
|
||||
isDefault := false
|
||||
if len(names) == 0 {
|
||||
isDefault = true
|
||||
names = defaultFilenames()
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
_, err := ref.StatFile(ctx, gwclient.StatRequest{Path: name})
|
||||
if err != nil {
|
||||
if isDefault {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
dt, err := ref.ReadFile(ctx, gwclient.ReadRequest{Filename: name})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files = append(files, File{Name: name, Data: dt})
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
383
build/build.go
383
build/build.go
@@ -3,6 +3,7 @@ package build
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
@@ -19,11 +21,16 @@ import (
|
||||
"github.com/docker/buildx/util/progress"
|
||||
clitypes "github.com/docker/cli/cli/config/types"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/pkg/urlutil"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/session"
|
||||
"github.com/moby/buildkit/session/upload/uploadprovider"
|
||||
"github.com/moby/buildkit/util/entitlements"
|
||||
"github.com/moby/buildkit/util/progress/progresswriter"
|
||||
"github.com/opencontainers/go-digest"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
@@ -55,13 +62,16 @@ type Options struct {
|
||||
CacheFrom []client.CacheOptionsEntry
|
||||
CacheTo []client.CacheOptionsEntry
|
||||
|
||||
Allow []entitlements.Entitlement
|
||||
// DockerTarget
|
||||
}
|
||||
|
||||
type Inputs struct {
|
||||
ContextPath string
|
||||
DockerfilePath string
|
||||
InStream io.Reader
|
||||
ContextPath string
|
||||
DockerfilePath string
|
||||
InStream io.Reader
|
||||
ContextState *llb.State
|
||||
DockerfileInline string
|
||||
}
|
||||
|
||||
type DriverInfo struct {
|
||||
@@ -170,8 +180,7 @@ func splitToDriverPairs(availablePlatforms map[string]int, opt map[string]Option
|
||||
return m
|
||||
}
|
||||
|
||||
func resolveDrivers(ctx context.Context, drivers []DriverInfo, opt map[string]Options, pw progress.Writer) (map[string][]driverPair, []*client.Client, error) {
|
||||
|
||||
func resolveDrivers(ctx context.Context, drivers []DriverInfo, auth Auth, opt map[string]Options, pw progress.Writer) (map[string][]driverPair, []*client.Client, error) {
|
||||
availablePlatforms := map[string]int{}
|
||||
for i, d := range drivers {
|
||||
for _, p := range d.Platform {
|
||||
@@ -276,14 +285,7 @@ func toRepoOnly(in string) (string, error) {
|
||||
return strings.Join(out, ","), nil
|
||||
}
|
||||
|
||||
func isDefaultMobyDriver(d driver.Driver) bool {
|
||||
_, ok := d.(interface {
|
||||
IsDefaultMobyDriver()
|
||||
})
|
||||
return ok
|
||||
}
|
||||
|
||||
func toSolveOpt(d driver.Driver, multiDriver bool, opt Options, dl dockerLoadCallback) (solveOpt *client.SolveOpt, release func(), err error) {
|
||||
func toSolveOpt(ctx context.Context, d driver.Driver, multiDriver bool, opt Options, pw progress.Writer, dl dockerLoadCallback) (solveOpt *client.SolveOpt, release func(), err error) {
|
||||
defers := make([]func(), 0, 2)
|
||||
releaseF := func() {
|
||||
for _, f := range defers {
|
||||
@@ -298,9 +300,6 @@ func toSolveOpt(d driver.Driver, multiDriver bool, opt Options, dl dockerLoadCal
|
||||
}()
|
||||
|
||||
if opt.ImageIDFile != "" {
|
||||
if multiDriver || len(opt.Platforms) != 0 {
|
||||
return nil, nil, errors.Errorf("image ID file cannot be specified when building for multiple platforms")
|
||||
}
|
||||
// Avoid leaving a stale file if we eventually fail
|
||||
if err := os.Remove(opt.ImageIDFile); err != nil && !os.IsNotExist(err) {
|
||||
return nil, nil, errors.Wrap(err, "removing image ID file")
|
||||
@@ -324,11 +323,18 @@ func toSolveOpt(d driver.Driver, multiDriver bool, opt Options, dl dockerLoadCal
|
||||
}
|
||||
|
||||
so := client.SolveOpt{
|
||||
Frontend: "dockerfile.v0",
|
||||
FrontendAttrs: map[string]string{},
|
||||
LocalDirs: map[string]string{},
|
||||
CacheExports: opt.CacheTo,
|
||||
CacheImports: opt.CacheFrom,
|
||||
Frontend: "dockerfile.v0",
|
||||
FrontendAttrs: map[string]string{},
|
||||
LocalDirs: map[string]string{},
|
||||
CacheExports: opt.CacheTo,
|
||||
CacheImports: opt.CacheFrom,
|
||||
AllowedEntitlements: opt.Allow,
|
||||
}
|
||||
|
||||
if v, ok := opt.BuildArgs["BUILDKIT_MULTI_PLATFORM"]; ok {
|
||||
if v, _ := strconv.ParseBool(v); v {
|
||||
so.FrontendAttrs["multi-platform"] = "true"
|
||||
}
|
||||
}
|
||||
|
||||
if multiDriver {
|
||||
@@ -336,15 +342,11 @@ func toSolveOpt(d driver.Driver, multiDriver bool, opt Options, dl dockerLoadCal
|
||||
so.FrontendAttrs["multi-platform"] = "true"
|
||||
}
|
||||
|
||||
_, isDefaultMobyDriver := d.(interface {
|
||||
IsDefaultMobyDriver()
|
||||
})
|
||||
|
||||
switch len(opt.Exports) {
|
||||
case 1:
|
||||
// valid
|
||||
case 0:
|
||||
if isDefaultMobyDriver {
|
||||
if d.IsMobyDriver() && !noDefaultLoad() {
|
||||
// backwards compat for docker driver only:
|
||||
// this ensures the build results in a docker image.
|
||||
opt.Exports = []client.ExportEntry{{Type: "image", Attrs: map[string]string{}}}
|
||||
@@ -379,6 +381,15 @@ func toSolveOpt(d driver.Driver, multiDriver bool, opt Options, dl dockerLoadCal
|
||||
}
|
||||
}
|
||||
|
||||
// cacheonly is a fake exporter to opt out of default behaviors
|
||||
exports := make([]client.ExportEntry, 0, len(opt.Exports))
|
||||
for _, e := range opt.Exports {
|
||||
if e.Type != "cacheonly" {
|
||||
exports = append(exports, e)
|
||||
}
|
||||
}
|
||||
opt.Exports = exports
|
||||
|
||||
// set up exporters
|
||||
for i, e := range opt.Exports {
|
||||
if (e.Type == "local" || e.Type == "tar") && opt.ImageIDFile != "" {
|
||||
@@ -389,7 +400,7 @@ func toSolveOpt(d driver.Driver, multiDriver bool, opt Options, dl dockerLoadCal
|
||||
}
|
||||
if e.Type == "docker" {
|
||||
if e.Output == nil {
|
||||
if isDefaultMobyDriver {
|
||||
if d.IsMobyDriver() {
|
||||
e.Type = "image"
|
||||
} else {
|
||||
w, cancel, err := dl(e.Attrs["context"])
|
||||
@@ -397,17 +408,19 @@ func toSolveOpt(d driver.Driver, multiDriver bool, opt Options, dl dockerLoadCal
|
||||
return nil, nil, err
|
||||
}
|
||||
defers = append(defers, cancel)
|
||||
opt.Exports[i].Output = w
|
||||
opt.Exports[i].Output = wrapWriteCloser(w)
|
||||
}
|
||||
} else if !d.Features()[driver.DockerExporter] {
|
||||
return nil, nil, notSupported(d, driver.DockerExporter)
|
||||
}
|
||||
}
|
||||
if e.Type == "image" && isDefaultMobyDriver {
|
||||
if e.Type == "image" && d.IsMobyDriver() {
|
||||
opt.Exports[i].Type = "moby"
|
||||
if e.Attrs["push"] != "" {
|
||||
if ok, _ := strconv.ParseBool(e.Attrs["push"]); ok {
|
||||
return nil, nil, errors.Errorf("auto-push is currently not implemented for docker driver")
|
||||
if ok, _ := strconv.ParseBool(e.Attrs["push-by-digest"]); ok {
|
||||
return nil, nil, errors.Errorf("push-by-digest is currently not implemented for docker driver, please create a new builder instance")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,7 +429,7 @@ func toSolveOpt(d driver.Driver, multiDriver bool, opt Options, dl dockerLoadCal
|
||||
so.Exports = opt.Exports
|
||||
so.Session = opt.Session
|
||||
|
||||
releaseLoad, err := LoadInputs(opt.Inputs, &so)
|
||||
releaseLoad, err := LoadInputs(ctx, d, opt.Inputs, pw, &so)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -454,6 +467,7 @@ func toSolveOpt(d driver.Driver, multiDriver bool, opt Options, dl dockerLoadCal
|
||||
switch opt.NetworkMode {
|
||||
case "host", "none":
|
||||
so.FrontendAttrs["force-network-mode"] = opt.NetworkMode
|
||||
so.AllowedEntitlements = append(so.AllowedEntitlements, entitlements.EntitlementNetworkHost)
|
||||
case "", "default":
|
||||
default:
|
||||
return nil, nil, errors.Errorf("network mode %q not supported by buildkit", opt.NetworkMode)
|
||||
@@ -469,7 +483,7 @@ func toSolveOpt(d driver.Driver, multiDriver bool, opt Options, dl dockerLoadCal
|
||||
return &so, releaseF, nil
|
||||
}
|
||||
|
||||
func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, docker DockerAPI, auth Auth, pw progress.Writer) (resp map[string]*client.SolveResponse, err error) {
|
||||
func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, docker DockerAPI, auth Auth, w progress.Writer) (resp map[string]*client.SolveResponse, err error) {
|
||||
if len(drivers) == 0 {
|
||||
return nil, errors.Errorf("driver required for build")
|
||||
}
|
||||
@@ -481,13 +495,13 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, do
|
||||
|
||||
var noMobyDriver driver.Driver
|
||||
for _, d := range drivers {
|
||||
if !isDefaultMobyDriver(d.Driver) {
|
||||
if !d.Driver.IsMobyDriver() {
|
||||
noMobyDriver = d.Driver
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if noMobyDriver != nil {
|
||||
if noMobyDriver != nil && !noDefaultLoad() {
|
||||
for _, opt := range opt {
|
||||
if len(opt.Exports) == 0 {
|
||||
logrus.Warnf("No output specified for %s driver. Build result will only remain in the build cache. To push result image into registry use --push or to load image into docker use --load", noMobyDriver.Factory().Name())
|
||||
@@ -496,10 +510,8 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, do
|
||||
}
|
||||
}
|
||||
|
||||
m, clients, err := resolveDrivers(ctx, drivers, opt, pw)
|
||||
m, clients, err := resolveDrivers(ctx, drivers, auth, opt, w)
|
||||
if err != nil {
|
||||
close(pw.Status())
|
||||
<-pw.Done()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -512,16 +524,19 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, do
|
||||
}
|
||||
}()
|
||||
|
||||
mw := progress.NewMultiWriter(pw)
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
for k, opt := range opt {
|
||||
multiDriver := len(m[k]) > 1
|
||||
hasMobyDriver := false
|
||||
for i, dp := range m[k] {
|
||||
d := drivers[dp.driverIndex].Driver
|
||||
if d.IsMobyDriver() {
|
||||
hasMobyDriver = true
|
||||
}
|
||||
opt.Platforms = dp.platforms
|
||||
so, release, err := toSolveOpt(d, multiDriver, opt, func(name string) (io.WriteCloser, func(), error) {
|
||||
return newDockerLoader(ctx, docker, name, mw)
|
||||
so, release, err := toSolveOpt(ctx, d, multiDriver, opt, w, func(name string) (io.WriteCloser, func(), error) {
|
||||
return newDockerLoader(ctx, docker, name, w)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -529,6 +544,28 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, do
|
||||
defers = append(defers, release)
|
||||
m[k][i].so = so
|
||||
}
|
||||
for _, at := range opt.Session {
|
||||
if s, ok := at.(interface {
|
||||
SetLogger(progresswriter.Logger)
|
||||
}); ok {
|
||||
s.SetLogger(func(s *client.SolveStatus) {
|
||||
w.Write(s)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// validate for multi-node push
|
||||
if hasMobyDriver && multiDriver {
|
||||
for _, dp := range m[k] {
|
||||
for _, e := range dp.so.Exports {
|
||||
if e.Type == "moby" {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resp = map[string]*client.SolveResponse{}
|
||||
@@ -537,7 +574,7 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, do
|
||||
multiTarget := len(opt) > 1
|
||||
|
||||
for k, opt := range opt {
|
||||
err := func() error {
|
||||
err := func(k string) error {
|
||||
opt := opt
|
||||
dps := m[k]
|
||||
multiDriver := len(m[k]) > 1
|
||||
@@ -549,8 +586,7 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, do
|
||||
var pushNames string
|
||||
|
||||
eg.Go(func() error {
|
||||
pw := mw.WithPrefix("default", false)
|
||||
defer close(pw.Status())
|
||||
pw := progress.WithPrefix(w, "default", false)
|
||||
wg.Wait()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -653,27 +689,43 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, do
|
||||
}
|
||||
|
||||
func(i int, dp driverPair, so client.SolveOpt) {
|
||||
pw := mw.WithPrefix(k, multiTarget)
|
||||
pw := progress.WithPrefix(w, k, multiTarget)
|
||||
|
||||
c := clients[dp.driverIndex]
|
||||
|
||||
var statusCh chan *client.SolveStatus
|
||||
if pw != nil {
|
||||
pw = progress.ResetTime(pw)
|
||||
statusCh = pw.Status()
|
||||
eg.Go(func() error {
|
||||
<-pw.Done()
|
||||
return pw.Err()
|
||||
})
|
||||
}
|
||||
pw = progress.ResetTime(pw)
|
||||
|
||||
eg.Go(func() error {
|
||||
defer wg.Done()
|
||||
rr, err := c.Solve(ctx, nil, so, statusCh)
|
||||
ch, done := progress.NewChannel(pw)
|
||||
defer func() { <-done }()
|
||||
rr, err := c.Solve(ctx, nil, so, ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res[i] = rr
|
||||
|
||||
d := drivers[dp.driverIndex].Driver
|
||||
if d.IsMobyDriver() {
|
||||
for _, e := range so.Exports {
|
||||
if e.Type == "moby" && e.Attrs["push"] != "" {
|
||||
if ok, _ := strconv.ParseBool(e.Attrs["push"]); ok {
|
||||
pushNames = e.Attrs["name"]
|
||||
if pushNames == "" {
|
||||
return errors.Errorf("tag is needed when pushing to registry")
|
||||
}
|
||||
pw := progress.ResetTime(pw)
|
||||
for _, name := range strings.Split(pushNames, ",") {
|
||||
if err := progress.Wrap(fmt.Sprintf("pushing %s with docker", name), pw.Write, func(l progress.SubLogger) error {
|
||||
return pushWithMoby(ctx, d, name, l)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -681,7 +733,7 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, do
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
}(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -694,6 +746,86 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, do
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func pushWithMoby(ctx context.Context, d driver.Driver, name string, l progress.SubLogger) error {
|
||||
api := d.Config().DockerAPI
|
||||
if api == nil {
|
||||
return errors.Errorf("invalid empty Docker API reference") // should never happen
|
||||
}
|
||||
creds, err := imagetools.RegistryAuthForRef(name, d.Config().Auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rc, err := api.ImagePush(ctx, name, types.ImagePushOptions{
|
||||
RegistryAuth: creds,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
started := map[string]*client.VertexStatus{}
|
||||
|
||||
defer func() {
|
||||
for _, st := range started {
|
||||
if st.Completed == nil {
|
||||
now := time.Now()
|
||||
st.Completed = &now
|
||||
l.SetStatus(st)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
dec := json.NewDecoder(rc)
|
||||
var parsedError error
|
||||
for {
|
||||
var jm jsonmessage.JSONMessage
|
||||
if err := dec.Decode(&jm); err != nil {
|
||||
if parsedError != nil {
|
||||
return parsedError
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
if jm.ID != "" {
|
||||
id := "pushing layer " + jm.ID
|
||||
st, ok := started[id]
|
||||
if !ok {
|
||||
if jm.Progress != nil || jm.Status == "Pushed" {
|
||||
now := time.Now()
|
||||
st = &client.VertexStatus{
|
||||
ID: id,
|
||||
Started: &now,
|
||||
}
|
||||
started[id] = st
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
st.Timestamp = time.Now()
|
||||
if jm.Progress != nil {
|
||||
st.Current = jm.Progress.Current
|
||||
st.Total = jm.Progress.Total
|
||||
}
|
||||
if jm.Error != nil {
|
||||
now := time.Now()
|
||||
st.Completed = &now
|
||||
}
|
||||
if jm.Status == "Pushed" {
|
||||
now := time.Now()
|
||||
st.Completed = &now
|
||||
st.Current = st.Total
|
||||
}
|
||||
l.SetStatus(st)
|
||||
}
|
||||
if jm.Error != nil {
|
||||
parsedError = jm.Error
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createTempDockerfile(r io.Reader) (string, error) {
|
||||
dir, err := ioutil.TempDir("", "dockerfile")
|
||||
if err != nil {
|
||||
@@ -710,7 +842,7 @@ func createTempDockerfile(r io.Reader) (string, error) {
|
||||
return dir, err
|
||||
}
|
||||
|
||||
func LoadInputs(inp Inputs, target *client.SolveOpt) (func(), error) {
|
||||
func LoadInputs(ctx context.Context, d driver.Driver, inp Inputs, pw progress.Writer, target *client.SolveOpt) (func(), error) {
|
||||
if inp.ContextPath == "" {
|
||||
return nil, errors.New("please specify build context (e.g. \".\" for the current directory)")
|
||||
}
|
||||
@@ -726,38 +858,45 @@ func LoadInputs(inp Inputs, target *client.SolveOpt) (func(), error) {
|
||||
)
|
||||
|
||||
switch {
|
||||
case inp.ContextState != nil:
|
||||
if target.FrontendInputs == nil {
|
||||
target.FrontendInputs = make(map[string]llb.State)
|
||||
}
|
||||
target.FrontendInputs["context"] = *inp.ContextState
|
||||
target.FrontendInputs["dockerfile"] = *inp.ContextState
|
||||
case inp.ContextPath == "-":
|
||||
if inp.DockerfilePath == "-" {
|
||||
return nil, errStdinConflict
|
||||
}
|
||||
|
||||
buf := bufio.NewReader(os.Stdin)
|
||||
buf := bufio.NewReader(inp.InStream)
|
||||
magic, err := buf.Peek(archiveHeaderSize * 2)
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, errors.Wrap(err, "failed to peek context header from STDIN")
|
||||
}
|
||||
|
||||
if isArchive(magic) {
|
||||
// stdin is context
|
||||
up := uploadprovider.New()
|
||||
target.FrontendAttrs["context"] = up.Add(buf)
|
||||
target.Session = append(target.Session, up)
|
||||
} else {
|
||||
if inp.DockerfilePath != "" {
|
||||
return nil, errDockerfileConflict
|
||||
if !(err == io.EOF && len(magic) == 0) {
|
||||
if isArchive(magic) {
|
||||
// stdin is context
|
||||
up := uploadprovider.New()
|
||||
target.FrontendAttrs["context"] = up.Add(buf)
|
||||
target.Session = append(target.Session, up)
|
||||
} else {
|
||||
if inp.DockerfilePath != "" {
|
||||
return nil, errDockerfileConflict
|
||||
}
|
||||
// stdin is dockerfile
|
||||
dockerfileReader = buf
|
||||
inp.ContextPath, _ = ioutil.TempDir("", "empty-dir")
|
||||
toRemove = append(toRemove, inp.ContextPath)
|
||||
target.LocalDirs["context"] = inp.ContextPath
|
||||
}
|
||||
// stdin is dockerfile
|
||||
dockerfileReader = buf
|
||||
inp.ContextPath, _ = ioutil.TempDir("", "empty-dir")
|
||||
toRemove = append(toRemove, inp.ContextPath)
|
||||
target.LocalDirs["context"] = inp.ContextPath
|
||||
}
|
||||
|
||||
case isLocalDir(inp.ContextPath):
|
||||
target.LocalDirs["context"] = inp.ContextPath
|
||||
switch inp.DockerfilePath {
|
||||
case "-":
|
||||
dockerfileReader = os.Stdin
|
||||
dockerfileReader = inp.InStream
|
||||
case "":
|
||||
dockerfileDir = inp.ContextPath
|
||||
default:
|
||||
@@ -774,23 +913,41 @@ func LoadInputs(inp Inputs, target *client.SolveOpt) (func(), error) {
|
||||
return nil, errors.Errorf("unable to prepare context: path %q not found", inp.ContextPath)
|
||||
}
|
||||
|
||||
if inp.DockerfileInline != "" {
|
||||
dockerfileReader = strings.NewReader(inp.DockerfileInline)
|
||||
}
|
||||
|
||||
if dockerfileReader != nil {
|
||||
dockerfileDir, err = createTempDockerfile(dockerfileReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
toRemove = append(toRemove, dockerfileDir)
|
||||
dockerfileName = "Dockerfile"
|
||||
target.FrontendAttrs["dockerfilekey"] = "dockerfile"
|
||||
}
|
||||
if urlutil.IsURL(inp.DockerfilePath) {
|
||||
dockerfileDir, err = createTempDockerfileFromURL(ctx, d, inp.DockerfilePath, pw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
toRemove = append(toRemove, dockerfileDir)
|
||||
dockerfileName = "Dockerfile"
|
||||
target.FrontendAttrs["dockerfilekey"] = "dockerfile"
|
||||
delete(target.FrontendInputs, "dockerfile")
|
||||
}
|
||||
|
||||
if dockerfileName == "" {
|
||||
dockerfileName = "Dockerfile"
|
||||
}
|
||||
target.FrontendAttrs["filename"] = dockerfileName
|
||||
|
||||
if dockerfileDir != "" {
|
||||
target.LocalDirs["dockerfile"] = dockerfileDir
|
||||
dockerfileName = handleLowercaseDockerfile(dockerfileDir, dockerfileName)
|
||||
}
|
||||
|
||||
target.FrontendAttrs["filename"] = dockerfileName
|
||||
|
||||
release := func() {
|
||||
for _, dir := range toRemove {
|
||||
os.RemoveAll(dir)
|
||||
@@ -805,40 +962,60 @@ func notSupported(d driver.Driver, f driver.Feature) error {
|
||||
|
||||
type dockerLoadCallback func(name string) (io.WriteCloser, func(), error)
|
||||
|
||||
func newDockerLoader(ctx context.Context, d DockerAPI, name string, mw *progress.MultiWriter) (io.WriteCloser, func(), error) {
|
||||
func newDockerLoader(ctx context.Context, d DockerAPI, name string, status progress.Writer) (io.WriteCloser, func(), error) {
|
||||
c, err := d.DockerAPI(name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pr, pw := io.Pipe()
|
||||
started := make(chan struct{})
|
||||
w := &waitingWriter{
|
||||
done := make(chan struct{})
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
var w *waitingWriter
|
||||
w = &waitingWriter{
|
||||
PipeWriter: pw,
|
||||
f: func() {
|
||||
resp, err := c.ImageLoad(ctx, pr, false)
|
||||
defer close(done)
|
||||
if err != nil {
|
||||
pr.CloseWithError(err)
|
||||
w.mu.Lock()
|
||||
w.err = err
|
||||
w.mu.Unlock()
|
||||
return
|
||||
}
|
||||
prog := mw.WithPrefix("", false)
|
||||
close(started)
|
||||
prog := progress.WithPrefix(status, "", false)
|
||||
progress.FromReader(prog, "importing to docker", resp.Body)
|
||||
},
|
||||
started: started,
|
||||
done: done,
|
||||
cancel: cancel,
|
||||
}
|
||||
return w, func() {
|
||||
pr.Close()
|
||||
}, nil
|
||||
}
|
||||
|
||||
func noDefaultLoad() bool {
|
||||
v, ok := os.LookupEnv("BUILDX_NO_DEFAULT_LOAD")
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
b, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
logrus.Warnf("invalid non-bool value for BUILDX_NO_DEFAULT_LOAD: %s", v)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
type waitingWriter struct {
|
||||
*io.PipeWriter
|
||||
f func()
|
||||
once sync.Once
|
||||
mu sync.Mutex
|
||||
err error
|
||||
started chan struct{}
|
||||
f func()
|
||||
once sync.Once
|
||||
mu sync.Mutex
|
||||
err error
|
||||
done chan struct{}
|
||||
cancel func()
|
||||
}
|
||||
|
||||
func (w *waitingWriter) Write(dt []byte) (int, error) {
|
||||
@@ -850,6 +1027,42 @@ func (w *waitingWriter) Write(dt []byte) (int, error) {
|
||||
|
||||
func (w *waitingWriter) Close() error {
|
||||
err := w.PipeWriter.Close()
|
||||
<-w.started
|
||||
<-w.done
|
||||
if err == nil {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
return w.err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// handle https://github.com/moby/moby/pull/10858
|
||||
func handleLowercaseDockerfile(dir, p string) string {
|
||||
if filepath.Base(p) != "Dockerfile" {
|
||||
return p
|
||||
}
|
||||
|
||||
f, err := os.Open(filepath.Dir(filepath.Join(dir, p)))
|
||||
if err != nil {
|
||||
return p
|
||||
}
|
||||
|
||||
names, err := f.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return p
|
||||
}
|
||||
|
||||
foundLowerCase := false
|
||||
for _, n := range names {
|
||||
if n == "Dockerfile" {
|
||||
return p
|
||||
}
|
||||
if n == "dockerfile" {
|
||||
foundLowerCase = true
|
||||
}
|
||||
}
|
||||
if foundLowerCase {
|
||||
return filepath.Join(filepath.Dir(p), "dockerfile")
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
21
build/entitlements.go
Normal file
21
build/entitlements.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"github.com/moby/buildkit/util/entitlements"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func ParseEntitlements(in []string) ([]entitlements.Entitlement, error) {
|
||||
out := make([]entitlements.Entitlement, 0, len(in))
|
||||
for _, v := range in {
|
||||
switch v {
|
||||
case "security.insecure":
|
||||
out = append(out, entitlements.EntitlementSecurityInsecure)
|
||||
case "network.host":
|
||||
out = append(out, entitlements.EntitlementNetworkHost)
|
||||
default:
|
||||
return nil, errors.Errorf("invalid entitlement: %v", v)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
@@ -2,6 +2,7 @@ package build
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@@ -81,7 +82,7 @@ func ParseOutputs(inp []string) ([]client.ExportEntry, error) {
|
||||
if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
|
||||
return nil, errors.Errorf("output file is required for %s exporter. refusing to write to console", out.Type)
|
||||
}
|
||||
out.Output = os.Stdout
|
||||
out.Output = wrapWriteCloser(os.Stdout)
|
||||
} else if dest != "" {
|
||||
fi, err := os.Stat(dest)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
@@ -94,7 +95,7 @@ func ParseOutputs(inp []string) ([]client.ExportEntry, error) {
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("failed to open %s", err)
|
||||
}
|
||||
out.Output = f
|
||||
out.Output = wrapWriteCloser(f)
|
||||
}
|
||||
delete(out.Attrs, "dest")
|
||||
case "registry":
|
||||
@@ -106,3 +107,9 @@ func ParseOutputs(inp []string) ([]client.ExportEntry, error) {
|
||||
}
|
||||
return outs, nil
|
||||
}
|
||||
|
||||
func wrapWriteCloser(wc io.WriteCloser) func(map[string]string) (io.WriteCloser, error) {
|
||||
return func(map[string]string) (io.WriteCloser, error) {
|
||||
return wc, nil
|
||||
}
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func ParseSecretSpecs(sl []string) (session.Attachable, error) {
|
||||
fs := make([]secretsprovider.FileSource, 0, len(sl))
|
||||
fs := make([]secretsprovider.Source, 0, len(sl))
|
||||
for _, v := range sl {
|
||||
s, err := parseSecret(v)
|
||||
if err != nil {
|
||||
@@ -18,21 +18,21 @@ func ParseSecretSpecs(sl []string) (session.Attachable, error) {
|
||||
}
|
||||
fs = append(fs, *s)
|
||||
}
|
||||
store, err := secretsprovider.NewFileStore(fs)
|
||||
store, err := secretsprovider.NewStore(fs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return secretsprovider.NewSecretProvider(store), nil
|
||||
}
|
||||
|
||||
func parseSecret(value string) (*secretsprovider.FileSource, error) {
|
||||
func parseSecret(value string) (*secretsprovider.Source, error) {
|
||||
csvReader := csv.NewReader(strings.NewReader(value))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse csv secret")
|
||||
}
|
||||
|
||||
fs := secretsprovider.FileSource{}
|
||||
fs := secretsprovider.Source{}
|
||||
|
||||
for _, field := range fields {
|
||||
parts := strings.SplitN(field, "=", 2)
|
||||
|
71
build/url.go
Normal file
71
build/url.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/buildx/driver"
|
||||
"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/pkg/errors"
|
||||
)
|
||||
|
||||
func createTempDockerfileFromURL(ctx context.Context, d driver.Driver, url string, pw progress.Writer) (string, error) {
|
||||
c, err := driver.Boot(ctx, d, pw)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var out string
|
||||
ch, done := progress.NewChannel(pw)
|
||||
defer func() { <-done }()
|
||||
_, err = c.Build(ctx, client.SolveOpt{}, "buildx", func(ctx context.Context, c gwclient.Client) (*gwclient.Result, error) {
|
||||
def, err := llb.HTTP(url, llb.Filename("Dockerfile"), llb.WithCustomNamef("[internal] load %s", url)).Marshal(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := c.Solve(ctx, gwclient.SolveRequest{
|
||||
Definition: def.ToPB(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ref, err := res.SingleRef()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stat, err := ref.StatFile(ctx, gwclient.StatRequest{
|
||||
Path: "Dockerfile",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if stat.Size() > 512*1024 {
|
||||
return nil, errors.Errorf("Dockerfile %s bigger than allowed max size", url)
|
||||
}
|
||||
|
||||
dt, err := ref.ReadFile(ctx, gwclient.ReadRequest{
|
||||
Filename: "Dockerfile",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dir, err := ioutil.TempDir("", "buildx")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ioutil.WriteFile(filepath.Join(dir, "Dockerfile"), dt, 0600); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = dir
|
||||
return nil, nil
|
||||
}, ch)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return out, nil
|
||||
}
|
@@ -4,20 +4,35 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/containerd/containerd/pkg/seed"
|
||||
"github.com/docker/buildx/commands"
|
||||
"github.com/docker/buildx/version"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli-plugins/plugin"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/debug"
|
||||
cliflags "github.com/docker/cli/cli/flags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/moby/buildkit/solver/errdefs"
|
||||
"github.com/moby/buildkit/util/stack"
|
||||
|
||||
// FIXME: "k8s.io/client-go/plugin/pkg/client/auth/azure" is excluded because of compilation error
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
|
||||
|
||||
_ "github.com/docker/buildx/driver/docker"
|
||||
_ "github.com/docker/buildx/driver/docker-container"
|
||||
_ "github.com/docker/buildx/driver/kubernetes"
|
||||
)
|
||||
|
||||
var experimental string
|
||||
|
||||
func init() {
|
||||
seed.WithTimeAndRand()
|
||||
stack.SetVersionInfo(version.Version, version.Revision)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if os.Getenv("DOCKER_CLI_PLUGIN_ORIGINAL_CLI_COMMAND") == "" {
|
||||
if len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName {
|
||||
@@ -36,13 +51,42 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
|
||||
return commands.NewRootCmd("buildx", true, dockerCli)
|
||||
},
|
||||
manager.Metadata{
|
||||
SchemaVersion: "0.1.0",
|
||||
Vendor: "Docker Inc.",
|
||||
Version: version.Version,
|
||||
Experimental: experimental != "",
|
||||
})
|
||||
dockerCli, err := command.NewDockerCli()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
p := commands.NewRootCmd("buildx", true, dockerCli)
|
||||
meta := manager.Metadata{
|
||||
SchemaVersion: "0.1.0",
|
||||
Vendor: "Docker Inc.",
|
||||
Version: version.Version,
|
||||
Experimental: experimental != "",
|
||||
}
|
||||
|
||||
if err := plugin.RunPlugin(dockerCli, p, meta); err != nil {
|
||||
if sterr, ok := err.(cli.StatusError); ok {
|
||||
if sterr.Status != "" {
|
||||
fmt.Fprintln(dockerCli.Err(), sterr.Status)
|
||||
}
|
||||
// StatusError should only be used for errors, and all errors should
|
||||
// have a non-zero exit status, so never exit with 0
|
||||
if sterr.StatusCode == 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(sterr.StatusCode)
|
||||
}
|
||||
for _, s := range errdefs.Sources(err) {
|
||||
s.Print(dockerCli.Err())
|
||||
}
|
||||
|
||||
if debug.IsEnabled() {
|
||||
fmt.Fprintf(dockerCli.Err(), "error: %+v", stack.Formatter(err))
|
||||
} else {
|
||||
fmt.Fprintf(dockerCli.Err(), "error: %v\n", err)
|
||||
}
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
1
codecov.yml
Normal file
1
codecov.yml
Normal file
@@ -0,0 +1 @@
|
||||
comment: false
|
117
commands/bake.go
117
commands/bake.go
@@ -1,11 +1,14 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/docker/buildx/bake"
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/pkg/errors"
|
||||
@@ -19,31 +22,80 @@ type bakeOptions struct {
|
||||
commonOptions
|
||||
}
|
||||
|
||||
func runBake(dockerCli command.Cli, targets []string, in bakeOptions) error {
|
||||
func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error) {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
if len(in.files) == 0 {
|
||||
files, err := defaultFiles()
|
||||
if err != nil {
|
||||
return err
|
||||
var url string
|
||||
|
||||
if len(targets) > 0 {
|
||||
if bake.IsRemoteURL(targets[0]) {
|
||||
url = targets[0]
|
||||
targets = targets[1:]
|
||||
}
|
||||
if len(files) == 0 {
|
||||
return errors.Errorf("no docker-compose.yml or docker-bake.hcl found, specify build file with -f/--file")
|
||||
}
|
||||
in.files = files
|
||||
}
|
||||
|
||||
if len(targets) == 0 {
|
||||
targets = []string{"default"}
|
||||
}
|
||||
|
||||
m, err := bake.ReadTargets(ctx, in.files, targets, in.overrides)
|
||||
overrides := in.overrides
|
||||
if in.exportPush {
|
||||
if in.exportLoad {
|
||||
return errors.Errorf("push and load may not be set together at the moment")
|
||||
}
|
||||
overrides = append(overrides, "*.output=type=registry")
|
||||
} else if in.exportLoad {
|
||||
overrides = append(overrides, "*.output=type=docker")
|
||||
}
|
||||
if in.noCache != nil {
|
||||
overrides = append(overrides, fmt.Sprintf("*.no-cache=%t", *in.noCache))
|
||||
}
|
||||
if in.pull != nil {
|
||||
overrides = append(overrides, fmt.Sprintf("*.pull=%t", *in.pull))
|
||||
}
|
||||
contextPathHash, _ := os.Getwd()
|
||||
|
||||
ctx2, cancel := context.WithCancel(context.TODO())
|
||||
defer cancel()
|
||||
printer := progress.NewPrinter(ctx2, os.Stderr, in.progress)
|
||||
|
||||
defer func() {
|
||||
if printer != nil {
|
||||
err1 := printer.Wait()
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
dis, err := getInstanceOrDefault(ctx, dockerCli, in.builder, contextPathHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var files []bake.File
|
||||
var inp *bake.Input
|
||||
if url != "" {
|
||||
files, inp, err = bake.ReadRemoteFiles(ctx, dis, url, in.files, printer)
|
||||
} else {
|
||||
files, err = bake.ReadLocalFiles(in.files)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m, err := bake.ReadTargets(ctx, files, targets, overrides)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if in.printOnly {
|
||||
dt, err := json.MarshalIndent(map[string]map[string]bake.Target{"target": m}, "", " ")
|
||||
dt, err := json.MarshalIndent(map[string]map[string]*bake.Target{"target": m}, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = printer.Wait()
|
||||
printer = nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -51,37 +103,16 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
bo, err := bake.TargetsToBuildOpt(m)
|
||||
bo, err := bake.TargetsToBuildOpt(m, inp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return buildTargets(ctx, dockerCli, bo, in.progress)
|
||||
_, err = build.Build(ctx, dis, bo, dockerAPI(dockerCli), dockerCli.ConfigFile(), printer)
|
||||
return err
|
||||
}
|
||||
|
||||
func defaultFiles() ([]string, error) {
|
||||
fns := []string{
|
||||
"docker-compose.yml", // support app
|
||||
"docker-compose.yaml", // support app
|
||||
"docker-bake.json",
|
||||
"docker-bake.override.json",
|
||||
"docker-bake.hcl",
|
||||
"docker-bake.override.hcl",
|
||||
}
|
||||
out := make([]string, 0, len(fns))
|
||||
for _, f := range fns {
|
||||
if _, err := os.Stat(f); err != nil {
|
||||
if os.IsNotExist(errors.Cause(err)) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, f)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func bakeCmd(dockerCli command.Cli) *cobra.Command {
|
||||
func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
var options bakeOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@@ -89,6 +120,14 @@ func bakeCmd(dockerCli command.Cli) *cobra.Command {
|
||||
Aliases: []string{"f"},
|
||||
Short: "Build from a file",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// reset to nil to avoid override is unset
|
||||
if !cmd.Flags().Lookup("no-cache").Changed {
|
||||
options.noCache = nil
|
||||
}
|
||||
if !cmd.Flags().Lookup("pull").Changed {
|
||||
options.pull = nil
|
||||
}
|
||||
options.commonOptions.builder = rootOpts.builder
|
||||
return runBake(dockerCli, args, options)
|
||||
},
|
||||
}
|
||||
@@ -97,9 +136,11 @@ func bakeCmd(dockerCli command.Cli) *cobra.Command {
|
||||
|
||||
flags.StringArrayVarP(&options.files, "file", "f", []string{}, "Build definition file")
|
||||
flags.BoolVar(&options.printOnly, "print", false, "Print the options without building")
|
||||
flags.StringArrayVar(&options.overrides, "set", nil, "Override target value (eg: target.key=value)")
|
||||
flags.StringArrayVar(&options.overrides, "set", nil, "Override target value (eg: targetpattern.key=value)")
|
||||
flags.BoolVar(&options.exportPush, "push", false, "Shorthand for --set=*.output=type=registry")
|
||||
flags.BoolVar(&options.exportLoad, "load", false, "Shorthand for --set=*.output=type=docker")
|
||||
|
||||
commonFlags(&options.commonOptions, flags)
|
||||
commonBuildFlags(&options.commonOptions, flags)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ package commands
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/build"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/moby/buildkit/session/auth/authprovider"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
@@ -37,13 +39,12 @@ type buildOptions struct {
|
||||
extraHosts []string
|
||||
networkMode string
|
||||
|
||||
exportPush bool
|
||||
exportLoad bool
|
||||
|
||||
// unimplemented
|
||||
squash bool
|
||||
quiet bool
|
||||
|
||||
allow []string
|
||||
|
||||
// hidden
|
||||
// untrusted bool
|
||||
// ulimits *opts.UlimitOpt
|
||||
@@ -62,9 +63,12 @@ type buildOptions struct {
|
||||
}
|
||||
|
||||
type commonOptions struct {
|
||||
noCache bool
|
||||
progress string
|
||||
pull bool
|
||||
builder string
|
||||
noCache *bool
|
||||
progress string
|
||||
pull *bool
|
||||
exportPush bool
|
||||
exportLoad bool
|
||||
}
|
||||
|
||||
func runBuild(dockerCli command.Cli, in buildOptions) error {
|
||||
@@ -72,11 +76,20 @@ func runBuild(dockerCli command.Cli, in buildOptions) error {
|
||||
return errors.Errorf("squash currently not implemented")
|
||||
}
|
||||
if in.quiet {
|
||||
return errors.Errorf("quiet currently not implemented")
|
||||
logrus.Warnf("quiet currently not implemented")
|
||||
}
|
||||
|
||||
ctx := appcontext.Context()
|
||||
|
||||
noCache := false
|
||||
if in.noCache != nil {
|
||||
noCache = *in.noCache
|
||||
}
|
||||
pull := false
|
||||
if in.pull != nil {
|
||||
pull = *in.pull
|
||||
}
|
||||
|
||||
opts := build.Options{
|
||||
Inputs: build.Inputs{
|
||||
ContextPath: in.contextPath,
|
||||
@@ -84,10 +97,10 @@ func runBuild(dockerCli command.Cli, in buildOptions) error {
|
||||
InStream: os.Stdin,
|
||||
},
|
||||
Tags: in.tags,
|
||||
Labels: listToMap(in.labels),
|
||||
BuildArgs: listToMap(in.buildArgs),
|
||||
Pull: in.pull,
|
||||
NoCache: in.noCache,
|
||||
Labels: listToMap(in.labels, false),
|
||||
BuildArgs: listToMap(in.buildArgs, true),
|
||||
Pull: pull,
|
||||
NoCache: noCache,
|
||||
Target: in.target,
|
||||
ImageIDFile: in.imageIDFile,
|
||||
ExtraHosts: in.extraHosts,
|
||||
@@ -167,24 +180,41 @@ func runBuild(dockerCli command.Cli, in buildOptions) error {
|
||||
}
|
||||
opts.CacheTo = cacheExports
|
||||
|
||||
return buildTargets(ctx, dockerCli, map[string]build.Options{"default": opts}, in.progress)
|
||||
allow, err := build.ParseEntitlements(in.allow)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.Allow = allow
|
||||
|
||||
// key string used for kubernetes "sticky" mode
|
||||
contextPathHash, err := filepath.Abs(in.contextPath)
|
||||
if err != nil {
|
||||
contextPathHash = in.contextPath
|
||||
}
|
||||
|
||||
return buildTargets(ctx, dockerCli, map[string]build.Options{"default": opts}, in.progress, contextPathHash, in.builder)
|
||||
}
|
||||
|
||||
func buildTargets(ctx context.Context, dockerCli command.Cli, opts map[string]build.Options, progressMode string) error {
|
||||
dis, err := getDefaultDrivers(ctx, dockerCli)
|
||||
func buildTargets(ctx context.Context, dockerCli command.Cli, opts map[string]build.Options, progressMode, contextPathHash, instance string) error {
|
||||
dis, err := getInstanceOrDefault(ctx, dockerCli, instance, contextPathHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx2, cancel := context.WithCancel(context.TODO())
|
||||
defer cancel()
|
||||
pw := progress.NewPrinter(ctx2, os.Stderr, progressMode)
|
||||
printer := progress.NewPrinter(ctx2, os.Stderr, progressMode)
|
||||
|
||||
_, err = build.Build(ctx, dis, opts, dockerAPI(dockerCli), dockerCli.ConfigFile(), printer)
|
||||
err1 := printer.Wait()
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
|
||||
_, err = build.Build(ctx, dis, opts, dockerAPI(dockerCli), dockerCli.ConfigFile(), pw)
|
||||
return err
|
||||
}
|
||||
|
||||
func buildCmd(dockerCli command.Cli) *cobra.Command {
|
||||
func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
var options buildOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@@ -194,6 +224,7 @@ func buildCmd(dockerCli command.Cli) *cobra.Command {
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.contextPath = args[0]
|
||||
options.builder = rootOpts.builder
|
||||
return runBuild(dockerCli, options)
|
||||
},
|
||||
}
|
||||
@@ -214,6 +245,8 @@ func buildCmd(dockerCli command.Cli) *cobra.Command {
|
||||
|
||||
flags.StringVar(&options.target, "target", "", "Set the target build stage to build.")
|
||||
|
||||
flags.StringSliceVar(&options.allow, "allow", []string{}, "Allow extra privileged entitlement, e.g. network.host, security.insecure")
|
||||
|
||||
// not implemented
|
||||
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the build output and print image ID on success")
|
||||
flags.StringVar(&options.networkMode, "network", "default", "Set the networking mode for the RUN instructions during build")
|
||||
@@ -271,23 +304,30 @@ func buildCmd(dockerCli command.Cli) *cobra.Command {
|
||||
|
||||
flags.StringArrayVarP(&options.outputs, "output", "o", []string{}, "Output destination (format: type=local,dest=path)")
|
||||
|
||||
commonFlags(&options.commonOptions, flags)
|
||||
commonBuildFlags(&options.commonOptions, flags)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func commonFlags(options *commonOptions, flags *pflag.FlagSet) {
|
||||
flags.BoolVar(&options.noCache, "no-cache", false, "Do not use cache when building the image")
|
||||
func commonBuildFlags(options *commonOptions, flags *pflag.FlagSet) {
|
||||
options.noCache = flags.Bool("no-cache", false, "Do not use cache when building the image")
|
||||
flags.StringVar(&options.progress, "progress", "auto", "Set type of progress output (auto, plain, tty). Use plain to show container output")
|
||||
flags.BoolVar(&options.pull, "pull", false, "Always attempt to pull a newer version of the image")
|
||||
options.pull = flags.Bool("pull", false, "Always attempt to pull a newer version of the image")
|
||||
}
|
||||
|
||||
func listToMap(values []string) map[string]string {
|
||||
func listToMap(values []string, defaultEnv bool) map[string]string {
|
||||
result := make(map[string]string, len(values))
|
||||
for _, value := range values {
|
||||
kv := strings.SplitN(value, "=", 2)
|
||||
if len(kv) == 1 {
|
||||
result[kv[0]] = ""
|
||||
if defaultEnv {
|
||||
v, ok := os.LookupEnv(kv[0])
|
||||
if ok {
|
||||
result[kv[0]] = v
|
||||
}
|
||||
} else {
|
||||
result[kv[0]] = ""
|
||||
}
|
||||
} else {
|
||||
result[kv[0]] = kv[1]
|
||||
}
|
||||
|
@@ -1,13 +1,16 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/driver"
|
||||
"github.com/docker/buildx/store"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/google/shlex"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -22,6 +25,9 @@ type createOptions struct {
|
||||
actionAppend bool
|
||||
actionLeave bool
|
||||
use bool
|
||||
flags string
|
||||
configFile string
|
||||
driverOpts []string
|
||||
// upgrade bool // perform upgrade of the driver
|
||||
}
|
||||
|
||||
@@ -107,6 +113,14 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||
ng.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
|
||||
if in.actionLeave {
|
||||
if err := ng.Leave(in.nodeName); err != nil {
|
||||
@@ -128,7 +142,17 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := ng.Update(in.nodeName, ep, in.platform, len(args) > 0, in.actionAppend); err != nil {
|
||||
|
||||
if in.driver == "kubernetes" {
|
||||
// naming endpoint to make --append works
|
||||
ep = fmt.Sprintf("%s://%s?deployment=%s", in.driver, in.name, in.nodeName)
|
||||
}
|
||||
|
||||
m, err := csvToMap(in.driverOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ng.Update(in.nodeName, ep, in.platform, len(args) > 0, in.actionAppend, flags, in.configFile, m); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -154,6 +178,11 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||
func createCmd(dockerCli command.Cli) *cobra.Command {
|
||||
var options createOptions
|
||||
|
||||
var drivers []string
|
||||
for s := range driver.GetFactories() {
|
||||
drivers = append(drivers, s)
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "create [OPTIONS] [CONTEXT|ENDPOINT]",
|
||||
Short: "Create a new builder instance",
|
||||
@@ -166,9 +195,12 @@ func createCmd(dockerCli command.Cli) *cobra.Command {
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.StringVar(&options.name, "name", "", "Builder instance name")
|
||||
flags.StringVar(&options.driver, "driver", "", "Driver to use (eg. docker-container)")
|
||||
flags.StringVar(&options.driver, "driver", "", fmt.Sprintf("Driver to use (available: %v)", drivers))
|
||||
flags.StringVar(&options.nodeName, "node", "", "Create/modify node with given name")
|
||||
flags.StringVar(&options.flags, "buildkitd-flags", "", "Flags for buildkitd daemon")
|
||||
flags.StringVar(&options.configFile, "config", "", "BuildKit config file")
|
||||
flags.StringArrayVar(&options.platform, "platform", []string{}, "Fixed platforms for current node")
|
||||
flags.StringArrayVar(&options.driverOpts, "driver-opt", []string{}, "Options for the driver")
|
||||
|
||||
flags.BoolVar(&options.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")
|
||||
@@ -178,3 +210,22 @@ func createCmd(dockerCli command.Cli) *cobra.Command {
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func csvToMap(in []string) (map[string]string, error) {
|
||||
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
|
||||
}
|
||||
|
198
commands/diskusage.go
Normal file
198
commands/diskusage.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tonistiigi/units"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type duOptions struct {
|
||||
builder string
|
||||
filter opts.FilterOpt
|
||||
verbose bool
|
||||
}
|
||||
|
||||
func runDiskUsage(dockerCli command.Cli, opts duOptions) error {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
pi, err := toBuildkitPruneInfo(opts.filter.Value())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dis, err := getInstanceOrDefault(ctx, dockerCli, opts.builder, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, di := range dis {
|
||||
if di.Err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
out := make([][]*client.UsageInfo, len(dis))
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
for i, di := range dis {
|
||||
func(i int, di build.DriverInfo) {
|
||||
eg.Go(func() error {
|
||||
if di.Driver != nil {
|
||||
c, err := di.Driver.Client(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
du, err := c.DiskUsage(ctx, client.WithFilter(pi.Filter))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out[i] = du
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}(i, di)
|
||||
}
|
||||
|
||||
if err := eg.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, '\t', 0)
|
||||
first := true
|
||||
for _, du := range out {
|
||||
if du == nil {
|
||||
continue
|
||||
}
|
||||
if opts.verbose {
|
||||
printVerbose(tw, du)
|
||||
} else {
|
||||
if first {
|
||||
printTableHeader(tw)
|
||||
first = false
|
||||
}
|
||||
for _, di := range du {
|
||||
printTableRow(tw, di)
|
||||
}
|
||||
|
||||
tw.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
if opts.filter.Value().Len() == 0 {
|
||||
printSummary(tw, out)
|
||||
}
|
||||
|
||||
tw.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
func duCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
options := duOptions{filter: opts.NewFilterOpt()}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "du",
|
||||
Short: "Disk usage",
|
||||
Args: cli.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.builder = rootOpts.builder
|
||||
return runDiskUsage(dockerCli, options)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.Var(&options.filter, "filter", "Provide filter values")
|
||||
flags.BoolVar(&options.verbose, "verbose", false, "Provide a more verbose output")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func printKV(w io.Writer, k string, v interface{}) {
|
||||
fmt.Fprintf(w, "%s:\t%v\n", k, v)
|
||||
}
|
||||
|
||||
func printVerbose(tw *tabwriter.Writer, du []*client.UsageInfo) {
|
||||
for _, di := range du {
|
||||
printKV(tw, "ID", di.ID)
|
||||
if di.Parent != "" {
|
||||
printKV(tw, "Parent", di.Parent)
|
||||
}
|
||||
printKV(tw, "Created at", di.CreatedAt)
|
||||
printKV(tw, "Mutable", di.Mutable)
|
||||
printKV(tw, "Reclaimable", !di.InUse)
|
||||
printKV(tw, "Shared", di.Shared)
|
||||
printKV(tw, "Size", fmt.Sprintf("%.2f", units.Bytes(di.Size)))
|
||||
if di.Description != "" {
|
||||
printKV(tw, "Description", di.Description)
|
||||
}
|
||||
printKV(tw, "Usage count", di.UsageCount)
|
||||
if di.LastUsedAt != nil {
|
||||
printKV(tw, "Last used", di.LastUsedAt)
|
||||
}
|
||||
if di.RecordType != "" {
|
||||
printKV(tw, "Type", di.RecordType)
|
||||
}
|
||||
|
||||
fmt.Fprintf(tw, "\n")
|
||||
}
|
||||
|
||||
tw.Flush()
|
||||
}
|
||||
|
||||
func printTableHeader(tw *tabwriter.Writer) {
|
||||
fmt.Fprintln(tw, "ID\tRECLAIMABLE\tSIZE\tLAST ACCESSED")
|
||||
}
|
||||
|
||||
func printTableRow(tw *tabwriter.Writer, di *client.UsageInfo) {
|
||||
id := di.ID
|
||||
if di.Mutable {
|
||||
id += "*"
|
||||
}
|
||||
size := fmt.Sprintf("%.2f", units.Bytes(di.Size))
|
||||
if di.Shared {
|
||||
size += "*"
|
||||
}
|
||||
fmt.Fprintf(tw, "%-71s\t%-11v\t%s\t\n", id, !di.InUse, size)
|
||||
}
|
||||
|
||||
func printSummary(tw *tabwriter.Writer, dus [][]*client.UsageInfo) {
|
||||
total := int64(0)
|
||||
reclaimable := int64(0)
|
||||
shared := int64(0)
|
||||
|
||||
for _, du := range dus {
|
||||
for _, di := range du {
|
||||
if di.Size > 0 {
|
||||
total += di.Size
|
||||
if !di.InUse {
|
||||
reclaimable += di.Size
|
||||
}
|
||||
}
|
||||
if di.Shared {
|
||||
shared += di.Size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tw = tabwriter.NewWriter(os.Stdout, 1, 8, 1, '\t', 0)
|
||||
|
||||
if shared > 0 {
|
||||
fmt.Fprintf(tw, "Shared:\t%.2f\n", units.Bytes(shared))
|
||||
fmt.Fprintf(tw, "Private:\t%.2f\n", units.Bytes(total-shared))
|
||||
}
|
||||
|
||||
fmt.Fprintf(tw, "Reclaimable:\t%.2f\n", units.Bytes(reclaimable))
|
||||
fmt.Fprintf(tw, "Total:\t%.2f\n", units.Bytes(total))
|
||||
tw.Flush()
|
||||
}
|
@@ -30,7 +30,7 @@ func runInspect(dockerCli command.Cli, in inspectOptions, name string) error {
|
||||
}
|
||||
|
||||
if in.raw {
|
||||
fmt.Printf("%s\n", dt)
|
||||
fmt.Printf("%s", dt) // avoid newline to keep digest
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
type inspectOptions struct {
|
||||
bootstrap bool
|
||||
builder string
|
||||
}
|
||||
|
||||
type dinfo struct {
|
||||
@@ -38,7 +39,7 @@ type nginfo struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func runInspect(dockerCli command.Cli, in inspectOptions, args []string) error {
|
||||
func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
txn, release, err := getStore(dockerCli)
|
||||
@@ -49,8 +50,8 @@ func runInspect(dockerCli command.Cli, in inspectOptions, args []string) error {
|
||||
|
||||
var ng *store.NodeGroup
|
||||
|
||||
if len(args) > 0 {
|
||||
ng, err = getNodeGroup(txn, dockerCli, args[0])
|
||||
if in.builder != "" {
|
||||
ng, err = getNodeGroup(txn, dockerCli, in.builder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -73,17 +74,19 @@ func runInspect(dockerCli command.Cli, in inspectOptions, args []string) error {
|
||||
|
||||
ngi := &nginfo{ng: ng}
|
||||
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
err = loadNodeGroupData(timeoutCtx, dockerCli, ngi)
|
||||
|
||||
var bootNgi *nginfo
|
||||
if in.bootstrap {
|
||||
var ok bool
|
||||
ok, err = boot(ctx, ngi)
|
||||
ok, err = boot(ctx, ngi, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bootNgi = ngi
|
||||
if ok {
|
||||
ngi = &nginfo{ng: ng}
|
||||
err = loadNodeGroupData(ctx, dockerCli, ngi)
|
||||
@@ -112,9 +115,14 @@ func runInspect(dockerCli command.Cli, in inspectOptions, args []string) error {
|
||||
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
||||
} else if err := ngi.drivers[i].err; err != nil {
|
||||
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
||||
} else if bootNgi != nil && len(bootNgi.drivers) > i && bootNgi.drivers[i].err != nil {
|
||||
fmt.Fprintf(w, "Error:\t%s\n", bootNgi.drivers[i].err.Error())
|
||||
} else {
|
||||
fmt.Fprintf(w, "Status:\t%s\n", ngi.drivers[i].info.Status)
|
||||
fmt.Fprintf(w, "Platforms:\t%s\n", strings.Join(platformutil.Format(platformutil.Dedupe(append(n.Platforms, ngi.drivers[i].platforms...))), ", "))
|
||||
if len(n.Flags) > 0 {
|
||||
fmt.Fprintf(w, "Flags:\t%s\n", strings.Join(n.Flags, " "))
|
||||
}
|
||||
fmt.Fprintf(w, "Platforms:\t%s\n", strings.Join(platformutil.FormatInGroups(n.Platforms, ngi.drivers[i].platforms), ", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,7 +132,7 @@ func runInspect(dockerCli command.Cli, in inspectOptions, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func inspectCmd(dockerCli command.Cli) *cobra.Command {
|
||||
func inspectCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
var options inspectOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@@ -132,7 +140,11 @@ func inspectCmd(dockerCli command.Cli) *cobra.Command {
|
||||
Short: "Inspect current builder instance",
|
||||
Args: cli.RequiresMaxArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runInspect(dockerCli, options, args)
|
||||
options.builder = rootOpts.builder
|
||||
if len(args) > 0 {
|
||||
options.builder = args[0]
|
||||
}
|
||||
return runInspect(dockerCli, options)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -145,7 +157,7 @@ func inspectCmd(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func boot(ctx context.Context, ngi *nginfo) (bool, error) {
|
||||
func boot(ctx context.Context, ngi *nginfo, dockerCli command.Cli) (bool, error) {
|
||||
toBoot := make([]int, 0, len(ngi.drivers))
|
||||
for i, d := range ngi.drivers {
|
||||
if d.err != nil || d.di.Err != nil || d.di.Driver == nil || d.info == nil {
|
||||
@@ -159,25 +171,27 @@ func boot(ctx context.Context, ngi *nginfo) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
pw := progress.NewPrinter(context.TODO(), os.Stderr, "auto")
|
||||
|
||||
mw := progress.NewMultiWriter(pw)
|
||||
printer := progress.NewPrinter(context.TODO(), os.Stderr, "auto")
|
||||
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
for _, idx := range toBoot {
|
||||
func(idx int) {
|
||||
eg.Go(func() error {
|
||||
pw := mw.WithPrefix(ngi.ng.Nodes[idx].Name, len(toBoot) > 1)
|
||||
pw := progress.WithPrefix(printer, ngi.ng.Nodes[idx].Name, len(toBoot) > 1)
|
||||
_, err := driver.Boot(ctx, ngi.drivers[idx].di.Driver, pw)
|
||||
if err != nil {
|
||||
ngi.drivers[idx].err = err
|
||||
}
|
||||
close(pw.Status())
|
||||
<-pw.Done()
|
||||
return nil
|
||||
})
|
||||
}(idx)
|
||||
}
|
||||
|
||||
return true, eg.Wait()
|
||||
err := eg.Wait()
|
||||
err1 := printer.Wait()
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
|
||||
return true, err
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@ func runLs(dockerCli command.Cli, in lsOptions) error {
|
||||
}
|
||||
defer release()
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
|
||||
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
ll, err := txn.List()
|
||||
@@ -126,11 +126,10 @@ func printngi(w io.Writer, ngi *nginfo) {
|
||||
if d.info != nil {
|
||||
status = d.info.Status.String()
|
||||
}
|
||||
p := append(n.Platforms, d.platforms...)
|
||||
if err != "" {
|
||||
fmt.Fprintf(w, " %s\t%s\t%s\n", n.Name, n.Endpoint, err)
|
||||
} else {
|
||||
fmt.Fprintf(w, " %s\t%s\t%s\t%s\n", n.Name, n.Endpoint, status, strings.Join(platformutil.Format(p), ", "))
|
||||
fmt.Fprintf(w, " %s\t%s\t%s\t%s\n", n.Name, n.Endpoint, status, strings.Join(platformutil.FormatInGroups(n.Platforms, d.platforms), ", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
197
commands/prune.go
Normal file
197
commands/prune.go
Normal file
@@ -0,0 +1,197 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tonistiigi/units"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type pruneOptions struct {
|
||||
builder string
|
||||
all bool
|
||||
filter opts.FilterOpt
|
||||
keepStorage opts.MemBytes
|
||||
force bool
|
||||
verbose bool
|
||||
}
|
||||
|
||||
const (
|
||||
normalWarning = `WARNING! This will remove all dangling 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 {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
pruneFilters := opts.filter.Value()
|
||||
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
|
||||
|
||||
pi, err := toBuildkitPruneInfo(pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
warning := normalWarning
|
||||
if opts.all {
|
||||
warning = allCacheWarning
|
||||
}
|
||||
|
||||
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
||||
return nil
|
||||
}
|
||||
|
||||
dis, err := getInstanceOrDefault(ctx, dockerCli, opts.builder, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, di := range dis {
|
||||
if di.Err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ch := make(chan client.UsageInfo)
|
||||
printed := make(chan struct{})
|
||||
|
||||
tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, '\t', 0)
|
||||
first := true
|
||||
total := int64(0)
|
||||
|
||||
go func() {
|
||||
defer close(printed)
|
||||
for du := range ch {
|
||||
total += du.Size
|
||||
if opts.verbose {
|
||||
printVerbose(tw, []*client.UsageInfo{&du})
|
||||
} else {
|
||||
if first {
|
||||
printTableHeader(tw)
|
||||
first = false
|
||||
}
|
||||
printTableRow(tw, &du)
|
||||
tw.Flush()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
for _, di := range dis {
|
||||
func(di build.DriverInfo) {
|
||||
eg.Go(func() error {
|
||||
if di.Driver != nil {
|
||||
c, err := di.Driver.Client(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
popts := []client.PruneOption{
|
||||
client.WithKeepOpt(pi.KeepDuration, opts.keepStorage.Value()),
|
||||
client.WithFilter(pi.Filter),
|
||||
}
|
||||
if opts.all {
|
||||
popts = append(popts, client.PruneAll)
|
||||
}
|
||||
return c.Prune(ctx, ch, popts...)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}(di)
|
||||
}
|
||||
|
||||
if err := eg.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
close(ch)
|
||||
<-printed
|
||||
|
||||
tw = tabwriter.NewWriter(os.Stdout, 1, 8, 1, '\t', 0)
|
||||
fmt.Fprintf(tw, "Total:\t%.2f\n", units.Bytes(total))
|
||||
tw.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
func pruneCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
options := pruneOptions{filter: opts.NewFilterOpt()}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "prune",
|
||||
Short: "Remove build cache ",
|
||||
Args: cli.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.builder = rootOpts.builder
|
||||
return runPrune(dockerCli, options)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&options.all, "all", "a", false, "Remove all unused images, not just dangling ones")
|
||||
flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'until=24h')")
|
||||
flags.Var(&options.keepStorage, "keep-storage", "Amount of disk space to keep for cache")
|
||||
flags.BoolVar(&options.verbose, "verbose", false, "Provide a more verbose output")
|
||||
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func toBuildkitPruneInfo(f filters.Args) (*client.PruneInfo, error) {
|
||||
var until time.Duration
|
||||
untilValues := f.Get("until") // canonical
|
||||
unusedForValues := f.Get("unused-for") // deprecated synonym for "until" filter
|
||||
|
||||
if len(untilValues) > 0 && len(unusedForValues) > 0 {
|
||||
return nil, errors.Errorf("conflicting filters %q and %q", "until", "unused-for")
|
||||
}
|
||||
filterKey := "until"
|
||||
if len(unusedForValues) > 0 {
|
||||
filterKey = "unused-for"
|
||||
}
|
||||
untilValues = append(untilValues, unusedForValues...)
|
||||
|
||||
switch len(untilValues) {
|
||||
case 0:
|
||||
// nothing to do
|
||||
case 1:
|
||||
var err error
|
||||
until, err = time.ParseDuration(untilValues[0])
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "%q filter expects a duration (e.g., '24h')", filterKey)
|
||||
}
|
||||
default:
|
||||
return nil, errors.Errorf("filters expect only one value")
|
||||
}
|
||||
|
||||
bkFilter := make([]string, 0, f.Len())
|
||||
for _, field := range f.Keys() {
|
||||
values := f.Get(field)
|
||||
switch len(values) {
|
||||
case 0:
|
||||
bkFilter = append(bkFilter, field)
|
||||
case 1:
|
||||
if field == "id" {
|
||||
bkFilter = append(bkFilter, field+"~="+values[0])
|
||||
} else {
|
||||
bkFilter = append(bkFilter, field+"=="+values[0])
|
||||
}
|
||||
default:
|
||||
return nil, errors.Errorf("filters expect only one value")
|
||||
}
|
||||
}
|
||||
return &client.PruneInfo{
|
||||
KeepDuration: until,
|
||||
Filter: []string{strings.Join(bkFilter, ",")},
|
||||
}, nil
|
||||
}
|
@@ -11,9 +11,10 @@ import (
|
||||
)
|
||||
|
||||
type rmOptions struct {
|
||||
builder string
|
||||
}
|
||||
|
||||
func runRm(dockerCli command.Cli, in rmOptions, args []string) error {
|
||||
func runRm(dockerCli command.Cli, in rmOptions) error {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
txn, release, err := getStore(dockerCli)
|
||||
@@ -22,8 +23,8 @@ func runRm(dockerCli command.Cli, in rmOptions, args []string) error {
|
||||
}
|
||||
defer release()
|
||||
|
||||
if len(args) > 0 {
|
||||
ng, err := getNodeGroup(txn, dockerCli, args[0])
|
||||
if in.builder != "" {
|
||||
ng, err := getNodeGroup(txn, dockerCli, in.builder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -49,7 +50,7 @@ func runRm(dockerCli command.Cli, in rmOptions, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func rmCmd(dockerCli command.Cli) *cobra.Command {
|
||||
func rmCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
var options rmOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@@ -57,7 +58,11 @@ func rmCmd(dockerCli command.Cli) *cobra.Command {
|
||||
Short: "Remove a builder instance",
|
||||
Args: cli.RequiresMaxArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runRm(dockerCli, options, args)
|
||||
options.builder = rootOpts.builder
|
||||
if len(args) > 0 {
|
||||
options.builder = args[0]
|
||||
}
|
||||
return runRm(dockerCli, options)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -65,7 +70,7 @@ func rmCmd(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
|
||||
func stop(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, rm bool) error {
|
||||
dis, err := driversForNodeGroup(ctx, dockerCli, ng)
|
||||
dis, err := driversForNodeGroup(ctx, dockerCli, ng, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -88,7 +93,7 @@ func stop(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, rm bo
|
||||
}
|
||||
|
||||
func stopCurrent(ctx context.Context, dockerCli command.Cli, rm bool) error {
|
||||
dis, err := getDefaultDrivers(ctx, dockerCli)
|
||||
dis, err := getDefaultDrivers(ctx, dockerCli, false, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -1,10 +1,13 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
imagetoolscmd "github.com/docker/buildx/commands/imagetools"
|
||||
"github.com/docker/cli/cli-plugins/plugin"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func NewRootCmd(name string, isPlugin bool, dockerCli command.Cli) *cobra.Command {
|
||||
@@ -22,19 +25,32 @@ func NewRootCmd(name string, isPlugin bool, dockerCli command.Cli) *cobra.Comman
|
||||
return cmd
|
||||
}
|
||||
|
||||
type rootOptions struct {
|
||||
builder string
|
||||
}
|
||||
|
||||
func addCommands(cmd *cobra.Command, dockerCli command.Cli) {
|
||||
opts := &rootOptions{}
|
||||
rootFlags(opts, cmd.PersistentFlags())
|
||||
|
||||
cmd.AddCommand(
|
||||
buildCmd(dockerCli),
|
||||
bakeCmd(dockerCli),
|
||||
buildCmd(dockerCli, opts),
|
||||
bakeCmd(dockerCli, opts),
|
||||
createCmd(dockerCli),
|
||||
rmCmd(dockerCli),
|
||||
rmCmd(dockerCli, opts),
|
||||
lsCmd(dockerCli),
|
||||
useCmd(dockerCli),
|
||||
inspectCmd(dockerCli),
|
||||
stopCmd(dockerCli),
|
||||
useCmd(dockerCli, opts),
|
||||
inspectCmd(dockerCli, opts),
|
||||
stopCmd(dockerCli, opts),
|
||||
installCmd(dockerCli),
|
||||
uninstallCmd(dockerCli),
|
||||
versionCmd(dockerCli),
|
||||
pruneCmd(dockerCli, opts),
|
||||
duCmd(dockerCli, opts),
|
||||
imagetoolscmd.RootCmd(dockerCli),
|
||||
)
|
||||
}
|
||||
|
||||
func rootFlags(options *rootOptions, flags *pflag.FlagSet) {
|
||||
flags.StringVar(&options.builder, "builder", os.Getenv("BUILDX_BUILDER"), "Override the configured builder instance")
|
||||
}
|
||||
|
@@ -8,9 +8,10 @@ import (
|
||||
)
|
||||
|
||||
type stopOptions struct {
|
||||
builder string
|
||||
}
|
||||
|
||||
func runStop(dockerCli command.Cli, in stopOptions, args []string) error {
|
||||
func runStop(dockerCli command.Cli, in stopOptions) error {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
txn, release, err := getStore(dockerCli)
|
||||
@@ -19,8 +20,8 @@ func runStop(dockerCli command.Cli, in stopOptions, args []string) error {
|
||||
}
|
||||
defer release()
|
||||
|
||||
if len(args) > 0 {
|
||||
ng, err := getNodeGroup(txn, dockerCli, args[0])
|
||||
if in.builder != "" {
|
||||
ng, err := getNodeGroup(txn, dockerCli, in.builder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -41,7 +42,7 @@ func runStop(dockerCli command.Cli, in stopOptions, args []string) error {
|
||||
return stopCurrent(ctx, dockerCli, false)
|
||||
}
|
||||
|
||||
func stopCmd(dockerCli command.Cli) *cobra.Command {
|
||||
func stopCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
var options stopOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@@ -49,7 +50,11 @@ func stopCmd(dockerCli command.Cli) *cobra.Command {
|
||||
Short: "Stop builder instance",
|
||||
Args: cli.RequiresMaxArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runStop(dockerCli, options, args)
|
||||
options.builder = rootOpts.builder
|
||||
if len(args) > 0 {
|
||||
options.builder = args[0]
|
||||
}
|
||||
return runStop(dockerCli, options)
|
||||
},
|
||||
}
|
||||
|
||||
|
@@ -12,21 +12,22 @@ import (
|
||||
type useOptions struct {
|
||||
isGlobal bool
|
||||
isDefault bool
|
||||
builder string
|
||||
}
|
||||
|
||||
func runUse(dockerCli command.Cli, in useOptions, name string) error {
|
||||
func runUse(dockerCli command.Cli, in useOptions) error {
|
||||
txn, release, err := getStore(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer release()
|
||||
|
||||
if _, err := txn.NodeGroupByName(name); err != nil {
|
||||
if _, err := txn.NodeGroupByName(in.builder); err != nil {
|
||||
if os.IsNotExist(errors.Cause(err)) {
|
||||
if name == "default" && name != dockerCli.CurrentContext() {
|
||||
if in.builder == "default" && in.builder != dockerCli.CurrentContext() {
|
||||
return errors.Errorf("run `docker context use default` to switch to default context")
|
||||
}
|
||||
if name == "default" || name == dockerCli.CurrentContext() {
|
||||
if in.builder == "default" || in.builder == dockerCli.CurrentContext() {
|
||||
ep, err := getCurrentEndpoint(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -41,35 +42,39 @@ func runUse(dockerCli command.Cli, in useOptions, name string) error {
|
||||
return err
|
||||
}
|
||||
for _, l := range list {
|
||||
if l.Name == name {
|
||||
return errors.Errorf("run `docker context use %s` to switch to context %s", name, name)
|
||||
if l.Name == in.builder {
|
||||
return errors.Errorf("run `docker context use %s` to switch to context %s", in.builder, in.builder)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return errors.Wrapf(err, "failed to find instance %q", name)
|
||||
return errors.Wrapf(err, "failed to find instance %q", in.builder)
|
||||
}
|
||||
|
||||
ep, err := getCurrentEndpoint(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := txn.SetCurrent(ep, name, in.isGlobal, in.isDefault); err != nil {
|
||||
if err := txn.SetCurrent(ep, in.builder, in.isGlobal, in.isDefault); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func useCmd(dockerCli command.Cli) *cobra.Command {
|
||||
func useCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
var options useOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "use [OPTIONS] NAME",
|
||||
Short: "Set the current builder instance",
|
||||
Args: cli.ExactArgs(1),
|
||||
Args: cli.RequiresMaxArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runUse(dockerCli, options, args[0])
|
||||
options.builder = rootOpts.builder
|
||||
if len(args) > 0 {
|
||||
options.builder = args[0]
|
||||
}
|
||||
return runUse(dockerCli, options)
|
||||
},
|
||||
}
|
||||
|
||||
|
153
commands/util.go
153
commands/util.go
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/driver"
|
||||
@@ -11,22 +12,37 @@ import (
|
||||
"github.com/docker/buildx/util/platformutil"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/context/docker"
|
||||
"github.com/docker/cli/cli/context/kubernetes"
|
||||
dopts "github.com/docker/cli/opts"
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// getStore returns current builder instance store
|
||||
func getStore(dockerCli command.Cli) (*store.Txn, func(), error) {
|
||||
dir := filepath.Dir(dockerCli.ConfigFile().Filename)
|
||||
s, err := store.New(dir)
|
||||
s, err := store.New(getConfigStorePath(dockerCli))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return s.Txn()
|
||||
}
|
||||
|
||||
// getConfigStorePath will look for correct configuration store path;
|
||||
// if `$BUILDX_CONFIG` is set - use it, otherwise use parent directory
|
||||
// of Docker config file (i.e. `${DOCKER_CONFIG}/buildx`)
|
||||
func getConfigStorePath(dockerCli command.Cli) string {
|
||||
if buildxConfig := os.Getenv("BUILDX_CONFIG"); buildxConfig != "" {
|
||||
logrus.Debugf("using config store %q based in \"$BUILDX_CONFIG\" environment variable", buildxConfig)
|
||||
return buildxConfig
|
||||
}
|
||||
|
||||
buildxConfig := filepath.Join(filepath.Dir(dockerCli.ConfigFile().Filename), "buildx")
|
||||
logrus.Debugf("using default config store %q", buildxConfig)
|
||||
return buildxConfig
|
||||
}
|
||||
|
||||
// getCurrentEndpoint returns the current default endpoint value
|
||||
func getCurrentEndpoint(dockerCli command.Cli) (string, error) {
|
||||
name := dockerCli.CurrentContext()
|
||||
@@ -133,7 +149,7 @@ func getNodeGroup(txn *store.Txn, dockerCli command.Cli, name string) (*store.No
|
||||
}
|
||||
|
||||
// driversForNodeGroup returns drivers for a nodegroup instance
|
||||
func driversForNodeGroup(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup) ([]build.DriverInfo, error) {
|
||||
func driversForNodeGroup(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, contextPathHash string) ([]build.DriverInfo, error) {
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
|
||||
dis := make([]build.DriverInfo, len(ng.Nodes))
|
||||
@@ -174,7 +190,37 @@ func driversForNodeGroup(ctx context.Context, dockerCli command.Cli, ng *store.N
|
||||
// TODO: replace the following line with dockerclient.WithAPIVersionNegotiation option in clientForEndpoint
|
||||
dockerapi.NegotiateAPIVersion(ctx)
|
||||
|
||||
d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, f, dockerapi)
|
||||
contextStore := dockerCli.ContextStore()
|
||||
|
||||
var kcc driver.KubeClientConfig
|
||||
kcc, err = kubernetes.ConfigFromContext(n.Endpoint, contextStore)
|
||||
if err != nil {
|
||||
// err is returned if n.Endpoint is non-context name like "unix:///var/run/docker.sock".
|
||||
// try again with name="default".
|
||||
// FIXME: n should retain real context name.
|
||||
kcc, err = kubernetes.ConfigFromContext("default", contextStore)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
tryToUseKubeConfigInCluster := false
|
||||
if kcc == nil {
|
||||
tryToUseKubeConfigInCluster = true
|
||||
} else {
|
||||
if _, err := kcc.ClientConfig(); err != nil {
|
||||
tryToUseKubeConfigInCluster = true
|
||||
}
|
||||
}
|
||||
if tryToUseKubeConfigInCluster {
|
||||
kccInCluster := driver.KubeClientConfigInCluster{}
|
||||
if _, err := kccInCluster.ClientConfig(); err == nil {
|
||||
logrus.Debug("using kube config in cluster")
|
||||
kcc = kccInCluster
|
||||
}
|
||||
}
|
||||
|
||||
d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, f, dockerapi, dockerCli.ConfigFile(), kcc, n.Flags, n.ConfigFile, assignDriverOptsByDriverInfo(n.DriverOpts, di), contextPathHash)
|
||||
if err != nil {
|
||||
di.Err = err
|
||||
return nil
|
||||
@@ -192,6 +238,20 @@ func driversForNodeGroup(ctx context.Context, dockerCli command.Cli, ng *store.N
|
||||
return dis, nil
|
||||
}
|
||||
|
||||
// pass platform as driver opts to provide for some drive, like kubernetes
|
||||
func assignDriverOptsByDriverInfo(opts map[string]string, driveInfo build.DriverInfo) map[string]string {
|
||||
m := map[string]string{}
|
||||
|
||||
if len(driveInfo.Platform) > 0 {
|
||||
m["platform"] = strings.Join(platformutil.Format(driveInfo.Platform), ",")
|
||||
}
|
||||
|
||||
for key := range opts {
|
||||
m[key] = opts[key]
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// clientForEndpoint returns a docker client for an endpoint
|
||||
func clientForEndpoint(dockerCli command.Cli, name string) (dockerclient.APIClient, error) {
|
||||
list, err := dockerCli.ContextStore().List()
|
||||
@@ -234,24 +294,66 @@ func clientForEndpoint(dockerCli command.Cli, name string) (dockerclient.APIClie
|
||||
return dockerclient.NewClientWithOpts(clientOpts...)
|
||||
}
|
||||
|
||||
// getDefaultDrivers returns drivers based on current cli config
|
||||
func getDefaultDrivers(ctx context.Context, dockerCli command.Cli) ([]build.DriverInfo, error) {
|
||||
func getInstanceOrDefault(ctx context.Context, dockerCli command.Cli, instance, contextPathHash string) ([]build.DriverInfo, error) {
|
||||
var defaultOnly bool
|
||||
|
||||
if instance == "default" && instance != dockerCli.CurrentContext() {
|
||||
return nil, errors.Errorf("use `docker --context=default buildx` to switch to default context")
|
||||
}
|
||||
if instance == "default" || instance == dockerCli.CurrentContext() {
|
||||
instance = ""
|
||||
defaultOnly = true
|
||||
}
|
||||
list, err := dockerCli.ContextStore().List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, l := range list {
|
||||
if l.Name == instance {
|
||||
return nil, errors.Errorf("use `docker --context=%s buildx` to switch to context %s", instance, instance)
|
||||
}
|
||||
}
|
||||
|
||||
if instance != "" {
|
||||
return getInstanceByName(ctx, dockerCli, instance, contextPathHash)
|
||||
}
|
||||
return getDefaultDrivers(ctx, dockerCli, defaultOnly, contextPathHash)
|
||||
}
|
||||
|
||||
func getInstanceByName(ctx context.Context, dockerCli command.Cli, instance, contextPathHash string) ([]build.DriverInfo, error) {
|
||||
txn, release, err := getStore(dockerCli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer release()
|
||||
|
||||
ng, err := getCurrentInstance(txn, dockerCli)
|
||||
ng, err := txn.NodeGroupByName(instance)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return driversForNodeGroup(ctx, dockerCli, ng, contextPathHash)
|
||||
}
|
||||
|
||||
if ng != nil {
|
||||
return driversForNodeGroup(ctx, dockerCli, ng)
|
||||
// getDefaultDrivers returns drivers based on current cli config
|
||||
func getDefaultDrivers(ctx context.Context, dockerCli command.Cli, defaultOnly bool, contextPathHash string) ([]build.DriverInfo, error) {
|
||||
txn, release, err := getStore(dockerCli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer release()
|
||||
|
||||
if !defaultOnly {
|
||||
ng, err := getCurrentInstance(txn, dockerCli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ng != nil {
|
||||
return driversForNodeGroup(ctx, dockerCli, ng, contextPathHash)
|
||||
}
|
||||
}
|
||||
|
||||
d, err := driver.GetDriver(ctx, "buildx_buildkit_default", nil, dockerCli.Client())
|
||||
d, err := driver.GetDriver(ctx, "buildx_buildkit_default", nil, dockerCli.Client(), dockerCli.ConfigFile(), nil, nil, "", nil, contextPathHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -294,7 +396,7 @@ func loadInfoData(ctx context.Context, d *dinfo) error {
|
||||
func loadNodeGroupData(ctx context.Context, dockerCli command.Cli, ngi *nginfo) error {
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
|
||||
dis, err := driversForNodeGroup(ctx, dockerCli, ngi.ng)
|
||||
dis, err := driversForNodeGroup(ctx, dockerCli, ngi.ng, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -312,7 +414,34 @@ func loadNodeGroupData(ctx context.Context, dockerCli command.Cli, ngi *nginfo)
|
||||
}(&ngi.drivers[i])
|
||||
}
|
||||
|
||||
return eg.Wait()
|
||||
if eg.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// skip when multi drivers
|
||||
if len(ngi.drivers) == 1 {
|
||||
for _, di := range ngi.drivers {
|
||||
// dynamic nodes are used in Kubernetes driver.
|
||||
// Kubernetes pods are dynamically mapped to BuildKit Nodes.
|
||||
if di.info != nil && len(di.info.DynamicNodes) > 0 {
|
||||
var drivers []dinfo
|
||||
for i := 0; i < len(di.info.DynamicNodes); i++ {
|
||||
// all []dinfo share *build.DriverInfo and *driver.Info
|
||||
diClone := di
|
||||
if pl := di.info.DynamicNodes[i].Platforms; len(pl) > 0 {
|
||||
diClone.platforms = pl
|
||||
}
|
||||
drivers = append(drivers, di)
|
||||
}
|
||||
// not append (remove the static nodes in the store)
|
||||
ngi.ng.Nodes = di.info.DynamicNodes
|
||||
ngi.ng.Dynamic = true
|
||||
ngi.drivers = drivers
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func dockerAPI(dockerCli command.Cli) *api {
|
||||
|
6
driver/bkimage/bkimage.go
Normal file
6
driver/bkimage/bkimage.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package bkimage
|
||||
|
||||
const (
|
||||
DefaultImage = "moby/buildkit:buildx-stable-1" // TODO: make this verified
|
||||
DefaultRootlessImage = "moby/buildkit:v0.6.2-rootless"
|
||||
)
|
@@ -1,6 +1,8 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -9,6 +11,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/buildx/driver"
|
||||
"github.com/docker/buildx/driver/bkimage"
|
||||
"github.com/docker/buildx/util/imagetools"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/docker/api/types"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
@@ -20,11 +24,20 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var buildkitImage = "moby/buildkit:master" // TODO: make this verified and configuratble
|
||||
|
||||
type Driver struct {
|
||||
driver.InitConfig
|
||||
factory driver.Factory
|
||||
netMode string
|
||||
image string
|
||||
env []string
|
||||
}
|
||||
|
||||
func (d *Driver) IsMobyDriver() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *Driver) Config() driver.InitConfig {
|
||||
return d.InitConfig
|
||||
}
|
||||
|
||||
func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error {
|
||||
@@ -40,7 +53,7 @@ func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error {
|
||||
if err := d.start(ctx, sub); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.wait(ctx); err != nil {
|
||||
if err := d.wait(ctx, sub); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -49,29 +62,67 @@ func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error {
|
||||
}
|
||||
|
||||
func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
|
||||
if err := l.Wrap("pulling image "+buildkitImage, func() error {
|
||||
rc, err := d.DockerAPI.ImageCreate(ctx, buildkitImage, types.ImageCreateOptions{})
|
||||
imageName := bkimage.DefaultImage
|
||||
if d.image != "" {
|
||||
imageName = d.image
|
||||
}
|
||||
|
||||
if err := l.Wrap("pulling image "+imageName, func() error {
|
||||
ra, err := imagetools.RegistryAuthForRef(imageName, d.Auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rc, err := d.DockerAPI.ImageCreate(ctx, imageName, types.ImageCreateOptions{
|
||||
RegistryAuth: ra,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(ioutil.Discard, rc)
|
||||
return err
|
||||
}); err != nil {
|
||||
return err
|
||||
// image pulling failed, check if it exists in local image store.
|
||||
// if not, return pulling error. otherwise log it.
|
||||
_, _, errInspect := d.DockerAPI.ImageInspectWithRaw(ctx, imageName)
|
||||
if errInspect != nil {
|
||||
return err
|
||||
}
|
||||
l.Wrap("pulling failed, using local image "+imageName, func() error { return nil })
|
||||
}
|
||||
|
||||
cfg := &container.Config{
|
||||
Image: imageName,
|
||||
Env: d.env,
|
||||
}
|
||||
if d.InitConfig.BuildkitFlags != nil {
|
||||
cfg.Cmd = d.InitConfig.BuildkitFlags
|
||||
}
|
||||
|
||||
if err := l.Wrap("creating container "+d.Name, func() error {
|
||||
_, err := d.DockerAPI.ContainerCreate(ctx, &container.Config{
|
||||
Image: buildkitImage,
|
||||
}, &container.HostConfig{
|
||||
hc := &container.HostConfig{
|
||||
Privileged: true,
|
||||
}, &network.NetworkingConfig{}, d.Name)
|
||||
UsernsMode: "host",
|
||||
}
|
||||
if d.netMode != "" {
|
||||
hc.NetworkMode = container.NetworkMode(d.netMode)
|
||||
}
|
||||
_, err := d.DockerAPI.ContainerCreate(ctx, cfg, hc, &network.NetworkingConfig{}, nil, d.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f := d.InitConfig.ConfigFile; f != "" {
|
||||
buf, err := readFileToTar(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.DockerAPI.CopyToContainer(ctx, d.Name, "/", buf, dockertypes.CopyToContainerOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := d.start(ctx, l); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.wait(ctx); err != nil {
|
||||
if err := d.wait(ctx, l); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -81,17 +132,28 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) wait(ctx context.Context) error {
|
||||
try := 0
|
||||
func (d *Driver) wait(ctx context.Context, l progress.SubLogger) error {
|
||||
try := 1
|
||||
for {
|
||||
if err := d.run(ctx, []string{"buildctl", "debug", "workers"}); err != nil {
|
||||
if try > 10 {
|
||||
bufStdout := &bytes.Buffer{}
|
||||
bufStderr := &bytes.Buffer{}
|
||||
if err := d.run(ctx, []string{"buildctl", "debug", "workers"}, bufStdout, bufStderr); err != nil {
|
||||
if try > 15 {
|
||||
if err != nil {
|
||||
d.copyLogs(context.TODO(), l)
|
||||
if bufStdout.Len() != 0 {
|
||||
l.Log(1, bufStdout.Bytes())
|
||||
}
|
||||
if bufStderr.Len() != 0 {
|
||||
l.Log(2, bufStderr.Bytes())
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(time.Duration(100+try*20) * time.Millisecond):
|
||||
case <-time.After(time.Duration(try*120) * time.Millisecond):
|
||||
try++
|
||||
continue
|
||||
}
|
||||
@@ -100,6 +162,21 @@ func (d *Driver) wait(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Driver) copyLogs(ctx context.Context, l progress.SubLogger) error {
|
||||
rc, err := d.DockerAPI.ContainerLogs(ctx, d.Name, types.ContainerLogsOptions{
|
||||
ShowStdout: true, ShowStderr: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stdout := &logWriter{logger: l, stream: 1}
|
||||
stderr := &logWriter{logger: l, stream: 2}
|
||||
if _, err := stdcopy.StdCopy(stdout, stderr, rc); err != nil {
|
||||
return err
|
||||
}
|
||||
return rc.Close()
|
||||
}
|
||||
|
||||
func (d *Driver) exec(ctx context.Context, cmd []string) (string, net.Conn, error) {
|
||||
execConfig := types.ExecConfig{
|
||||
Cmd: cmd,
|
||||
@@ -124,12 +201,12 @@ func (d *Driver) exec(ctx context.Context, cmd []string) (string, net.Conn, erro
|
||||
return execID, resp.Conn, nil
|
||||
}
|
||||
|
||||
func (d *Driver) run(ctx context.Context, cmd []string) error {
|
||||
func (d *Driver) run(ctx context.Context, cmd []string, stdout, stderr io.Writer) (err error) {
|
||||
id, conn, err := d.exec(ctx, cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(ioutil.Discard, conn); err != nil {
|
||||
if _, err := stdcopy.StdCopy(stdout, stderr, conn); err != nil {
|
||||
return err
|
||||
}
|
||||
conn.Close()
|
||||
@@ -202,7 +279,7 @@ func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
||||
|
||||
conn = demuxConn(conn)
|
||||
|
||||
return client.New(ctx, "", client.WithDialer(func(string, time.Duration) (net.Conn, error) {
|
||||
return client.New(ctx, "", client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
||||
return conn, nil
|
||||
}))
|
||||
}
|
||||
@@ -224,7 +301,7 @@ func (d *Driver) Features() map[driver.Feature]bool {
|
||||
func demuxConn(c net.Conn) net.Conn {
|
||||
pr, pw := io.Pipe()
|
||||
// TODO: rewrite parser with Reader() to avoid goroutine switch
|
||||
go stdcopy.StdCopy(pw, os.Stdout, c)
|
||||
go stdcopy.StdCopy(pw, os.Stderr, c)
|
||||
return &demux{
|
||||
Conn: c,
|
||||
Reader: pr,
|
||||
@@ -239,3 +316,36 @@ type demux struct {
|
||||
func (d *demux) Read(dt []byte) (int, error) {
|
||||
return d.Reader.Read(dt)
|
||||
}
|
||||
|
||||
func readFileToTar(fn string) (*bytes.Buffer, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
tw := tar.NewWriter(buf)
|
||||
dt, err := ioutil.ReadFile(fn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := tw.WriteHeader(&tar.Header{
|
||||
Name: "/etc/buildkit/buildkitd.toml",
|
||||
Size: int64(len(dt)),
|
||||
Mode: 0644,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := tw.Write(dt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
type logWriter struct {
|
||||
logger progress.SubLogger
|
||||
stream int
|
||||
}
|
||||
|
||||
func (l *logWriter) Write(dt []byte) (int, error) {
|
||||
l.logger.Log(l.stream, dt)
|
||||
return len(dt), nil
|
||||
}
|
||||
|
@@ -2,6 +2,8 @@ package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/driver"
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
@@ -37,8 +39,28 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
|
||||
if cfg.DockerAPI == nil {
|
||||
return nil, errors.Errorf("%s driver requires docker API access", f.Name())
|
||||
}
|
||||
d := &Driver{factory: f, InitConfig: cfg}
|
||||
for k, v := range cfg.DriverOpts {
|
||||
switch {
|
||||
case k == "network":
|
||||
d.netMode = v
|
||||
if v == "host" {
|
||||
d.InitConfig.BuildkitFlags = append(d.InitConfig.BuildkitFlags, "--allow-insecure-entitlement=network.host")
|
||||
}
|
||||
case k == "image":
|
||||
d.image = v
|
||||
case strings.HasPrefix(k, "env."):
|
||||
envName := strings.TrimPrefix(k, "env.")
|
||||
if envName == "" {
|
||||
return nil, errors.Errorf("invalid env option %q, expecting env.FOO=bar", k)
|
||||
}
|
||||
d.env = append(d.env, fmt.Sprintf("%s=%s", envName, v))
|
||||
default:
|
||||
return nil, errors.Errorf("invalid driver option %s for docker-container driver", k)
|
||||
}
|
||||
}
|
||||
|
||||
return &Driver{factory: f, InitConfig: cfg}, nil
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (f *factory) AllowsInstances() bool {
|
||||
|
@@ -3,7 +3,6 @@ package docker
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/docker/buildx/driver"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
@@ -39,7 +38,7 @@ func (d *Driver) Rm(ctx context.Context, force bool) error {
|
||||
}
|
||||
|
||||
func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
||||
return client.New(ctx, "", client.WithDialer(func(string, time.Duration) (net.Conn, error) {
|
||||
return client.New(ctx, "", client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
||||
return d.DockerAPI.DialHijack(ctx, "/grpc", "h2c", nil)
|
||||
}))
|
||||
}
|
||||
@@ -48,9 +47,8 @@ func (d *Driver) Features() map[driver.Feature]bool {
|
||||
return map[driver.Feature]bool{
|
||||
driver.OCIExporter: false,
|
||||
driver.DockerExporter: false,
|
||||
|
||||
driver.CacheExport: false,
|
||||
driver.MultiPlatform: false,
|
||||
driver.CacheExport: false,
|
||||
driver.MultiPlatform: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,4 +56,10 @@ func (d *Driver) Factory() driver.Factory {
|
||||
return d.factory
|
||||
}
|
||||
|
||||
func (d *Driver) IsDefaultMobyDriver() {}
|
||||
func (d *Driver) IsMobyDriver() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *Driver) Config() driver.InitConfig {
|
||||
return d.InitConfig
|
||||
}
|
||||
|
@@ -44,6 +44,9 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
|
||||
if cfg.DockerAPI == nil {
|
||||
return nil, errors.Errorf("docker driver requires docker API access")
|
||||
}
|
||||
if cfg.ConfigFile != "" {
|
||||
return nil, errors.Errorf("setting config file is not supported for docker driver, use dockerd configuration file")
|
||||
}
|
||||
|
||||
return &Driver{factory: f, InitConfig: cfg}, nil
|
||||
}
|
||||
|
@@ -3,7 +3,9 @@ package driver
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/buildx/store"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
clitypes "github.com/docker/cli/cli/config/types"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@@ -39,6 +41,12 @@ func (s Status) String() string {
|
||||
|
||||
type Info struct {
|
||||
Status Status
|
||||
// DynamicNodes must be empty if the actual nodes are statically listed in the store
|
||||
DynamicNodes []store.Node
|
||||
}
|
||||
|
||||
type Auth interface {
|
||||
GetAuthConfig(registryHostname string) (clitypes.AuthConfig, error)
|
||||
}
|
||||
|
||||
type Driver interface {
|
||||
@@ -49,6 +57,8 @@ type Driver interface {
|
||||
Rm(ctx context.Context, force bool) error
|
||||
Client(ctx context.Context) (*client.Client, error)
|
||||
Features() map[Feature]bool
|
||||
IsMobyDriver() bool
|
||||
Config() InitConfig
|
||||
}
|
||||
|
||||
func Boot(ctx context.Context, d Driver, pw progress.Writer) (*client.Client, error) {
|
||||
@@ -63,11 +73,7 @@ func Boot(ctx context.Context, d Driver, pw progress.Writer) (*client.Client, er
|
||||
if try > 2 {
|
||||
return nil, errors.Errorf("failed to bootstrap %T driver in attempts", d)
|
||||
}
|
||||
if err := d.Bootstrap(ctx, func(s *client.SolveStatus) {
|
||||
if pw != nil {
|
||||
pw.Status() <- s
|
||||
}
|
||||
}); err != nil {
|
||||
if err := d.Bootstrap(ctx, pw.Write); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
189
driver/kubernetes/driver.go
Normal file
189
driver/kubernetes/driver.go
Normal file
@@ -0,0 +1,189 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/buildx/driver"
|
||||
"github.com/docker/buildx/driver/kubernetes/execconn"
|
||||
"github.com/docker/buildx/driver/kubernetes/manifest"
|
||||
"github.com/docker/buildx/driver/kubernetes/podchooser"
|
||||
"github.com/docker/buildx/store"
|
||||
"github.com/docker/buildx/util/platformutil"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/pkg/errors"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
clientappsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
|
||||
clientcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
DriverName = "kubernetes"
|
||||
)
|
||||
|
||||
const (
|
||||
// valid values for driver-opt loadbalance
|
||||
LoadbalanceRandom = "random"
|
||||
LoadbalanceSticky = "sticky"
|
||||
)
|
||||
|
||||
type Driver struct {
|
||||
driver.InitConfig
|
||||
factory driver.Factory
|
||||
minReplicas int
|
||||
deployment *appsv1.Deployment
|
||||
clientset *kubernetes.Clientset
|
||||
deploymentClient clientappsv1.DeploymentInterface
|
||||
podClient clientcorev1.PodInterface
|
||||
podChooser podchooser.PodChooser
|
||||
}
|
||||
|
||||
func (d *Driver) IsMobyDriver() bool {
|
||||
return false
|
||||
}
|
||||
func (d *Driver) Config() driver.InitConfig {
|
||||
return d.InitConfig
|
||||
}
|
||||
|
||||
func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error {
|
||||
return progress.Wrap("[internal] booting buildkit", l, func(sub progress.SubLogger) error {
|
||||
_, err := d.deploymentClient.Get(ctx, d.deployment.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
// TODO: return err if err != ErrNotFound
|
||||
_, err = d.deploymentClient.Create(ctx, d.deployment, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error while calling deploymentClient.Create for %q", d.deployment.Name)
|
||||
}
|
||||
}
|
||||
return sub.Wrap(
|
||||
fmt.Sprintf("waiting for %d pods to be ready", d.minReplicas),
|
||||
func() error {
|
||||
if err := d.wait(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Driver) wait(ctx context.Context) error {
|
||||
// TODO: use watch API
|
||||
var (
|
||||
err error
|
||||
depl *appsv1.Deployment
|
||||
)
|
||||
for try := 0; try < 100; try++ {
|
||||
depl, err = d.deploymentClient.Get(ctx, d.deployment.Name, metav1.GetOptions{})
|
||||
if err == nil {
|
||||
if depl.Status.ReadyReplicas >= int32(d.minReplicas) {
|
||||
return nil
|
||||
}
|
||||
err = errors.Errorf("expected %d replicas to be ready, got %d",
|
||||
d.minReplicas, depl.Status.ReadyReplicas)
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(time.Duration(100+try*20) * time.Millisecond):
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
|
||||
depl, err := d.deploymentClient.Get(ctx, d.deployment.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
// TODO: return err if err != ErrNotFound
|
||||
return &driver.Info{
|
||||
Status: driver.Inactive,
|
||||
}, nil
|
||||
}
|
||||
if depl.Status.ReadyReplicas <= 0 {
|
||||
return &driver.Info{
|
||||
Status: driver.Stopped,
|
||||
}, nil
|
||||
}
|
||||
pods, err := podchooser.ListRunningPods(ctx, d.podClient, depl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var dynNodes []store.Node
|
||||
for _, p := range pods {
|
||||
node := store.Node{
|
||||
Name: p.Name,
|
||||
// Other fields are unset (TODO: detect real platforms)
|
||||
}
|
||||
|
||||
if p.Annotations != nil {
|
||||
if p, ok := p.Annotations[manifest.AnnotationPlatform]; ok {
|
||||
ps, err := platformutil.Parse(strings.Split(p, ","))
|
||||
if err == nil {
|
||||
node.Platforms = ps
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dynNodes = append(dynNodes, node)
|
||||
}
|
||||
return &driver.Info{
|
||||
Status: driver.Running,
|
||||
DynamicNodes: dynNodes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Driver) Stop(ctx context.Context, force bool) error {
|
||||
// future version may scale the replicas to zero here
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) Rm(ctx context.Context, force bool) error {
|
||||
if err := d.deploymentClient.Delete(ctx, d.deployment.Name, metav1.DeleteOptions{}); err != nil {
|
||||
return errors.Wrapf(err, "error while calling deploymentClient.Delete for %q", d.deployment.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
||||
restClient := d.clientset.CoreV1().RESTClient()
|
||||
restClientConfig, err := d.KubeClientConfig.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pod, err := d.podChooser.ChoosePod(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(pod.Spec.Containers) == 0 {
|
||||
return nil, errors.Errorf("pod %s does not have any container", pod.Name)
|
||||
}
|
||||
containerName := pod.Spec.Containers[0].Name
|
||||
cmd := []string{"buildctl", "dial-stdio"}
|
||||
conn, err := execconn.ExecConn(restClient, restClientConfig,
|
||||
pod.Namespace, pod.Name, containerName, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.New(ctx, "", client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
||||
return conn, nil
|
||||
}))
|
||||
}
|
||||
|
||||
func (d *Driver) Factory() driver.Factory {
|
||||
return d.factory
|
||||
}
|
||||
|
||||
func (d *Driver) Features() map[driver.Feature]bool {
|
||||
return map[driver.Feature]bool{
|
||||
driver.OCIExporter: true,
|
||||
driver.DockerExporter: d.DockerAPI != nil,
|
||||
|
||||
driver.CacheExport: true,
|
||||
driver.MultiPlatform: true, // Untested (needs multiple Driver instances)
|
||||
}
|
||||
}
|
135
driver/kubernetes/execconn/execconn.go
Normal file
135
driver/kubernetes/execconn/execconn.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package execconn
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
func ExecConn(restClient rest.Interface, restConfig *rest.Config, namespace, pod, container string, cmd []string) (net.Conn, error) {
|
||||
req := restClient.
|
||||
Post().
|
||||
Namespace(namespace).
|
||||
Resource("pods").
|
||||
Name(pod).
|
||||
SubResource("exec").
|
||||
VersionedParams(&corev1.PodExecOptions{
|
||||
Container: container,
|
||||
Command: cmd,
|
||||
Stdin: true,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
TTY: false,
|
||||
}, scheme.ParameterCodec)
|
||||
exec, err := remotecommand.NewSPDYExecutor(restConfig, "POST", req.URL())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stdinR, stdinW := io.Pipe()
|
||||
stdoutR, stdoutW := io.Pipe()
|
||||
kc := &kubeConn{
|
||||
stdin: stdinW,
|
||||
stdout: stdoutR,
|
||||
localAddr: dummyAddr{network: "dummy", s: "dummy-0"},
|
||||
remoteAddr: dummyAddr{network: "dummy", s: "dummy-1"},
|
||||
}
|
||||
go func() {
|
||||
serr := exec.Stream(remotecommand.StreamOptions{
|
||||
Stdin: stdinR,
|
||||
Stdout: stdoutW,
|
||||
Stderr: os.Stderr,
|
||||
Tty: false,
|
||||
})
|
||||
if serr != nil {
|
||||
logrus.Error(serr)
|
||||
}
|
||||
}()
|
||||
return kc, nil
|
||||
}
|
||||
|
||||
type kubeConn struct {
|
||||
stdin io.WriteCloser
|
||||
stdout io.ReadCloser
|
||||
stdioClosedMu sync.Mutex // for stdinClosed and stdoutClosed
|
||||
stdinClosed bool
|
||||
stdoutClosed bool
|
||||
localAddr net.Addr
|
||||
remoteAddr net.Addr
|
||||
}
|
||||
|
||||
func (c *kubeConn) Write(p []byte) (int, error) {
|
||||
return c.stdin.Write(p)
|
||||
}
|
||||
|
||||
func (c *kubeConn) Read(p []byte) (int, error) {
|
||||
return c.stdout.Read(p)
|
||||
}
|
||||
|
||||
func (c *kubeConn) CloseWrite() error {
|
||||
err := c.stdin.Close()
|
||||
c.stdioClosedMu.Lock()
|
||||
c.stdinClosed = true
|
||||
c.stdioClosedMu.Unlock()
|
||||
return err
|
||||
}
|
||||
func (c *kubeConn) CloseRead() error {
|
||||
err := c.stdout.Close()
|
||||
c.stdioClosedMu.Lock()
|
||||
c.stdoutClosed = true
|
||||
c.stdioClosedMu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *kubeConn) Close() error {
|
||||
var err error
|
||||
c.stdioClosedMu.Lock()
|
||||
stdinClosed := c.stdinClosed
|
||||
c.stdioClosedMu.Unlock()
|
||||
if !stdinClosed {
|
||||
err = c.CloseWrite()
|
||||
}
|
||||
c.stdioClosedMu.Lock()
|
||||
stdoutClosed := c.stdoutClosed
|
||||
c.stdioClosedMu.Unlock()
|
||||
if !stdoutClosed {
|
||||
err = c.CloseRead()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *kubeConn) LocalAddr() net.Addr {
|
||||
return c.localAddr
|
||||
}
|
||||
func (c *kubeConn) RemoteAddr() net.Addr {
|
||||
return c.remoteAddr
|
||||
}
|
||||
func (c *kubeConn) SetDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
func (c *kubeConn) SetReadDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
func (c *kubeConn) SetWriteDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type dummyAddr struct {
|
||||
network string
|
||||
s string
|
||||
}
|
||||
|
||||
func (d dummyAddr) Network() string {
|
||||
return d.network
|
||||
}
|
||||
|
||||
func (d dummyAddr) String() string {
|
||||
return d.s
|
||||
}
|
165
driver/kubernetes/factory.go
Normal file
165
driver/kubernetes/factory.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/driver"
|
||||
"github.com/docker/buildx/driver/bkimage"
|
||||
"github.com/docker/buildx/driver/kubernetes/manifest"
|
||||
"github.com/docker/buildx/driver/kubernetes/podchooser"
|
||||
"github.com/docker/buildx/util/platformutil"
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
const prioritySupported = 40
|
||||
const priorityUnsupported = 80
|
||||
|
||||
func init() {
|
||||
driver.Register(&factory{})
|
||||
}
|
||||
|
||||
type factory struct {
|
||||
}
|
||||
|
||||
func (*factory) Name() string {
|
||||
return DriverName
|
||||
}
|
||||
|
||||
func (*factory) Usage() string {
|
||||
return DriverName
|
||||
}
|
||||
|
||||
func (*factory) Priority(ctx context.Context, api dockerclient.APIClient) int {
|
||||
if api == nil {
|
||||
return priorityUnsupported
|
||||
}
|
||||
return prioritySupported
|
||||
}
|
||||
|
||||
func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver, error) {
|
||||
if cfg.KubeClientConfig == nil {
|
||||
return nil, errors.Errorf("%s driver requires kubernetes API access", DriverName)
|
||||
}
|
||||
deploymentName, err := buildxNameToDeploymentName(cfg.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
namespace, _, err := cfg.KubeClientConfig.Namespace()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot determine Kubernetes namespace, specify manually")
|
||||
}
|
||||
restClientConfig, err := cfg.KubeClientConfig.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientset, err := kubernetes.NewForConfig(restClientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d := &Driver{
|
||||
factory: f,
|
||||
InitConfig: cfg,
|
||||
clientset: clientset,
|
||||
}
|
||||
deploymentOpt := &manifest.DeploymentOpt{
|
||||
Name: deploymentName,
|
||||
Image: bkimage.DefaultImage,
|
||||
Replicas: 1,
|
||||
BuildkitFlags: cfg.BuildkitFlags,
|
||||
Rootless: false,
|
||||
}
|
||||
loadbalance := LoadbalanceSticky
|
||||
imageOverride := ""
|
||||
for k, v := range cfg.DriverOpts {
|
||||
switch k {
|
||||
case "image":
|
||||
imageOverride = v
|
||||
case "namespace":
|
||||
namespace = v
|
||||
case "replicas":
|
||||
deploymentOpt.Replicas, err = strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "rootless":
|
||||
deploymentOpt.Rootless, err = strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deploymentOpt.Image = bkimage.DefaultRootlessImage
|
||||
case "platform":
|
||||
if v != "" {
|
||||
platforms, err := platformutil.Parse(strings.Split(v, ","))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deploymentOpt.Platforms = platforms
|
||||
}
|
||||
case "nodeselector":
|
||||
kvs := strings.Split(strings.Trim(v, `"`), ",")
|
||||
s := map[string]string{}
|
||||
for i := range kvs {
|
||||
kv := strings.Split(kvs[i], "=")
|
||||
if len(kv) == 2 {
|
||||
s[kv[0]] = kv[1]
|
||||
}
|
||||
}
|
||||
deploymentOpt.NodeSelector = s
|
||||
case "loadbalance":
|
||||
switch v {
|
||||
case LoadbalanceSticky:
|
||||
case LoadbalanceRandom:
|
||||
default:
|
||||
return nil, errors.Errorf("invalid loadbalance %q", v)
|
||||
}
|
||||
loadbalance = v
|
||||
default:
|
||||
return nil, errors.Errorf("invalid driver option %s for driver %s", k, DriverName)
|
||||
}
|
||||
}
|
||||
if imageOverride != "" {
|
||||
deploymentOpt.Image = imageOverride
|
||||
}
|
||||
d.deployment, err = manifest.NewDeployment(deploymentOpt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.minReplicas = deploymentOpt.Replicas
|
||||
d.deploymentClient = clientset.AppsV1().Deployments(namespace)
|
||||
d.podClient = clientset.CoreV1().Pods(namespace)
|
||||
switch loadbalance {
|
||||
case LoadbalanceSticky:
|
||||
d.podChooser = &podchooser.StickyPodChooser{
|
||||
Key: cfg.ContextPathHash,
|
||||
PodClient: d.podClient,
|
||||
Deployment: d.deployment,
|
||||
}
|
||||
case LoadbalanceRandom:
|
||||
d.podChooser = &podchooser.RandomPodChooser{
|
||||
PodClient: d.podClient,
|
||||
Deployment: d.deployment,
|
||||
}
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (f *factory) AllowsInstances() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// buildxNameToDeploymentName converts buildx name to Kubernetes Deployment name.
|
||||
//
|
||||
// eg. "buildx_buildkit_loving_mendeleev0" -> "loving-mendeleev0"
|
||||
func buildxNameToDeploymentName(bx string) (string, error) {
|
||||
// TODO: commands.util.go should not pass "buildx_buildkit_" prefix to drivers
|
||||
if !strings.HasPrefix(bx, "buildx_buildkit_") {
|
||||
return "", errors.Errorf("expected a string with \"buildx_buildkit_\", got %q", bx)
|
||||
}
|
||||
s := strings.TrimPrefix(bx, "buildx_buildkit_")
|
||||
s = strings.ReplaceAll(s, "_", "-")
|
||||
return s, nil
|
||||
}
|
110
driver/kubernetes/manifest/manifest.go
Normal file
110
driver/kubernetes/manifest/manifest.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package manifest
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/util/platformutil"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type DeploymentOpt struct {
|
||||
Namespace string
|
||||
Name string
|
||||
Image string
|
||||
Replicas int
|
||||
BuildkitFlags []string
|
||||
Rootless bool
|
||||
NodeSelector map[string]string
|
||||
Platforms []v1.Platform
|
||||
}
|
||||
|
||||
const (
|
||||
containerName = "buildkitd"
|
||||
AnnotationPlatform = "buildx.docker.com/platform"
|
||||
)
|
||||
|
||||
func NewDeployment(opt *DeploymentOpt) (*appsv1.Deployment, error) {
|
||||
labels := map[string]string{
|
||||
"app": opt.Name,
|
||||
}
|
||||
annotations := map[string]string{}
|
||||
replicas := int32(opt.Replicas)
|
||||
privileged := true
|
||||
args := opt.BuildkitFlags
|
||||
|
||||
if len(opt.Platforms) > 0 {
|
||||
annotations[AnnotationPlatform] = strings.Join(platformutil.Format(opt.Platforms), ",")
|
||||
}
|
||||
|
||||
d := &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: appsv1.SchemeGroupVersion.String(),
|
||||
Kind: "Deployment",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: opt.Namespace,
|
||||
Name: opt.Name,
|
||||
Labels: labels,
|
||||
Annotations: annotations,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Replicas: &replicas,
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: labels,
|
||||
},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: labels,
|
||||
Annotations: annotations,
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: containerName,
|
||||
Image: opt.Image,
|
||||
Args: args,
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: &privileged,
|
||||
},
|
||||
ReadinessProbe: &corev1.Probe{
|
||||
Handler: corev1.Handler{
|
||||
Exec: &corev1.ExecAction{
|
||||
Command: []string{"buildctl", "debug", "workers"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if opt.Rootless {
|
||||
if err := toRootless(d); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(opt.NodeSelector) > 0 {
|
||||
d.Spec.Template.Spec.NodeSelector = opt.NodeSelector
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func toRootless(d *appsv1.Deployment) error {
|
||||
d.Spec.Template.Spec.Containers[0].Args = append(
|
||||
d.Spec.Template.Spec.Containers[0].Args,
|
||||
"--oci-worker-no-process-sandbox",
|
||||
)
|
||||
d.Spec.Template.Spec.Containers[0].SecurityContext = nil
|
||||
if d.Spec.Template.ObjectMeta.Annotations == nil {
|
||||
d.Spec.Template.ObjectMeta.Annotations = make(map[string]string, 2)
|
||||
}
|
||||
d.Spec.Template.ObjectMeta.Annotations["container.apparmor.security.beta.kubernetes.io/"+containerName] = "unconfined"
|
||||
d.Spec.Template.ObjectMeta.Annotations["container.seccomp.security.alpha.kubernetes.io/"+containerName] = "unconfined"
|
||||
return nil
|
||||
}
|
97
driver/kubernetes/podchooser/podchooser.go
Normal file
97
driver/kubernetes/podchooser/podchooser.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package podchooser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/serialx/hashring"
|
||||
"github.com/sirupsen/logrus"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
)
|
||||
|
||||
type PodChooser interface {
|
||||
ChoosePod(ctx context.Context) (*corev1.Pod, error)
|
||||
}
|
||||
|
||||
type RandomPodChooser struct {
|
||||
RandSource rand.Source
|
||||
PodClient clientcorev1.PodInterface
|
||||
Deployment *appsv1.Deployment
|
||||
}
|
||||
|
||||
func (pc *RandomPodChooser) ChoosePod(ctx context.Context) (*corev1.Pod, error) {
|
||||
pods, err := ListRunningPods(ctx, pc.PodClient, pc.Deployment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
randSource := pc.RandSource
|
||||
if randSource == nil {
|
||||
randSource = rand.NewSource(time.Now().Unix())
|
||||
}
|
||||
rnd := rand.New(randSource)
|
||||
n := rnd.Int() % len(pods)
|
||||
logrus.Debugf("RandomPodChooser.ChoosePod(): len(pods)=%d, n=%d", len(pods), n)
|
||||
return pods[n], nil
|
||||
}
|
||||
|
||||
type StickyPodChooser struct {
|
||||
Key string
|
||||
PodClient clientcorev1.PodInterface
|
||||
Deployment *appsv1.Deployment
|
||||
}
|
||||
|
||||
func (pc *StickyPodChooser) ChoosePod(ctx context.Context) (*corev1.Pod, error) {
|
||||
pods, err := ListRunningPods(ctx, pc.PodClient, pc.Deployment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var podNames []string
|
||||
podMap := make(map[string]*corev1.Pod, len(pods))
|
||||
for _, pod := range pods {
|
||||
podNames = append(podNames, pod.Name)
|
||||
podMap[pod.Name] = pod
|
||||
}
|
||||
ring := hashring.New(podNames)
|
||||
chosen, ok := ring.GetNode(pc.Key)
|
||||
if !ok {
|
||||
// NOTREACHED
|
||||
logrus.Errorf("no pod found for key %q", pc.Key)
|
||||
rpc := &RandomPodChooser{
|
||||
PodClient: pc.PodClient,
|
||||
Deployment: pc.Deployment,
|
||||
}
|
||||
return rpc.ChoosePod(ctx)
|
||||
}
|
||||
return podMap[chosen], nil
|
||||
}
|
||||
|
||||
func ListRunningPods(ctx context.Context, client clientcorev1.PodInterface, depl *appsv1.Deployment) ([]*corev1.Pod, error) {
|
||||
selector, err := metav1.LabelSelectorAsSelector(depl.Spec.Selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
listOpts := metav1.ListOptions{
|
||||
LabelSelector: selector.String(),
|
||||
}
|
||||
podList, err := client.List(ctx, listOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var runningPods []*corev1.Pod
|
||||
for i := range podList.Items {
|
||||
pod := &podList.Items[i]
|
||||
if pod.Status.Phase == corev1.PodRunning {
|
||||
logrus.Debugf("pod runnning: %q", pod.Name)
|
||||
runningPods = append(runningPods, pod)
|
||||
}
|
||||
}
|
||||
sort.Slice(runningPods, func(i, j int) bool {
|
||||
return runningPods[i].Name < runningPods[j].Name
|
||||
})
|
||||
return runningPods, nil
|
||||
}
|
@@ -2,9 +2,15 @@ package driver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -21,12 +27,36 @@ type BuildkitConfig struct {
|
||||
// Rootless bool
|
||||
}
|
||||
|
||||
type KubeClientConfig interface {
|
||||
ClientConfig() (*rest.Config, error)
|
||||
Namespace() (string, bool, error)
|
||||
}
|
||||
|
||||
type KubeClientConfigInCluster struct{}
|
||||
|
||||
func (k KubeClientConfigInCluster) ClientConfig() (*rest.Config, error) {
|
||||
return rest.InClusterConfig()
|
||||
}
|
||||
|
||||
func (k KubeClientConfigInCluster) Namespace() (string, bool, error) {
|
||||
namespace, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
return strings.TrimSpace(string(namespace)), true, nil
|
||||
}
|
||||
|
||||
type InitConfig struct {
|
||||
// This object needs updates to be generic for different drivers
|
||||
Name string
|
||||
DockerAPI dockerclient.APIClient
|
||||
BuildkitConfig BuildkitConfig
|
||||
Meta map[string]interface{}
|
||||
Name string
|
||||
DockerAPI dockerclient.APIClient
|
||||
KubeClientConfig KubeClientConfig
|
||||
BuildkitFlags []string
|
||||
ConfigFile string
|
||||
DriverOpts map[string]string
|
||||
Auth Auth
|
||||
// ContextPathHash can be used for determining pods in the driver instance
|
||||
ContextPathHash string
|
||||
}
|
||||
|
||||
var drivers map[string]Factory
|
||||
@@ -71,10 +101,16 @@ func GetFactory(name string, instanceRequired bool) Factory {
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetDriver(ctx context.Context, name string, f Factory, api dockerclient.APIClient) (Driver, error) {
|
||||
func GetDriver(ctx context.Context, name string, f Factory, api dockerclient.APIClient, auth Auth, kcc KubeClientConfig, flags []string, config string, do map[string]string, contextPathHash string) (Driver, error) {
|
||||
ic := InitConfig{
|
||||
DockerAPI: api,
|
||||
Name: name,
|
||||
DockerAPI: api,
|
||||
KubeClientConfig: kcc,
|
||||
Name: name,
|
||||
BuildkitFlags: flags,
|
||||
ConfigFile: config,
|
||||
DriverOpts: do,
|
||||
Auth: auth,
|
||||
ContextPathHash: contextPathHash,
|
||||
}
|
||||
if f == nil {
|
||||
var err error
|
||||
@@ -83,5 +119,27 @@ func GetDriver(ctx context.Context, name string, f Factory, api dockerclient.API
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return f.New(ctx, ic)
|
||||
d, err := f.New(ctx, ic)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cachedDriver{Driver: d}, nil
|
||||
}
|
||||
|
||||
func GetFactories() map[string]Factory {
|
||||
return drivers
|
||||
}
|
||||
|
||||
type cachedDriver struct {
|
||||
Driver
|
||||
client *client.Client
|
||||
err error
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (d *cachedDriver) Client(ctx context.Context) (*client.Client, error) {
|
||||
d.once.Do(func() {
|
||||
d.client, d.err = d.Driver.Client(ctx)
|
||||
})
|
||||
return d.client, d.err
|
||||
}
|
||||
|
96
go.mod
96
go.mod
@@ -1,88 +1,68 @@
|
||||
module github.com/docker/buildx
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
||||
github.com/Microsoft/hcsshim v0.8.6 // indirect
|
||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
|
||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect
|
||||
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 // indirect
|
||||
github.com/bitly/go-simplejson v0.5.0 // indirect
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
||||
github.com/bugsnag/bugsnag-go v1.4.1 // indirect
|
||||
github.com/bugsnag/panicwrap v1.2.0 // indirect
|
||||
github.com/cenkalti/backoff v2.1.1+incompatible // indirect
|
||||
github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e // indirect
|
||||
github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50
|
||||
github.com/containerd/containerd v1.3.0-0.20190426060238-3a3f0aac8819
|
||||
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448 // indirect
|
||||
github.com/containerd/typeurl v0.0.0-20190228175220-2a93cfde8c20 // indirect
|
||||
github.com/containerd/console v1.0.1
|
||||
github.com/containerd/containerd v1.4.1-0.20201117152358-0edc412565dc
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853 // indirect
|
||||
github.com/docker/cli v1.14.0-0.20190523191156-ab688a9a79a1
|
||||
github.com/docker/cli v20.10.0-beta1.0.20201029214301-1d20b15adc38+incompatible
|
||||
github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496 // indirect
|
||||
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible
|
||||
github.com/docker/docker v1.14.0-0.20190410063227-3998dffb806f3887f804b813069f59bc14a7f3c1
|
||||
github.com/docker/docker-credential-helpers v0.6.1 // indirect
|
||||
github.com/docker/distribution v2.7.1+incompatible
|
||||
github.com/docker/docker v20.10.0-beta1.0.20201110211921-af34b94a78a1+incompatible
|
||||
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-metrics v0.0.0-20170502235133-d466d4f6fd96 // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/libtrust v0.0.0-20150526203908-9cbd2a1374f4 // indirect
|
||||
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect
|
||||
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484 // indirect
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.4.1 // indirect
|
||||
github.com/gofrs/flock v0.7.0
|
||||
github.com/fvbommel/sortorder v1.0.1 // indirect
|
||||
github.com/gofrs/flock v0.7.3
|
||||
github.com/gofrs/uuid v3.2.0+incompatible // indirect
|
||||
github.com/gogo/protobuf v1.2.1 // indirect
|
||||
github.com/google/certificate-transparency-go v1.0.21 // indirect
|
||||
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect
|
||||
github.com/gorilla/mux v1.7.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
|
||||
github.com/hashicorp/go-version v1.1.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0
|
||||
github.com/imdario/mergo v0.3.7 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.6.0
|
||||
github.com/jinzhu/gorm v1.9.2 // indirect
|
||||
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect
|
||||
github.com/jinzhu/now v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.6 // indirect
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||
github.com/lib/pq v1.0.0 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.5 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.10.0 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/miekg/pkcs11 v0.0.0-20190322140431-074fd7a1ed19 // indirect
|
||||
github.com/moby/buildkit v0.5.2-0.20190513182223-f238f1efb04f
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1
|
||||
github.com/moby/buildkit v0.8.1-0.20201205083753-0af7b1b9c693
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.0.1
|
||||
github.com/opencontainers/runtime-spec v1.0.1 // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/prometheus/client_golang v0.8.0 // indirect
|
||||
github.com/prometheus/client_model v0.0.0-20170216185247-6f3806018612 // indirect
|
||||
github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1 // indirect
|
||||
github.com/prometheus/procfs v0.0.0-20180612222113-7d6f385de8be // indirect
|
||||
github.com/sirupsen/logrus v1.4.0
|
||||
github.com/spf13/cobra v0.0.3
|
||||
github.com/spf13/pflag v1.0.3
|
||||
github.com/spf13/viper v1.3.2 // indirect
|
||||
github.com/stretchr/testify v1.3.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.7.1 // indirect
|
||||
github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/theupdateframework/notary v0.6.1 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v0.0.0-20160323030313-93e72a773fad // indirect
|
||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 // indirect
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f
|
||||
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc // indirect
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/zclconf/go-cty v1.4.0
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
|
||||
gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect
|
||||
gopkg.in/fatih/pool.v2 v2.0.0 // indirect
|
||||
gopkg.in/gorethink/gorethink.v3 v3.0.5 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
k8s.io/api v0.0.0-20180712090710-2d6f90ab1293 // indirect
|
||||
k8s.io/apimachinery v0.0.0-20180621070125-103fd098999d // indirect
|
||||
k8s.io/client-go v2.0.0-alpha.0.0.20180806134042-1f13a808da65+incompatible // indirect
|
||||
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 // indirect
|
||||
k8s.io/api v0.19.0
|
||||
k8s.io/apimachinery v0.19.0
|
||||
k8s.io/client-go v0.19.0
|
||||
)
|
||||
|
||||
replace github.com/jaguilar/vt100 => github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305
|
||||
replace (
|
||||
// protobuf: corresponds to containerd (through buildkit)
|
||||
github.com/golang/protobuf => github.com/golang/protobuf v1.3.5
|
||||
github.com/jaguilar/vt100 => github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305
|
||||
|
||||
// genproto: corresponds to containerd (through buildkit)
|
||||
google.golang.org/genproto => google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63
|
||||
)
|
||||
|
@@ -1,56 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
. $(dirname $0)/util
|
||||
set -eu
|
||||
|
||||
: ${TARGETPLATFORM=$CLI_PLATFORM}
|
||||
: ${CONTINUOUS_INTEGRATION=}
|
||||
|
||||
set -ex
|
||||
platformFlag=""
|
||||
if [ -n "$TARGETPLATFORM" ]; then
|
||||
platformFlag="--platform $TARGETPLATFORM"
|
||||
fi
|
||||
|
||||
progressFlag=""
|
||||
if [ "$CONTINUOUS_INTEGRATION" == "true" ]; then progressFlag="--progress=plain"; fi
|
||||
|
||||
binariesDocker() {
|
||||
mkdir -p bin/tmp
|
||||
export DOCKER_BUILDKIT=1
|
||||
iidfile=$(mktemp -t docker-iidfile.XXXXXXXXXX)
|
||||
|
||||
platformFlag=""
|
||||
if [ -n "$TARGETPLATFORM" ]; then
|
||||
platformFlag="--build-arg=TARGETPLATFORM=$TARGETPLATFORM"
|
||||
fi
|
||||
|
||||
docker build $platformFlag --target=binaries --iidfile $iidfile --force-rm .
|
||||
iid=$(cat $iidfile)
|
||||
containerID=$(docker create $iid copy)
|
||||
docker cp $containerID:/ bin/tmp
|
||||
mv bin/tmp/build* bin/
|
||||
rm -rf bin/tmp
|
||||
docker rm $containerID
|
||||
docker rmi -f $iid
|
||||
rm -f $iidfile
|
||||
}
|
||||
|
||||
binaries() {
|
||||
platformFlag=""
|
||||
if [ ! -z "$TARGETPLATFORM" ]; then
|
||||
platformFlag="--frontend-opt=platform=$TARGETPLATFORM"
|
||||
fi
|
||||
buildctl build $progressFlag --frontend=dockerfile.v0 \
|
||||
--local context=. --local dockerfile=. \
|
||||
--frontend-opt target=binaries $platformFlag \
|
||||
--output type=local,dest=./bin/
|
||||
}
|
||||
|
||||
case $buildmode in
|
||||
"buildkit")
|
||||
binaries
|
||||
;;
|
||||
"docker-buildkit")
|
||||
binariesDocker
|
||||
;;
|
||||
*)
|
||||
echo "buildctl or docker with buildkit support is required"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
buildxCmd build $platformFlag \
|
||||
--target "binaries" \
|
||||
--output "type=local,dest=./bin/" \
|
||||
.
|
||||
|
38
hack/build_ci_first_pass
Executable file
38
hack/build_ci_first_pass
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
TYP=$1
|
||||
|
||||
. $(dirname $0)/util
|
||||
set -e
|
||||
|
||||
usage() {
|
||||
echo "usage: ./hack/build_ci_first_pass <binaries>"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ -z "$TYP" ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
importCacheFlags=""
|
||||
exportCacheFlags=""
|
||||
if [ "$GITHUB_ACTIONS" = "true" ]; then
|
||||
if [ -n "$cacheRefFrom" ]; then
|
||||
importCacheFlags="--cache-from=type=local,src=$cacheRefFrom"
|
||||
fi
|
||||
if [ -n "$cacheRefTo" ]; then
|
||||
exportCacheFlags="--cache-to=type=local,dest=$cacheRefTo"
|
||||
fi
|
||||
fi
|
||||
|
||||
case $TYP in
|
||||
"binaries")
|
||||
buildxCmd build $importCacheFlags $exportCacheFlags \
|
||||
--target "binaries" \
|
||||
$currentcontext
|
||||
;;
|
||||
*)
|
||||
echo >&2 "Unknown type $TYP"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
20
hack/cross
20
hack/cross
@@ -1,20 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
. $(dirname $0)/util
|
||||
set -e
|
||||
|
||||
: ${TARGETPLATFORM=linux/amd64,linux/arm/v7,linux/arm64,darwin/amd64,windows/amd64,linux/ppc64le,linux/s390x}
|
||||
: ${CONTINUOUS_INTEGRATION=}
|
||||
: ${EXPORT_LOCAL=}
|
||||
|
||||
set -ex
|
||||
importCacheFlags=""
|
||||
if [ "$GITHUB_ACTIONS" = "true" ]; then
|
||||
if [ -n "$cacheRefFrom" ]; then
|
||||
importCacheFlags="--cache-from=type=local,src=$cacheRefFrom"
|
||||
fi
|
||||
fi
|
||||
|
||||
exportFlag=""
|
||||
if [ -n "$EXPORT_LOCAL" ]; then
|
||||
exportFlag="--output=type=local,dest=$EXPORT_LOCAL"
|
||||
exportFlag="--output=type=local,dest=$EXPORT_LOCAL"
|
||||
fi
|
||||
|
||||
progressFlag=""
|
||||
if [ "$CONTINUOUS_INTEGRATION" == "true" ]; then progressFlag="--progress=plain";
|
||||
fi
|
||||
|
||||
buildctl build $progressFlag --frontend=dockerfile.v0 --local context=. --local dockerfile=. --opt platform=$TARGETPLATFORM $exportFlag --opt target=binaries
|
||||
buildxCmd build $importCacheFlags $exportFlag \
|
||||
--target "binaries" \
|
||||
--platform "$TARGETPLATFORM" \
|
||||
$currentcontext
|
||||
|
@@ -10,7 +10,7 @@ if [ -n "$TMUX_ENTRYPOINT" ]; then
|
||||
tmux new-window
|
||||
tmux a -t demo
|
||||
else
|
||||
( $dockerdCmd 2>/var/log/dockerd.log & )
|
||||
( $dockerdCmd &>/var/log/dockerd.log & )
|
||||
exec ash
|
||||
fi
|
||||
|
||||
|
@@ -10,4 +10,4 @@ services:
|
||||
context: .
|
||||
dockerfile: Dockerfile.webapp
|
||||
args:
|
||||
buildno: 1
|
||||
buildno: 1
|
||||
|
@@ -1,10 +1,12 @@
|
||||
# syntax=docker/dockerfile:1.0-experimental
|
||||
|
||||
FROM golang:1.12-alpine
|
||||
RUN apk add --no-cache git
|
||||
RUN go get -u gopkg.in/alecthomas/gometalinter.v1 \
|
||||
FROM golang:1.13-alpine
|
||||
RUN apk add --no-cache git yamllint
|
||||
RUN go get -u gopkg.in/alecthomas/gometalinter.v1 \
|
||||
&& mv /go/bin/gometalinter.v1 /go/bin/gometalinter \
|
||||
&& gometalinter --install
|
||||
WORKDIR /go/src/github.com/docker/buildx
|
||||
RUN --mount=target=/go/src/github.com/docker/buildx \
|
||||
gometalinter --config=gometalinter.json ./...
|
||||
gometalinter --config=gometalinter.json ./...
|
||||
RUN --mount=target=/go/src/github.com/docker/buildx \
|
||||
yamllint -c .yamllint.yml --strict .
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# syntax = docker/dockerfile:1.0-experimental
|
||||
FROM golang:1.12-alpine AS vendored
|
||||
FROM golang:1.13-alpine AS vendored
|
||||
RUN apk add --no-cache git rsync
|
||||
WORKDIR /src
|
||||
RUN --mount=target=/context \
|
||||
|
35
hack/lint
35
hack/lint
@@ -1,37 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
. $(dirname $0)/util
|
||||
set -eu -o pipefail -x
|
||||
set -eu
|
||||
|
||||
: ${CONTINUOUS_INTEGRATION=}
|
||||
|
||||
progressFlag=""
|
||||
if [ "$CONTINUOUS_INTEGRATION" == "true" ]; then progressFlag="--progress=plain"; fi
|
||||
|
||||
lintDocker() {
|
||||
export DOCKER_BUILDKIT=1
|
||||
iidfile=$(mktemp -t docker-iidfile.XXXXXXXXXX)
|
||||
docker build --iidfile $iidfile -f ./hack/dockerfiles/lint.Dockerfile --force-rm .
|
||||
iid=$(cat $iidfile)
|
||||
docker rmi $iid
|
||||
rm -f $iidfile
|
||||
}
|
||||
|
||||
lint() {
|
||||
buildctl build $progressFlag --frontend=dockerfile.v0 \
|
||||
--local context=. --local dockerfile=. \
|
||||
--frontend-opt filename=./hack/dockerfiles/lint.Dockerfile
|
||||
}
|
||||
|
||||
case $buildmode in
|
||||
"buildkit")
|
||||
lint
|
||||
;;
|
||||
"docker-buildkit")
|
||||
lintDocker
|
||||
;;
|
||||
*)
|
||||
echo "buildctl or docker with buildkit support is required"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
buildxCmd build --file ./hack/dockerfiles/lint.Dockerfile .
|
||||
|
27
hack/release
27
hack/release
@@ -3,30 +3,29 @@
|
||||
TAG=$1
|
||||
OUT=$2
|
||||
|
||||
. $(dirname $0)/util
|
||||
set -eu -o pipefail
|
||||
|
||||
: ${PLATFORMS=linux/amd64}
|
||||
: ${CONTINUOUS_INTEGRATION=}
|
||||
|
||||
progressFlag=""
|
||||
if [ "$CONTINUOUS_INTEGRATION" == "true" ]; then progressFlag="--progress=plain"; fi
|
||||
|
||||
|
||||
usage() {
|
||||
echo "usage: ./hack/release <tag> <out>"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ -z "$TAG" ] || [ -z "$OUT" ]; then
|
||||
if [ -z "$TAG" ] || [ -z "$OUT" ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
importCacheFlags=""
|
||||
if [[ -n "$cacheRefFrom" ]] && [[ "$cacheType" = "local" ]]; then
|
||||
for ref in $cacheRefFrom; do
|
||||
importCacheFlags="$importCacheFlags--cache-from=type=local,src=$ref "
|
||||
done
|
||||
fi
|
||||
|
||||
set -x
|
||||
|
||||
buildctl build $progressFlag --frontend=dockerfile.v0 \
|
||||
--local context=. --local dockerfile=. \
|
||||
--opt target=release \
|
||||
--opt platform=$PLATFORMS \
|
||||
--exporter local \
|
||||
--exporter-opt output=$OUT
|
||||
buildxCmd build $importCacheFlags \
|
||||
--target "release" \
|
||||
--platform "$PLATFORMS" \
|
||||
--output "type=local,dest=$OUT" \
|
||||
$currentcontext
|
||||
|
64
hack/test
64
hack/test
@@ -3,51 +3,45 @@
|
||||
. $(dirname $0)/util
|
||||
set -eu -o pipefail
|
||||
|
||||
: ${CONTINUOUS_INTEGRATION=}
|
||||
: ${BUILDX_NOCACHE=}
|
||||
: ${TEST_COVERAGE=}
|
||||
|
||||
progressFlag=""
|
||||
if [ "$CONTINUOUS_INTEGRATION" == "true" ]; then progressFlag="--progress=plain"; fi
|
||||
importCacheFlags=""
|
||||
if [ -n "$cacheRefFrom" ]; then
|
||||
if [ "$cacheType" = "local" ]; then
|
||||
for ref in $cacheRefFrom; do
|
||||
importCacheFlags="$importCacheFlags--cache-from=type=local,src=$ref "
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
iid="buildx-tests"
|
||||
iidfile=$(mktemp -t docker-iidfile.XXXXXXXXXX)
|
||||
set -x
|
||||
|
||||
case $buildmode in
|
||||
"buildkit")
|
||||
tmpfile=$(mktemp -t docker-iidfile.XXXXXXXXXX)
|
||||
buildctl build $progressFlag --frontend=dockerfile.v0 \
|
||||
--local context=. --local dockerfile=. \
|
||||
--frontend-opt target=integration-tests \
|
||||
--output type=docker,name=$iid,dest=$tmpfile
|
||||
docker load -i $tmpfile
|
||||
rm $tmpfile
|
||||
;;
|
||||
"docker-buildkit")
|
||||
export DOCKER_BUILDKIT=1
|
||||
docker build --iidfile $iidfile --target integration-tests --force-rm .
|
||||
iid=$(cat $iidfile)
|
||||
;;
|
||||
*)
|
||||
echo "docker with buildkit support is required"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
cacheVolume="buildx-cache"
|
||||
if ! docker inspect "$cacheVolume" 2>&1 >/dev/null ; then
|
||||
cacheVolume=$(docker create --name=buildx-cache -v /root/.cache -v /go/pkg/mod alpine)
|
||||
coverageVol=""
|
||||
coverageFlags=""
|
||||
if [ "$TEST_COVERAGE" = "1" ]; then
|
||||
covdir="$(pwd)/coverage"
|
||||
mkdir -p "$covdir"
|
||||
coverageVol="-v $covdir:/coverage"
|
||||
coverageFlags="-coverprofile=/coverage/coverage.txt -covermode=atomic"
|
||||
fi
|
||||
|
||||
docker run --rm -v /tmp --volumes-from=$cacheVolume --privileged $iid go test ${TESTFLAGS:--v} ${TESTPKGS:-./...}
|
||||
buildxCmd build $importCacheFlags \
|
||||
--target "integration-tests" \
|
||||
--output "type=docker,name=$iid" \
|
||||
$currentcontext
|
||||
|
||||
cacheVolume="buildx-cache"
|
||||
if ! docker inspect "$cacheVolume" > /dev/null 2>&1; then
|
||||
cacheVolume=$(docker create --name=buildx-cache -v /root/.cache -v /go/pkg/mod alpine)
|
||||
fi
|
||||
|
||||
docker run --rm -v /tmp $coverageVol --volumes-from=$cacheVolume --privileged $iid go test $coverageFlags ${TESTFLAGS:--v} ${TESTPKGS:-./...}
|
||||
|
||||
if [ -n "$BUILDX_NOCACHE" ]; then
|
||||
docker rm -v $cacheVolume
|
||||
fi
|
||||
|
||||
case $buildmode in
|
||||
"docker-buildkit")
|
||||
rm "$iidfile"
|
||||
docker rmi $iid
|
||||
;;
|
||||
esac
|
||||
rm "$iidfile"
|
||||
docker rmi $iid
|
||||
|
@@ -1,45 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
. $(dirname $0)/util
|
||||
set -eu -o pipefail -x
|
||||
set -eu
|
||||
|
||||
: ${CONTINUOUS_INTEGRATION=}
|
||||
output=$(mktemp -d -t buildx-output.XXXXXXXXXX)
|
||||
|
||||
progressFlag=""
|
||||
if [ "$CONTINUOUS_INTEGRATION" == "true" ]; then progressFlag="--progress=plain"; fi
|
||||
buildxCmd build \
|
||||
--target "update" \
|
||||
--output "type=local,dest=$output" \
|
||||
--file "./hack/dockerfiles/vendor.Dockerfile" \
|
||||
.
|
||||
|
||||
case $buildmode in
|
||||
"buildkit")
|
||||
output=$(mktemp -d -t buildctl-output.XXXXXXXXXX)
|
||||
buildctl build $progressFlag --frontend=dockerfile.v0 --local context=. --local dockerfile=. \
|
||||
--frontend-opt target=update \
|
||||
--frontend-opt filename=./hack/dockerfiles/vendor.Dockerfile \
|
||||
--output type=local,dest=$output
|
||||
rm -rf ./vendor
|
||||
cp -R "$output/out/" .
|
||||
rm -rf $output
|
||||
;;
|
||||
*)
|
||||
iidfile=$(mktemp -t docker-iidfile.XXXXXXXXXX)
|
||||
case $buildmode in
|
||||
"docker-buildkit")
|
||||
export DOCKER_BUILDKIT=1
|
||||
docker build --iidfile $iidfile -f ./hack/dockerfiles/vendor.Dockerfile --target update --force-rm .
|
||||
;;
|
||||
*)
|
||||
echo "buildctl or docker with buildkit support is required"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
iid=$(cat $iidfile)
|
||||
cid=$(docker create $iid noop)
|
||||
rm -rf ./vendor
|
||||
|
||||
docker cp $cid:/out/go.mod .
|
||||
docker cp $cid:/out/go.sum .
|
||||
docker cp $cid:/out/vendor .
|
||||
|
||||
docker rm $cid
|
||||
rm -f $iidfile
|
||||
;;
|
||||
esac
|
||||
rm -rf ./vendor
|
||||
cp -R "$output"/out/* .
|
||||
rm -rf $output
|
||||
|
72
hack/util
72
hack/util
@@ -1,32 +1,66 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
: ${CI=}
|
||||
: ${PREFER_BUILDCTL=}
|
||||
: ${PREFER_LEGACY=}
|
||||
: ${CLI_PLATFORM=}
|
||||
: ${GITHUB_ACTIONS=}
|
||||
: ${CACHEDIR_FROM=}
|
||||
: ${CACHEDIR_TO=}
|
||||
|
||||
newerEqualThan() { # $1=minimum wanted version $2=actual-version
|
||||
[ "$1" = "$(printf "$1\n$2" | sort -V | head -n 1)" ]
|
||||
if [ "$PREFER_BUILDCTL" = "1" ]; then
|
||||
echo >&2 "WARNING: PREFER_BUILDCTL is no longer supported. Ignoring."
|
||||
fi
|
||||
|
||||
if [ "$PREFER_LEGACY" = "1" ]; then
|
||||
echo >&2 "WARNING: PREFER_LEGACY is no longer supported. Ignoring."
|
||||
fi
|
||||
|
||||
progressFlag=""
|
||||
if [ "$CI" = "true" ]; then
|
||||
progressFlag="--progress=plain"
|
||||
fi
|
||||
|
||||
buildxCmd() {
|
||||
if docker buildx version >/dev/null 2>&1; then
|
||||
set -x
|
||||
docker buildx "$@" $progressFlag
|
||||
elif buildx version >/dev/null 2>&1; then
|
||||
set -x
|
||||
buildx "$@" $progressFlag
|
||||
elif docker version >/dev/null 2>&1; then
|
||||
set -x
|
||||
DOCKER_BUILDKIT=1 docker "$@" $progressFlag
|
||||
else
|
||||
echo >&2 "ERROR: Please enable DOCKER_BUILDKIT or install standalone buildx"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
buildmode="legacy"
|
||||
if [ "$PREFER_BUILDCTL" = "1" ]; then
|
||||
buildmode="buildkit";
|
||||
else
|
||||
serverVersion=$(docker info --format '{{.ServerVersion}}')
|
||||
experimental=$(docker info --format '{{.ExperimentalBuild}}')
|
||||
if [ "$PREFER_LEGACY" != "1" ] && ( newerEqualThan "18.09" $serverVersion || \
|
||||
( newerEqualThan "18.06" $serverVersion && [ "true" = "$experimental" ] ) || \
|
||||
[ "$DOCKER_BUILDKIT" = "1" ]); then
|
||||
buildmode="docker-buildkit";
|
||||
if [ -z "$CLI_PLATFORM" ]; then
|
||||
if [ "$(uname -s)" = "Darwin" ]; then
|
||||
arch="$(uname -m)"
|
||||
if [ "$arch" = "x86_64" ]; then
|
||||
arch="amd64"
|
||||
fi
|
||||
CLI_PLATFORM="darwin/$arch"
|
||||
elif uname -s | grep MINGW > /dev/null 2>&1 ; then
|
||||
CLI_PLATFORM="windows/amd64"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$CLI_PLATFORM" ]; then
|
||||
rawos=$(uname -s)
|
||||
if [ "$rawos" = "Darwin" ]; then
|
||||
CLI_PLATFORM="darwin/amd64"
|
||||
elif uname -s | grep MINGW 2>&1 >/dev/null ; then
|
||||
CLI_PLATFORM="windows/amd64"
|
||||
fi
|
||||
cacheType=""
|
||||
cacheRefFrom=""
|
||||
cacheRefTo=""
|
||||
currentref=""
|
||||
if [ "$GITHUB_ACTIONS" = "true" ]; then
|
||||
currentref="git://github.com/$GITHUB_REPOSITORY#$GITHUB_REF"
|
||||
cacheType="local"
|
||||
cacheRefFrom="$CACHEDIR_FROM"
|
||||
cacheRefTo="$CACHEDIR_TO"
|
||||
fi
|
||||
|
||||
currentcontext="."
|
||||
if [ -n "$currentref" ]; then
|
||||
currentcontext="--build-arg BUILDKIT_CONTEXT_KEEP_GIT_DIR=1 $currentref"
|
||||
fi
|
||||
|
@@ -1,40 +1,20 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
set -eu
|
||||
|
||||
: ${CONTINUOUS_INTEGRATION=}
|
||||
: ${DOCKER_BUILDKIT=}
|
||||
|
||||
progressFlag=""
|
||||
if [ "$CONTINUOUS_INTEGRATION" = "true" ]; then progressFlag="--progress=plain"; fi
|
||||
|
||||
case ${1:-} in
|
||||
'')
|
||||
. $(dirname $0)/util
|
||||
case $buildmode in
|
||||
"buildkit")
|
||||
buildctl build $progressFlag --frontend=dockerfile.v0 --local context=. --local dockerfile=. --frontend-opt filename=./hack/dockerfiles/vendor.Dockerfile --frontend-opt target=validate
|
||||
'')
|
||||
. $(dirname $0)/util
|
||||
buildxCmd build \
|
||||
--target validate \
|
||||
--file ./hack/dockerfiles/vendor.Dockerfile \
|
||||
.
|
||||
;;
|
||||
"docker-buildkit")
|
||||
export DOCKER_BUILDKIT=1
|
||||
iidfile=$(mktemp -t docker-iidfile.XXXXXXXXXX)
|
||||
docker build --iidfile $iidfile -f ./hack/dockerfiles/vendor.Dockerfile --target validate --force-rm . || exit 1
|
||||
iid=$(cat $iidfile)
|
||||
docker rmi $iid
|
||||
rm -f $iidfile
|
||||
;;
|
||||
*)
|
||||
echo "buildkit support is required"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
check)
|
||||
status="$(git status --porcelain -- go.mod go.sum vendor 2>/dev/null)"
|
||||
diffs=$(echo "$status" | grep -v '^[RAD] ' || true)
|
||||
if [ "$diffs" ]; then
|
||||
check)
|
||||
status="$(git status --porcelain -- go.mod go.sum vendor 2>/dev/null)"
|
||||
diffs=$(echo "$status" | grep -v '^[RAD] ' || true)
|
||||
if [ "$diffs" ]; then
|
||||
{
|
||||
set +x
|
||||
set +x
|
||||
echo 'The result of "make vendor" differs'
|
||||
echo
|
||||
echo "$diffs"
|
||||
@@ -43,7 +23,7 @@ check)
|
||||
echo
|
||||
} >&2
|
||||
exit 1
|
||||
fi
|
||||
echo 'Congratulations! All vendoring changes are done the right way.'
|
||||
;;
|
||||
fi
|
||||
echo 'Congratulations! All vendoring changes are done the right way.'
|
||||
;;
|
||||
esac
|
||||
|
@@ -10,18 +10,25 @@ import (
|
||||
)
|
||||
|
||||
type NodeGroup struct {
|
||||
Name string
|
||||
Driver string
|
||||
Nodes []Node
|
||||
Name string
|
||||
Driver string
|
||||
Nodes []Node
|
||||
Dynamic bool
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
Name string
|
||||
Endpoint string
|
||||
Platforms []specs.Platform
|
||||
Name string
|
||||
Endpoint string
|
||||
Platforms []specs.Platform
|
||||
Flags []string
|
||||
ConfigFile string
|
||||
DriverOpts map[string]string
|
||||
}
|
||||
|
||||
func (ng *NodeGroup) Leave(name string) error {
|
||||
if ng.Dynamic {
|
||||
return errors.New("dynamic node group does not support Leave")
|
||||
}
|
||||
i := ng.findNode(name)
|
||||
if i == -1 {
|
||||
return errors.Errorf("node %q not found for %s", name, ng.Name)
|
||||
@@ -33,7 +40,10 @@ func (ng *NodeGroup) Leave(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ng *NodeGroup) Update(name, endpoint string, platforms []string, endpointsSet bool, actionAppend bool) error {
|
||||
func (ng *NodeGroup) Update(name, endpoint string, platforms []string, endpointsSet bool, actionAppend bool, flags []string, configFile string, do map[string]string) error {
|
||||
if ng.Dynamic {
|
||||
return errors.New("dynamic node group does not support Update")
|
||||
}
|
||||
i := ng.findNode(name)
|
||||
if i == -1 && !actionAppend {
|
||||
if len(ng.Nodes) > 0 {
|
||||
@@ -55,6 +65,9 @@ func (ng *NodeGroup) Update(name, endpoint string, platforms []string, endpoints
|
||||
if len(platforms) > 0 {
|
||||
n.Platforms = pp
|
||||
}
|
||||
if flags != nil {
|
||||
n.Flags = flags
|
||||
}
|
||||
ng.Nodes[i] = n
|
||||
if err := ng.validateDuplicates(endpoint, i); err != nil {
|
||||
return err
|
||||
@@ -72,9 +85,12 @@ func (ng *NodeGroup) Update(name, endpoint string, platforms []string, endpoints
|
||||
}
|
||||
|
||||
n := Node{
|
||||
Name: name,
|
||||
Endpoint: endpoint,
|
||||
Platforms: pp,
|
||||
Name: name,
|
||||
Endpoint: endpoint,
|
||||
Platforms: pp,
|
||||
ConfigFile: configFile,
|
||||
Flags: flags,
|
||||
DriverOpts: do,
|
||||
}
|
||||
ng.Nodes = append(ng.Nodes, n)
|
||||
|
||||
|
@@ -11,16 +11,16 @@ func TestNodeGroupUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ng := &NodeGroup{}
|
||||
err := ng.Update("foo", "foo0", []string{"linux/amd64"}, true, false)
|
||||
err := ng.Update("foo", "foo0", []string{"linux/amd64"}, true, false, []string{"--debug"}, "", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ng.Update("foo1", "foo1", []string{"linux/arm64", "linux/arm/v7"}, true, true)
|
||||
err = ng.Update("foo1", "foo1", []string{"linux/arm64", "linux/arm/v7"}, true, true, nil, "", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, len(ng.Nodes), 2)
|
||||
|
||||
// update
|
||||
err = ng.Update("foo", "foo2", []string{"linux/amd64", "linux/arm"}, true, false)
|
||||
err = ng.Update("foo", "foo2", []string{"linux/amd64", "linux/arm"}, true, false, nil, "", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, len(ng.Nodes), 2)
|
||||
@@ -28,9 +28,11 @@ func TestNodeGroupUpdate(t *testing.T) {
|
||||
require.Equal(t, []string{"linux/arm64"}, platformutil.Format(ng.Nodes[1].Platforms))
|
||||
|
||||
require.Equal(t, "foo2", ng.Nodes[0].Endpoint)
|
||||
require.Equal(t, []string{"--debug"}, ng.Nodes[0].Flags)
|
||||
require.Equal(t, []string(nil), ng.Nodes[1].Flags)
|
||||
|
||||
// duplicate endpoint
|
||||
err = ng.Update("foo1", "foo2", nil, true, false)
|
||||
err = ng.Update("foo1", "foo2", nil, true, false, nil, "", nil)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "duplicate endpoint")
|
||||
|
||||
|
@@ -15,7 +15,6 @@ import (
|
||||
)
|
||||
|
||||
func New(root string) (*Store, error) {
|
||||
root = filepath.Join(root, "buildx")
|
||||
if err := os.MkdirAll(filepath.Join(root, "instances"), 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -3,6 +3,8 @@ package imagetools
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
@@ -107,3 +109,26 @@ func toCredentialsFunc(a Auth) func(string) (string, string, error) {
|
||||
return ac.Username, ac.Password, nil
|
||||
}
|
||||
}
|
||||
|
||||
func RegistryAuthForRef(ref string, a Auth) (string, error) {
|
||||
if a == nil {
|
||||
return "", nil
|
||||
}
|
||||
r, err := parseRef(ref)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
host := reference.Domain(r)
|
||||
if host == "docker.io" {
|
||||
host = "https://index.docker.io/v1/"
|
||||
}
|
||||
ac, err := a.GetAuthConfig(host)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf, err := json.Marshal(ac)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(buf), nil
|
||||
}
|
||||
|
@@ -53,6 +53,27 @@ func Dedupe(in []specs.Platform) []specs.Platform {
|
||||
return out
|
||||
}
|
||||
|
||||
func FormatInGroups(gg ...[]specs.Platform) []string {
|
||||
m := map[string]struct{}{}
|
||||
out := make([]string, 0, len(gg))
|
||||
for i, g := range gg {
|
||||
for _, p := range g {
|
||||
p := platforms.Normalize(p)
|
||||
key := platforms.Format(p)
|
||||
if _, ok := m[key]; ok {
|
||||
continue
|
||||
}
|
||||
m[key] = struct{}{}
|
||||
v := platforms.Format(p)
|
||||
if i == 0 {
|
||||
v += "*"
|
||||
}
|
||||
out = append(out, v)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func Format(in []specs.Platform) []string {
|
||||
if len(in) == 0 {
|
||||
return nil
|
||||
|
@@ -11,7 +11,6 @@ import (
|
||||
)
|
||||
|
||||
func FromReader(w Writer, name string, rc io.ReadCloser) {
|
||||
status := w.Status()
|
||||
dgst := digest.FromBytes([]byte(identity.NewID()))
|
||||
tm := time.Now()
|
||||
|
||||
@@ -21,9 +20,9 @@ func FromReader(w Writer, name string, rc io.ReadCloser) {
|
||||
Started: &tm,
|
||||
}
|
||||
|
||||
status <- &client.SolveStatus{
|
||||
w.Write(&client.SolveStatus{
|
||||
Vertexes: []*client.Vertex{&vtx},
|
||||
}
|
||||
})
|
||||
|
||||
_, err := io.Copy(ioutil.Discard, rc)
|
||||
|
||||
@@ -33,8 +32,7 @@ func FromReader(w Writer, name string, rc io.ReadCloser) {
|
||||
if err != nil {
|
||||
vtx2.Error = err.Error()
|
||||
}
|
||||
status <- &client.SolveStatus{
|
||||
w.Write(&client.SolveStatus{
|
||||
Vertexes: []*client.Vertex{&vtx2},
|
||||
}
|
||||
close(status)
|
||||
})
|
||||
}
|
||||
|
@@ -1,101 +1,32 @@
|
||||
package progress
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/moby/buildkit/client"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type MultiWriter struct {
|
||||
w Writer
|
||||
eg *errgroup.Group
|
||||
once sync.Once
|
||||
ready chan struct{}
|
||||
}
|
||||
|
||||
func (mw *MultiWriter) WithPrefix(pfx string, force bool) Writer {
|
||||
in := make(chan *client.SolveStatus)
|
||||
out := mw.w.Status()
|
||||
p := &prefixed{
|
||||
main: mw.w,
|
||||
in: in,
|
||||
func WithPrefix(w Writer, pfx string, force bool) Writer {
|
||||
return &prefixed{
|
||||
main: w,
|
||||
pfx: pfx,
|
||||
force: force,
|
||||
}
|
||||
mw.eg.Go(func() error {
|
||||
mw.once.Do(func() {
|
||||
close(mw.ready)
|
||||
})
|
||||
for {
|
||||
select {
|
||||
case v, ok := <-in:
|
||||
if ok {
|
||||
if force {
|
||||
for _, v := range v.Vertexes {
|
||||
v.Name = addPrefix(pfx, v.Name)
|
||||
}
|
||||
}
|
||||
out <- v
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case <-mw.Done():
|
||||
return mw.Err()
|
||||
}
|
||||
}
|
||||
})
|
||||
return p
|
||||
}
|
||||
|
||||
func (mw *MultiWriter) Done() <-chan struct{} {
|
||||
return mw.w.Done()
|
||||
}
|
||||
|
||||
func (mw *MultiWriter) Err() error {
|
||||
return mw.w.Err()
|
||||
}
|
||||
|
||||
func (mw *MultiWriter) Status() chan *client.SolveStatus {
|
||||
return nil
|
||||
}
|
||||
|
||||
type prefixed struct {
|
||||
main Writer
|
||||
in chan *client.SolveStatus
|
||||
main Writer
|
||||
pfx string
|
||||
force bool
|
||||
}
|
||||
|
||||
func (p *prefixed) Done() <-chan struct{} {
|
||||
return p.main.Done()
|
||||
}
|
||||
|
||||
func (p *prefixed) Err() error {
|
||||
return p.main.Err()
|
||||
}
|
||||
|
||||
func (p *prefixed) Status() chan *client.SolveStatus {
|
||||
return p.in
|
||||
}
|
||||
|
||||
func NewMultiWriter(pw Writer) *MultiWriter {
|
||||
if pw == nil {
|
||||
return nil
|
||||
}
|
||||
eg, _ := errgroup.WithContext(context.TODO())
|
||||
|
||||
ready := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
<-ready
|
||||
eg.Wait()
|
||||
close(pw.Status())
|
||||
}()
|
||||
|
||||
return &MultiWriter{
|
||||
w: pw,
|
||||
eg: eg,
|
||||
ready: ready,
|
||||
func (p *prefixed) Write(v *client.SolveStatus) {
|
||||
if p.force {
|
||||
for _, v := range v.Vertexes {
|
||||
v.Name = addPrefix(p.pfx, v.Name)
|
||||
}
|
||||
}
|
||||
p.main.Write(v)
|
||||
}
|
||||
|
||||
func addPrefix(pfx, name string) string {
|
||||
|
@@ -9,32 +9,27 @@ import (
|
||||
"github.com/moby/buildkit/util/progress/progressui"
|
||||
)
|
||||
|
||||
type printer struct {
|
||||
type Printer struct {
|
||||
status chan *client.SolveStatus
|
||||
done <-chan struct{}
|
||||
err error
|
||||
}
|
||||
|
||||
func (p *printer) Done() <-chan struct{} {
|
||||
return p.done
|
||||
}
|
||||
|
||||
func (p *printer) Err() error {
|
||||
func (p *Printer) Wait() error {
|
||||
close(p.status)
|
||||
<-p.done
|
||||
return p.err
|
||||
}
|
||||
|
||||
func (p *printer) Status() chan *client.SolveStatus {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return p.status
|
||||
func (p *Printer) Write(s *client.SolveStatus) {
|
||||
p.status <- s
|
||||
}
|
||||
|
||||
func NewPrinter(ctx context.Context, out *os.File, mode string) Writer {
|
||||
func NewPrinter(ctx context.Context, out console.File, mode string) *Printer {
|
||||
statusCh := make(chan *client.SolveStatus)
|
||||
doneCh := make(chan struct{})
|
||||
|
||||
pw := &printer{
|
||||
pw := &Printer{
|
||||
status: statusCh,
|
||||
done: doneCh,
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ type Logger func(*client.SolveStatus)
|
||||
type SubLogger interface {
|
||||
Wrap(name string, fn func() error) error
|
||||
Log(stream int, dt []byte)
|
||||
SetStatus(*client.VertexStatus)
|
||||
}
|
||||
|
||||
func Wrap(name string, l Logger, fn func(SubLogger) error) (err error) {
|
||||
@@ -88,3 +89,10 @@ func (sl *subLogger) Log(stream int, dt []byte) {
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
func (sl *subLogger) SetStatus(st *client.VertexStatus) {
|
||||
st.Vertex = sl.dgst
|
||||
sl.logger(&client.SolveStatus{
|
||||
Statuses: []*client.VertexStatus{st},
|
||||
})
|
||||
}
|
||||
|
@@ -7,56 +7,45 @@ import (
|
||||
)
|
||||
|
||||
func ResetTime(in Writer) Writer {
|
||||
w := &pw{Writer: in, status: make(chan *client.SolveStatus), tm: time.Now()}
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-in.Done():
|
||||
return
|
||||
case st, ok := <-w.status:
|
||||
if !ok {
|
||||
close(in.Status())
|
||||
return
|
||||
}
|
||||
if w.diff == nil {
|
||||
for _, v := range st.Vertexes {
|
||||
if v.Started != nil {
|
||||
d := v.Started.Sub(w.tm)
|
||||
w.diff = &d
|
||||
}
|
||||
}
|
||||
}
|
||||
if w.diff != nil {
|
||||
for _, v := range st.Vertexes {
|
||||
if v.Started != nil {
|
||||
d := v.Started.Add(-*w.diff)
|
||||
v.Started = &d
|
||||
}
|
||||
if v.Completed != nil {
|
||||
d := v.Completed.Add(-*w.diff)
|
||||
v.Completed = &d
|
||||
}
|
||||
}
|
||||
for _, v := range st.Statuses {
|
||||
if v.Started != nil {
|
||||
d := v.Started.Add(-*w.diff)
|
||||
v.Started = &d
|
||||
}
|
||||
if v.Completed != nil {
|
||||
d := v.Completed.Add(-*w.diff)
|
||||
v.Completed = &d
|
||||
}
|
||||
v.Timestamp = v.Timestamp.Add(-*w.diff)
|
||||
}
|
||||
for _, v := range st.Logs {
|
||||
v.Timestamp = v.Timestamp.Add(-*w.diff)
|
||||
}
|
||||
}
|
||||
in.Status() <- st
|
||||
return &pw{Writer: in, status: make(chan *client.SolveStatus), tm: time.Now()}
|
||||
}
|
||||
|
||||
func (w *pw) Write(st *client.SolveStatus) {
|
||||
if w.diff == nil {
|
||||
for _, v := range st.Vertexes {
|
||||
if v.Started != nil {
|
||||
d := v.Started.Sub(w.tm)
|
||||
w.diff = &d
|
||||
}
|
||||
}
|
||||
}()
|
||||
return w
|
||||
}
|
||||
if w.diff != nil {
|
||||
for _, v := range st.Vertexes {
|
||||
if v.Started != nil {
|
||||
d := v.Started.Add(-*w.diff)
|
||||
v.Started = &d
|
||||
}
|
||||
if v.Completed != nil {
|
||||
d := v.Completed.Add(-*w.diff)
|
||||
v.Completed = &d
|
||||
}
|
||||
}
|
||||
for _, v := range st.Statuses {
|
||||
if v.Started != nil {
|
||||
d := v.Started.Add(-*w.diff)
|
||||
v.Started = &d
|
||||
}
|
||||
if v.Completed != nil {
|
||||
d := v.Completed.Add(-*w.diff)
|
||||
v.Completed = &d
|
||||
}
|
||||
v.Timestamp = v.Timestamp.Add(-*w.diff)
|
||||
}
|
||||
for _, v := range st.Logs {
|
||||
v.Timestamp = v.Timestamp.Add(-*w.diff)
|
||||
}
|
||||
}
|
||||
w.Writer.Write(st)
|
||||
}
|
||||
|
||||
type pw struct {
|
||||
|
@@ -9,13 +9,10 @@ import (
|
||||
)
|
||||
|
||||
type Writer interface {
|
||||
Done() <-chan struct{}
|
||||
Err() error
|
||||
Status() chan *client.SolveStatus
|
||||
Write(*client.SolveStatus)
|
||||
}
|
||||
|
||||
func Write(w Writer, name string, f func() error) {
|
||||
status := w.Status()
|
||||
dgst := digest.FromBytes([]byte(identity.NewID()))
|
||||
tm := time.Now()
|
||||
|
||||
@@ -25,9 +22,9 @@ func Write(w Writer, name string, f func() error) {
|
||||
Started: &tm,
|
||||
}
|
||||
|
||||
status <- &client.SolveStatus{
|
||||
w.Write(&client.SolveStatus{
|
||||
Vertexes: []*client.Vertex{&vtx},
|
||||
}
|
||||
})
|
||||
|
||||
err := f()
|
||||
|
||||
@@ -37,7 +34,23 @@ func Write(w Writer, name string, f func() error) {
|
||||
if err != nil {
|
||||
vtx2.Error = err.Error()
|
||||
}
|
||||
status <- &client.SolveStatus{
|
||||
w.Write(&client.SolveStatus{
|
||||
Vertexes: []*client.Vertex{&vtx2},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func NewChannel(w Writer) (chan *client.SolveStatus, chan struct{}) {
|
||||
ch := make(chan *client.SolveStatus)
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
for {
|
||||
v, ok := <-ch
|
||||
if !ok {
|
||||
close(done)
|
||||
return
|
||||
}
|
||||
w.Write(v)
|
||||
}
|
||||
}()
|
||||
return ch, done
|
||||
}
|
||||
|
202
vendor/cloud.google.com/go/LICENSE
generated
vendored
Normal file
202
vendor/cloud.google.com/go/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
518
vendor/cloud.google.com/go/compute/metadata/metadata.go
generated
vendored
Normal file
518
vendor/cloud.google.com/go/compute/metadata/metadata.go
generated
vendored
Normal file
@@ -0,0 +1,518 @@
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package metadata provides access to Google Compute Engine (GCE)
|
||||
// metadata and API service accounts.
|
||||
//
|
||||
// This package is a wrapper around the GCE metadata service,
|
||||
// as documented at https://developers.google.com/compute/docs/metadata.
|
||||
package metadata // import "cloud.google.com/go/compute/metadata"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// metadataIP is the documented metadata server IP address.
|
||||
metadataIP = "169.254.169.254"
|
||||
|
||||
// metadataHostEnv is the environment variable specifying the
|
||||
// GCE metadata hostname. If empty, the default value of
|
||||
// metadataIP ("169.254.169.254") is used instead.
|
||||
// This is variable name is not defined by any spec, as far as
|
||||
// I know; it was made up for the Go package.
|
||||
metadataHostEnv = "GCE_METADATA_HOST"
|
||||
|
||||
userAgent = "gcloud-golang/0.1"
|
||||
)
|
||||
|
||||
type cachedValue struct {
|
||||
k string
|
||||
trim bool
|
||||
mu sync.Mutex
|
||||
v string
|
||||
}
|
||||
|
||||
var (
|
||||
projID = &cachedValue{k: "project/project-id", trim: true}
|
||||
projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
|
||||
instID = &cachedValue{k: "instance/id", trim: true}
|
||||
)
|
||||
|
||||
var defaultClient = &Client{hc: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 2 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
},
|
||||
}}
|
||||
|
||||
// NotDefinedError is returned when requested metadata is not defined.
|
||||
//
|
||||
// The underlying string is the suffix after "/computeMetadata/v1/".
|
||||
//
|
||||
// This error is not returned if the value is defined to be the empty
|
||||
// string.
|
||||
type NotDefinedError string
|
||||
|
||||
func (suffix NotDefinedError) Error() string {
|
||||
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
|
||||
}
|
||||
|
||||
func (c *cachedValue) get(cl *Client) (v string, err error) {
|
||||
defer c.mu.Unlock()
|
||||
c.mu.Lock()
|
||||
if c.v != "" {
|
||||
return c.v, nil
|
||||
}
|
||||
if c.trim {
|
||||
v, err = cl.getTrimmed(c.k)
|
||||
} else {
|
||||
v, err = cl.Get(c.k)
|
||||
}
|
||||
if err == nil {
|
||||
c.v = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
onGCEOnce sync.Once
|
||||
onGCE bool
|
||||
)
|
||||
|
||||
// OnGCE reports whether this process is running on Google Compute Engine.
|
||||
func OnGCE() bool {
|
||||
onGCEOnce.Do(initOnGCE)
|
||||
return onGCE
|
||||
}
|
||||
|
||||
func initOnGCE() {
|
||||
onGCE = testOnGCE()
|
||||
}
|
||||
|
||||
func testOnGCE() bool {
|
||||
// The user explicitly said they're on GCE, so trust them.
|
||||
if os.Getenv(metadataHostEnv) != "" {
|
||||
return true
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
resc := make(chan bool, 2)
|
||||
|
||||
// Try two strategies in parallel.
|
||||
// See https://github.com/googleapis/google-cloud-go/issues/194
|
||||
go func() {
|
||||
req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
res, err := defaultClient.hc.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
resc <- false
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
resc <- res.Header.Get("Metadata-Flavor") == "Google"
|
||||
}()
|
||||
|
||||
go func() {
|
||||
addrs, err := net.LookupHost("metadata.google.internal")
|
||||
if err != nil || len(addrs) == 0 {
|
||||
resc <- false
|
||||
return
|
||||
}
|
||||
resc <- strsContains(addrs, metadataIP)
|
||||
}()
|
||||
|
||||
tryHarder := systemInfoSuggestsGCE()
|
||||
if tryHarder {
|
||||
res := <-resc
|
||||
if res {
|
||||
// The first strategy succeeded, so let's use it.
|
||||
return true
|
||||
}
|
||||
// Wait for either the DNS or metadata server probe to
|
||||
// contradict the other one and say we are running on
|
||||
// GCE. Give it a lot of time to do so, since the system
|
||||
// info already suggests we're running on a GCE BIOS.
|
||||
timer := time.NewTimer(5 * time.Second)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case res = <-resc:
|
||||
return res
|
||||
case <-timer.C:
|
||||
// Too slow. Who knows what this system is.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// There's no hint from the system info that we're running on
|
||||
// GCE, so use the first probe's result as truth, whether it's
|
||||
// true or false. The goal here is to optimize for speed for
|
||||
// users who are NOT running on GCE. We can't assume that
|
||||
// either a DNS lookup or an HTTP request to a blackholed IP
|
||||
// address is fast. Worst case this should return when the
|
||||
// metaClient's Transport.ResponseHeaderTimeout or
|
||||
// Transport.Dial.Timeout fires (in two seconds).
|
||||
return <-resc
|
||||
}
|
||||
|
||||
// systemInfoSuggestsGCE reports whether the local system (without
|
||||
// doing network requests) suggests that we're running on GCE. If this
|
||||
// returns true, testOnGCE tries a bit harder to reach its metadata
|
||||
// server.
|
||||
func systemInfoSuggestsGCE() bool {
|
||||
if runtime.GOOS != "linux" {
|
||||
// We don't have any non-Linux clues available, at least yet.
|
||||
return false
|
||||
}
|
||||
slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name")
|
||||
name := strings.TrimSpace(string(slurp))
|
||||
return name == "Google" || name == "Google Compute Engine"
|
||||
}
|
||||
|
||||
// Subscribe calls Client.Subscribe on the default client.
|
||||
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||
return defaultClient.Subscribe(suffix, fn)
|
||||
}
|
||||
|
||||
// Get calls Client.Get on the default client.
|
||||
func Get(suffix string) (string, error) { return defaultClient.Get(suffix) }
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func ProjectID() (string, error) { return defaultClient.ProjectID() }
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func NumericProjectID() (string, error) { return defaultClient.NumericProjectID() }
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func InternalIP() (string, error) { return defaultClient.InternalIP() }
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func ExternalIP() (string, error) { return defaultClient.ExternalIP() }
|
||||
|
||||
// Email calls Client.Email on the default client.
|
||||
func Email(serviceAccount string) (string, error) { return defaultClient.Email(serviceAccount) }
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func Hostname() (string, error) { return defaultClient.Hostname() }
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func InstanceTags() ([]string, error) { return defaultClient.InstanceTags() }
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func InstanceID() (string, error) { return defaultClient.InstanceID() }
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func InstanceName() (string, error) { return defaultClient.InstanceName() }
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func Zone() (string, error) { return defaultClient.Zone() }
|
||||
|
||||
// InstanceAttributes calls Client.InstanceAttributes on the default client.
|
||||
func InstanceAttributes() ([]string, error) { return defaultClient.InstanceAttributes() }
|
||||
|
||||
// ProjectAttributes calls Client.ProjectAttributes on the default client.
|
||||
func ProjectAttributes() ([]string, error) { return defaultClient.ProjectAttributes() }
|
||||
|
||||
// InstanceAttributeValue calls Client.InstanceAttributeValue on the default client.
|
||||
func InstanceAttributeValue(attr string) (string, error) {
|
||||
return defaultClient.InstanceAttributeValue(attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue calls Client.ProjectAttributeValue on the default client.
|
||||
func ProjectAttributeValue(attr string) (string, error) {
|
||||
return defaultClient.ProjectAttributeValue(attr)
|
||||
}
|
||||
|
||||
// Scopes calls Client.Scopes on the default client.
|
||||
func Scopes(serviceAccount string) ([]string, error) { return defaultClient.Scopes(serviceAccount) }
|
||||
|
||||
func strsContains(ss []string, s string) bool {
|
||||
for _, v := range ss {
|
||||
if v == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// A Client provides metadata.
|
||||
type Client struct {
|
||||
hc *http.Client
|
||||
}
|
||||
|
||||
// NewClient returns a Client that can be used to fetch metadata.
|
||||
// Returns the client that uses the specified http.Client for HTTP requests.
|
||||
// If nil is specified, returns the default client.
|
||||
func NewClient(c *http.Client) *Client {
|
||||
if c == nil {
|
||||
return defaultClient
|
||||
}
|
||||
|
||||
return &Client{hc: c}
|
||||
}
|
||||
|
||||
// getETag returns a value from the metadata service as well as the associated ETag.
|
||||
// This func is otherwise equivalent to Get.
|
||||
func (c *Client) getETag(suffix string) (value, etag string, err error) {
|
||||
// Using a fixed IP makes it very difficult to spoof the metadata service in
|
||||
// a container, which is an important use-case for local testing of cloud
|
||||
// deployments. To enable spoofing of the metadata service, the environment
|
||||
// variable GCE_METADATA_HOST is first inspected to decide where metadata
|
||||
// requests shall go.
|
||||
host := os.Getenv(metadataHostEnv)
|
||||
if host == "" {
|
||||
// Using 169.254.169.254 instead of "metadata" here because Go
|
||||
// binaries built with the "netgo" tag and without cgo won't
|
||||
// know the search suffix for "metadata" is
|
||||
// ".google.internal", and this IP address is documented as
|
||||
// being stable anyway.
|
||||
host = metadataIP
|
||||
}
|
||||
u := "http://" + host + "/computeMetadata/v1/" + suffix
|
||||
req, err := http.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
req.Header.Set("Metadata-Flavor", "Google")
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
res, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return "", "", NotDefinedError(suffix)
|
||||
}
|
||||
all, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return "", "", &Error{Code: res.StatusCode, Message: string(all)}
|
||||
}
|
||||
return string(all), res.Header.Get("Etag"), nil
|
||||
}
|
||||
|
||||
// Get returns a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
//
|
||||
// If the GCE_METADATA_HOST environment variable is not defined, a default of
|
||||
// 169.254.169.254 will be used instead.
|
||||
//
|
||||
// If the requested metadata is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
func (c *Client) Get(suffix string) (string, error) {
|
||||
val, _, err := c.getETag(suffix)
|
||||
return val, err
|
||||
}
|
||||
|
||||
func (c *Client) getTrimmed(suffix string) (s string, err error) {
|
||||
s, err = c.Get(suffix)
|
||||
s = strings.TrimSpace(s)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) lines(suffix string) ([]string, error) {
|
||||
j, err := c.Get(suffix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := strings.Split(strings.TrimSpace(j), "\n")
|
||||
for i := range s {
|
||||
s[i] = strings.TrimSpace(s[i])
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func (c *Client) ProjectID() (string, error) { return projID.get(c) }
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func (c *Client) NumericProjectID() (string, error) { return projNum.get(c) }
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func (c *Client) InstanceID() (string, error) { return instID.get(c) }
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func (c *Client) InternalIP() (string, error) {
|
||||
return c.getTrimmed("instance/network-interfaces/0/ip")
|
||||
}
|
||||
|
||||
// Email returns the email address associated with the service account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
func (c *Client) Email(serviceAccount string) (string, error) {
|
||||
if serviceAccount == "" {
|
||||
serviceAccount = "default"
|
||||
}
|
||||
return c.getTrimmed("instance/service-accounts/" + serviceAccount + "/email")
|
||||
}
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func (c *Client) ExternalIP() (string, error) {
|
||||
return c.getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
|
||||
}
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func (c *Client) Hostname() (string, error) {
|
||||
return c.getTrimmed("instance/hostname")
|
||||
}
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func (c *Client) InstanceTags() ([]string, error) {
|
||||
var s []string
|
||||
j, err := c.Get("instance/tags")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func (c *Client) InstanceName() (string, error) {
|
||||
return c.getTrimmed("instance/name")
|
||||
}
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func (c *Client) Zone() (string, error) {
|
||||
zone, err := c.getTrimmed("instance/zone")
|
||||
// zone is of the form "projects/<projNum>/zones/<zoneName>".
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return zone[strings.LastIndex(zone, "/")+1:], nil
|
||||
}
|
||||
|
||||
// InstanceAttributes returns the list of user-defined attributes,
|
||||
// assigned when initially creating a GCE VM instance. The value of an
|
||||
// attribute can be obtained with InstanceAttributeValue.
|
||||
func (c *Client) InstanceAttributes() ([]string, error) { return c.lines("instance/attributes/") }
|
||||
|
||||
// ProjectAttributes returns the list of user-defined attributes
|
||||
// applying to the project as a whole, not just this VM. The value of
|
||||
// an attribute can be obtained with ProjectAttributeValue.
|
||||
func (c *Client) ProjectAttributes() ([]string, error) { return c.lines("project/attributes/") }
|
||||
|
||||
// InstanceAttributeValue returns the value of the provided VM
|
||||
// instance attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// InstanceAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) InstanceAttributeValue(attr string) (string, error) {
|
||||
return c.Get("instance/attributes/" + attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue returns the value of the provided
|
||||
// project attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// ProjectAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) ProjectAttributeValue(attr string) (string, error) {
|
||||
return c.Get("project/attributes/" + attr)
|
||||
}
|
||||
|
||||
// Scopes returns the service account scopes for the given account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
func (c *Client) Scopes(serviceAccount string) ([]string, error) {
|
||||
if serviceAccount == "" {
|
||||
serviceAccount = "default"
|
||||
}
|
||||
return c.lines("instance/service-accounts/" + serviceAccount + "/scopes")
|
||||
}
|
||||
|
||||
// Subscribe subscribes to a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
// The suffix may contain query parameters.
|
||||
//
|
||||
// Subscribe calls fn with the latest metadata value indicated by the provided
|
||||
// suffix. If the metadata value is deleted, fn is called with the empty string
|
||||
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
|
||||
// is deleted. Subscribe returns the error value returned from the last call to
|
||||
// fn, which may be nil when ok == false.
|
||||
func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||
const failedSubscribeSleep = time.Second * 5
|
||||
|
||||
// First check to see if the metadata value exists at all.
|
||||
val, lastETag, err := c.getETag(suffix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fn(val, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok := true
|
||||
if strings.ContainsRune(suffix, '?') {
|
||||
suffix += "&wait_for_change=true&last_etag="
|
||||
} else {
|
||||
suffix += "?wait_for_change=true&last_etag="
|
||||
}
|
||||
for {
|
||||
val, etag, err := c.getETag(suffix + url.QueryEscape(lastETag))
|
||||
if err != nil {
|
||||
if _, deleted := err.(NotDefinedError); !deleted {
|
||||
time.Sleep(failedSubscribeSleep)
|
||||
continue // Retry on other errors.
|
||||
}
|
||||
ok = false
|
||||
}
|
||||
lastETag = etag
|
||||
|
||||
if err := fn(val, ok); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error contains an error response from the server.
|
||||
type Error struct {
|
||||
// Code is the HTTP response status code.
|
||||
Code int
|
||||
// Message is the server response message.
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message)
|
||||
}
|
18
vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
18
vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
@@ -16,6 +16,7 @@ import (
|
||||
//sys createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) = CreateIoCompletionPort
|
||||
//sys getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
|
||||
//sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
|
||||
//sys wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
|
||||
|
||||
type atomicBool int32
|
||||
|
||||
@@ -79,6 +80,7 @@ type win32File struct {
|
||||
wg sync.WaitGroup
|
||||
wgLock sync.RWMutex
|
||||
closing atomicBool
|
||||
socket bool
|
||||
readDeadline deadlineHandler
|
||||
writeDeadline deadlineHandler
|
||||
}
|
||||
@@ -109,7 +111,13 @@ func makeWin32File(h syscall.Handle) (*win32File, error) {
|
||||
}
|
||||
|
||||
func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
|
||||
return makeWin32File(h)
|
||||
// If we return the result of makeWin32File directly, it can result in an
|
||||
// interface-wrapped nil, rather than a nil interface value.
|
||||
f, err := makeWin32File(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// closeHandle closes the resources associated with a Win32 handle
|
||||
@@ -190,6 +198,10 @@ func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, er
|
||||
if f.closing.isSet() {
|
||||
err = ErrFileClosed
|
||||
}
|
||||
} else if err != nil && f.socket {
|
||||
// err is from Win32. Query the overlapped structure to get the winsock error.
|
||||
var bytes, flags uint32
|
||||
err = wsaGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags)
|
||||
}
|
||||
case <-timeout:
|
||||
cancelIoEx(f.handle, &c.o)
|
||||
@@ -265,6 +277,10 @@ func (f *win32File) Flush() error {
|
||||
return syscall.FlushFileBuffers(f.handle)
|
||||
}
|
||||
|
||||
func (f *win32File) Fd() uintptr {
|
||||
return uintptr(f.handle)
|
||||
}
|
||||
|
||||
func (d *deadlineHandler) set(deadline time.Time) error {
|
||||
d.setLock.Lock()
|
||||
defer d.setLock.Unlock()
|
||||
|
9
vendor/github.com/Microsoft/go-winio/go.mod
generated
vendored
Normal file
9
vendor/github.com/Microsoft/go-winio/go.mod
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
module github.com/Microsoft/go-winio
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/sirupsen/logrus v1.4.1
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3
|
||||
)
|
18
vendor/github.com/Microsoft/go-winio/go.sum
generated
vendored
Normal file
18
vendor/github.com/Microsoft/go-winio/go.sum
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 h1:7TYNF4UdlohbFwpNH04CoPMp1cHUZgO1Ebq5r2hIjfo=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
305
vendor/github.com/Microsoft/go-winio/hvsock.go
generated
vendored
Normal file
305
vendor/github.com/Microsoft/go-winio/hvsock.go
generated
vendored
Normal file
@@ -0,0 +1,305 @@
|
||||
package winio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Microsoft/go-winio/pkg/guid"
|
||||
)
|
||||
|
||||
//sys bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind
|
||||
|
||||
const (
|
||||
afHvSock = 34 // AF_HYPERV
|
||||
|
||||
socketError = ^uintptr(0)
|
||||
)
|
||||
|
||||
// An HvsockAddr is an address for a AF_HYPERV socket.
|
||||
type HvsockAddr struct {
|
||||
VMID guid.GUID
|
||||
ServiceID guid.GUID
|
||||
}
|
||||
|
||||
type rawHvsockAddr struct {
|
||||
Family uint16
|
||||
_ uint16
|
||||
VMID guid.GUID
|
||||
ServiceID guid.GUID
|
||||
}
|
||||
|
||||
// Network returns the address's network name, "hvsock".
|
||||
func (addr *HvsockAddr) Network() string {
|
||||
return "hvsock"
|
||||
}
|
||||
|
||||
func (addr *HvsockAddr) String() string {
|
||||
return fmt.Sprintf("%s:%s", &addr.VMID, &addr.ServiceID)
|
||||
}
|
||||
|
||||
// VsockServiceID returns an hvsock service ID corresponding to the specified AF_VSOCK port.
|
||||
func VsockServiceID(port uint32) guid.GUID {
|
||||
g, _ := guid.FromString("00000000-facb-11e6-bd58-64006a7986d3")
|
||||
g.Data1 = port
|
||||
return g
|
||||
}
|
||||
|
||||
func (addr *HvsockAddr) raw() rawHvsockAddr {
|
||||
return rawHvsockAddr{
|
||||
Family: afHvSock,
|
||||
VMID: addr.VMID,
|
||||
ServiceID: addr.ServiceID,
|
||||
}
|
||||
}
|
||||
|
||||
func (addr *HvsockAddr) fromRaw(raw *rawHvsockAddr) {
|
||||
addr.VMID = raw.VMID
|
||||
addr.ServiceID = raw.ServiceID
|
||||
}
|
||||
|
||||
// HvsockListener is a socket listener for the AF_HYPERV address family.
|
||||
type HvsockListener struct {
|
||||
sock *win32File
|
||||
addr HvsockAddr
|
||||
}
|
||||
|
||||
// HvsockConn is a connected socket of the AF_HYPERV address family.
|
||||
type HvsockConn struct {
|
||||
sock *win32File
|
||||
local, remote HvsockAddr
|
||||
}
|
||||
|
||||
func newHvSocket() (*win32File, error) {
|
||||
fd, err := syscall.Socket(afHvSock, syscall.SOCK_STREAM, 1)
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("socket", err)
|
||||
}
|
||||
f, err := makeWin32File(fd)
|
||||
if err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
f.socket = true
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ListenHvsock listens for connections on the specified hvsock address.
|
||||
func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) {
|
||||
l := &HvsockListener{addr: *addr}
|
||||
sock, err := newHvSocket()
|
||||
if err != nil {
|
||||
return nil, l.opErr("listen", err)
|
||||
}
|
||||
sa := addr.raw()
|
||||
err = bind(sock.handle, unsafe.Pointer(&sa), int32(unsafe.Sizeof(sa)))
|
||||
if err != nil {
|
||||
return nil, l.opErr("listen", os.NewSyscallError("socket", err))
|
||||
}
|
||||
err = syscall.Listen(sock.handle, 16)
|
||||
if err != nil {
|
||||
return nil, l.opErr("listen", os.NewSyscallError("listen", err))
|
||||
}
|
||||
return &HvsockListener{sock: sock, addr: *addr}, nil
|
||||
}
|
||||
|
||||
func (l *HvsockListener) opErr(op string, err error) error {
|
||||
return &net.OpError{Op: op, Net: "hvsock", Addr: &l.addr, Err: err}
|
||||
}
|
||||
|
||||
// Addr returns the listener's network address.
|
||||
func (l *HvsockListener) Addr() net.Addr {
|
||||
return &l.addr
|
||||
}
|
||||
|
||||
// Accept waits for the next connection and returns it.
|
||||
func (l *HvsockListener) Accept() (_ net.Conn, err error) {
|
||||
sock, err := newHvSocket()
|
||||
if err != nil {
|
||||
return nil, l.opErr("accept", err)
|
||||
}
|
||||
defer func() {
|
||||
if sock != nil {
|
||||
sock.Close()
|
||||
}
|
||||
}()
|
||||
c, err := l.sock.prepareIo()
|
||||
if err != nil {
|
||||
return nil, l.opErr("accept", err)
|
||||
}
|
||||
defer l.sock.wg.Done()
|
||||
|
||||
// AcceptEx, per documentation, requires an extra 16 bytes per address.
|
||||
const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{}))
|
||||
var addrbuf [addrlen * 2]byte
|
||||
|
||||
var bytes uint32
|
||||
err = syscall.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0, addrlen, addrlen, &bytes, &c.o)
|
||||
_, err = l.sock.asyncIo(c, nil, bytes, err)
|
||||
if err != nil {
|
||||
return nil, l.opErr("accept", os.NewSyscallError("acceptex", err))
|
||||
}
|
||||
conn := &HvsockConn{
|
||||
sock: sock,
|
||||
}
|
||||
conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0])))
|
||||
conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen])))
|
||||
sock = nil
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Close closes the listener, causing any pending Accept calls to fail.
|
||||
func (l *HvsockListener) Close() error {
|
||||
return l.sock.Close()
|
||||
}
|
||||
|
||||
/* Need to finish ConnectEx handling
|
||||
func DialHvsock(ctx context.Context, addr *HvsockAddr) (*HvsockConn, error) {
|
||||
sock, err := newHvSocket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if sock != nil {
|
||||
sock.Close()
|
||||
}
|
||||
}()
|
||||
c, err := sock.prepareIo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer sock.wg.Done()
|
||||
var bytes uint32
|
||||
err = windows.ConnectEx(windows.Handle(sock.handle), sa, nil, 0, &bytes, &c.o)
|
||||
_, err = sock.asyncIo(ctx, c, nil, bytes, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn := &HvsockConn{
|
||||
sock: sock,
|
||||
remote: *addr,
|
||||
}
|
||||
sock = nil
|
||||
return conn, nil
|
||||
}
|
||||
*/
|
||||
|
||||
func (conn *HvsockConn) opErr(op string, err error) error {
|
||||
return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err}
|
||||
}
|
||||
|
||||
func (conn *HvsockConn) Read(b []byte) (int, error) {
|
||||
c, err := conn.sock.prepareIo()
|
||||
if err != nil {
|
||||
return 0, conn.opErr("read", err)
|
||||
}
|
||||
defer conn.sock.wg.Done()
|
||||
buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))}
|
||||
var flags, bytes uint32
|
||||
err = syscall.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil)
|
||||
n, err := conn.sock.asyncIo(c, &conn.sock.readDeadline, bytes, err)
|
||||
if err != nil {
|
||||
if _, ok := err.(syscall.Errno); ok {
|
||||
err = os.NewSyscallError("wsarecv", err)
|
||||
}
|
||||
return 0, conn.opErr("read", err)
|
||||
} else if n == 0 {
|
||||
err = io.EOF
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (conn *HvsockConn) Write(b []byte) (int, error) {
|
||||
t := 0
|
||||
for len(b) != 0 {
|
||||
n, err := conn.write(b)
|
||||
if err != nil {
|
||||
return t + n, err
|
||||
}
|
||||
t += n
|
||||
b = b[n:]
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (conn *HvsockConn) write(b []byte) (int, error) {
|
||||
c, err := conn.sock.prepareIo()
|
||||
if err != nil {
|
||||
return 0, conn.opErr("write", err)
|
||||
}
|
||||
defer conn.sock.wg.Done()
|
||||
buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))}
|
||||
var bytes uint32
|
||||
err = syscall.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil)
|
||||
n, err := conn.sock.asyncIo(c, &conn.sock.writeDeadline, bytes, err)
|
||||
if err != nil {
|
||||
if _, ok := err.(syscall.Errno); ok {
|
||||
err = os.NewSyscallError("wsasend", err)
|
||||
}
|
||||
return 0, conn.opErr("write", err)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Close closes the socket connection, failing any pending read or write calls.
|
||||
func (conn *HvsockConn) Close() error {
|
||||
return conn.sock.Close()
|
||||
}
|
||||
|
||||
func (conn *HvsockConn) shutdown(how int) error {
|
||||
err := syscall.Shutdown(conn.sock.handle, syscall.SHUT_RD)
|
||||
if err != nil {
|
||||
return os.NewSyscallError("shutdown", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseRead shuts down the read end of the socket.
|
||||
func (conn *HvsockConn) CloseRead() error {
|
||||
err := conn.shutdown(syscall.SHUT_RD)
|
||||
if err != nil {
|
||||
return conn.opErr("close", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseWrite shuts down the write end of the socket, notifying the other endpoint that
|
||||
// no more data will be written.
|
||||
func (conn *HvsockConn) CloseWrite() error {
|
||||
err := conn.shutdown(syscall.SHUT_WR)
|
||||
if err != nil {
|
||||
return conn.opErr("close", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LocalAddr returns the local address of the connection.
|
||||
func (conn *HvsockConn) LocalAddr() net.Addr {
|
||||
return &conn.local
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote address of the connection.
|
||||
func (conn *HvsockConn) RemoteAddr() net.Addr {
|
||||
return &conn.remote
|
||||
}
|
||||
|
||||
// SetDeadline implements the net.Conn SetDeadline method.
|
||||
func (conn *HvsockConn) SetDeadline(t time.Time) error {
|
||||
conn.SetReadDeadline(t)
|
||||
conn.SetWriteDeadline(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetReadDeadline implements the net.Conn SetReadDeadline method.
|
||||
func (conn *HvsockConn) SetReadDeadline(t time.Time) error {
|
||||
return conn.sock.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
// SetWriteDeadline implements the net.Conn SetWriteDeadline method.
|
||||
func (conn *HvsockConn) SetWriteDeadline(t time.Time) error {
|
||||
return conn.sock.SetWriteDeadline(t)
|
||||
}
|
17
vendor/github.com/Microsoft/go-winio/pipe.go
generated
vendored
17
vendor/github.com/Microsoft/go-winio/pipe.go
generated
vendored
@@ -182,13 +182,14 @@ func (s pipeAddress) String() string {
|
||||
}
|
||||
|
||||
// tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout.
|
||||
func tryDialPipe(ctx context.Context, path *string) (syscall.Handle, error) {
|
||||
func tryDialPipe(ctx context.Context, path *string, access uint32) (syscall.Handle, error) {
|
||||
for {
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return syscall.Handle(0), ctx.Err()
|
||||
default:
|
||||
h, err := createFile(*path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
|
||||
h, err := createFile(*path, access, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
|
||||
if err == nil {
|
||||
return h, nil
|
||||
}
|
||||
@@ -197,7 +198,7 @@ func tryDialPipe(ctx context.Context, path *string) (syscall.Handle, error) {
|
||||
}
|
||||
// Wait 10 msec and try again. This is a rather simplistic
|
||||
// view, as we always try each 10 milliseconds.
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,7 +211,7 @@ func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
|
||||
if timeout != nil {
|
||||
absTimeout = time.Now().Add(*timeout)
|
||||
} else {
|
||||
absTimeout = time.Now().Add(time.Second * 2)
|
||||
absTimeout = time.Now().Add(2 * time.Second)
|
||||
}
|
||||
ctx, _ := context.WithDeadline(context.Background(), absTimeout)
|
||||
conn, err := DialPipeContext(ctx, path)
|
||||
@@ -223,9 +224,15 @@ func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
|
||||
// DialPipeContext attempts to connect to a named pipe by `path` until `ctx`
|
||||
// cancellation or timeout.
|
||||
func DialPipeContext(ctx context.Context, path string) (net.Conn, error) {
|
||||
return DialPipeAccess(ctx, path, syscall.GENERIC_READ|syscall.GENERIC_WRITE)
|
||||
}
|
||||
|
||||
// DialPipeAccess attempts to connect to a named pipe by `path` with `access` until `ctx`
|
||||
// cancellation or timeout.
|
||||
func DialPipeAccess(ctx context.Context, path string, access uint32) (net.Conn, error) {
|
||||
var err error
|
||||
var h syscall.Handle
|
||||
h, err = tryDialPipe(ctx, &path)
|
||||
h, err = tryDialPipe(ctx, &path, access)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
235
vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go
generated
vendored
Normal file
235
vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go
generated
vendored
Normal file
@@ -0,0 +1,235 @@
|
||||
// Package guid provides a GUID type. The backing structure for a GUID is
|
||||
// identical to that used by the golang.org/x/sys/windows GUID type.
|
||||
// There are two main binary encodings used for a GUID, the big-endian encoding,
|
||||
// and the Windows (mixed-endian) encoding. See here for details:
|
||||
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding
|
||||
package guid
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// Variant specifies which GUID variant (or "type") of the GUID. It determines
|
||||
// how the entirety of the rest of the GUID is interpreted.
|
||||
type Variant uint8
|
||||
|
||||
// The variants specified by RFC 4122.
|
||||
const (
|
||||
// VariantUnknown specifies a GUID variant which does not conform to one of
|
||||
// the variant encodings specified in RFC 4122.
|
||||
VariantUnknown Variant = iota
|
||||
VariantNCS
|
||||
VariantRFC4122
|
||||
VariantMicrosoft
|
||||
VariantFuture
|
||||
)
|
||||
|
||||
// Version specifies how the bits in the GUID were generated. For instance, a
|
||||
// version 4 GUID is randomly generated, and a version 5 is generated from the
|
||||
// hash of an input string.
|
||||
type Version uint8
|
||||
|
||||
var _ = (encoding.TextMarshaler)(GUID{})
|
||||
var _ = (encoding.TextUnmarshaler)(&GUID{})
|
||||
|
||||
// GUID represents a GUID/UUID. It has the same structure as
|
||||
// golang.org/x/sys/windows.GUID so that it can be used with functions expecting
|
||||
// that type. It is defined as its own type so that stringification and
|
||||
// marshaling can be supported. The representation matches that used by native
|
||||
// Windows code.
|
||||
type GUID windows.GUID
|
||||
|
||||
// NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122.
|
||||
func NewV4() (GUID, error) {
|
||||
var b [16]byte
|
||||
if _, err := rand.Read(b[:]); err != nil {
|
||||
return GUID{}, err
|
||||
}
|
||||
|
||||
g := FromArray(b)
|
||||
g.setVersion(4) // Version 4 means randomly generated.
|
||||
g.setVariant(VariantRFC4122)
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// NewV5 returns a new version 5 (generated from a string via SHA-1 hashing)
|
||||
// GUID, as defined by RFC 4122. The RFC is unclear on the encoding of the name,
|
||||
// and the sample code treats it as a series of bytes, so we do the same here.
|
||||
//
|
||||
// Some implementations, such as those found on Windows, treat the name as a
|
||||
// big-endian UTF16 stream of bytes. If that is desired, the string can be
|
||||
// encoded as such before being passed to this function.
|
||||
func NewV5(namespace GUID, name []byte) (GUID, error) {
|
||||
b := sha1.New()
|
||||
namespaceBytes := namespace.ToArray()
|
||||
b.Write(namespaceBytes[:])
|
||||
b.Write(name)
|
||||
|
||||
a := [16]byte{}
|
||||
copy(a[:], b.Sum(nil))
|
||||
|
||||
g := FromArray(a)
|
||||
g.setVersion(5) // Version 5 means generated from a string.
|
||||
g.setVariant(VariantRFC4122)
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func fromArray(b [16]byte, order binary.ByteOrder) GUID {
|
||||
var g GUID
|
||||
g.Data1 = order.Uint32(b[0:4])
|
||||
g.Data2 = order.Uint16(b[4:6])
|
||||
g.Data3 = order.Uint16(b[6:8])
|
||||
copy(g.Data4[:], b[8:16])
|
||||
return g
|
||||
}
|
||||
|
||||
func (g GUID) toArray(order binary.ByteOrder) [16]byte {
|
||||
b := [16]byte{}
|
||||
order.PutUint32(b[0:4], g.Data1)
|
||||
order.PutUint16(b[4:6], g.Data2)
|
||||
order.PutUint16(b[6:8], g.Data3)
|
||||
copy(b[8:16], g.Data4[:])
|
||||
return b
|
||||
}
|
||||
|
||||
// FromArray constructs a GUID from a big-endian encoding array of 16 bytes.
|
||||
func FromArray(b [16]byte) GUID {
|
||||
return fromArray(b, binary.BigEndian)
|
||||
}
|
||||
|
||||
// ToArray returns an array of 16 bytes representing the GUID in big-endian
|
||||
// encoding.
|
||||
func (g GUID) ToArray() [16]byte {
|
||||
return g.toArray(binary.BigEndian)
|
||||
}
|
||||
|
||||
// FromWindowsArray constructs a GUID from a Windows encoding array of bytes.
|
||||
func FromWindowsArray(b [16]byte) GUID {
|
||||
return fromArray(b, binary.LittleEndian)
|
||||
}
|
||||
|
||||
// ToWindowsArray returns an array of 16 bytes representing the GUID in Windows
|
||||
// encoding.
|
||||
func (g GUID) ToWindowsArray() [16]byte {
|
||||
return g.toArray(binary.LittleEndian)
|
||||
}
|
||||
|
||||
func (g GUID) String() string {
|
||||
return fmt.Sprintf(
|
||||
"%08x-%04x-%04x-%04x-%012x",
|
||||
g.Data1,
|
||||
g.Data2,
|
||||
g.Data3,
|
||||
g.Data4[:2],
|
||||
g.Data4[2:])
|
||||
}
|
||||
|
||||
// FromString parses a string containing a GUID and returns the GUID. The only
|
||||
// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
|
||||
// format.
|
||||
func FromString(s string) (GUID, error) {
|
||||
if len(s) != 36 {
|
||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||
}
|
||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||
}
|
||||
|
||||
var g GUID
|
||||
|
||||
data1, err := strconv.ParseUint(s[0:8], 16, 32)
|
||||
if err != nil {
|
||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||
}
|
||||
g.Data1 = uint32(data1)
|
||||
|
||||
data2, err := strconv.ParseUint(s[9:13], 16, 16)
|
||||
if err != nil {
|
||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||
}
|
||||
g.Data2 = uint16(data2)
|
||||
|
||||
data3, err := strconv.ParseUint(s[14:18], 16, 16)
|
||||
if err != nil {
|
||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||
}
|
||||
g.Data3 = uint16(data3)
|
||||
|
||||
for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} {
|
||||
v, err := strconv.ParseUint(s[x:x+2], 16, 8)
|
||||
if err != nil {
|
||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||
}
|
||||
g.Data4[i] = uint8(v)
|
||||
}
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (g *GUID) setVariant(v Variant) {
|
||||
d := g.Data4[0]
|
||||
switch v {
|
||||
case VariantNCS:
|
||||
d = (d & 0x7f)
|
||||
case VariantRFC4122:
|
||||
d = (d & 0x3f) | 0x80
|
||||
case VariantMicrosoft:
|
||||
d = (d & 0x1f) | 0xc0
|
||||
case VariantFuture:
|
||||
d = (d & 0x0f) | 0xe0
|
||||
case VariantUnknown:
|
||||
fallthrough
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid variant: %d", v))
|
||||
}
|
||||
g.Data4[0] = d
|
||||
}
|
||||
|
||||
// Variant returns the GUID variant, as defined in RFC 4122.
|
||||
func (g GUID) Variant() Variant {
|
||||
b := g.Data4[0]
|
||||
if b&0x80 == 0 {
|
||||
return VariantNCS
|
||||
} else if b&0xc0 == 0x80 {
|
||||
return VariantRFC4122
|
||||
} else if b&0xe0 == 0xc0 {
|
||||
return VariantMicrosoft
|
||||
} else if b&0xe0 == 0xe0 {
|
||||
return VariantFuture
|
||||
}
|
||||
return VariantUnknown
|
||||
}
|
||||
|
||||
func (g *GUID) setVersion(v Version) {
|
||||
g.Data3 = (g.Data3 & 0x0fff) | (uint16(v) << 12)
|
||||
}
|
||||
|
||||
// Version returns the GUID version, as defined in RFC 4122.
|
||||
func (g GUID) Version() Version {
|
||||
return Version((g.Data3 & 0xF000) >> 12)
|
||||
}
|
||||
|
||||
// MarshalText returns the textual representation of the GUID.
|
||||
func (g GUID) MarshalText() ([]byte, error) {
|
||||
return []byte(g.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText takes the textual representation of a GUID, and unmarhals it
|
||||
// into this GUID.
|
||||
func (g *GUID) UnmarshalText(text []byte) error {
|
||||
g2, err := FromString(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*g = g2
|
||||
return nil
|
||||
}
|
2
vendor/github.com/Microsoft/go-winio/syscall.go
generated
vendored
2
vendor/github.com/Microsoft/go-winio/syscall.go
generated
vendored
@@ -1,3 +1,3 @@
|
||||
package winio
|
||||
|
||||
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go
|
||||
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go
|
||||
|
151
vendor/github.com/Microsoft/go-winio/vhd/vhd.go
generated
vendored
Normal file
151
vendor/github.com/Microsoft/go-winio/vhd/vhd.go
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
// +build windows
|
||||
|
||||
package vhd
|
||||
|
||||
import "syscall"
|
||||
|
||||
//go:generate go run mksyscall_windows.go -output zvhd.go vhd.go
|
||||
|
||||
//sys createVirtualDisk(virtualStorageType *virtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, flags uint32, providerSpecificFlags uint32, parameters *createVirtualDiskParameters, o *syscall.Overlapped, handle *syscall.Handle) (err error) [failretval != 0] = VirtDisk.CreateVirtualDisk
|
||||
//sys openVirtualDisk(virtualStorageType *virtualStorageType, path string, virtualDiskAccessMask uint32, flags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (err error) [failretval != 0] = VirtDisk.OpenVirtualDisk
|
||||
//sys detachVirtualDisk(handle syscall.Handle, flags uint32, providerSpecificFlags uint32) (err error) [failretval != 0] = VirtDisk.DetachVirtualDisk
|
||||
|
||||
type virtualStorageType struct {
|
||||
DeviceID uint32
|
||||
VendorID [16]byte
|
||||
}
|
||||
|
||||
type (
|
||||
createVirtualDiskFlag uint32
|
||||
VirtualDiskAccessMask uint32
|
||||
VirtualDiskFlag uint32
|
||||
)
|
||||
|
||||
const (
|
||||
// Flags for creating a VHD (not exported)
|
||||
createVirtualDiskFlagNone createVirtualDiskFlag = 0
|
||||
createVirtualDiskFlagFullPhysicalAllocation createVirtualDiskFlag = 1
|
||||
createVirtualDiskFlagPreventWritesToSourceDisk createVirtualDiskFlag = 2
|
||||
createVirtualDiskFlagDoNotCopyMetadataFromParent createVirtualDiskFlag = 4
|
||||
|
||||
// Access Mask for opening a VHD
|
||||
VirtualDiskAccessNone VirtualDiskAccessMask = 0
|
||||
VirtualDiskAccessAttachRO VirtualDiskAccessMask = 65536
|
||||
VirtualDiskAccessAttachRW VirtualDiskAccessMask = 131072
|
||||
VirtualDiskAccessDetach VirtualDiskAccessMask = 262144
|
||||
VirtualDiskAccessGetInfo VirtualDiskAccessMask = 524288
|
||||
VirtualDiskAccessCreate VirtualDiskAccessMask = 1048576
|
||||
VirtualDiskAccessMetaOps VirtualDiskAccessMask = 2097152
|
||||
VirtualDiskAccessRead VirtualDiskAccessMask = 851968
|
||||
VirtualDiskAccessAll VirtualDiskAccessMask = 4128768
|
||||
VirtualDiskAccessWritable VirtualDiskAccessMask = 3276800
|
||||
|
||||
// Flags for opening a VHD
|
||||
OpenVirtualDiskFlagNone VirtualDiskFlag = 0
|
||||
OpenVirtualDiskFlagNoParents VirtualDiskFlag = 0x1
|
||||
OpenVirtualDiskFlagBlankFile VirtualDiskFlag = 0x2
|
||||
OpenVirtualDiskFlagBootDrive VirtualDiskFlag = 0x4
|
||||
OpenVirtualDiskFlagCachedIO VirtualDiskFlag = 0x8
|
||||
OpenVirtualDiskFlagCustomDiffChain VirtualDiskFlag = 0x10
|
||||
OpenVirtualDiskFlagParentCachedIO VirtualDiskFlag = 0x20
|
||||
OpenVirtualDiskFlagVhdSetFileOnly VirtualDiskFlag = 0x40
|
||||
OpenVirtualDiskFlagIgnoreRelativeParentLocator VirtualDiskFlag = 0x80
|
||||
OpenVirtualDiskFlagNoWriteHardening VirtualDiskFlag = 0x100
|
||||
)
|
||||
|
||||
type createVersion2 struct {
|
||||
UniqueID [16]byte // GUID
|
||||
MaximumSize uint64
|
||||
BlockSizeInBytes uint32
|
||||
SectorSizeInBytes uint32
|
||||
ParentPath *uint16 // string
|
||||
SourcePath *uint16 // string
|
||||
OpenFlags uint32
|
||||
ParentVirtualStorageType virtualStorageType
|
||||
SourceVirtualStorageType virtualStorageType
|
||||
ResiliencyGUID [16]byte // GUID
|
||||
}
|
||||
|
||||
type createVirtualDiskParameters struct {
|
||||
Version uint32 // Must always be set to 2
|
||||
Version2 createVersion2
|
||||
}
|
||||
|
||||
type openVersion2 struct {
|
||||
GetInfoOnly int32 // bool but 4-byte aligned
|
||||
ReadOnly int32 // bool but 4-byte aligned
|
||||
ResiliencyGUID [16]byte // GUID
|
||||
}
|
||||
|
||||
type openVirtualDiskParameters struct {
|
||||
Version uint32 // Must always be set to 2
|
||||
Version2 openVersion2
|
||||
}
|
||||
|
||||
// CreateVhdx will create a simple vhdx file at the given path using default values.
|
||||
func CreateVhdx(path string, maxSizeInGb, blockSizeInMb uint32) error {
|
||||
var (
|
||||
defaultType virtualStorageType
|
||||
handle syscall.Handle
|
||||
)
|
||||
|
||||
parameters := createVirtualDiskParameters{
|
||||
Version: 2,
|
||||
Version2: createVersion2{
|
||||
MaximumSize: uint64(maxSizeInGb) * 1024 * 1024 * 1024,
|
||||
BlockSizeInBytes: blockSizeInMb * 1024 * 1024,
|
||||
},
|
||||
}
|
||||
|
||||
if err := createVirtualDisk(
|
||||
&defaultType,
|
||||
path,
|
||||
uint32(VirtualDiskAccessNone),
|
||||
nil,
|
||||
uint32(createVirtualDiskFlagNone),
|
||||
0,
|
||||
¶meters,
|
||||
nil,
|
||||
&handle); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := syscall.CloseHandle(handle); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DetachVhd detaches a mounted container layer vhd found at `path`.
|
||||
func DetachVhd(path string) error {
|
||||
handle, err := OpenVirtualDisk(
|
||||
path,
|
||||
VirtualDiskAccessNone,
|
||||
OpenVirtualDiskFlagCachedIO|OpenVirtualDiskFlagIgnoreRelativeParentLocator)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.CloseHandle(handle)
|
||||
return detachVirtualDisk(handle, 0, 0)
|
||||
}
|
||||
|
||||
// OpenVirtualDisk obtains a handle to a VHD opened with supplied access mask and flags.
|
||||
func OpenVirtualDisk(path string, accessMask VirtualDiskAccessMask, flag VirtualDiskFlag) (syscall.Handle, error) {
|
||||
var (
|
||||
defaultType virtualStorageType
|
||||
handle syscall.Handle
|
||||
)
|
||||
parameters := openVirtualDiskParameters{Version: 2}
|
||||
if err := openVirtualDisk(
|
||||
&defaultType,
|
||||
path,
|
||||
uint32(accessMask),
|
||||
uint32(flag),
|
||||
¶meters,
|
||||
&handle); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return handle, nil
|
||||
}
|
99
vendor/github.com/Microsoft/go-winio/vhd/zvhd.go
generated
vendored
Normal file
99
vendor/github.com/Microsoft/go-winio/vhd/zvhd.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
|
||||
|
||||
package vhd
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
)
|
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent
|
||||
// allocations at runtime.
|
||||
func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return nil
|
||||
case errnoERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
// TODO: add more here, after collecting data on the common
|
||||
// error values see on Windows. (perhaps when running
|
||||
// all.bat?)
|
||||
return e
|
||||
}
|
||||
|
||||
var (
|
||||
modVirtDisk = windows.NewLazySystemDLL("VirtDisk.dll")
|
||||
|
||||
procCreateVirtualDisk = modVirtDisk.NewProc("CreateVirtualDisk")
|
||||
procOpenVirtualDisk = modVirtDisk.NewProc("OpenVirtualDisk")
|
||||
procDetachVirtualDisk = modVirtDisk.NewProc("DetachVirtualDisk")
|
||||
)
|
||||
|
||||
func createVirtualDisk(virtualStorageType *virtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, flags uint32, providerSpecificFlags uint32, parameters *createVirtualDiskParameters, o *syscall.Overlapped, handle *syscall.Handle) (err error) {
|
||||
var _p0 *uint16
|
||||
_p0, err = syscall.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return _createVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, securityDescriptor, flags, providerSpecificFlags, parameters, o, handle)
|
||||
}
|
||||
|
||||
func _createVirtualDisk(virtualStorageType *virtualStorageType, path *uint16, virtualDiskAccessMask uint32, securityDescriptor *uintptr, flags uint32, providerSpecificFlags uint32, parameters *createVirtualDiskParameters, o *syscall.Overlapped, handle *syscall.Handle) (err error) {
|
||||
r1, _, e1 := syscall.Syscall9(procCreateVirtualDisk.Addr(), 9, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(flags), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(handle)))
|
||||
if r1 != 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func openVirtualDisk(virtualStorageType *virtualStorageType, path string, virtualDiskAccessMask uint32, flags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (err error) {
|
||||
var _p0 *uint16
|
||||
_p0, err = syscall.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return _openVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, flags, parameters, handle)
|
||||
}
|
||||
|
||||
func _openVirtualDisk(virtualStorageType *virtualStorageType, path *uint16, virtualDiskAccessMask uint32, flags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (err error) {
|
||||
r1, _, e1 := syscall.Syscall6(procOpenVirtualDisk.Addr(), 6, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(flags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(handle)))
|
||||
if r1 != 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func detachVirtualDisk(handle syscall.Handle, flags uint32, providerSpecificFlags uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procDetachVirtualDisk.Addr(), 3, uintptr(handle), uintptr(flags), uintptr(providerSpecificFlags))
|
||||
if r1 != 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
33
vendor/github.com/Microsoft/go-winio/zsyscall_windows.go
generated
vendored
33
vendor/github.com/Microsoft/go-winio/zsyscall_windows.go
generated
vendored
@@ -38,6 +38,7 @@ func errnoErr(e syscall.Errno) error {
|
||||
|
||||
var (
|
||||
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
modws2_32 = windows.NewLazySystemDLL("ws2_32.dll")
|
||||
modntdll = windows.NewLazySystemDLL("ntdll.dll")
|
||||
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||
|
||||
@@ -45,6 +46,7 @@ var (
|
||||
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
|
||||
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
|
||||
procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes")
|
||||
procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult")
|
||||
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
|
||||
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
|
||||
procCreateFileW = modkernel32.NewProc("CreateFileW")
|
||||
@@ -73,6 +75,7 @@ var (
|
||||
procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW")
|
||||
procBackupRead = modkernel32.NewProc("BackupRead")
|
||||
procBackupWrite = modkernel32.NewProc("BackupWrite")
|
||||
procbind = modws2_32.NewProc("bind")
|
||||
)
|
||||
|
||||
func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) {
|
||||
@@ -124,6 +127,24 @@ func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err erro
|
||||
return
|
||||
}
|
||||
|
||||
func wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) {
|
||||
var _p0 uint32
|
||||
if wait {
|
||||
_p0 = 1
|
||||
} else {
|
||||
_p0 = 0
|
||||
}
|
||||
r1, _, e1 := syscall.Syscall6(procWSAGetOverlappedResult.Addr(), 5, uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags)), 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(o)), 0)
|
||||
if r1 == 0 {
|
||||
@@ -527,3 +548,15 @@ func backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, p
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))
|
||||
if r1 == socketError {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
3
vendor/github.com/Microsoft/hcsshim/CODEOWNERS
generated
vendored
Normal file
3
vendor/github.com/Microsoft/hcsshim/CODEOWNERS
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
* @microsoft/containerplat
|
||||
|
||||
/hcn/* @nagiesek
|
@@ -12,7 +12,7 @@ plugins = ["grpc", "fieldpath"]
|
||||
# Paths that should be treated as include roots in relation to the vendor
|
||||
# directory. These will be calculated with the vendor directory nearest the
|
||||
# target package.
|
||||
packages = ["github.com/gogo/protobuf", "github.com/gogo/googleapis"]
|
||||
packages = ["github.com/gogo/protobuf"]
|
||||
|
||||
# Paths that will be added untouched to the end of the includes. We use
|
||||
# `/usr/local/include` to pickup the common install location of protobuf.
|
||||
@@ -29,59 +29,26 @@ plugins = ["grpc", "fieldpath"]
|
||||
"google/protobuf/field_mask.proto" = "github.com/gogo/protobuf/types"
|
||||
"google/protobuf/timestamp.proto" = "github.com/gogo/protobuf/types"
|
||||
"google/protobuf/duration.proto" = "github.com/gogo/protobuf/types"
|
||||
"google/rpc/status.proto" = "github.com/gogo/googleapis/google/rpc"
|
||||
"github/containerd/cgroups/stats/v1/metrics.proto" = "github.com/containerd/cgroups/stats/v1"
|
||||
|
||||
[[overrides]]
|
||||
prefixes = ["github.com/containerd/containerd/api/events"]
|
||||
plugins = ["fieldpath"] # disable grpc for this package
|
||||
|
||||
[[overrides]]
|
||||
prefixes = ["github.com/containerd/containerd/api/services/ttrpc/events/v1"]
|
||||
plugins = ["ttrpc", "fieldpath"]
|
||||
|
||||
[[overrides]]
|
||||
# enable ttrpc and disable fieldpath and grpc for the shim
|
||||
prefixes = ["github.com/containerd/containerd/runtime/v1/shim/v1", "github.com/containerd/containerd/runtime/v2/task"]
|
||||
prefixes = ["github.com/Microsoft/hcsshim/internal/shimdiag"]
|
||||
plugins = ["ttrpc"]
|
||||
|
||||
# Aggregrate the API descriptors to lock down API changes.
|
||||
[[descriptors]]
|
||||
prefix = "github.com/containerd/containerd/api"
|
||||
target = "api/next.pb.txt"
|
||||
ignore_files = [
|
||||
"google/protobuf/descriptor.proto",
|
||||
"gogoproto/gogo.proto"
|
||||
]
|
||||
# Lock down runhcs config
|
||||
|
||||
# Lock down runc config
|
||||
[[descriptors]]
|
||||
prefix = "github.com/containerd/containerd/runtime/linux/runctypes"
|
||||
target = "runtime/linux/runctypes/next.pb.txt"
|
||||
prefix = "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options"
|
||||
target = "cmd/containerd-shim-runhcs-v1/options/next.pb.txt"
|
||||
ignore_files = [
|
||||
"google/protobuf/descriptor.proto",
|
||||
"gogoproto/gogo.proto"
|
||||
]
|
||||
|
||||
[[descriptors]]
|
||||
prefix = "github.com/containerd/containerd/runtime/v2/runc/options"
|
||||
target = "runtime/v2/runc/options/next.pb.txt"
|
||||
prefix = "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/stats"
|
||||
target = "cmd/containerd-shim-runhcs-v1/stats/next.pb.txt"
|
||||
ignore_files = [
|
||||
"google/protobuf/descriptor.proto",
|
||||
"gogoproto/gogo.proto"
|
||||
]
|
||||
|
||||
[[descriptors]]
|
||||
prefix = "github.com/containerd/containerd/runtime/v2/runhcs/options"
|
||||
target = "runtime/v2/runhcs/options/next.pb.txt"
|
||||
ignore_files = [
|
||||
"google/protobuf/descriptor.proto",
|
||||
"gogoproto/gogo.proto"
|
||||
]
|
||||
|
||||
[[descriptors]]
|
||||
prefix = "github.com/containerd/containerd/windows/hcsshimtypes"
|
||||
target = "windows/hcsshimtypes/next.pb.txt"
|
||||
ignore_files = [
|
||||
"google/protobuf/descriptor.proto",
|
||||
"gogoproto/gogo.proto"
|
||||
]
|
||||
]
|
7
vendor/github.com/Microsoft/hcsshim/README.md
generated
vendored
7
vendor/github.com/Microsoft/hcsshim/README.md
generated
vendored
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://ci.appveyor.com/project/WindowsVirtualization/hcsshim/branch/master)
|
||||
|
||||
This package contains the Golang interface for using the Windows [Host Compute Service](https://blogs.technet.microsoft.com/virtualization/2017/01/27/introducing-the-host-compute-service-hcs/) (HCS) to launch and manage [Windows Containers](https://docs.microsoft.com/en-us/virtualization/windowscontainers/about/). It also contains other helpers and functions for managing Windows Containers such as the Golang interface for the Host Network Service (HNS).
|
||||
This package contains the Golang interface for using the Windows [Host Compute Service](https://techcommunity.microsoft.com/t5/containers/introducing-the-host-compute-service-hcs/ba-p/382332) (HCS) to launch and manage [Windows Containers](https://docs.microsoft.com/en-us/virtualization/windowscontainers/about/). It also contains other helpers and functions for managing Windows Containers such as the Golang interface for the Host Network Service (HNS).
|
||||
|
||||
It is primarily used in the [Moby Project](https://github.com/moby/moby), but it can be freely used by other projects as well.
|
||||
|
||||
@@ -16,6 +16,11 @@ When you submit a pull request, a CLA-bot will automatically determine whether y
|
||||
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
|
||||
provided by the bot. You will only need to do this once across all repos using our CLA.
|
||||
|
||||
We also ask that contributors [sign their commits](https://git-scm.com/docs/git-commit) using `git commit -s` or `git commit --signoff` to certify they either authored the work themselves or otherwise have permission to use it in this project.
|
||||
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
32
vendor/github.com/Microsoft/hcsshim/appveyor.yml
generated
vendored
32
vendor/github.com/Microsoft/hcsshim/appveyor.yml
generated
vendored
@@ -6,24 +6,40 @@ clone_folder: c:\gopath\src\github.com\Microsoft\hcsshim
|
||||
|
||||
environment:
|
||||
GOPATH: c:\gopath
|
||||
PATH: C:\mingw-w64\x86_64-7.2.0-posix-seh-rt_v5-rev1\mingw64\bin;%GOPATH%\bin;C:\gometalinter-2.0.12-windows-amd64;%PATH%
|
||||
PATH: "%GOPATH%\\bin;C:\\gometalinter-2.0.12-windows-amd64;%PATH%"
|
||||
|
||||
stack: go 1.11
|
||||
stack: go 1.13.4
|
||||
|
||||
build_script:
|
||||
- appveyor DownloadFile https://github.com/alecthomas/gometalinter/releases/download/v2.0.12/gometalinter-2.0.12-windows-amd64.zip
|
||||
- 7z x gometalinter-2.0.12-windows-amd64.zip -y -oC:\ > NUL
|
||||
- gometalinter.exe --config .gometalinter.json ./...
|
||||
- go build ./cmd/wclayer
|
||||
- go build ./cmd/containerd-shim-runhcs-v1
|
||||
- go build ./cmd/runhcs
|
||||
- go build ./cmd/tar2ext4
|
||||
- go build ./cmd/wclayer
|
||||
- go build ./cmd/device-util
|
||||
- go build ./internal/tools/grantvmgroupaccess
|
||||
- go build ./internal/tools/uvmboot
|
||||
- go build ./internal/tools/zapdir
|
||||
- go test -v ./... -tags admin
|
||||
- go test -c ./test/functional/ -tags functional
|
||||
- go test -c ./test/runhcs/ -tags integration
|
||||
- cd test
|
||||
- go test -v ./internal -tags admin
|
||||
- go test -c ./containerd-shim-runhcs-v1/ -tags functional
|
||||
- go test -c ./cri-containerd/ -tags functional
|
||||
- go test -c ./functional/ -tags functional
|
||||
- go test -c ./runhcs/ -tags functional
|
||||
|
||||
artifacts:
|
||||
- path: 'wclayer.exe'
|
||||
- path: 'containerd-shim-runhcs-v1.exe'
|
||||
- path: 'runhcs.exe'
|
||||
- path: 'tar2ext4.exe'
|
||||
- path: 'functional.test.exe'
|
||||
- path: 'runhcs.test.exe'
|
||||
- path: 'device-util.exe'
|
||||
- path: 'wclayer.exe'
|
||||
- path: 'grantvmgroupaccess.exe'
|
||||
- path: 'uvmboot.exe'
|
||||
- path: 'zapdir.exe'
|
||||
- path: './test/containerd-shim-runhcs-v1.test.exe'
|
||||
- path: './test/cri-containerd.test.exe'
|
||||
- path: './test/functional.test.exe'
|
||||
- path: './test/runhcs.test.exe'
|
||||
|
75
vendor/github.com/Microsoft/hcsshim/container.go
generated
vendored
75
vendor/github.com/Microsoft/hcsshim/container.go
generated
vendored
@@ -1,8 +1,10 @@
|
||||
package hcsshim
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/hcs"
|
||||
@@ -52,7 +54,10 @@ const (
|
||||
type ResourceModificationRequestResponse = schema1.ResourceModificationRequestResponse
|
||||
|
||||
type container struct {
|
||||
system *hcs.System
|
||||
system *hcs.System
|
||||
waitOnce sync.Once
|
||||
waitErr error
|
||||
waitCh chan struct{}
|
||||
}
|
||||
|
||||
// createComputeSystemAdditionalJSON is read from the environment at initialisation
|
||||
@@ -71,61 +76,87 @@ func CreateContainer(id string, c *ContainerConfig) (Container, error) {
|
||||
return nil, fmt.Errorf("failed to merge additional JSON '%s': %s", createContainerAdditionalJSON, err)
|
||||
}
|
||||
|
||||
system, err := hcs.CreateComputeSystem(id, fullConfig)
|
||||
system, err := hcs.CreateComputeSystem(context.Background(), id, fullConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &container{system}, err
|
||||
return &container{system: system}, err
|
||||
}
|
||||
|
||||
// OpenContainer opens an existing container by ID.
|
||||
func OpenContainer(id string) (Container, error) {
|
||||
system, err := hcs.OpenComputeSystem(id)
|
||||
system, err := hcs.OpenComputeSystem(context.Background(), id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &container{system}, err
|
||||
return &container{system: system}, err
|
||||
}
|
||||
|
||||
// GetContainers gets a list of the containers on the system that match the query
|
||||
func GetContainers(q ComputeSystemQuery) ([]ContainerProperties, error) {
|
||||
return hcs.GetComputeSystems(q)
|
||||
return hcs.GetComputeSystems(context.Background(), q)
|
||||
}
|
||||
|
||||
// Start synchronously starts the container.
|
||||
func (container *container) Start() error {
|
||||
return convertSystemError(container.system.Start(), container)
|
||||
return convertSystemError(container.system.Start(context.Background()), container)
|
||||
}
|
||||
|
||||
// Shutdown requests a container shutdown, but it may not actually be shutdown until Wait() succeeds.
|
||||
func (container *container) Shutdown() error {
|
||||
return convertSystemError(container.system.Shutdown(), container)
|
||||
err := container.system.Shutdown(context.Background())
|
||||
if err != nil {
|
||||
return convertSystemError(err, container)
|
||||
}
|
||||
return &ContainerError{Container: container, Err: ErrVmcomputeOperationPending, Operation: "hcsshim::ComputeSystem::Shutdown"}
|
||||
}
|
||||
|
||||
// Terminate requests a container terminate, but it may not actually be terminated until Wait() succeeds.
|
||||
func (container *container) Terminate() error {
|
||||
return convertSystemError(container.system.Terminate(), container)
|
||||
err := container.system.Terminate(context.Background())
|
||||
if err != nil {
|
||||
return convertSystemError(err, container)
|
||||
}
|
||||
return &ContainerError{Container: container, Err: ErrVmcomputeOperationPending, Operation: "hcsshim::ComputeSystem::Terminate"}
|
||||
}
|
||||
|
||||
// Waits synchronously waits for the container to shutdown or terminate.
|
||||
func (container *container) Wait() error {
|
||||
return convertSystemError(container.system.Wait(), container)
|
||||
err := container.system.Wait()
|
||||
if err == nil {
|
||||
err = container.system.ExitError()
|
||||
}
|
||||
return convertSystemError(err, container)
|
||||
}
|
||||
|
||||
// WaitTimeout synchronously waits for the container to terminate or the duration to elapse. It
|
||||
// returns false if timeout occurs.
|
||||
func (container *container) WaitTimeout(t time.Duration) error {
|
||||
return convertSystemError(container.system.WaitTimeout(t), container)
|
||||
func (container *container) WaitTimeout(timeout time.Duration) error {
|
||||
container.waitOnce.Do(func() {
|
||||
container.waitCh = make(chan struct{})
|
||||
go func() {
|
||||
container.waitErr = container.Wait()
|
||||
close(container.waitCh)
|
||||
}()
|
||||
})
|
||||
t := time.NewTimer(timeout)
|
||||
defer t.Stop()
|
||||
select {
|
||||
case <-t.C:
|
||||
return &ContainerError{Container: container, Err: ErrTimeout, Operation: "hcsshim::ComputeSystem::Wait"}
|
||||
case <-container.waitCh:
|
||||
return container.waitErr
|
||||
}
|
||||
}
|
||||
|
||||
// Pause pauses the execution of a container.
|
||||
func (container *container) Pause() error {
|
||||
return convertSystemError(container.system.Pause(), container)
|
||||
return convertSystemError(container.system.Pause(context.Background()), container)
|
||||
}
|
||||
|
||||
// Resume resumes the execution of a container.
|
||||
func (container *container) Resume() error {
|
||||
return convertSystemError(container.system.Resume(), container)
|
||||
return convertSystemError(container.system.Resume(context.Background()), container)
|
||||
}
|
||||
|
||||
// HasPendingUpdates returns true if the container has updates pending to install
|
||||
@@ -135,7 +166,7 @@ func (container *container) HasPendingUpdates() (bool, error) {
|
||||
|
||||
// Statistics returns statistics for the container. This is a legacy v1 call
|
||||
func (container *container) Statistics() (Statistics, error) {
|
||||
properties, err := container.system.Properties(schema1.PropertyTypeStatistics)
|
||||
properties, err := container.system.Properties(context.Background(), schema1.PropertyTypeStatistics)
|
||||
if err != nil {
|
||||
return Statistics{}, convertSystemError(err, container)
|
||||
}
|
||||
@@ -145,7 +176,7 @@ func (container *container) Statistics() (Statistics, error) {
|
||||
|
||||
// ProcessList returns an array of ProcessListItems for the container. This is a legacy v1 call
|
||||
func (container *container) ProcessList() ([]ProcessListItem, error) {
|
||||
properties, err := container.system.Properties(schema1.PropertyTypeProcessList)
|
||||
properties, err := container.system.Properties(context.Background(), schema1.PropertyTypeProcessList)
|
||||
if err != nil {
|
||||
return nil, convertSystemError(err, container)
|
||||
}
|
||||
@@ -155,7 +186,7 @@ func (container *container) ProcessList() ([]ProcessListItem, error) {
|
||||
|
||||
// This is a legacy v1 call
|
||||
func (container *container) MappedVirtualDisks() (map[int]MappedVirtualDiskController, error) {
|
||||
properties, err := container.system.Properties(schema1.PropertyTypeMappedVirtualDisk)
|
||||
properties, err := container.system.Properties(context.Background(), schema1.PropertyTypeMappedVirtualDisk)
|
||||
if err != nil {
|
||||
return nil, convertSystemError(err, container)
|
||||
}
|
||||
@@ -165,20 +196,20 @@ func (container *container) MappedVirtualDisks() (map[int]MappedVirtualDiskContr
|
||||
|
||||
// CreateProcess launches a new process within the container.
|
||||
func (container *container) CreateProcess(c *ProcessConfig) (Process, error) {
|
||||
p, err := container.system.CreateProcess(c)
|
||||
p, err := container.system.CreateProcess(context.Background(), c)
|
||||
if err != nil {
|
||||
return nil, convertSystemError(err, container)
|
||||
}
|
||||
return &process{p}, nil
|
||||
return &process{p: p.(*hcs.Process)}, nil
|
||||
}
|
||||
|
||||
// OpenProcess gets an interface to an existing process within the container.
|
||||
func (container *container) OpenProcess(pid int) (Process, error) {
|
||||
p, err := container.system.OpenProcess(pid)
|
||||
p, err := container.system.OpenProcess(context.Background(), pid)
|
||||
if err != nil {
|
||||
return nil, convertSystemError(err, container)
|
||||
}
|
||||
return &process{p}, nil
|
||||
return &process{p: p}, nil
|
||||
}
|
||||
|
||||
// Close cleans up any state associated with the container but does not terminate or wait for it.
|
||||
@@ -188,5 +219,5 @@ func (container *container) Close() error {
|
||||
|
||||
// Modify the System
|
||||
func (container *container) Modify(config *ResourceModificationRequestResponse) error {
|
||||
return convertSystemError(container.system.Modify(config), container)
|
||||
return convertSystemError(container.system.Modify(context.Background(), config), container)
|
||||
}
|
||||
|
35
vendor/github.com/Microsoft/hcsshim/go.mod
generated
vendored
Normal file
35
vendor/github.com/Microsoft/hcsshim/go.mod
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
module github.com/Microsoft/hcsshim
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab
|
||||
github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59
|
||||
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1
|
||||
github.com/containerd/containerd v1.3.2
|
||||
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc // indirect
|
||||
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448 // indirect
|
||||
github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3
|
||||
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de
|
||||
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd
|
||||
github.com/gogo/protobuf v1.3.1
|
||||
github.com/golang/protobuf v1.3.2 // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2 // indirect
|
||||
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f // indirect
|
||||
github.com/opencontainers/runtime-spec v1.0.2
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7 // indirect
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/stretchr/testify v1.4.0 // indirect
|
||||
github.com/urfave/cli v1.22.2
|
||||
go.opencensus.io v0.22.0
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 // indirect
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58
|
||||
golang.org/x/sys v0.0.0-20200120151820-655fe14d7479
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 // indirect
|
||||
google.golang.org/grpc v1.23.1
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.8 // indirect
|
||||
gotest.tools v2.2.0+incompatible // indirect
|
||||
)
|
157
vendor/github.com/Microsoft/hcsshim/go.sum
generated
vendored
Normal file
157
vendor/github.com/Microsoft/hcsshim/go.sum
generated
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab h1:9pygWVFqbY9lPxM0peffumuVDyMuIMzNLyO9uFjJuQo=
|
||||
github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
||||
github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59 h1:qWj4qVYZ95vLWwqyNJCQg7rDsG5wPdze0UaPolH7DUk=
|
||||
github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
|
||||
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1 h1:uict5mhHFTzKLUCufdSLym7z/J0CbBJT59lYbP9wtbg=
|
||||
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
||||
github.com/containerd/containerd v1.3.2 h1:ForxmXkA6tPIvffbrDAcPUIB32QgXkt2XFj+F0UxetA=
|
||||
github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8=
|
||||
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448 h1:PUD50EuOMkXVcpBIA/R95d56duJR9VxhwncsFbNnxW4=
|
||||
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
|
||||
github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3 h1:esQOJREg8nw8aXj6uCN5dfW5cKUBiEJ/+nni1Q/D/sw=
|
||||
github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
|
||||
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de h1:dlfGmNcE3jDAecLqwKPMNX6nk2qh1c1Vg1/YTzpOOF4=
|
||||
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
|
||||
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd h1:JNn81o/xG+8NEo3bC/vx9pbi/g2WI8mtP2/nXzu297Y=
|
||||
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
|
||||
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2 h1:QhPf3A2AZW3tTGvHPg0TA+CR3oHbVLlXUhlghqISp1I=
|
||||
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f h1:a969LJ4IQFwRHYqonHtUDMSh9i54WcKggeEkQ3fZMl4=
|
||||
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0=
|
||||
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7 h1:hhvfGDVThBnd4kYisSFmYuHYeUhglxcwag7FhVPH9zM=
|
||||
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
|
||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09 h1:KaQtG+aDELoNmXYas3TVkGNYRuq8JQ1aa7LJt8EXVyo=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 h1:7TYNF4UdlohbFwpNH04CoPMp1cHUZgO1Ebq5r2hIjfo=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200120151820-655fe14d7479 h1:LhLiKguPgZL+Tglay4GhVtfF0kb8cvOJ0dHTCBO8YNI=
|
||||
golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb h1:i1Ppqkc3WQXikh8bXiwHqAN5Rv3/qDCcRk0/Otx73BY=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk=
|
||||
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
10
vendor/github.com/Microsoft/hcsshim/hnsendpoint.go
generated
vendored
10
vendor/github.com/Microsoft/hcsshim/hnsendpoint.go
generated
vendored
@@ -39,11 +39,21 @@ func HNSListEndpointRequest() ([]HNSEndpoint, error) {
|
||||
|
||||
// HotAttachEndpoint makes a HCS Call to attach the endpoint to the container
|
||||
func HotAttachEndpoint(containerID string, endpointID string) error {
|
||||
endpoint, err := GetHNSEndpointByID(endpointID)
|
||||
isAttached, err := endpoint.IsAttached(containerID)
|
||||
if isAttached {
|
||||
return err
|
||||
}
|
||||
return modifyNetworkEndpoint(containerID, endpointID, Add)
|
||||
}
|
||||
|
||||
// HotDetachEndpoint makes a HCS Call to detach the endpoint from the container
|
||||
func HotDetachEndpoint(containerID string, endpointID string) error {
|
||||
endpoint, err := GetHNSEndpointByID(endpointID)
|
||||
isAttached, err := endpoint.IsAttached(containerID)
|
||||
if !isAttached {
|
||||
return err
|
||||
}
|
||||
return modifyNetworkEndpoint(containerID, endpointID, Remove)
|
||||
}
|
||||
|
||||
|
3
vendor/github.com/Microsoft/hcsshim/hnspolicy.go
generated
vendored
3
vendor/github.com/Microsoft/hcsshim/hnspolicy.go
generated
vendored
@@ -21,8 +21,11 @@ const (
|
||||
OutboundNat = hns.OutboundNat
|
||||
ExternalLoadBalancer = hns.ExternalLoadBalancer
|
||||
Route = hns.Route
|
||||
Proxy = hns.Proxy
|
||||
)
|
||||
|
||||
type ProxyPolicy = hns.ProxyPolicy
|
||||
|
||||
type NatPolicy = hns.NatPolicy
|
||||
|
||||
type QosPolicy = hns.QosPolicy
|
||||
|
83
vendor/github.com/Microsoft/hcsshim/internal/cow/cow.go
generated
vendored
Normal file
83
vendor/github.com/Microsoft/hcsshim/internal/cow/cow.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
package cow
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/schema1"
|
||||
hcsschema "github.com/Microsoft/hcsshim/internal/schema2"
|
||||
)
|
||||
|
||||
// Process is the interface for an OS process running in a container or utility VM.
|
||||
type Process interface {
|
||||
// Close releases resources associated with the process and closes the
|
||||
// writer and readers returned by Stdio. Depending on the implementation,
|
||||
// this may also terminate the process.
|
||||
Close() error
|
||||
// CloseStdin causes the process's stdin handle to receive EOF/EPIPE/whatever
|
||||
// is appropriate to indicate that no more data is available.
|
||||
CloseStdin(ctx context.Context) error
|
||||
// Pid returns the process ID.
|
||||
Pid() int
|
||||
// Stdio returns the stdio streams for a process. These may be nil if a stream
|
||||
// was not requested during CreateProcess.
|
||||
Stdio() (_ io.Writer, _ io.Reader, _ io.Reader)
|
||||
// ResizeConsole resizes the virtual terminal associated with the process.
|
||||
ResizeConsole(ctx context.Context, width, height uint16) error
|
||||
// Kill sends a SIGKILL or equivalent signal to the process and returns whether
|
||||
// the signal was delivered. It does not wait for the process to terminate.
|
||||
Kill(ctx context.Context) (bool, error)
|
||||
// Signal sends a signal to the process and returns whether the signal was
|
||||
// delivered. The input is OS specific (either
|
||||
// guestrequest.SignalProcessOptionsWCOW or
|
||||
// guestrequest.SignalProcessOptionsLCOW). It does not wait for the process
|
||||
// to terminate.
|
||||
Signal(ctx context.Context, options interface{}) (bool, error)
|
||||
// Wait waits for the process to complete, or for a connection to the process to be
|
||||
// terminated by some error condition (including calling Close).
|
||||
Wait() error
|
||||
// ExitCode returns the exit code of the process. Returns an error if the process is
|
||||
// not running.
|
||||
ExitCode() (int, error)
|
||||
}
|
||||
|
||||
// ProcessHost is the interface for creating processes.
|
||||
type ProcessHost interface {
|
||||
// CreateProcess creates a process. The configuration is host specific
|
||||
// (either hcsschema.ProcessParameters or lcow.ProcessParameters).
|
||||
CreateProcess(ctx context.Context, config interface{}) (Process, error)
|
||||
// OS returns the host's operating system, "linux" or "windows".
|
||||
OS() string
|
||||
// IsOCI specifies whether this is an OCI-compliant process host. If true,
|
||||
// then the configuration passed to CreateProcess should have an OCI process
|
||||
// spec (or nil if this is the initial process in an OCI container).
|
||||
// Otherwise, it should have the HCS-specific process parameters.
|
||||
IsOCI() bool
|
||||
}
|
||||
|
||||
// Container is the interface for container objects, either running on the host or
|
||||
// in a utility VM.
|
||||
type Container interface {
|
||||
ProcessHost
|
||||
// Close releases the resources associated with the container. Depending on
|
||||
// the implementation, this may also terminate the container.
|
||||
Close() error
|
||||
// ID returns the container ID.
|
||||
ID() string
|
||||
// Properties returns the requested container properties targeting a V1 schema container.
|
||||
Properties(ctx context.Context, types ...schema1.PropertyType) (*schema1.ContainerProperties, error)
|
||||
// PropertiesV2 returns the requested container properties targeting a V2 schema container.
|
||||
PropertiesV2(ctx context.Context, types ...hcsschema.PropertyType) (*hcsschema.Properties, error)
|
||||
// Start starts a container.
|
||||
Start(ctx context.Context) error
|
||||
// Shutdown sends a shutdown request to the container (but does not wait for
|
||||
// the shutdown to complete).
|
||||
Shutdown(ctx context.Context) error
|
||||
// Terminate sends a terminate request to the container (but does not wait
|
||||
// for the terminate to complete).
|
||||
Terminate(ctx context.Context) error
|
||||
// Wait waits for the container to terminate, or for the connection to the
|
||||
// container to be terminated by some error condition (including calling
|
||||
// Close).
|
||||
Wait() error
|
||||
}
|
100
vendor/github.com/Microsoft/hcsshim/internal/guestrequest/types.go
generated
vendored
100
vendor/github.com/Microsoft/hcsshim/internal/guestrequest/types.go
generated
vendored
@@ -1,100 +0,0 @@
|
||||
package guestrequest
|
||||
|
||||
import (
|
||||
"github.com/Microsoft/hcsshim/internal/schema2"
|
||||
)
|
||||
|
||||
// Arguably, many of these (at least CombinedLayers) should have been generated
|
||||
// by swagger.
|
||||
//
|
||||
// This will also change package name due to an inbound breaking change.
|
||||
|
||||
// This class is used by a modify request to add or remove a combined layers
|
||||
// structure in the guest. For windows, the GCS applies a filter in ContainerRootPath
|
||||
// using the specified layers as the parent content. Ignores property ScratchPath
|
||||
// since the container path is already the scratch path. For linux, the GCS unions
|
||||
// the specified layers and ScratchPath together, placing the resulting union
|
||||
// filesystem at ContainerRootPath.
|
||||
type CombinedLayers struct {
|
||||
ContainerRootPath string `json:"ContainerRootPath,omitempty"`
|
||||
Layers []hcsschema.Layer `json:"Layers,omitempty"`
|
||||
ScratchPath string `json:"ScratchPath,omitempty"`
|
||||
}
|
||||
|
||||
// Defines the schema for hosted settings passed to GCS and/or OpenGCS
|
||||
|
||||
// SCSI. Scratch space for remote file-system commands, or R/W layer for containers
|
||||
type LCOWMappedVirtualDisk struct {
|
||||
MountPath string `json:"MountPath,omitempty"` // /tmp/scratch for an LCOW utility VM being used as a service VM
|
||||
Lun uint8 `json:"Lun,omitempty"`
|
||||
Controller uint8 `json:"Controller,omitempty"`
|
||||
ReadOnly bool `json:"ReadOnly,omitempty"`
|
||||
}
|
||||
|
||||
type WCOWMappedVirtualDisk struct {
|
||||
ContainerPath string `json:"ContainerPath,omitempty"`
|
||||
Lun int32 `json:"Lun,omitempty"`
|
||||
}
|
||||
|
||||
type LCOWMappedDirectory struct {
|
||||
MountPath string `json:"MountPath,omitempty"`
|
||||
Port int32 `json:"Port,omitempty"`
|
||||
ShareName string `json:"ShareName,omitempty"` // If empty not using ANames (not currently supported)
|
||||
ReadOnly bool `json:"ReadOnly,omitempty"`
|
||||
}
|
||||
|
||||
// Read-only layers over VPMem
|
||||
type LCOWMappedVPMemDevice struct {
|
||||
DeviceNumber uint32 `json:"DeviceNumber,omitempty"`
|
||||
MountPath string `json:"MountPath,omitempty"` // /tmp/pN
|
||||
}
|
||||
|
||||
type LCOWNetworkAdapter struct {
|
||||
NamespaceID string `json:",omitempty"`
|
||||
ID string `json:",omitempty"`
|
||||
MacAddress string `json:",omitempty"`
|
||||
IPAddress string `json:",omitempty"`
|
||||
PrefixLength uint8 `json:",omitempty"`
|
||||
GatewayAddress string `json:",omitempty"`
|
||||
DNSSuffix string `json:",omitempty"`
|
||||
DNSServerList string `json:",omitempty"`
|
||||
EnableLowMetric bool `json:",omitempty"`
|
||||
EncapOverhead uint16 `json:",omitempty"`
|
||||
}
|
||||
|
||||
type ResourceType string
|
||||
|
||||
const (
|
||||
// These are constants for v2 schema modify guest requests.
|
||||
ResourceTypeMappedDirectory ResourceType = "MappedDirectory"
|
||||
ResourceTypeMappedVirtualDisk ResourceType = "MappedVirtualDisk"
|
||||
ResourceTypeNetwork ResourceType = "Network"
|
||||
ResourceTypeNetworkNamespace ResourceType = "NetworkNamespace"
|
||||
ResourceTypeCombinedLayers ResourceType = "CombinedLayers"
|
||||
ResourceTypeVPMemDevice ResourceType = "VPMemDevice"
|
||||
)
|
||||
|
||||
// GuestRequest is for modify commands passed to the guest.
|
||||
type GuestRequest struct {
|
||||
RequestType string `json:"RequestType,omitempty"`
|
||||
ResourceType ResourceType `json:"ResourceType,omitempty"`
|
||||
Settings interface{} `json:"Settings,omitempty"`
|
||||
}
|
||||
|
||||
type NetworkModifyRequest struct {
|
||||
AdapterId string `json:"AdapterId,omitempty"`
|
||||
RequestType string `json:"RequestType,omitempty"`
|
||||
Settings interface{} `json:"Settings,omitempty"`
|
||||
}
|
||||
|
||||
type RS4NetworkModifyRequest struct {
|
||||
AdapterInstanceId string `json:"AdapterInstanceId,omitempty"`
|
||||
RequestType string `json:"RequestType,omitempty"`
|
||||
Settings interface{} `json:"Settings,omitempty"`
|
||||
}
|
||||
|
||||
// SignalProcessOptions is the options passed to either WCOW or LCOW
|
||||
// to signal a given process.
|
||||
type SignalProcessOptions struct {
|
||||
Signal int `json:,omitempty`
|
||||
}
|
69
vendor/github.com/Microsoft/hcsshim/internal/guid/guid.go
generated
vendored
69
vendor/github.com/Microsoft/hcsshim/internal/guid/guid.go
generated
vendored
@@ -1,69 +0,0 @@
|
||||
package guid
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var _ = (json.Marshaler)(&GUID{})
|
||||
var _ = (json.Unmarshaler)(&GUID{})
|
||||
|
||||
type GUID [16]byte
|
||||
|
||||
func New() GUID {
|
||||
g := GUID{}
|
||||
_, err := io.ReadFull(rand.Reader, g[:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
func (g GUID) String() string {
|
||||
return fmt.Sprintf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x-%02x", g[3], g[2], g[1], g[0], g[5], g[4], g[7], g[6], g[8:10], g[10:])
|
||||
}
|
||||
|
||||
func FromString(s string) GUID {
|
||||
if len(s) != 36 {
|
||||
panic(fmt.Sprintf("invalid GUID length: %d", len(s)))
|
||||
}
|
||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||
panic("invalid GUID format")
|
||||
}
|
||||
indexOrder := [16]int{
|
||||
0, 2, 4, 6,
|
||||
9, 11,
|
||||
14, 16,
|
||||
19, 21,
|
||||
24, 26, 28, 30, 32, 34,
|
||||
}
|
||||
byteOrder := [16]int{
|
||||
3, 2, 1, 0,
|
||||
5, 4,
|
||||
7, 6,
|
||||
8, 9,
|
||||
10, 11, 12, 13, 14, 15,
|
||||
}
|
||||
var g GUID
|
||||
for i, x := range indexOrder {
|
||||
b, err := strconv.ParseInt(s[x:x+2], 16, 16)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
g[byteOrder[i]] = byte(b)
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
func (g GUID) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(g.String())
|
||||
}
|
||||
|
||||
func (g *GUID) UnmarshalJSON(data []byte) error {
|
||||
*g = FromString(strings.Trim(string(data), "\""))
|
||||
return nil
|
||||
}
|
102
vendor/github.com/Microsoft/hcsshim/internal/hcs/callback.go
generated
vendored
102
vendor/github.com/Microsoft/hcsshim/internal/hcs/callback.go
generated
vendored
@@ -1,10 +1,13 @@
|
||||
package hcs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/interop"
|
||||
"github.com/Microsoft/hcsshim/internal/logfields"
|
||||
"github.com/Microsoft/hcsshim/internal/vmcompute"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -40,35 +43,83 @@ var (
|
||||
)
|
||||
|
||||
type hcsNotification uint32
|
||||
|
||||
func (hn hcsNotification) String() string {
|
||||
switch hn {
|
||||
case hcsNotificationSystemExited:
|
||||
return "SystemExited"
|
||||
case hcsNotificationSystemCreateCompleted:
|
||||
return "SystemCreateCompleted"
|
||||
case hcsNotificationSystemStartCompleted:
|
||||
return "SystemStartCompleted"
|
||||
case hcsNotificationSystemPauseCompleted:
|
||||
return "SystemPauseCompleted"
|
||||
case hcsNotificationSystemResumeCompleted:
|
||||
return "SystemResumeCompleted"
|
||||
case hcsNotificationSystemCrashReport:
|
||||
return "SystemCrashReport"
|
||||
case hcsNotificationSystemSiloJobCreated:
|
||||
return "SystemSiloJobCreated"
|
||||
case hcsNotificationSystemSaveCompleted:
|
||||
return "SystemSaveCompleted"
|
||||
case hcsNotificationSystemRdpEnhancedModeStateChanged:
|
||||
return "SystemRdpEnhancedModeStateChanged"
|
||||
case hcsNotificationSystemShutdownFailed:
|
||||
return "SystemShutdownFailed"
|
||||
case hcsNotificationSystemGetPropertiesCompleted:
|
||||
return "SystemGetPropertiesCompleted"
|
||||
case hcsNotificationSystemModifyCompleted:
|
||||
return "SystemModifyCompleted"
|
||||
case hcsNotificationSystemCrashInitiated:
|
||||
return "SystemCrashInitiated"
|
||||
case hcsNotificationSystemGuestConnectionClosed:
|
||||
return "SystemGuestConnectionClosed"
|
||||
case hcsNotificationProcessExited:
|
||||
return "ProcessExited"
|
||||
case hcsNotificationInvalid:
|
||||
return "Invalid"
|
||||
case hcsNotificationServiceDisconnect:
|
||||
return "ServiceDisconnect"
|
||||
default:
|
||||
return fmt.Sprintf("Unknown: %d", hn)
|
||||
}
|
||||
}
|
||||
|
||||
type notificationChannel chan error
|
||||
|
||||
type notifcationWatcherContext struct {
|
||||
channels notificationChannels
|
||||
handle hcsCallback
|
||||
handle vmcompute.HcsCallback
|
||||
|
||||
systemID string
|
||||
processID int
|
||||
}
|
||||
|
||||
type notificationChannels map[hcsNotification]notificationChannel
|
||||
|
||||
func newChannels() notificationChannels {
|
||||
func newSystemChannels() notificationChannels {
|
||||
channels := make(notificationChannels)
|
||||
for _, notif := range []hcsNotification{
|
||||
hcsNotificationServiceDisconnect,
|
||||
hcsNotificationSystemExited,
|
||||
hcsNotificationSystemCreateCompleted,
|
||||
hcsNotificationSystemStartCompleted,
|
||||
hcsNotificationSystemPauseCompleted,
|
||||
hcsNotificationSystemResumeCompleted,
|
||||
} {
|
||||
channels[notif] = make(notificationChannel, 1)
|
||||
}
|
||||
return channels
|
||||
}
|
||||
|
||||
channels[hcsNotificationSystemExited] = make(notificationChannel, 1)
|
||||
channels[hcsNotificationSystemCreateCompleted] = make(notificationChannel, 1)
|
||||
channels[hcsNotificationSystemStartCompleted] = make(notificationChannel, 1)
|
||||
channels[hcsNotificationSystemPauseCompleted] = make(notificationChannel, 1)
|
||||
channels[hcsNotificationSystemResumeCompleted] = make(notificationChannel, 1)
|
||||
channels[hcsNotificationProcessExited] = make(notificationChannel, 1)
|
||||
channels[hcsNotificationServiceDisconnect] = make(notificationChannel, 1)
|
||||
channels[hcsNotificationSystemCrashReport] = make(notificationChannel, 1)
|
||||
channels[hcsNotificationSystemSiloJobCreated] = make(notificationChannel, 1)
|
||||
channels[hcsNotificationSystemSaveCompleted] = make(notificationChannel, 1)
|
||||
channels[hcsNotificationSystemRdpEnhancedModeStateChanged] = make(notificationChannel, 1)
|
||||
channels[hcsNotificationSystemShutdownFailed] = make(notificationChannel, 1)
|
||||
channels[hcsNotificationSystemGetPropertiesCompleted] = make(notificationChannel, 1)
|
||||
channels[hcsNotificationSystemModifyCompleted] = make(notificationChannel, 1)
|
||||
channels[hcsNotificationSystemCrashInitiated] = make(notificationChannel, 1)
|
||||
channels[hcsNotificationSystemGuestConnectionClosed] = make(notificationChannel, 1)
|
||||
|
||||
func newProcessChannels() notificationChannels {
|
||||
channels := make(notificationChannels)
|
||||
for _, notif := range []hcsNotification{
|
||||
hcsNotificationServiceDisconnect,
|
||||
hcsNotificationProcessExited,
|
||||
} {
|
||||
channels[notif] = make(notificationChannel, 1)
|
||||
}
|
||||
return channels
|
||||
}
|
||||
|
||||
@@ -92,12 +143,17 @@ func notificationWatcher(notificationType hcsNotification, callbackNumber uintpt
|
||||
return 0
|
||||
}
|
||||
|
||||
log := logrus.WithFields(logrus.Fields{
|
||||
"notification-type": notificationType.String(),
|
||||
"system-id": context.systemID,
|
||||
})
|
||||
if context.processID != 0 {
|
||||
log.Data[logfields.ProcessID] = context.processID
|
||||
}
|
||||
log.Debug("HCS notification")
|
||||
|
||||
if channel, ok := context.channels[notificationType]; ok {
|
||||
channel <- result
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"notification-type": notificationType,
|
||||
}).Warn("Received a callback of an unsupported type")
|
||||
}
|
||||
|
||||
return 0
|
||||
|
7
vendor/github.com/Microsoft/hcsshim/internal/hcs/cgo.go
generated
vendored
7
vendor/github.com/Microsoft/hcsshim/internal/hcs/cgo.go
generated
vendored
@@ -1,7 +0,0 @@
|
||||
package hcs
|
||||
|
||||
import "C"
|
||||
|
||||
// This import is needed to make the library compile as CGO because HCSSHIM
|
||||
// only works with CGO due to callbacks from HCS comming back from a C thread
|
||||
// which is not supported without CGO. See https://github.com/golang/go/issues/10973
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user