Compare commits

..

5 Commits

Author SHA1 Message Date
Tõnis Tiigi
0de5f1ce3b Merge pull request #2309 from tonistiigi/v0.13.0-picks
[v0.13] cherry-picks for v0.13.0
2024-03-05 10:02:26 -08:00
Tonis Tiigi
0565a47ad4 vendor: update to buildkit v0.13.0
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
(cherry picked from commit 849456c198)
2024-03-05 09:11:17 -08:00
CrazyMax
ab350f48d2 test: multi exporters
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
(cherry picked from commit 9a2536dd0d)
2024-03-05 09:10:54 -08:00
CrazyMax
1861c07eab build: handle push/load shorthands for multi exporters
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
(cherry picked from commit a03263acf8)
2024-03-05 09:10:40 -08:00
Tõnis Tiigi
84913655a8 Merge pull request #2300 from vvoland/vendor-moby-v26-0.13
[0.13] vendor: github.com/docker/docker v26.0.0-rc1
2024-03-04 10:04:39 -08:00
339 changed files with 5856 additions and 14974 deletions

View File

@@ -30,8 +30,35 @@ env:
GOTESTSUM_VERSION: "v1.9.0" # same as one in Dockerfile GOTESTSUM_VERSION: "v1.9.0" # same as one in Dockerfile
jobs: jobs:
prepare-test-integration:
runs-on: ubuntu-22.04
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
version: ${{ env.BUILDX_VERSION }}
driver-opts: image=${{ env.BUILDKIT_IMAGE }}
buildkitd-flags: --debug
-
name: Build
uses: docker/bake-action@v4
with:
targets: integration-test-base
set: |
*.cache-from=type=gha,scope=${{ env.TEST_CACHE_SCOPE }}
*.cache-to=type=gha,scope=${{ env.TEST_CACHE_SCOPE }}
test-integration: test-integration:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
needs:
- prepare-test-integration
env: env:
TESTFLAGS_DOCKER: "-v --parallel=1 --timeout=30m" TESTFLAGS_DOCKER: "-v --parallel=1 --timeout=30m"
TEST_IMAGE_BUILD: "0" TEST_IMAGE_BUILD: "0"
@@ -39,55 +66,18 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
buildkit:
- master
- latest
- buildx-stable-1
- v0.13.1
- v0.12.5
- v0.11.6
worker: worker:
- docker
- docker\+containerd # same as docker, but with containerd snapshotter
- docker-container - docker-container
- remote - remote
pkg: pkg:
- ./tests - ./tests
mode:
- ""
- experimental
include:
- worker: docker
pkg: ./tests
- worker: docker+containerd # same as docker, but with containerd snapshotter
pkg: ./tests
- worker: docker
pkg: ./tests
mode: experimental
- worker: docker+containerd # same as docker, but with containerd snapshotter
pkg: ./tests
mode: experimental
steps: steps:
- -
name: Prepare name: Prepare
run: | run: |
echo "TESTREPORTS_NAME=${{ github.job }}-$(echo "${{ matrix.pkg }}-${{ matrix.buildkit }}-${{ matrix.worker }}-${{ matrix.mode }}" | tr -dc '[:alnum:]-\n\r' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV echo "TESTREPORTS_NAME=${{ github.job }}-$(echo "${{ matrix.pkg }}-${{ matrix.worker }}" | tr -dc '[:alnum:]-\n\r' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
if [ -n "${{ matrix.buildkit }}" ]; then
echo "TEST_BUILDKIT_TAG=${{ matrix.buildkit }}" >> $GITHUB_ENV
fi
testFlags="--run=//worker=$(echo "${{ matrix.worker }}" | sed 's/\+/\\+/g')$"
case "${{ matrix.worker }}" in
docker | docker+containerd)
echo "TESTFLAGS=${{ env.TESTFLAGS_DOCKER }} $testFlags" >> $GITHUB_ENV
;;
*)
echo "TESTFLAGS=${{ env.TESTFLAGS }} $testFlags" >> $GITHUB_ENV
;;
esac
if [[ "${{ matrix.worker }}" == "docker"* ]]; then
echo "TEST_DOCKERD=1" >> $GITHUB_ENV
fi
if [ "${{ matrix.mode }}" = "experimental" ]; then
echo "TEST_BUILDX_EXPERIMENTAL=1" >> $GITHUB_ENV
fi
- -
name: Checkout name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -109,6 +99,7 @@ jobs:
with: with:
targets: integration-test targets: integration-test
set: | set: |
*.cache-from=type=gha,scope=${{ env.TEST_CACHE_SCOPE }}
*.output=type=docker,name=${{ env.TEST_IMAGE_ID }} *.output=type=docker,name=${{ env.TEST_IMAGE_ID }}
- -
name: Test name: Test
@@ -116,6 +107,8 @@ jobs:
./hack/test ./hack/test
env: env:
TEST_REPORT_SUFFIX: "-${{ env.TESTREPORTS_NAME }}" TEST_REPORT_SUFFIX: "-${{ env.TESTREPORTS_NAME }}"
TEST_DOCKERD: "${{ startsWith(matrix.worker, 'docker') && '1' || '0' }}"
TESTFLAGS: "${{ (matrix.worker == 'docker' || matrix.worker == 'docker\\+containerd') && env.TESTFLAGS_DOCKER || env.TESTFLAGS }} --run=//worker=${{ matrix.worker }}$"
TESTPKGS: "${{ matrix.pkg }}" TESTPKGS: "${{ matrix.pkg }}"
- -
name: Send to Codecov name: Send to Codecov
@@ -356,9 +349,33 @@ jobs:
- -
name: GitHub Release name: GitHub Release
if: startsWith(github.ref, 'refs/tags/v') if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564 # v2.0.4 uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
draft: true draft: true
files: ${{ env.DESTDIR }}/* files: ${{ env.DESTDIR }}/*
buildkit-edge:
runs-on: ubuntu-22.04
continue-on-error: true
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
version: ${{ env.BUILDX_VERSION }}
driver-opts: image=moby/buildkit:master
buildkitd-flags: --debug
-
# Just run a bake target to check eveything runs fine
name: Build
uses: docker/bake-action@v4
with:
targets: binaries

View File

@@ -1,11 +1,6 @@
name: docs-release name: docs-release
on: on:
workflow_dispatch:
inputs:
tag:
description: 'Git tag'
required: true
release: release:
types: types:
- released - released
@@ -13,7 +8,7 @@ on:
jobs: jobs:
open-pr: open-pr:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
if: ${{ (github.event.release.prerelease != true || github.event.inputs.tag != '') && github.repository == 'docker/buildx' }} if: ${{ github.event.release.prerelease != true && github.repository == 'docker/buildx' }}
steps: steps:
- -
name: Checkout docs repo name: Checkout docs repo
@@ -26,11 +21,7 @@ jobs:
name: Prepare name: Prepare
run: | run: |
rm -rf ./data/buildx/* rm -rf ./data/buildx/*
if [ -n "${{ github.event.inputs.tag }}" ]; then rm -rf ./_vendor/github.com/docker/buildx
echo "RELEASE_NAME=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
else
echo "RELEASE_NAME=${{ github.event.release.name }}" >> $GITHUB_ENV
fi
- -
name: Set up Docker Buildx name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
@@ -38,9 +29,8 @@ jobs:
name: Generate yaml name: Generate yaml
uses: docker/bake-action@v4 uses: docker/bake-action@v4
with: with:
source: ${{ github.server_url }}/${{ github.repository }}.git#${{ env.RELEASE_NAME }} source: ${{ github.server_url }}/${{ github.repository }}.git#${{ github.event.release.name }}
targets: update-docs targets: update-docs
provenance: false
set: | set: |
*.output=/tmp/buildx-docs *.output=/tmp/buildx-docs
env: env:
@@ -51,21 +41,23 @@ jobs:
cp /tmp/buildx-docs/out/reference/*.yaml ./data/buildx/ cp /tmp/buildx-docs/out/reference/*.yaml ./data/buildx/
- -
name: Update vendor name: Update vendor
run: | uses: docker/bake-action@v4
make vendor with:
env: source: ${{ github.server_url }}/${{ github.repository }}.git#${{ github.event.release.name }}
VENDOR_MODULE: github.com/docker/buildx@${{ env.RELEASE_NAME }} targets: vendor
set: |
vendor.args.MODULE=github.com/docker/buildx@${{ github.event.release.name }}
- -
name: Create PR on docs repo name: Create PR on docs repo
uses: peter-evans/create-pull-request@c55203cfde3e5c11a452d352b4393e68b85b4533 # v6.0.3 uses: peter-evans/create-pull-request@a4f52f8033a6168103c2538976c07b467e8163bc
with: with:
token: ${{ secrets.GHPAT_DOCS_DISPATCH }} token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
push-to-fork: docker-tools-robot/docker.github.io push-to-fork: docker-tools-robot/docker.github.io
commit-message: "vendor: github.com/docker/buildx ${{ env.RELEASE_NAME }}" commit-message: "vendor: github.com/docker/buildx ${{ github.event.release.name }}"
signoff: true signoff: true
branch: dispatch/buildx-ref-${{ env.RELEASE_NAME }} branch: dispatch/buildx-ref-${{ github.event.release.name }}
delete-branch: true delete-branch: true
title: Update buildx reference to ${{ env.RELEASE_NAME }} title: Update buildx reference to ${{ github.event.release.name }}
body: | body: |
Update the buildx reference documentation to keep in sync with the latest release `${{ env.RELEASE_NAME }}` Update the buildx reference documentation to keep in sync with the latest release `${{ github.event.release.name }}`
draft: false draft: false

View File

@@ -37,7 +37,6 @@ jobs:
uses: docker/bake-action@v4 uses: docker/bake-action@v4
with: with:
targets: update-docs targets: update-docs
provenance: false
set: | set: |
*.output=/tmp/buildx-docs *.output=/tmp/buildx-docs
*.cache-from=type=gha,scope=docs-yaml *.cache-from=type=gha,scope=docs-yaml

View File

@@ -82,8 +82,6 @@ jobs:
driver-opt: qemu.install=true driver-opt: qemu.install=true
- driver: remote - driver: remote
endpoint: tcp://localhost:1234 endpoint: tcp://localhost:1234
- driver: docker-container
metadata-provenance: max
exclude: exclude:
- driver: docker - driver: docker
multi-node: mnode-true multi-node: mnode-true
@@ -131,15 +129,70 @@ jobs:
else else
echo "MULTI_NODE=0" >> $GITHUB_ENV echo "MULTI_NODE=0" >> $GITHUB_ENV
fi fi
if [ -n "${{ matrix.metadata-provenance }}" ]; then
echo "BUILDX_METADATA_PROVENANCE=${{ matrix.metadata-provenance }}" >> $GITHUB_ENV
fi
- -
name: Install k3s name: Install k3s
if: matrix.driver == 'kubernetes' if: matrix.driver == 'kubernetes'
uses: crazy-max/.github/.github/actions/install-k3s@fa6141aedf23596fb8bdcceab9cce8dadaa31bd9 uses: actions/github-script@v7
with: with:
version: ${{ env.K3S_VERSION }} script: |
const fs = require('fs');
let wait = function(milliseconds) {
return new Promise((resolve, reject) => {
if (typeof(milliseconds) !== 'number') {
throw new Error('milleseconds not a number');
}
setTimeout(() => resolve("done!"), milliseconds)
});
}
try {
const kubeconfig="/tmp/buildkit-k3s/kubeconfig.yaml";
core.info(`storing kubeconfig in ${kubeconfig}`);
await exec.exec('docker', ["run", "-d",
"--privileged",
"--name=buildkit-k3s",
"-e", "K3S_KUBECONFIG_OUTPUT="+kubeconfig,
"-e", "K3S_KUBECONFIG_MODE=666",
"-v", "/tmp/buildkit-k3s:/tmp/buildkit-k3s",
"-p", "6443:6443",
"-p", "80:80",
"-p", "443:443",
"-p", "8080:8080",
"rancher/k3s:${{ env.K3S_VERSION }}", "server"
]);
await wait(10000);
core.exportVariable('KUBECONFIG', kubeconfig);
let nodeName;
for (let count = 1; count <= 5; count++) {
try {
const nodeNameOutput = await exec.getExecOutput("kubectl get nodes --no-headers -oname");
nodeName = nodeNameOutput.stdout
} catch (error) {
core.info(`Unable to resolve node name (${error.message}). Attempt ${count} of 5.`)
} finally {
if (nodeName) {
break;
}
await wait(5000);
}
}
if (!nodeName) {
throw new Error(`Unable to resolve node name after 5 attempts.`);
}
await exec.exec(`kubectl wait --for=condition=Ready ${nodeName}`);
} catch (error) {
core.setFailed(error.message);
}
-
name: Print KUBECONFIG
if: matrix.driver == 'kubernetes'
run: |
yq ${{ env.KUBECONFIG }}
- -
name: Launch remote buildkitd name: Launch remote buildkitd
if: matrix.driver == 'remote' if: matrix.driver == 'remote'

View File

@@ -19,6 +19,8 @@ on:
jobs: jobs:
validate: validate:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env:
GOLANGCI_LINT_MULTIPLATFORM: 1
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -28,12 +30,6 @@ jobs:
- validate-docs - validate-docs
- validate-generated-files - validate-generated-files
steps: steps:
-
name: Prepare
run: |
if [ "$GITHUB_REPOSITORY" = "docker/buildx" ]; then
echo "GOLANGCI_LINT_MULTIPLATFORM=1" >> $GITHUB_ENV
fi
- -
name: Checkout name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4

View File

@@ -3,20 +3,15 @@
ARG GO_VERSION=1.21 ARG GO_VERSION=1.21
ARG XX_VERSION=1.4.0 ARG XX_VERSION=1.4.0
# for testing ARG DOCKER_VERSION=25.0.2
ARG DOCKER_VERSION=26.0.0
ARG GOTESTSUM_VERSION=v1.9.0 ARG GOTESTSUM_VERSION=v1.9.0
ARG REGISTRY_VERSION=2.8.0 ARG REGISTRY_VERSION=2.8.0
ARG BUILDKIT_VERSION=v0.13.1 ARG BUILDKIT_VERSION=v0.12.5
ARG UNDOCK_VERSION=0.7.0
# xx is a helper for cross-compilation
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS golatest FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS golatest
FROM moby/moby-bin:$DOCKER_VERSION AS docker-engine
FROM dockereng/cli-bin:$DOCKER_VERSION AS docker-cli
FROM registry:$REGISTRY_VERSION AS registry
FROM moby/buildkit:$BUILDKIT_VERSION AS buildkit
FROM crazymax/undock:$UNDOCK_VERSION AS undock
FROM golatest AS gobase FROM golatest AS gobase
COPY --from=xx / / COPY --from=xx / /
@@ -25,6 +20,26 @@ ENV GOFLAGS=-mod=vendor
ENV CGO_ENABLED=0 ENV CGO_ENABLED=0
WORKDIR /src WORKDIR /src
FROM registry:$REGISTRY_VERSION AS registry
FROM moby/buildkit:$BUILDKIT_VERSION AS buildkit
FROM gobase AS docker
ARG TARGETPLATFORM
ARG DOCKER_VERSION
WORKDIR /opt/docker
RUN DOCKER_ARCH=$(case ${TARGETPLATFORM:-linux/amd64} in \
"linux/amd64") echo "x86_64" ;; \
"linux/arm/v6") echo "armel" ;; \
"linux/arm/v7") echo "armhf" ;; \
"linux/arm64") echo "aarch64" ;; \
"linux/ppc64le") echo "ppc64le" ;; \
"linux/s390x") echo "s390x" ;; \
*) echo "" ;; esac) \
&& echo "DOCKER_ARCH=$DOCKER_ARCH" \
&& wget -qO- "https://download.docker.com/linux/static/stable/${DOCKER_ARCH}/docker-${DOCKER_VERSION}.tgz" | tar xvz --strip 1
RUN ./dockerd --version && ./containerd --version && ./ctr --version && ./runc --version
FROM gobase AS gotestsum FROM gobase AS gotestsum
ARG GOTESTSUM_VERSION ARG GOTESTSUM_VERSION
ENV GOFLAGS= ENV GOFLAGS=
@@ -90,11 +105,9 @@ RUN apk add --no-cache \
xz xz
COPY --link --from=gotestsum /out/gotestsum /usr/bin/ COPY --link --from=gotestsum /out/gotestsum /usr/bin/
COPY --link --from=registry /bin/registry /usr/bin/ COPY --link --from=registry /bin/registry /usr/bin/
COPY --link --from=docker-engine / /usr/bin/ COPY --link --from=docker /opt/docker/* /usr/bin/
COPY --link --from=docker-cli / /usr/bin/
COPY --link --from=buildkit /usr/bin/buildkitd /usr/bin/ COPY --link --from=buildkit /usr/bin/buildkitd /usr/bin/
COPY --link --from=buildkit /usr/bin/buildctl /usr/bin/ COPY --link --from=buildkit /usr/bin/buildctl /usr/bin/
COPY --link --from=undock /usr/local/bin/undock /usr/bin/
COPY --link --from=binaries /buildx /usr/bin/ COPY --link --from=binaries /buildx /usr/bin/
FROM integration-test-base AS integration-test FROM integration-test-base AS integration-test

View File

@@ -894,17 +894,19 @@ func (t *Target) AddOverrides(overrides map[string]Override) error {
} }
t.Pull = &pull t.Pull = &pull
case "push": case "push":
push, err := strconv.ParseBool(value) _, err := strconv.ParseBool(value)
if err != nil { if err != nil {
return errors.Errorf("invalid value %s for boolean key push", value) return errors.Errorf("invalid value %s for boolean key push", value)
} }
t.Outputs = setPushOverride(t.Outputs, push) if len(t.Outputs) == 0 {
case "load": t.Outputs = append(t.Outputs, "type=image,push=true")
load, err := strconv.ParseBool(value) } else {
if err != nil { for i, output := range t.Outputs {
return errors.Errorf("invalid value %s for boolean key load", value) if typ := parseOutputType(output); typ == "image" || typ == "registry" {
t.Outputs[i] = t.Outputs[i] + ",push=" + value
}
}
} }
t.Outputs = setLoadOverride(t.Outputs, load)
default: default:
return errors.Errorf("unknown key: %s", keys[0]) return errors.Errorf("unknown key: %s", keys[0])
} }
@@ -1392,90 +1394,23 @@ func removeAttestDupes(s []string) []string {
return res return res
} }
func parseOutput(str string) map[string]string { func parseOutputType(str string) string {
csvReader := csv.NewReader(strings.NewReader(str)) csvReader := csv.NewReader(strings.NewReader(str))
fields, err := csvReader.Read() fields, err := csvReader.Read()
if err != nil { if err != nil {
return nil return ""
} }
res := map[string]string{}
for _, field := range fields { for _, field := range fields {
parts := strings.SplitN(field, "=", 2) parts := strings.SplitN(field, "=", 2)
if len(parts) == 2 { if len(parts) == 2 {
res[parts[0]] = parts[1] if parts[0] == "type" {
return parts[1]
} }
} }
return res
}
func parseOutputType(str string) string {
if out := parseOutput(str); out != nil {
if v, ok := out["type"]; ok {
return v
}
} }
return "" return ""
} }
func setPushOverride(outputs []string, push bool) []string {
var out []string
setPush := true
for _, output := range outputs {
typ := parseOutputType(output)
if typ == "image" || typ == "registry" {
// no need to set push if image or registry types already defined
setPush = false
if typ == "registry" {
if !push {
// don't set registry output if "push" is false
continue
}
// no need to set "push" attribute to true for registry
out = append(out, output)
continue
}
out = append(out, output+",push="+strconv.FormatBool(push))
} else {
if typ != "docker" {
// if there is any output that is not docker, don't set "push"
setPush = false
}
out = append(out, output)
}
}
if push && setPush {
out = append(out, "type=image,push=true")
}
return out
}
func setLoadOverride(outputs []string, load bool) []string {
if !load {
return outputs
}
setLoad := true
for _, output := range outputs {
if typ := parseOutputType(output); typ == "docker" {
if v := parseOutput(output); v != nil {
// dest set means we want to output as tar so don't set load
if _, ok := v["dest"]; !ok {
setLoad = false
break
}
}
} else if typ != "image" && typ != "registry" && typ != "oci" {
// if there is any output that is not an image, registry
// or oci, don't set "load" similar to push override
setLoad = false
break
}
}
if setLoad {
outputs = append(outputs, "type=docker")
}
return outputs
}
func validateTargetName(name string) error { func validateTargetName(name string) error {
if !targetNamePattern.MatchString(name) { if !targetNamePattern.MatchString(name) {
return errors.Errorf("only %q are allowed", validTargetNameChars) return errors.Errorf("only %q are allowed", validTargetNameChars)

View File

@@ -217,20 +217,8 @@ target "webapp" {
} }
func TestPushOverride(t *testing.T) { func TestPushOverride(t *testing.T) {
t.Run("empty output", func(t *testing.T) { t.Parallel()
fp := File{
Name: "docker-bake.hcl",
Data: []byte(
`target "app" {
}`),
}
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.push=true"}, nil)
require.NoError(t, err)
require.Equal(t, 1, len(m["app"].Outputs))
require.Equal(t, "type=image,push=true", m["app"].Outputs[0])
})
t.Run("type image", func(t *testing.T) {
fp := File{ fp := File{
Name: "docker-bake.hcl", Name: "docker-bake.hcl",
Data: []byte( Data: []byte(
@@ -238,231 +226,39 @@ func TestPushOverride(t *testing.T) {
output = ["type=image,compression=zstd"] output = ["type=image,compression=zstd"]
}`), }`),
} }
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.push=true"}, nil) ctx := context.TODO()
m, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, []string{"*.push=true"}, nil)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(m["app"].Outputs)) require.Equal(t, 1, len(m["app"].Outputs))
require.Equal(t, "type=image,compression=zstd,push=true", m["app"].Outputs[0]) require.Equal(t, "type=image,compression=zstd,push=true", m["app"].Outputs[0])
})
t.Run("type image push false", func(t *testing.T) { fp = File{
fp := File{
Name: "docker-bake.hcl", Name: "docker-bake.hcl",
Data: []byte( Data: []byte(
`target "app" { `target "app" {
output = ["type=image,compression=zstd"] output = ["type=image,compression=zstd"]
}`), }`),
} }
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.push=false"}, nil) ctx = context.TODO()
m, _, err = ReadTargets(ctx, []File{fp}, []string{"app"}, []string{"*.push=false"}, nil)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(m["app"].Outputs)) require.Equal(t, 1, len(m["app"].Outputs))
require.Equal(t, "type=image,compression=zstd,push=false", m["app"].Outputs[0]) require.Equal(t, "type=image,compression=zstd,push=false", m["app"].Outputs[0])
})
t.Run("type registry", func(t *testing.T) { fp = File{
fp := File{
Name: "docker-bake.hcl", Name: "docker-bake.hcl",
Data: []byte( Data: []byte(
`target "app" { `target "app" {
output = ["type=registry"]
}`), }`),
} }
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.push=true"}, nil) ctx = context.TODO()
m, _, err = ReadTargets(ctx, []File{fp}, []string{"app"}, []string{"*.push=true"}, nil)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(m["app"].Outputs)) require.Equal(t, 1, len(m["app"].Outputs))
require.Equal(t, "type=registry", m["app"].Outputs[0]) require.Equal(t, "type=image,push=true", m["app"].Outputs[0])
})
t.Run("type registry push false", func(t *testing.T) {
fp := File{
Name: "docker-bake.hcl",
Data: []byte(
`target "app" {
output = ["type=registry"]
}`),
}
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.push=false"}, nil)
require.NoError(t, err)
require.Equal(t, 0, len(m["app"].Outputs))
})
t.Run("type local and empty target", func(t *testing.T) {
fp := File{
Name: "docker-bake.hcl",
Data: []byte(
`target "foo" {
output = [ "type=local,dest=out" ]
}
target "bar" {
}`),
}
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"foo", "bar"}, []string{"*.push=true"}, nil)
require.NoError(t, err)
require.Equal(t, 2, len(m))
require.Equal(t, 1, len(m["foo"].Outputs))
require.Equal(t, []string{"type=local,dest=out"}, m["foo"].Outputs)
require.Equal(t, 1, len(m["bar"].Outputs))
require.Equal(t, []string{"type=image,push=true"}, m["bar"].Outputs)
})
}
func TestLoadOverride(t *testing.T) {
t.Run("empty output", func(t *testing.T) {
fp := File{
Name: "docker-bake.hcl",
Data: []byte(
`target "app" {
}`),
}
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil)
require.NoError(t, err)
require.Equal(t, 1, len(m["app"].Outputs))
require.Equal(t, "type=docker", m["app"].Outputs[0])
})
t.Run("type docker", func(t *testing.T) {
fp := File{
Name: "docker-bake.hcl",
Data: []byte(
`target "app" {
output = ["type=docker"]
}`),
}
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil)
require.NoError(t, err)
require.Equal(t, 1, len(m["app"].Outputs))
require.Equal(t, []string{"type=docker"}, m["app"].Outputs)
})
t.Run("type image", func(t *testing.T) {
fp := File{
Name: "docker-bake.hcl",
Data: []byte(
`target "app" {
output = ["type=image"]
}`),
}
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil)
require.NoError(t, err)
require.Equal(t, 2, len(m["app"].Outputs))
require.Equal(t, []string{"type=image", "type=docker"}, m["app"].Outputs)
})
t.Run("type image load false", func(t *testing.T) {
fp := File{
Name: "docker-bake.hcl",
Data: []byte(
`target "app" {
output = ["type=image"]
}`),
}
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=false"}, nil)
require.NoError(t, err)
require.Equal(t, 1, len(m["app"].Outputs))
require.Equal(t, []string{"type=image"}, m["app"].Outputs)
})
t.Run("type registry", func(t *testing.T) {
fp := File{
Name: "docker-bake.hcl",
Data: []byte(
`target "app" {
output = ["type=registry"]
}`),
}
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil)
require.NoError(t, err)
require.Equal(t, 2, len(m["app"].Outputs))
require.Equal(t, []string{"type=registry", "type=docker"}, m["app"].Outputs)
})
t.Run("type oci", func(t *testing.T) {
fp := File{
Name: "docker-bake.hcl",
Data: []byte(
`target "app" {
output = ["type=oci,dest=out"]
}`),
}
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil)
require.NoError(t, err)
require.Equal(t, 2, len(m["app"].Outputs))
require.Equal(t, []string{"type=oci,dest=out", "type=docker"}, m["app"].Outputs)
})
t.Run("type docker with dest", func(t *testing.T) {
fp := File{
Name: "docker-bake.hcl",
Data: []byte(
`target "app" {
output = ["type=docker,dest=out"]
}`),
}
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil)
require.NoError(t, err)
require.Equal(t, 2, len(m["app"].Outputs))
require.Equal(t, []string{"type=docker,dest=out", "type=docker"}, m["app"].Outputs)
})
t.Run("type local and empty target", func(t *testing.T) {
fp := File{
Name: "docker-bake.hcl",
Data: []byte(
`target "foo" {
output = [ "type=local,dest=out" ]
}
target "bar" {
}`),
}
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"foo", "bar"}, []string{"*.load=true"}, nil)
require.NoError(t, err)
require.Equal(t, 2, len(m))
require.Equal(t, 1, len(m["foo"].Outputs))
require.Equal(t, []string{"type=local,dest=out"}, m["foo"].Outputs)
require.Equal(t, 1, len(m["bar"].Outputs))
require.Equal(t, []string{"type=docker"}, m["bar"].Outputs)
})
}
func TestLoadAndPushOverride(t *testing.T) {
t.Run("type local and empty target", func(t *testing.T) {
fp := File{
Name: "docker-bake.hcl",
Data: []byte(
`target "foo" {
output = [ "type=local,dest=out" ]
}
target "bar" {
}`),
}
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"foo", "bar"}, []string{"*.load=true", "*.push=true"}, nil)
require.NoError(t, err)
require.Equal(t, 2, len(m))
require.Equal(t, 1, len(m["foo"].Outputs))
sort.Strings(m["foo"].Outputs)
require.Equal(t, []string{"type=local,dest=out"}, m["foo"].Outputs)
require.Equal(t, 2, len(m["bar"].Outputs))
sort.Strings(m["bar"].Outputs)
require.Equal(t, []string{"type=docker", "type=image,push=true"}, m["bar"].Outputs)
})
t.Run("type registry", func(t *testing.T) {
fp := File{
Name: "docker-bake.hcl",
Data: []byte(
`target "foo" {
output = [ "type=registry" ]
}`),
}
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"foo"}, []string{"*.load=true", "*.push=true"}, nil)
require.NoError(t, err)
require.Equal(t, 1, len(m))
require.Equal(t, 2, len(m["foo"].Outputs))
sort.Strings(m["foo"].Outputs)
require.Equal(t, []string{"type=docker", "type=registry"}, m["foo"].Outputs)
})
} }
func TestReadTargetsCompose(t *testing.T) { func TestReadTargetsCompose(t *testing.T) {

View File

@@ -1445,39 +1445,6 @@ func TestVarUnsupportedType(t *testing.T) {
require.Error(t, err) require.Error(t, err)
} }
func TestHCLIndexOfFunc(t *testing.T) {
dt := []byte(`
variable "APP_VERSIONS" {
default = [
"1.42.4",
"1.42.3"
]
}
target "default" {
args = {
APP_VERSION = app_version
}
matrix = {
app_version = APP_VERSIONS
}
name="app-${replace(app_version, ".", "-")}"
tags = [
"app:${app_version}",
indexof(APP_VERSIONS, app_version) == 0 ? "app:latest" : "",
]
}
`)
c, err := ParseFile(dt, "docker-bake.hcl")
require.NoError(t, err)
require.Equal(t, 2, len(c.Targets))
require.Equal(t, "app-1-42-4", c.Targets[0].Name)
require.Equal(t, "app:latest", c.Targets[0].Tags[1])
require.Equal(t, "app-1-42-3", c.Targets[1].Name)
require.Empty(t, c.Targets[1].Tags[1])
}
func ptrstr(s interface{}) *string { func ptrstr(s interface{}) *string {
var n *string var n *string
if reflect.ValueOf(s).Kind() == reflect.String { if reflect.ValueOf(s).Kind() == reflect.String {

View File

@@ -9,7 +9,6 @@ import (
"github.com/hashicorp/go-cty-funcs/uuid" "github.com/hashicorp/go-cty-funcs/uuid"
"github.com/hashicorp/hcl/v2/ext/tryfunc" "github.com/hashicorp/hcl/v2/ext/tryfunc"
"github.com/hashicorp/hcl/v2/ext/typeexpr" "github.com/hashicorp/hcl/v2/ext/typeexpr"
"github.com/pkg/errors"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function" "github.com/zclconf/go-cty/cty/function"
"github.com/zclconf/go-cty/cty/function/stdlib" "github.com/zclconf/go-cty/cty/function/stdlib"
@@ -53,7 +52,6 @@ var stdlibFunctions = map[string]function.Function{
"hasindex": stdlib.HasIndexFunc, "hasindex": stdlib.HasIndexFunc,
"indent": stdlib.IndentFunc, "indent": stdlib.IndentFunc,
"index": stdlib.IndexFunc, "index": stdlib.IndexFunc,
"indexof": indexOfFunc,
"int": stdlib.IntFunc, "int": stdlib.IntFunc,
"join": stdlib.JoinFunc, "join": stdlib.JoinFunc,
"jsondecode": stdlib.JSONDecodeFunc, "jsondecode": stdlib.JSONDecodeFunc,
@@ -117,51 +115,6 @@ var stdlibFunctions = map[string]function.Function{
"zipmap": stdlib.ZipmapFunc, "zipmap": stdlib.ZipmapFunc,
} }
// indexOfFunc constructs a function that finds the element index for a given
// value in a list.
var indexOfFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "list",
Type: cty.DynamicPseudoType,
},
{
Name: "value",
Type: cty.DynamicPseudoType,
},
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
if !(args[0].Type().IsListType() || args[0].Type().IsTupleType()) {
return cty.NilVal, errors.New("argument must be a list or tuple")
}
if !args[0].IsKnown() {
return cty.UnknownVal(cty.Number), nil
}
if args[0].LengthInt() == 0 { // Easy path
return cty.NilVal, errors.New("cannot search an empty list")
}
for it := args[0].ElementIterator(); it.Next(); {
i, v := it.Element()
eq, err := stdlib.Equal(v, args[1])
if err != nil {
return cty.NilVal, err
}
if !eq.IsKnown() {
return cty.UnknownVal(cty.Number), nil
}
if eq.True() {
return i, nil
}
}
return cty.NilVal, errors.New("item not found")
},
})
// timestampFunc constructs a function that returns a string representation of the current date and time. // timestampFunc constructs a function that returns a string representation of the current date and time.
// //
// This function was imported from terraform's datetime utilities. // This function was imported from terraform's datetime utilities.

View File

@@ -1,49 +0,0 @@
package hclparser
import (
"testing"
"github.com/zclconf/go-cty/cty"
)
func TestIndexOf(t *testing.T) {
type testCase struct {
input cty.Value
key cty.Value
want cty.Value
wantErr bool
}
tests := map[string]testCase{
"index 0": {
input: cty.TupleVal([]cty.Value{cty.StringVal("one"), cty.NumberIntVal(2.0), cty.NumberIntVal(3), cty.StringVal("four")}),
key: cty.StringVal("one"),
want: cty.NumberIntVal(0),
},
"index 3": {
input: cty.TupleVal([]cty.Value{cty.StringVal("one"), cty.NumberIntVal(2.0), cty.NumberIntVal(3), cty.StringVal("four")}),
key: cty.StringVal("four"),
want: cty.NumberIntVal(3),
},
"index -1": {
input: cty.TupleVal([]cty.Value{cty.StringVal("one"), cty.NumberIntVal(2.0), cty.NumberIntVal(3), cty.StringVal("four")}),
key: cty.StringVal("3"),
wantErr: true,
},
}
for name, test := range tests {
name, test := name, test
t.Run(name, func(t *testing.T) {
got, err := indexOfFunc.Call([]cty.Value{test.input, test.key})
if err != nil {
if test.wantErr {
return
}
t.Fatalf("unexpected error: %s", err)
}
if !got.RawEquals(test.want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.want)
}
})
}
}

View File

@@ -4,8 +4,6 @@ import (
"archive/tar" "archive/tar"
"bytes" "bytes"
"context" "context"
"os"
"strings"
"github.com/docker/buildx/builder" "github.com/docker/buildx/builder"
controllerapi "github.com/docker/buildx/controller/pb" controllerapi "github.com/docker/buildx/controller/pb"
@@ -25,34 +23,13 @@ type Input struct {
} }
func ReadRemoteFiles(ctx context.Context, nodes []builder.Node, url string, names []string, pw progress.Writer) ([]File, *Input, error) { func ReadRemoteFiles(ctx context.Context, nodes []builder.Node, url string, names []string, pw progress.Writer) ([]File, *Input, error) {
var sessions []session.Attachable var session []session.Attachable
var filename string var filename string
st, ok := dockerui.DetectGitContext(url, false) st, ok := dockerui.DetectGitContext(url, false)
if ok { if ok {
if ssh, err := controllerapi.CreateSSH([]*controllerapi.SSH{{ ssh, err := controllerapi.CreateSSH([]*controllerapi.SSH{{ID: "default"}})
ID: "default", if err == nil {
Paths: strings.Split(os.Getenv("BUILDX_BAKE_GIT_SSH"), ","), session = append(session, ssh)
}}); err == nil {
sessions = append(sessions, ssh)
}
var gitAuthSecrets []*controllerapi.Secret
if _, ok := os.LookupEnv("BUILDX_BAKE_GIT_AUTH_TOKEN"); ok {
gitAuthSecrets = append(gitAuthSecrets, &controllerapi.Secret{
ID: llb.GitAuthTokenKey,
Env: "BUILDX_BAKE_GIT_AUTH_TOKEN",
})
}
if _, ok := os.LookupEnv("BUILDX_BAKE_GIT_AUTH_HEADER"); ok {
gitAuthSecrets = append(gitAuthSecrets, &controllerapi.Secret{
ID: llb.GitAuthHeaderKey,
Env: "BUILDX_BAKE_GIT_AUTH_HEADER",
})
}
if len(gitAuthSecrets) > 0 {
if secrets, err := controllerapi.CreateSecrets(gitAuthSecrets); err == nil {
sessions = append(sessions, secrets)
}
} }
} else { } else {
st, filename, ok = dockerui.DetectHTTPContext(url) st, filename, ok = dockerui.DetectHTTPContext(url)
@@ -82,7 +59,7 @@ func ReadRemoteFiles(ctx context.Context, nodes []builder.Node, url string, name
ch, done := progress.NewChannel(pw) ch, done := progress.NewChannel(pw)
defer func() { <-done }() defer func() { <-done }()
_, err = c.Build(ctx, client.SolveOpt{Session: sessions, Internal: true}, "buildx", func(ctx context.Context, c gwclient.Client) (*gwclient.Result, error) { _, err = c.Build(ctx, client.SolveOpt{Session: session, Internal: true}, "buildx", func(ctx context.Context, c gwclient.Client) (*gwclient.Result, error) {
def, err := st.Marshal(ctx) def, err := st.Marshal(ctx)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -1,6 +1,7 @@
package build package build
import ( import (
"bufio"
"bytes" "bytes"
"context" "context"
_ "crypto/sha256" // ensure digests can be computed _ "crypto/sha256" // ensure digests can be computed
@@ -9,32 +10,44 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"syscall"
"time" "time"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/content/local"
"github.com/containerd/containerd/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/platforms"
"github.com/distribution/reference" "github.com/distribution/reference"
"github.com/docker/buildx/builder" "github.com/docker/buildx/builder"
"github.com/docker/buildx/driver" "github.com/docker/buildx/driver"
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/desktop" "github.com/docker/buildx/util/desktop"
"github.com/docker/buildx/util/dockerutil" "github.com/docker/buildx/util/dockerutil"
"github.com/docker/buildx/util/imagetools" "github.com/docker/buildx/util/imagetools"
"github.com/docker/buildx/util/osutil"
"github.com/docker/buildx/util/progress" "github.com/docker/buildx/util/progress"
"github.com/docker/buildx/util/resolver" "github.com/docker/buildx/util/resolver"
"github.com/docker/buildx/util/waitmap" "github.com/docker/buildx/util/waitmap"
"github.com/docker/cli/opts" "github.com/docker/cli/opts"
imagetypes "github.com/docker/docker/api/types/image" imagetypes "github.com/docker/docker/api/types/image"
"github.com/docker/docker/builder/remotecontext/urlutil"
"github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/jsonmessage"
"github.com/moby/buildkit/client" "github.com/moby/buildkit/client"
"github.com/moby/buildkit/client/llb" "github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/client/ociindex"
"github.com/moby/buildkit/exporter/containerimage/exptypes" "github.com/moby/buildkit/exporter/containerimage/exptypes"
gateway "github.com/moby/buildkit/frontend/gateway/client" gateway "github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/identity"
"github.com/moby/buildkit/session" "github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/upload/uploadprovider"
"github.com/moby/buildkit/solver/errdefs" "github.com/moby/buildkit/solver/errdefs"
"github.com/moby/buildkit/solver/pb" "github.com/moby/buildkit/solver/pb"
spb "github.com/moby/buildkit/sourcepolicy/pb" spb "github.com/moby/buildkit/sourcepolicy/pb"
"github.com/moby/buildkit/util/apicaps"
"github.com/moby/buildkit/util/entitlements" "github.com/moby/buildkit/util/entitlements"
"github.com/moby/buildkit/util/progress/progresswriter" "github.com/moby/buildkit/util/progress/progresswriter"
"github.com/moby/buildkit/util/tracing" "github.com/moby/buildkit/util/tracing"
@@ -53,10 +66,7 @@ var (
const ( const (
//nolint:gosec // G101: false-positive //nolint:gosec // G101: false-positive
printFallbackImage = "docker/dockerfile:1.5@sha256:dbbd5e059e8a07ff7ea6233b213b36aa516b4c53c645f1817a4dd18b83cbea56" printFallbackImage = "docker/dockerfile:1.5.2-labs@sha256:f2e91734a84c0922ff47aa4098ab775f1dfa932430d2888dd5cad5251fafdac4"
// https://github.com/moby/buildkit/commit/71f99c52a669dc0322b5ea57bc28a09c20427227
//nolint:gosec // G101: false-positive
printLintFallbackImage = "docker.io/docker/dockerfile-upstream@sha256:47663570b6cc49ed90dc6e3215090a366989ab934d12dc93856a8ae0d27a95e7"
) )
type Options struct { type Options struct {
@@ -85,7 +95,6 @@ type Options struct {
Session []session.Attachable Session []session.Attachable
Linked bool // Linked marks this target as exclusively linked (not requested by the user). Linked bool // Linked marks this target as exclusively linked (not requested by the user).
PrintFunc *PrintFunc PrintFunc *PrintFunc
WithProvenanceResponse bool
SourcePolicy *spb.Policy SourcePolicy *spb.Policy
GroupRef string GroupRef string
} }
@@ -93,7 +102,6 @@ type Options struct {
type PrintFunc struct { type PrintFunc struct {
Name string Name string
Format string Format string
IgnoreStatus bool
} }
type Inputs struct { type Inputs struct {
@@ -149,6 +157,324 @@ func toRepoOnly(in string) (string, error) {
return strings.Join(out, ","), nil return strings.Join(out, ","), nil
} }
func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt Options, bopts gateway.BuildOpts, configDir string, pw progress.Writer, docker *dockerutil.Client) (solveOpt *client.SolveOpt, release func(), err error) {
nodeDriver := node.Driver
defers := make([]func(), 0, 2)
releaseF := func() {
for _, f := range defers {
f()
}
}
defer func() {
if err != nil {
releaseF()
}
}()
// inline cache from build arg
if v, ok := opt.BuildArgs["BUILDKIT_INLINE_CACHE"]; ok {
if v, _ := strconv.ParseBool(v); v {
opt.CacheTo = append(opt.CacheTo, client.CacheOptionsEntry{
Type: "inline",
Attrs: map[string]string{},
})
}
}
for _, e := range opt.CacheTo {
if e.Type != "inline" && !nodeDriver.Features(ctx)[driver.CacheExport] {
return nil, nil, notSupported(driver.CacheExport, nodeDriver, "https://docs.docker.com/go/build-cache-backends/")
}
}
cacheTo := make([]client.CacheOptionsEntry, 0, len(opt.CacheTo))
for _, e := range opt.CacheTo {
if e.Type == "gha" {
if !bopts.LLBCaps.Contains(apicaps.CapID("cache.gha")) {
continue
}
} else if e.Type == "s3" {
if !bopts.LLBCaps.Contains(apicaps.CapID("cache.s3")) {
continue
}
}
cacheTo = append(cacheTo, e)
}
cacheFrom := make([]client.CacheOptionsEntry, 0, len(opt.CacheFrom))
for _, e := range opt.CacheFrom {
if e.Type == "gha" {
if !bopts.LLBCaps.Contains(apicaps.CapID("cache.gha")) {
continue
}
} else if e.Type == "s3" {
if !bopts.LLBCaps.Contains(apicaps.CapID("cache.s3")) {
continue
}
}
cacheFrom = append(cacheFrom, e)
}
so := client.SolveOpt{
Ref: opt.Ref,
Frontend: "dockerfile.v0",
FrontendAttrs: map[string]string{},
LocalDirs: map[string]string{},
CacheExports: cacheTo,
CacheImports: cacheFrom,
AllowedEntitlements: opt.Allow,
SourcePolicy: opt.SourcePolicy,
}
if so.Ref == "" {
so.Ref = identity.NewID()
}
if opt.CgroupParent != "" {
so.FrontendAttrs["cgroup-parent"] = opt.CgroupParent
}
if v, ok := opt.BuildArgs["BUILDKIT_MULTI_PLATFORM"]; ok {
if v, _ := strconv.ParseBool(v); v {
so.FrontendAttrs["multi-platform"] = "true"
}
}
if multiDriver {
// force creation of manifest list
so.FrontendAttrs["multi-platform"] = "true"
}
attests := make(map[string]string)
for k, v := range opt.Attests {
if v != nil {
attests[k] = *v
}
}
supportAttestations := bopts.LLBCaps.Contains(apicaps.CapID("exporter.image.attestations")) && nodeDriver.Features(ctx)[driver.MultiPlatform]
if len(attests) > 0 {
if !supportAttestations {
if !nodeDriver.Features(ctx)[driver.MultiPlatform] {
return nil, nil, notSupported("Attestation", nodeDriver, "https://docs.docker.com/go/attestations/")
}
return nil, nil, errors.Errorf("Attestations are not supported by the current BuildKit daemon")
}
for k, v := range attests {
so.FrontendAttrs["attest:"+k] = v
}
}
if _, ok := opt.Attests["provenance"]; !ok && supportAttestations {
const noAttestEnv = "BUILDX_NO_DEFAULT_ATTESTATIONS"
var noProv bool
if v, ok := os.LookupEnv(noAttestEnv); ok {
noProv, err = strconv.ParseBool(v)
if err != nil {
return nil, nil, errors.Wrap(err, "invalid "+noAttestEnv)
}
}
if !noProv {
so.FrontendAttrs["attest:provenance"] = "mode=min,inline-only=true"
}
}
switch len(opt.Exports) {
case 1:
// valid
case 0:
if nodeDriver.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{}}}
}
default:
if err := bopts.LLBCaps.Supports(pb.CapMultipleExporters); err != nil {
return nil, nil, errors.Errorf("multiple outputs currently unsupported by the current BuildKit daemon, please upgrade to version v0.13+ or use a single output")
}
}
// fill in image exporter names from tags
if len(opt.Tags) > 0 {
tags := make([]string, len(opt.Tags))
for i, tag := range opt.Tags {
ref, err := reference.Parse(tag)
if err != nil {
return nil, nil, errors.Wrapf(err, "invalid tag %q", tag)
}
tags[i] = ref.String()
}
for i, e := range opt.Exports {
switch e.Type {
case "image", "oci", "docker":
opt.Exports[i].Attrs["name"] = strings.Join(tags, ",")
}
}
} else {
for _, e := range opt.Exports {
if e.Type == "image" && e.Attrs["name"] == "" && e.Attrs["push"] != "" {
if ok, _ := strconv.ParseBool(e.Attrs["push"]); ok {
return nil, nil, errors.Errorf("tag is needed when pushing to registry")
}
}
}
}
// 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 == "oci" && !nodeDriver.Features(ctx)[driver.OCIExporter] {
return nil, nil, notSupported(driver.OCIExporter, nodeDriver, "https://docs.docker.com/go/build-exporters/")
}
if e.Type == "docker" {
features := docker.Features(ctx, e.Attrs["context"])
if features[dockerutil.OCIImporter] && e.Output == nil {
// rely on oci importer if available (which supports
// multi-platform images), otherwise fall back to docker
opt.Exports[i].Type = "oci"
} else if len(opt.Platforms) > 1 || len(attests) > 0 {
if e.Output != nil {
return nil, nil, errors.Errorf("docker exporter does not support exporting manifest lists, use the oci exporter instead")
}
return nil, nil, errors.Errorf("docker exporter does not currently support exporting manifest lists")
}
if e.Output == nil {
if nodeDriver.IsMobyDriver() {
e.Type = "image"
} else {
w, cancel, err := docker.LoadImage(ctx, e.Attrs["context"], pw)
if err != nil {
return nil, nil, err
}
defers = append(defers, cancel)
opt.Exports[i].Output = func(_ map[string]string) (io.WriteCloser, error) {
return w, nil
}
}
} else if !nodeDriver.Features(ctx)[driver.DockerExporter] {
return nil, nil, notSupported(driver.DockerExporter, nodeDriver, "https://docs.docker.com/go/build-exporters/")
}
}
if e.Type == "image" && nodeDriver.IsMobyDriver() {
opt.Exports[i].Type = "moby"
if e.Attrs["push"] != "" {
if ok, _ := strconv.ParseBool(e.Attrs["push"]); ok {
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")
}
}
}
}
if e.Type == "docker" || e.Type == "image" || e.Type == "oci" {
// inline buildinfo attrs from build arg
if v, ok := opt.BuildArgs["BUILDKIT_INLINE_BUILDINFO_ATTRS"]; ok {
e.Attrs["buildinfo-attrs"] = v
}
}
}
so.Exports = opt.Exports
so.Session = opt.Session
releaseLoad, err := LoadInputs(ctx, nodeDriver, opt.Inputs, pw, &so)
if err != nil {
return nil, nil, err
}
defers = append(defers, releaseLoad)
if sharedKey := so.LocalDirs["context"]; sharedKey != "" {
if p, err := filepath.Abs(sharedKey); err == nil {
sharedKey = filepath.Base(p)
}
so.SharedKey = sharedKey + ":" + confutil.TryNodeIdentifier(configDir)
}
if opt.Pull {
so.FrontendAttrs["image-resolve-mode"] = pb.AttrImageResolveModeForcePull
} else if nodeDriver.IsMobyDriver() {
// moby driver always resolves local images by default
so.FrontendAttrs["image-resolve-mode"] = pb.AttrImageResolveModePreferLocal
}
if opt.Target != "" {
so.FrontendAttrs["target"] = opt.Target
}
if len(opt.NoCacheFilter) > 0 {
so.FrontendAttrs["no-cache"] = strings.Join(opt.NoCacheFilter, ",")
}
if opt.NoCache {
so.FrontendAttrs["no-cache"] = ""
}
for k, v := range opt.BuildArgs {
so.FrontendAttrs["build-arg:"+k] = v
}
for k, v := range opt.Labels {
so.FrontendAttrs["label:"+k] = v
}
for k, v := range node.ProxyConfig {
if _, ok := opt.BuildArgs[k]; !ok {
so.FrontendAttrs["build-arg:"+k] = v
}
}
// set platforms
if len(opt.Platforms) != 0 {
pp := make([]string, len(opt.Platforms))
for i, p := range opt.Platforms {
pp[i] = platforms.Format(p)
}
if len(pp) > 1 && !nodeDriver.Features(ctx)[driver.MultiPlatform] {
return nil, nil, notSupported(driver.MultiPlatform, nodeDriver, "https://docs.docker.com/go/build-multi-platform/")
}
so.FrontendAttrs["platform"] = strings.Join(pp, ",")
}
// setup networkmode
switch opt.NetworkMode {
case "host":
so.FrontendAttrs["force-network-mode"] = opt.NetworkMode
so.AllowedEntitlements = append(so.AllowedEntitlements, entitlements.EntitlementNetworkHost)
case "none":
so.FrontendAttrs["force-network-mode"] = opt.NetworkMode
case "", "default":
default:
return nil, nil, errors.Errorf("network mode %q not supported by buildkit - you can define a custom network for your builder using the network driver-opt in buildx create", opt.NetworkMode)
}
// setup extrahosts
extraHosts, err := toBuildkitExtraHosts(ctx, opt.ExtraHosts, nodeDriver)
if err != nil {
return nil, nil, err
}
if len(extraHosts) > 0 {
so.FrontendAttrs["add-hosts"] = extraHosts
}
// setup shm size
if opt.ShmSize.Value() > 0 {
so.FrontendAttrs["shm-size"] = strconv.FormatInt(opt.ShmSize.Value(), 10)
}
// setup ulimits
ulimits, err := toBuildkitUlimits(opt.Ulimits)
if err != nil {
return nil, nil, err
} else if len(ulimits) > 0 {
so.FrontendAttrs["ulimit"] = ulimits
}
return &so, releaseF, nil
}
func Build(ctx context.Context, nodes []builder.Node, opt map[string]Options, docker *dockerutil.Client, configDir string, w progress.Writer) (resp map[string]*client.SolveResponse, err error) { func Build(ctx context.Context, nodes []builder.Node, opt map[string]Options, docker *dockerutil.Client, configDir string, w progress.Writer) (resp map[string]*client.SolveResponse, err error) {
return BuildWithResultHandler(ctx, nodes, opt, docker, configDir, w, nil) return BuildWithResultHandler(ctx, nodes, opt, docker, configDir, w, nil)
} }
@@ -163,7 +489,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
return nil, errors.Wrapf(err, "no valid drivers found") return nil, errors.Wrapf(err, "no valid drivers found")
} }
var noMobyDriver *driver.DriverHandle var noMobyDriver driver.Driver
for _, n := range nodes { for _, n := range nodes {
if !n.Driver.IsMobyDriver() { if !n.Driver.IsMobyDriver() {
noMobyDriver = n.Driver noMobyDriver = n.Driver
@@ -174,10 +500,6 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
if noMobyDriver != nil && !noDefaultLoad() && noPrintFunc(opt) { if noMobyDriver != nil && !noDefaultLoad() && noPrintFunc(opt) {
var noOutputTargets []string var noOutputTargets []string
for name, opt := range opt { for name, opt := range opt {
if noMobyDriver.Features(ctx)[driver.DefaultLoad] {
continue
}
if !opt.Linked && len(opt.Exports) == 0 { if !opt.Linked && len(opt.Exports) == 0 {
noOutputTargets = append(noOutputTargets, name) noOutputTargets = append(noOutputTargets, name)
} }
@@ -228,7 +550,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
if err != nil { if err != nil {
return nil, err return nil, err
} }
so, release, err := toSolveOpt(ctx, np.Node(), multiDriver, opt, gatewayOpts, configDir, addVCSLocalDir, w, docker) so, release, err := toSolveOpt(ctx, np.Node(), multiDriver, opt, gatewayOpts, configDir, w, docker)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -238,6 +560,9 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
for k, v := range gitattrs { for k, v := range gitattrs {
so.FrontendAttrs[k] = v so.FrontendAttrs[k] = v
} }
if addVCSLocalDir != nil {
addVCSLocalDir(so)
}
defers = append(defers, release) defers = append(defers, release)
reqn = append(reqn, &reqForNode{ reqn = append(reqn, &reqForNode{
resolvedNode: np, resolvedNode: np,
@@ -404,8 +729,25 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
res, err := c.Solve(ctx, req) res, err := c.Solve(ctx, req)
if err != nil { if err != nil {
req, ok := fallbackPrintError(err, req) fallback := false
if ok { var reqErr *errdefs.UnsupportedSubrequestError
if errors.As(err, &reqErr) {
switch reqErr.Name {
case "frontend.outline", "frontend.targets":
fallback = true
default:
return nil, err
}
} else {
return nil, err
}
// buildkit v0.8 vendored in Docker 20.10 does not support typed errors
if strings.Contains(err.Error(), "unsupported request frontend.outline") || strings.Contains(err.Error(), "unsupported request frontend.targets") {
fallback = true
}
if fallback {
req.FrontendOpt["build-arg:BUILDKIT_SYNTAX"] = printFallbackImage
res2, err2 := c.Solve(ctx, req) res2, err2 := c.Solve(ctx, req)
if err2 != nil { if err2 != nil {
return nil, err return nil, err
@@ -455,7 +797,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
} else { } else {
rr, err = c.Build(ctx, *so, "buildx", buildFunc, ch) rr, err = c.Build(ctx, *so, "buildx", buildFunc, ch)
} }
if !so.Internal && desktop.BuildBackendEnabled() && node.Driver.HistoryAPISupported(ctx) { if desktop.BuildBackendEnabled() && node.Driver.HistoryAPISupported(ctx) {
if err != nil { if err != nil {
return &desktop.ErrorWithBuildRef{ return &desktop.ErrorWithBuildRef{
Ref: buildRef, Ref: buildRef,
@@ -476,11 +818,6 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
rr.ExporterResponse[k] = string(v) rr.ExporterResponse[k] = string(v)
} }
rr.ExporterResponse["buildx.build.ref"] = buildRef rr.ExporterResponse["buildx.build.ref"] = buildRef
if opt.WithProvenanceResponse && node.Driver.HistoryAPISupported(ctx) {
if err := setRecordProvenance(ctx, c, rr, so.Ref, pw); err != nil {
return err
}
}
node := dp.Node().Driver node := dp.Node().Driver
if node.IsMobyDriver() { if node.IsMobyDriver() {
@@ -541,7 +878,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
} }
if pushNames != "" { if pushNames != "" {
err := progress.Write(pw, fmt.Sprintf("merging manifest list %s", pushNames), func() error { progress.Write(pw, fmt.Sprintf("merging manifest list %s", pushNames), func() error {
descs := make([]specs.Descriptor, 0, len(res)) descs := make([]specs.Descriptor, 0, len(res))
for _, r := range res { for _, r := range res {
@@ -637,9 +974,6 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
} }
return nil return nil
}) })
if err != nil {
return err
}
} }
return nil return nil
}) })
@@ -658,7 +992,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
return resp, nil return resp, nil
} }
func pushWithMoby(ctx context.Context, d *driver.DriverHandle, name string, l progress.SubLogger) error { func pushWithMoby(ctx context.Context, d driver.Driver, name string, l progress.SubLogger) error {
api := d.Config().DockerAPI api := d.Config().DockerAPI
if api == nil { if api == nil {
return errors.Errorf("invalid empty Docker API reference") // should never happen return errors.Errorf("invalid empty Docker API reference") // should never happen
@@ -738,7 +1072,7 @@ func pushWithMoby(ctx context.Context, d *driver.DriverHandle, name string, l pr
return nil return nil
} }
func remoteDigestWithMoby(ctx context.Context, d *driver.DriverHandle, name string) (string, error) { func remoteDigestWithMoby(ctx context.Context, d driver.Driver, name string) (string, error) {
api := d.Config().DockerAPI api := d.Config().DockerAPI
if api == nil { if api == nil {
return "", errors.Errorf("invalid empty Docker API reference") // should never happen return "", errors.Errorf("invalid empty Docker API reference") // should never happen
@@ -761,6 +1095,231 @@ func remoteDigestWithMoby(ctx context.Context, d *driver.DriverHandle, name stri
return remoteImage.Descriptor.Digest.String(), nil return remoteImage.Descriptor.Digest.String(), nil
} }
func createTempDockerfile(r io.Reader) (string, error) {
dir, err := os.MkdirTemp("", "dockerfile")
if err != nil {
return "", err
}
f, err := os.Create(filepath.Join(dir, "Dockerfile"))
if err != nil {
return "", err
}
defer f.Close()
if _, err := io.Copy(f, r); err != nil {
return "", err
}
return dir, err
}
func LoadInputs(ctx context.Context, d *driver.DriverHandle, 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)")
}
// TODO: handle stdin, symlinks, remote contexts, check files exist
var (
err error
dockerfileReader io.Reader
dockerfileDir string
dockerfileName = inp.DockerfilePath
toRemove []string
)
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(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 !(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, _ = os.MkdirTemp("", "empty-dir")
toRemove = append(toRemove, inp.ContextPath)
target.LocalDirs["context"] = inp.ContextPath
}
}
case osutil.IsLocalDir(inp.ContextPath):
target.LocalDirs["context"] = inp.ContextPath
switch inp.DockerfilePath {
case "-":
dockerfileReader = inp.InStream
case "":
dockerfileDir = inp.ContextPath
default:
dockerfileDir = filepath.Dir(inp.DockerfilePath)
dockerfileName = filepath.Base(inp.DockerfilePath)
}
case IsRemoteURL(inp.ContextPath):
if inp.DockerfilePath == "-" {
dockerfileReader = inp.InStream
} else if filepath.IsAbs(inp.DockerfilePath) {
dockerfileDir = filepath.Dir(inp.DockerfilePath)
dockerfileName = filepath.Base(inp.DockerfilePath)
target.FrontendAttrs["dockerfilekey"] = "dockerfile"
}
target.FrontendAttrs["context"] = inp.ContextPath
default:
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"
}
if dockerfileDir != "" {
target.LocalDirs["dockerfile"] = dockerfileDir
dockerfileName = handleLowercaseDockerfile(dockerfileDir, dockerfileName)
}
target.FrontendAttrs["filename"] = dockerfileName
for k, v := range inp.NamedContexts {
target.FrontendAttrs["frontend.caps"] = "moby.buildkit.frontend.contexts+forward"
if v.State != nil {
target.FrontendAttrs["context:"+k] = "input:" + k
if target.FrontendInputs == nil {
target.FrontendInputs = make(map[string]llb.State)
}
target.FrontendInputs[k] = *v.State
continue
}
if IsRemoteURL(v.Path) || strings.HasPrefix(v.Path, "docker-image://") || strings.HasPrefix(v.Path, "target:") {
target.FrontendAttrs["context:"+k] = v.Path
continue
}
// handle OCI layout
if strings.HasPrefix(v.Path, "oci-layout://") {
pathAlone := strings.TrimPrefix(v.Path, "oci-layout://")
localPath := pathAlone
localPath, dig, hasDigest := strings.Cut(localPath, "@")
localPath, tag, hasTag := strings.Cut(localPath, ":")
if !hasTag {
tag = "latest"
hasTag = true
}
idx := ociindex.NewStoreIndex(localPath)
if !hasDigest {
// lookup by name
desc, err := idx.Get(tag)
if err != nil {
return nil, err
}
if desc != nil {
dig = string(desc.Digest)
hasDigest = true
}
}
if !hasDigest {
// lookup single
desc, err := idx.GetSingle()
if err != nil {
return nil, err
}
if desc != nil {
dig = string(desc.Digest)
hasDigest = true
}
}
if !hasDigest {
return nil, errors.Errorf("oci-layout reference %q could not be resolved", v.Path)
}
_, err := digest.Parse(dig)
if err != nil {
return nil, errors.Wrapf(err, "invalid oci-layout digest %s", dig)
}
store, err := local.NewStore(localPath)
if err != nil {
return nil, errors.Wrapf(err, "invalid store at %s", localPath)
}
storeName := identity.NewID()
if target.OCIStores == nil {
target.OCIStores = map[string]content.Store{}
}
target.OCIStores[storeName] = store
layout := "oci-layout://" + storeName
if hasTag {
layout += ":" + tag
}
if hasDigest {
layout += "@" + dig
}
target.FrontendAttrs["context:"+k] = layout
continue
}
st, err := os.Stat(v.Path)
if err != nil {
return nil, errors.Wrapf(err, "failed to get build context %v", k)
}
if !st.IsDir() {
return nil, errors.Wrapf(syscall.ENOTDIR, "failed to get build context path %v", v)
}
localName := k
if k == "context" || k == "dockerfile" {
localName = "_" + k // underscore to avoid collisions
}
target.LocalDirs[localName] = v.Path
target.FrontendAttrs["context:"+k] = "local:" + localName
}
release := func() {
for _, dir := range toRemove {
os.RemoveAll(dir)
}
}
return release, nil
}
func resultKey(index int, name string) string { func resultKey(index int, name string) string {
return fmt.Sprintf("%d-%s", index, name) return fmt.Sprintf("%d-%s", index, name)
} }
@@ -784,11 +1343,11 @@ func calculateChildTargets(reqs map[string][]*reqForNode, opt map[string]Options
} }
func waitContextDeps(ctx context.Context, index int, results *waitmap.Map, so *client.SolveOpt) error { func waitContextDeps(ctx context.Context, index int, results *waitmap.Map, so *client.SolveOpt) error {
m := map[string][]string{} m := map[string]string{}
for k, v := range so.FrontendAttrs { for k, v := range so.FrontendAttrs {
if strings.HasPrefix(k, "context:") && strings.HasPrefix(v, "target:") { if strings.HasPrefix(k, "context:") && strings.HasPrefix(v, "target:") {
target := resultKey(index, strings.TrimPrefix(v, "target:")) target := resultKey(index, strings.TrimPrefix(v, "target:"))
m[target] = append(m[target], k) m[target] = k
} }
} }
if len(m) == 0 { if len(m) == 0 {
@@ -803,7 +1362,7 @@ func waitContextDeps(ctx context.Context, index int, results *waitmap.Map, so *c
return err return err
} }
for k, contexts := range m { for k, v := range m {
r, ok := res[k] r, ok := res[k]
if !ok { if !ok {
continue continue
@@ -818,8 +1377,6 @@ func waitContextDeps(ctx context.Context, index int, results *waitmap.Map, so *c
if so.FrontendInputs == nil { if so.FrontendInputs == nil {
so.FrontendInputs = map[string]llb.State{} so.FrontendInputs = map[string]llb.State{}
} }
for _, v := range contexts {
if len(rr.Refs) > 0 { if len(rr.Refs) > 0 {
for platform, r := range rr.Refs { for platform, r := range rr.Refs {
st, err := r.ToState() st, err := r.ToState()
@@ -868,48 +1425,56 @@ func waitContextDeps(ctx context.Context, index int, results *waitmap.Map, so *c
} }
} }
} }
}
return nil return nil
} }
func fallbackPrintError(err error, req gateway.SolveRequest) (gateway.SolveRequest, bool) { func notSupported(f driver.Feature, d driver.Driver, docs string) error {
if _, ok := req.FrontendOpt["requestid"]; !ok { return errors.Errorf(`%s is not supported for the %s driver.
return req, false Switch to a different driver, or turn on the containerd image store, and try again.
Learn more at %s`, f, d.Factory().Name(), docs)
}
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
}
// handle https://github.com/moby/moby/pull/10858
func handleLowercaseDockerfile(dir, p string) string {
if filepath.Base(p) != "Dockerfile" {
return p
} }
fallback := false f, err := os.Open(filepath.Dir(filepath.Join(dir, p)))
fallbackLint := false if err != nil {
var reqErr *errdefs.UnsupportedSubrequestError return p
if errors.As(err, &reqErr) {
switch reqErr.Name {
case "frontend.lint":
fallbackLint = true
fallthrough
case "frontend.outline", "frontend.targets":
fallback = true
default:
return req, false
}
} }
// buildkit v0.8 vendored in Docker 20.10 does not support typed errors names, err := f.Readdirnames(-1)
for _, req := range []string{"frontend.outline", "frontend.targets", "frontend.lint"} { if err != nil {
if strings.Contains(err.Error(), "unsupported request "+req) { return p
fallback = true
}
if req == "frontend.lint" {
fallbackLint = true
}
} }
if fallback { foundLowerCase := false
req.FrontendOpt["build-arg:BUILDKIT_SYNTAX"] = printFallbackImage for _, n := range names {
if fallbackLint { if n == "Dockerfile" {
req.FrontendOpt["build-arg:BUILDKIT_SYNTAX"] = printLintFallbackImage return p
} }
return req, true if n == "dockerfile" {
foundLowerCase = true
} }
return req, false }
if foundLowerCase {
return filepath.Join(filepath.Dir(p), "dockerfile")
}
return p
} }
func noPrintFunc(opt map[string]Options) bool { func noPrintFunc(opt map[string]Options) bool {

View File

@@ -3,7 +3,6 @@ package build
import ( import (
"context" "context"
"fmt" "fmt"
"sync"
"github.com/containerd/containerd/platforms" "github.com/containerd/containerd/platforms"
"github.com/docker/buildx/builder" "github.com/docker/buildx/builder"
@@ -47,22 +46,10 @@ func (dp resolvedNode) BuildOpts(ctx context.Context) (gateway.BuildOpts, error)
type matchMaker func(specs.Platform) platforms.MatchComparer type matchMaker func(specs.Platform) platforms.MatchComparer
type cachedGroup[T any] struct {
g flightcontrol.Group[T]
cache map[int]T
cacheMu sync.Mutex
}
func newCachedGroup[T any]() cachedGroup[T] {
return cachedGroup[T]{
cache: map[int]T{},
}
}
type nodeResolver struct { type nodeResolver struct {
nodes []builder.Node nodes []builder.Node
clients cachedGroup[*client.Client] clients flightcontrol.Group[*client.Client]
buildOpts cachedGroup[gateway.BuildOpts] opt flightcontrol.Group[gateway.BuildOpts]
} }
func resolveDrivers(ctx context.Context, nodes []builder.Node, opt map[string]Options, pw progress.Writer) (map[string][]*resolvedNode, error) { func resolveDrivers(ctx context.Context, nodes []builder.Node, opt map[string]Options, pw progress.Writer) (map[string][]*resolvedNode, error) {
@@ -77,8 +64,6 @@ func resolveDrivers(ctx context.Context, nodes []builder.Node, opt map[string]Op
func newDriverResolver(nodes []builder.Node) *nodeResolver { func newDriverResolver(nodes []builder.Node) *nodeResolver {
r := &nodeResolver{ r := &nodeResolver{
nodes: nodes, nodes: nodes,
clients: newCachedGroup[*client.Client](),
buildOpts: newCachedGroup[gateway.BuildOpts](),
} }
return r return r
} }
@@ -194,7 +179,6 @@ func (r *nodeResolver) resolve(ctx context.Context, ps []specs.Platform, pw prog
resolver: r, resolver: r,
driverIndex: 0, driverIndex: 0,
}) })
nodeIdxs = append(nodeIdxs, 0)
} else { } else {
for i, idx := range nodeIdxs { for i, idx := range nodeIdxs {
node := &resolvedNode{ node := &resolvedNode{
@@ -253,24 +237,11 @@ func (r *nodeResolver) boot(ctx context.Context, idxs []int, pw progress.Writer)
for i, idx := range idxs { for i, idx := range idxs {
i, idx := i, idx i, idx := i, idx
eg.Go(func() error { eg.Go(func() error {
c, err := r.clients.g.Do(ctx, fmt.Sprint(idx), func(ctx context.Context) (*client.Client, error) { c, err := r.clients.Do(ctx, fmt.Sprint(idx), func(ctx context.Context) (*client.Client, error) {
if r.nodes[idx].Driver == nil { if r.nodes[idx].Driver == nil {
return nil, nil return nil, nil
} }
r.clients.cacheMu.Lock() return driver.Boot(ctx, baseCtx, r.nodes[idx].Driver, pw)
c, ok := r.clients.cache[idx]
r.clients.cacheMu.Unlock()
if ok {
return c, nil
}
c, err := driver.Boot(ctx, baseCtx, r.nodes[idx].Driver, pw)
if err != nil {
return nil, err
}
r.clients.cacheMu.Lock()
r.clients.cache[idx] = c
r.clients.cacheMu.Unlock()
return c, nil
}) })
if err != nil { if err != nil {
return err return err
@@ -301,25 +272,14 @@ func (r *nodeResolver) opts(ctx context.Context, idxs []int, pw progress.Writer)
continue continue
} }
eg.Go(func() error { eg.Go(func() error {
opt, err := r.buildOpts.g.Do(ctx, fmt.Sprint(idx), func(ctx context.Context) (gateway.BuildOpts, error) { opt, err := r.opt.Do(ctx, fmt.Sprint(idx), func(ctx context.Context) (gateway.BuildOpts, error) {
r.buildOpts.cacheMu.Lock() opt := gateway.BuildOpts{}
opt, ok := r.buildOpts.cache[idx]
r.buildOpts.cacheMu.Unlock()
if ok {
return opt, nil
}
_, err := c.Build(ctx, client.SolveOpt{ _, err := c.Build(ctx, client.SolveOpt{
Internal: true, Internal: true,
}, "buildx", func(ctx context.Context, c gateway.Client) (*gateway.Result, error) { }, "buildx", func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
opt = c.BuildOpts() opt = c.BuildOpts()
return nil, nil return nil, nil
}, nil) }, nil)
if err != nil {
return gateway.BuildOpts{}, err
}
r.buildOpts.cacheMu.Lock()
r.buildOpts.cache[idx] = opt
r.buildOpts.cacheMu.Unlock()
return opt, err return opt, err
}) })
if err != nil { if err != nil {

View File

@@ -17,7 +17,7 @@ import (
const DockerfileLabel = "com.docker.image.source.entrypoint" const DockerfileLabel = "com.docker.image.source.entrypoint"
func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath string) (map[string]string, func(key, dir string, so *client.SolveOpt), error) { func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath string) (map[string]string, func(*client.SolveOpt), error) {
res := make(map[string]string) res := make(map[string]string)
if contextPath == "" { if contextPath == "" {
return nil, nil, nil return nil, nil, nil
@@ -112,20 +112,26 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st
} }
} }
return res, func(key, dir string, so *client.SolveOpt) { return res, func(so *client.SolveOpt) {
if !setGitInfo || root == "" { if !setGitInfo || root == "" {
return return
} }
dir, err := filepath.Abs(dir) for k, dir := range so.LocalDirs {
dir, err = filepath.EvalSymlinks(dir)
if err != nil { if err != nil {
return continue
}
dir, err = filepath.Abs(dir)
if err != nil {
continue
} }
if lp, err := osutil.GetLongPathName(dir); err == nil { if lp, err := osutil.GetLongPathName(dir); err == nil {
dir = lp dir = lp
} }
dir = osutil.SanitizePath(dir) dir = osutil.SanitizePath(dir)
if r, err := filepath.Rel(root, dir); err == nil && !strings.HasPrefix(r, "..") { if r, err := filepath.Rel(root, dir); err == nil && !strings.HasPrefix(r, "..") {
so.FrontendAttrs["vcs:localdir:"+key] = r so.FrontendAttrs["vcs:localdir:"+k] = r
}
} }
}, nil }, nil
} }

View File

@@ -161,17 +161,19 @@ func TestLocalDirs(t *testing.T) {
so := &client.SolveOpt{ so := &client.SolveOpt{
FrontendAttrs: map[string]string{}, FrontendAttrs: map[string]string{},
LocalDirs: map[string]string{
"context": ".",
"dockerfile": ".",
},
} }
_, addVCSLocalDir, err := getGitAttributes(context.Background(), ".", "Dockerfile") _, addVCSLocalDir, err := getGitAttributes(context.Background(), ".", "Dockerfile")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, addVCSLocalDir) require.NotNil(t, addVCSLocalDir)
require.NoError(t, setLocalMount("context", ".", so, addVCSLocalDir)) addVCSLocalDir(so)
require.Contains(t, so.FrontendAttrs, "vcs:localdir:context") require.Contains(t, so.FrontendAttrs, "vcs:localdir:context")
assert.Equal(t, ".", so.FrontendAttrs["vcs:localdir:context"]) assert.Equal(t, ".", so.FrontendAttrs["vcs:localdir:context"])
require.NoError(t, setLocalMount("dockerfile", ".", so, addVCSLocalDir))
require.Contains(t, so.FrontendAttrs, "vcs:localdir:dockerfile") require.Contains(t, so.FrontendAttrs, "vcs:localdir:dockerfile")
assert.Equal(t, ".", so.FrontendAttrs["vcs:localdir:dockerfile"]) assert.Equal(t, ".", so.FrontendAttrs["vcs:localdir:dockerfile"])
} }
@@ -193,17 +195,19 @@ func TestLocalDirsSub(t *testing.T) {
so := &client.SolveOpt{ so := &client.SolveOpt{
FrontendAttrs: map[string]string{}, FrontendAttrs: map[string]string{},
LocalDirs: map[string]string{
"context": ".",
"dockerfile": "app",
},
} }
_, addVCSLocalDir, err := getGitAttributes(context.Background(), ".", "app/Dockerfile") _, addVCSLocalDir, err := getGitAttributes(context.Background(), ".", "app/Dockerfile")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, addVCSLocalDir) require.NotNil(t, addVCSLocalDir)
require.NoError(t, setLocalMount("context", ".", so, addVCSLocalDir)) addVCSLocalDir(so)
require.Contains(t, so.FrontendAttrs, "vcs:localdir:context") require.Contains(t, so.FrontendAttrs, "vcs:localdir:context")
assert.Equal(t, ".", so.FrontendAttrs["vcs:localdir:context"]) assert.Equal(t, ".", so.FrontendAttrs["vcs:localdir:context"])
require.NoError(t, setLocalMount("dockerfile", "app", so, addVCSLocalDir))
require.Contains(t, so.FrontendAttrs, "vcs:localdir:dockerfile") require.Contains(t, so.FrontendAttrs, "vcs:localdir:dockerfile")
assert.Equal(t, "app", so.FrontendAttrs["vcs:localdir:dockerfile"]) assert.Equal(t, "app", so.FrontendAttrs["vcs:localdir:dockerfile"])
} }

View File

@@ -1,646 +0,0 @@
package build
import (
"bufio"
"context"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/content/local"
"github.com/containerd/containerd/platforms"
"github.com/distribution/reference"
"github.com/docker/buildx/builder"
"github.com/docker/buildx/driver"
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/dockerutil"
"github.com/docker/buildx/util/osutil"
"github.com/docker/buildx/util/progress"
"github.com/docker/docker/builder/remotecontext/urlutil"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/client/ociindex"
gateway "github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/identity"
"github.com/moby/buildkit/session/upload/uploadprovider"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/apicaps"
"github.com/moby/buildkit/util/entitlements"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/tonistiigi/fsutil"
)
func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt Options, bopts gateway.BuildOpts, configDir string, addVCSLocalDir func(key, dir string, so *client.SolveOpt), pw progress.Writer, docker *dockerutil.Client) (_ *client.SolveOpt, release func(), err error) {
nodeDriver := node.Driver
defers := make([]func(), 0, 2)
releaseF := func() {
for _, f := range defers {
f()
}
}
defer func() {
if err != nil {
releaseF()
}
}()
// inline cache from build arg
if v, ok := opt.BuildArgs["BUILDKIT_INLINE_CACHE"]; ok {
if v, _ := strconv.ParseBool(v); v {
opt.CacheTo = append(opt.CacheTo, client.CacheOptionsEntry{
Type: "inline",
Attrs: map[string]string{},
})
}
}
for _, e := range opt.CacheTo {
if e.Type != "inline" && !nodeDriver.Features(ctx)[driver.CacheExport] {
return nil, nil, notSupported(driver.CacheExport, nodeDriver, "https://docs.docker.com/go/build-cache-backends/")
}
}
cacheTo := make([]client.CacheOptionsEntry, 0, len(opt.CacheTo))
for _, e := range opt.CacheTo {
if e.Type == "gha" {
if !bopts.LLBCaps.Contains(apicaps.CapID("cache.gha")) {
continue
}
} else if e.Type == "s3" {
if !bopts.LLBCaps.Contains(apicaps.CapID("cache.s3")) {
continue
}
}
cacheTo = append(cacheTo, e)
}
cacheFrom := make([]client.CacheOptionsEntry, 0, len(opt.CacheFrom))
for _, e := range opt.CacheFrom {
if e.Type == "gha" {
if !bopts.LLBCaps.Contains(apicaps.CapID("cache.gha")) {
continue
}
} else if e.Type == "s3" {
if !bopts.LLBCaps.Contains(apicaps.CapID("cache.s3")) {
continue
}
}
cacheFrom = append(cacheFrom, e)
}
so := client.SolveOpt{
Ref: opt.Ref,
Frontend: "dockerfile.v0",
FrontendAttrs: map[string]string{},
LocalMounts: map[string]fsutil.FS{},
CacheExports: cacheTo,
CacheImports: cacheFrom,
AllowedEntitlements: opt.Allow,
SourcePolicy: opt.SourcePolicy,
}
if so.Ref == "" {
so.Ref = identity.NewID()
}
if opt.CgroupParent != "" {
so.FrontendAttrs["cgroup-parent"] = opt.CgroupParent
}
if v, ok := opt.BuildArgs["BUILDKIT_MULTI_PLATFORM"]; ok {
if v, _ := strconv.ParseBool(v); v {
so.FrontendAttrs["multi-platform"] = "true"
}
}
if multiDriver {
// force creation of manifest list
so.FrontendAttrs["multi-platform"] = "true"
}
attests := make(map[string]string)
for k, v := range opt.Attests {
if v != nil {
attests[k] = *v
}
}
supportAttestations := bopts.LLBCaps.Contains(apicaps.CapID("exporter.image.attestations")) && nodeDriver.Features(ctx)[driver.MultiPlatform]
if len(attests) > 0 {
if !supportAttestations {
if !nodeDriver.Features(ctx)[driver.MultiPlatform] {
return nil, nil, notSupported("Attestation", nodeDriver, "https://docs.docker.com/go/attestations/")
}
return nil, nil, errors.Errorf("Attestations are not supported by the current BuildKit daemon")
}
for k, v := range attests {
so.FrontendAttrs["attest:"+k] = v
}
}
if _, ok := opt.Attests["provenance"]; !ok && supportAttestations {
const noAttestEnv = "BUILDX_NO_DEFAULT_ATTESTATIONS"
var noProv bool
if v, ok := os.LookupEnv(noAttestEnv); ok {
noProv, err = strconv.ParseBool(v)
if err != nil {
return nil, nil, errors.Wrap(err, "invalid "+noAttestEnv)
}
}
if !noProv {
so.FrontendAttrs["attest:provenance"] = "mode=min,inline-only=true"
}
}
switch len(opt.Exports) {
case 1:
// valid
case 0:
if !noDefaultLoad() && opt.PrintFunc == nil {
if nodeDriver.IsMobyDriver() {
// 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{}}}
} else if nodeDriver.Features(ctx)[driver.DefaultLoad] {
opt.Exports = []client.ExportEntry{{Type: "docker", Attrs: map[string]string{}}}
}
}
default:
if err := bopts.LLBCaps.Supports(pb.CapMultipleExporters); err != nil {
return nil, nil, errors.Errorf("multiple outputs currently unsupported by the current BuildKit daemon, please upgrade to version v0.13+ or use a single output")
}
}
// fill in image exporter names from tags
if len(opt.Tags) > 0 {
tags := make([]string, len(opt.Tags))
for i, tag := range opt.Tags {
ref, err := reference.Parse(tag)
if err != nil {
return nil, nil, errors.Wrapf(err, "invalid tag %q", tag)
}
tags[i] = ref.String()
}
for i, e := range opt.Exports {
switch e.Type {
case "image", "oci", "docker":
opt.Exports[i].Attrs["name"] = strings.Join(tags, ",")
}
}
} else {
for _, e := range opt.Exports {
if e.Type == "image" && e.Attrs["name"] == "" && e.Attrs["push"] != "" {
if ok, _ := strconv.ParseBool(e.Attrs["push"]); ok {
return nil, nil, errors.Errorf("tag is needed when pushing to registry")
}
}
}
}
// 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 == "oci" && !nodeDriver.Features(ctx)[driver.OCIExporter] {
return nil, nil, notSupported(driver.OCIExporter, nodeDriver, "https://docs.docker.com/go/build-exporters/")
}
if e.Type == "docker" {
features := docker.Features(ctx, e.Attrs["context"])
if features[dockerutil.OCIImporter] && e.Output == nil {
// rely on oci importer if available (which supports
// multi-platform images), otherwise fall back to docker
opt.Exports[i].Type = "oci"
} else if len(opt.Platforms) > 1 || len(attests) > 0 {
if e.Output != nil {
return nil, nil, errors.Errorf("docker exporter does not support exporting manifest lists, use the oci exporter instead")
}
return nil, nil, errors.Errorf("docker exporter does not currently support exporting manifest lists")
}
if e.Output == nil {
if nodeDriver.IsMobyDriver() {
e.Type = "image"
} else {
w, cancel, err := docker.LoadImage(ctx, e.Attrs["context"], pw)
if err != nil {
return nil, nil, err
}
defers = append(defers, cancel)
opt.Exports[i].Output = func(_ map[string]string) (io.WriteCloser, error) {
return w, nil
}
}
} else if !nodeDriver.Features(ctx)[driver.DockerExporter] {
return nil, nil, notSupported(driver.DockerExporter, nodeDriver, "https://docs.docker.com/go/build-exporters/")
}
}
if e.Type == "image" && nodeDriver.IsMobyDriver() {
opt.Exports[i].Type = "moby"
if e.Attrs["push"] != "" {
if ok, _ := strconv.ParseBool(e.Attrs["push"]); ok {
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")
}
}
}
}
if e.Type == "docker" || e.Type == "image" || e.Type == "oci" {
// inline buildinfo attrs from build arg
if v, ok := opt.BuildArgs["BUILDKIT_INLINE_BUILDINFO_ATTRS"]; ok {
e.Attrs["buildinfo-attrs"] = v
}
}
}
so.Exports = opt.Exports
so.Session = opt.Session
releaseLoad, err := loadInputs(ctx, nodeDriver, opt.Inputs, addVCSLocalDir, pw, &so)
if err != nil {
return nil, nil, err
}
defers = append(defers, releaseLoad)
if sharedKey := so.LocalDirs["context"]; sharedKey != "" {
if p, err := filepath.Abs(sharedKey); err == nil {
sharedKey = filepath.Base(p)
}
so.SharedKey = sharedKey + ":" + confutil.TryNodeIdentifier(configDir)
}
if opt.Pull {
so.FrontendAttrs["image-resolve-mode"] = pb.AttrImageResolveModeForcePull
} else if nodeDriver.IsMobyDriver() {
// moby driver always resolves local images by default
so.FrontendAttrs["image-resolve-mode"] = pb.AttrImageResolveModePreferLocal
}
if opt.Target != "" {
so.FrontendAttrs["target"] = opt.Target
}
if len(opt.NoCacheFilter) > 0 {
so.FrontendAttrs["no-cache"] = strings.Join(opt.NoCacheFilter, ",")
}
if opt.NoCache {
so.FrontendAttrs["no-cache"] = ""
}
for k, v := range opt.BuildArgs {
so.FrontendAttrs["build-arg:"+k] = v
}
for k, v := range opt.Labels {
so.FrontendAttrs["label:"+k] = v
}
for k, v := range node.ProxyConfig {
if _, ok := opt.BuildArgs[k]; !ok {
so.FrontendAttrs["build-arg:"+k] = v
}
}
// set platforms
if len(opt.Platforms) != 0 {
pp := make([]string, len(opt.Platforms))
for i, p := range opt.Platforms {
pp[i] = platforms.Format(p)
}
if len(pp) > 1 && !nodeDriver.Features(ctx)[driver.MultiPlatform] {
return nil, nil, notSupported(driver.MultiPlatform, nodeDriver, "https://docs.docker.com/go/build-multi-platform/")
}
so.FrontendAttrs["platform"] = strings.Join(pp, ",")
}
// setup networkmode
switch opt.NetworkMode {
case "host":
so.FrontendAttrs["force-network-mode"] = opt.NetworkMode
so.AllowedEntitlements = append(so.AllowedEntitlements, entitlements.EntitlementNetworkHost)
case "none":
so.FrontendAttrs["force-network-mode"] = opt.NetworkMode
case "", "default":
default:
return nil, nil, errors.Errorf("network mode %q not supported by buildkit - you can define a custom network for your builder using the network driver-opt in buildx create", opt.NetworkMode)
}
// setup extrahosts
extraHosts, err := toBuildkitExtraHosts(ctx, opt.ExtraHosts, nodeDriver)
if err != nil {
return nil, nil, err
}
if len(extraHosts) > 0 {
so.FrontendAttrs["add-hosts"] = extraHosts
}
// setup shm size
if opt.ShmSize.Value() > 0 {
so.FrontendAttrs["shm-size"] = strconv.FormatInt(opt.ShmSize.Value(), 10)
}
// setup ulimits
ulimits, err := toBuildkitUlimits(opt.Ulimits)
if err != nil {
return nil, nil, err
} else if len(ulimits) > 0 {
so.FrontendAttrs["ulimit"] = ulimits
}
// mark info request as internal
if opt.PrintFunc != nil {
so.Internal = true
}
return &so, releaseF, nil
}
func loadInputs(ctx context.Context, d *driver.DriverHandle, inp Inputs, addVCSLocalDir func(key, dir string, so *client.SolveOpt), 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)")
}
// TODO: handle stdin, symlinks, remote contexts, check files exist
var (
err error
dockerfileReader io.Reader
dockerfileDir string
dockerfileName = inp.DockerfilePath
toRemove []string
)
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(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 !(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, _ = os.MkdirTemp("", "empty-dir")
toRemove = append(toRemove, inp.ContextPath)
if err := setLocalMount("context", inp.ContextPath, target, addVCSLocalDir); err != nil {
return nil, err
}
}
}
case osutil.IsLocalDir(inp.ContextPath):
if err := setLocalMount("context", inp.ContextPath, target, addVCSLocalDir); err != nil {
return nil, err
}
switch inp.DockerfilePath {
case "-":
dockerfileReader = inp.InStream
case "":
dockerfileDir = inp.ContextPath
default:
dockerfileDir = filepath.Dir(inp.DockerfilePath)
dockerfileName = filepath.Base(inp.DockerfilePath)
}
case IsRemoteURL(inp.ContextPath):
if inp.DockerfilePath == "-" {
dockerfileReader = inp.InStream
} else if filepath.IsAbs(inp.DockerfilePath) {
dockerfileDir = filepath.Dir(inp.DockerfilePath)
dockerfileName = filepath.Base(inp.DockerfilePath)
target.FrontendAttrs["dockerfilekey"] = "dockerfile"
}
target.FrontendAttrs["context"] = inp.ContextPath
default:
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"
}
if dockerfileDir != "" {
if err := setLocalMount("dockerfile", dockerfileDir, target, addVCSLocalDir); err != nil {
return nil, err
}
dockerfileName = handleLowercaseDockerfile(dockerfileDir, dockerfileName)
}
target.FrontendAttrs["filename"] = dockerfileName
for k, v := range inp.NamedContexts {
target.FrontendAttrs["frontend.caps"] = "moby.buildkit.frontend.contexts+forward"
if v.State != nil {
target.FrontendAttrs["context:"+k] = "input:" + k
if target.FrontendInputs == nil {
target.FrontendInputs = make(map[string]llb.State)
}
target.FrontendInputs[k] = *v.State
continue
}
if IsRemoteURL(v.Path) || strings.HasPrefix(v.Path, "docker-image://") || strings.HasPrefix(v.Path, "target:") {
target.FrontendAttrs["context:"+k] = v.Path
continue
}
// handle OCI layout
if strings.HasPrefix(v.Path, "oci-layout://") {
pathAlone := strings.TrimPrefix(v.Path, "oci-layout://")
localPath := pathAlone
localPath, dig, hasDigest := strings.Cut(localPath, "@")
localPath, tag, hasTag := strings.Cut(localPath, ":")
if !hasTag {
tag = "latest"
hasTag = true
}
idx := ociindex.NewStoreIndex(localPath)
if !hasDigest {
// lookup by name
desc, err := idx.Get(tag)
if err != nil {
return nil, err
}
if desc != nil {
dig = string(desc.Digest)
hasDigest = true
}
}
if !hasDigest {
// lookup single
desc, err := idx.GetSingle()
if err != nil {
return nil, err
}
if desc != nil {
dig = string(desc.Digest)
hasDigest = true
}
}
if !hasDigest {
return nil, errors.Errorf("oci-layout reference %q could not be resolved", v.Path)
}
_, err := digest.Parse(dig)
if err != nil {
return nil, errors.Wrapf(err, "invalid oci-layout digest %s", dig)
}
store, err := local.NewStore(localPath)
if err != nil {
return nil, errors.Wrapf(err, "invalid store at %s", localPath)
}
storeName := identity.NewID()
if target.OCIStores == nil {
target.OCIStores = map[string]content.Store{}
}
target.OCIStores[storeName] = store
layout := "oci-layout://" + storeName
if hasTag {
layout += ":" + tag
}
if hasDigest {
layout += "@" + dig
}
target.FrontendAttrs["context:"+k] = layout
continue
}
st, err := os.Stat(v.Path)
if err != nil {
return nil, errors.Wrapf(err, "failed to get build context %v", k)
}
if !st.IsDir() {
return nil, errors.Wrapf(syscall.ENOTDIR, "failed to get build context path %v", v)
}
localName := k
if k == "context" || k == "dockerfile" {
localName = "_" + k // underscore to avoid collisions
}
if err := setLocalMount(localName, v.Path, target, addVCSLocalDir); err != nil {
return nil, err
}
target.FrontendAttrs["context:"+k] = "local:" + localName
}
release := func() {
for _, dir := range toRemove {
os.RemoveAll(dir)
}
}
return release, nil
}
func setLocalMount(name, root string, so *client.SolveOpt, addVCSLocalDir func(key, dir string, so *client.SolveOpt)) error {
lm, err := fsutil.NewFS(root)
if err != nil {
return err
}
root, err = filepath.EvalSymlinks(root) // keep same behavior as fsutil.NewFS
if err != nil {
return err
}
if so.LocalMounts == nil {
so.LocalMounts = map[string]fsutil.FS{}
}
so.LocalMounts[name] = lm
if addVCSLocalDir != nil {
addVCSLocalDir(name, root, so)
}
return nil
}
func createTempDockerfile(r io.Reader) (string, error) {
dir, err := os.MkdirTemp("", "dockerfile")
if err != nil {
return "", err
}
f, err := os.Create(filepath.Join(dir, "Dockerfile"))
if err != nil {
return "", err
}
defer f.Close()
if _, err := io.Copy(f, r); err != nil {
return "", err
}
return dir, 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
}

View File

@@ -1,157 +0,0 @@
package build
import (
"context"
"encoding/base64"
"encoding/json"
"io"
"strings"
"sync"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/content/proxy"
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/progress"
controlapi "github.com/moby/buildkit/api/services/control"
"github.com/moby/buildkit/client"
provenancetypes "github.com/moby/buildkit/solver/llbsolver/provenance/types"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
)
type provenancePredicate struct {
Builder *provenanceBuilder `json:"builder,omitempty"`
provenancetypes.ProvenancePredicate
}
type provenanceBuilder struct {
ID string `json:"id,omitempty"`
}
func setRecordProvenance(ctx context.Context, c *client.Client, sr *client.SolveResponse, ref string, pw progress.Writer) error {
mode := confutil.MetadataProvenance()
if mode == confutil.MetadataProvenanceModeDisabled {
return nil
}
pw = progress.ResetTime(pw)
return progress.Wrap("resolving provenance for metadata file", pw.Write, func(l progress.SubLogger) error {
res, err := fetchProvenance(ctx, c, ref, mode)
if err != nil {
return err
}
for k, v := range res {
sr.ExporterResponse[k] = v
}
return nil
})
}
func fetchProvenance(ctx context.Context, c *client.Client, ref string, mode confutil.MetadataProvenanceMode) (out map[string]string, err error) {
cl, err := c.ControlClient().ListenBuildHistory(ctx, &controlapi.BuildHistoryRequest{
Ref: ref,
EarlyExit: true,
})
if err != nil {
return nil, err
}
var mu sync.Mutex
eg, ctx := errgroup.WithContext(ctx)
store := proxy.NewContentStore(c.ContentClient())
for {
ev, err := cl.Recv()
if errors.Is(err, io.EOF) {
break
} else if err != nil {
return nil, err
}
if ev.Record == nil {
continue
}
if ev.Record.Result != nil {
desc := lookupProvenance(ev.Record.Result)
if desc == nil {
continue
}
eg.Go(func() error {
dt, err := content.ReadBlob(ctx, store, *desc)
if err != nil {
return errors.Wrapf(err, "failed to load provenance blob from build record")
}
prv, err := encodeProvenance(dt, mode)
if err != nil {
return err
}
mu.Lock()
if out == nil {
out = make(map[string]string)
}
out["buildx.build.provenance"] = prv
mu.Unlock()
return nil
})
} else if ev.Record.Results != nil {
for platform, res := range ev.Record.Results {
platform := platform
desc := lookupProvenance(res)
if desc == nil {
continue
}
eg.Go(func() error {
dt, err := content.ReadBlob(ctx, store, *desc)
if err != nil {
return errors.Wrapf(err, "failed to load provenance blob from build record")
}
prv, err := encodeProvenance(dt, mode)
if err != nil {
return err
}
mu.Lock()
if out == nil {
out = make(map[string]string)
}
out["buildx.build.provenance/"+platform] = prv
mu.Unlock()
return nil
})
}
}
}
return out, eg.Wait()
}
func lookupProvenance(res *controlapi.BuildResultInfo) *ocispecs.Descriptor {
for _, a := range res.Attestations {
if a.MediaType == "application/vnd.in-toto+json" && strings.HasPrefix(a.Annotations["in-toto.io/predicate-type"], "https://slsa.dev/provenance/") {
return &ocispecs.Descriptor{
Digest: a.Digest,
Size: a.Size_,
MediaType: a.MediaType,
Annotations: a.Annotations,
}
}
}
return nil
}
func encodeProvenance(dt []byte, mode confutil.MetadataProvenanceMode) (string, error) {
var prv provenancePredicate
if err := json.Unmarshal(dt, &prv); err != nil {
return "", errors.Wrapf(err, "failed to unmarshal provenance")
}
if prv.Builder != nil && prv.Builder.ID == "" {
// reset builder if id is empty
prv.Builder = nil
}
if mode == confutil.MetadataProvenanceModeMin {
// reset fields for minimal provenance
prv.BuildConfig = nil
prv.Metadata = nil
}
dtprv, err := json.Marshal(prv)
if err != nil {
return "", errors.Wrapf(err, "failed to marshal provenance")
}
return base64.StdEncoding.EncodeToString(dtprv), nil
}

View File

@@ -5,8 +5,6 @@ import (
"bytes" "bytes"
"context" "context"
"net" "net"
"os"
"strconv"
"strings" "strings"
"github.com/docker/buildx/driver" "github.com/docker/buildx/driver"
@@ -14,7 +12,6 @@ import (
"github.com/docker/docker/builder/remotecontext/urlutil" "github.com/docker/docker/builder/remotecontext/urlutil"
"github.com/moby/buildkit/util/gitutil" "github.com/moby/buildkit/util/gitutil"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus"
) )
const ( const (
@@ -104,21 +101,3 @@ func toBuildkitUlimits(inp *opts.UlimitOpt) (string, error) {
} }
return strings.Join(ulimits, ","), nil return strings.Join(ulimits, ","), nil
} }
func notSupported(f driver.Feature, d *driver.DriverHandle, docs string) error {
return errors.Errorf(`%s is not supported for the %s driver.
Switch to a different driver, or turn on the containerd image store, and try again.
Learn more at %s`, f, d.Factory().Name(), docs)
}
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
}

View File

@@ -142,7 +142,7 @@ func (b *Builder) LoadNodes(ctx context.Context, opts ...LoadNodesOption) (_ []N
} }
} }
d, err := driver.GetDriver(ctx, driver.BuilderName(n.Name), factory, n.Endpoint, dockerapi, imageopt.Auth, kcc, n.BuildkitdFlags, n.Files, n.DriverOpts, n.Platforms, b.opts.contextPathHash, lno.dialMeta) d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, factory, n.Endpoint, dockerapi, imageopt.Auth, kcc, n.BuildkitdFlags, n.Files, n.DriverOpts, n.Platforms, b.opts.contextPathHash, lno.dialMeta)
if err != nil { if err != nil {
node.Err = err node.Err = err
return nil return nil

View File

@@ -1,7 +1,6 @@
package main package main
import ( import (
"context"
"fmt" "fmt"
"os" "os"
@@ -16,7 +15,6 @@ import (
cliflags "github.com/docker/cli/cli/flags" cliflags "github.com/docker/cli/cli/flags"
"github.com/moby/buildkit/solver/errdefs" "github.com/moby/buildkit/solver/errdefs"
"github.com/moby/buildkit/util/stack" "github.com/moby/buildkit/util/stack"
"go.opentelemetry.io/otel"
//nolint:staticcheck // vendored dependencies may still use this //nolint:staticcheck // vendored dependencies may still use this
"github.com/containerd/containerd/pkg/seed" "github.com/containerd/containerd/pkg/seed"
@@ -40,27 +38,10 @@ func runStandalone(cmd *command.DockerCli) error {
if err := cmd.Initialize(cliflags.NewClientOptions()); err != nil { if err := cmd.Initialize(cliflags.NewClientOptions()); err != nil {
return err return err
} }
defer flushMetrics(cmd)
rootCmd := commands.NewRootCmd(os.Args[0], false, cmd) rootCmd := commands.NewRootCmd(os.Args[0], false, cmd)
return rootCmd.Execute() return rootCmd.Execute()
} }
// flushMetrics will manually flush metrics from the configured
// meter provider. This is needed when running in standalone mode
// because the meter provider is initialized by the cli library,
// but the mechanism for forcing it to report is not presently
// exposed and not invoked when run in standalone mode.
// There are plans to fix that in the next release, but this is
// needed temporarily until the API for this is more thorough.
func flushMetrics(cmd *command.DockerCli) {
if mp, ok := cmd.MeterProvider().(command.MeterProvider); ok {
if err := mp.ForceFlush(context.Background()); err != nil {
otel.Handle(err)
}
}
}
func runPlugin(cmd *command.DockerCli) error { func runPlugin(cmd *command.DockerCli) error {
rootCmd := commands.NewRootCmd("buildx", true, cmd) rootCmd := commands.NewRootCmd("buildx", true, cmd)
return plugin.RunPlugin(cmd, rootCmd, manager.Metadata{ return plugin.RunPlugin(cmd, rootCmd, manager.Metadata{

View File

@@ -72,10 +72,10 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
overrides := in.overrides overrides := in.overrides
if in.exportPush { if in.exportPush {
overrides = append(overrides, "*.push=true") overrides = append(overrides, "*.output=type=registry")
} }
if in.exportLoad { if in.exportLoad {
overrides = append(overrides, "*.load=true") overrides = append(overrides, "*.output=type=docker")
} }
if cFlags.noCache != nil { if cFlags.noCache != nil {
overrides = append(overrides, fmt.Sprintf("*.no-cache=%t", *cFlags.noCache)) overrides = append(overrides, fmt.Sprintf("*.no-cache=%t", *cFlags.noCache))
@@ -202,12 +202,12 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
return nil return nil
} }
// local state group
groupRef := identity.NewID() groupRef := identity.NewID()
var refs []string var refs []string
for k, b := range bo { for k, b := range bo {
b.Ref = identity.NewID() b.Ref = identity.NewID()
b.GroupRef = groupRef b.GroupRef = groupRef
b.WithProvenanceResponse = len(in.metadataFile) > 0
refs = append(refs, b.Ref) refs = append(refs, b.Ref)
bo[k] = b bo[k] = b
} }

View File

@@ -48,7 +48,6 @@ import (
"github.com/moby/buildkit/client" "github.com/moby/buildkit/client"
"github.com/moby/buildkit/exporter/containerimage/exptypes" "github.com/moby/buildkit/exporter/containerimage/exptypes"
"github.com/moby/buildkit/frontend/subrequests" "github.com/moby/buildkit/frontend/subrequests"
"github.com/moby/buildkit/frontend/subrequests/lint"
"github.com/moby/buildkit/frontend/subrequests/outline" "github.com/moby/buildkit/frontend/subrequests/outline"
"github.com/moby/buildkit/frontend/subrequests/targets" "github.com/moby/buildkit/frontend/subrequests/targets"
"github.com/moby/buildkit/solver/errdefs" "github.com/moby/buildkit/solver/errdefs"
@@ -206,8 +205,6 @@ func (o *buildOptions) toControllerOptions() (*controllerapi.BuildOptions, error
return nil, err return nil, err
} }
opts.WithProvenanceResponse = opts.PrintFunc == nil && len(o.metadataFile) > 0
return &opts, nil return &opts, nil
} }
@@ -269,7 +266,11 @@ func (o *buildOptionsHash) String() string {
} }
func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) (err error) { func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) (err error) {
mp := dockerCli.MeterProvider() mp, err := metricutil.NewMeterProvider(ctx, dockerCli)
if err != nil {
return err
}
defer mp.Report(context.Background())
ctx, end, err := tracing.TraceCurrentCommand(ctx, "build") ctx, end, err := tracing.TraceCurrentCommand(ctx, "build")
if err != nil { if err != nil {
@@ -337,7 +338,7 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
done := timeBuildCommand(mp, attributes) done := timeBuildCommand(mp, attributes)
var resp *client.SolveResponse var resp *client.SolveResponse
var retErr error var retErr error
if confutil.IsExperimental() { if isExperimental() {
resp, retErr = runControllerBuild(ctx, dockerCli, opts, options, printer) resp, retErr = runControllerBuild(ctx, dockerCli, opts, options, printer)
} else { } else {
resp, retErr = runBasicBuild(ctx, dockerCli, opts, options, printer) resp, retErr = runBasicBuild(ctx, dockerCli, opts, options, printer)
@@ -365,12 +366,13 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
return errors.Wrap(err, "writing image ID file") return errors.Wrap(err, "writing image ID file")
} }
} }
if opts.PrintFunc != nil { if options.metadataFile != "" {
if err := printResult(opts.PrintFunc, resp.ExporterResponse); err != nil { if err := writeMetadataFile(options.metadataFile, decodeExporterResponse(resp.ExporterResponse)); err != nil {
return err return err
} }
} else if options.metadataFile != "" { }
if err := writeMetadataFile(options.metadataFile, decodeExporterResponse(resp.ExporterResponse)); err != nil { if opts.PrintFunc != nil {
if err := printResult(opts.PrintFunc, resp.ExporterResponse); err != nil {
return err return err
} }
} }
@@ -419,22 +421,14 @@ func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *contro
var ref string var ref string
var retErr error var retErr error
var resp *client.SolveResponse var resp *client.SolveResponse
f := ioset.NewSingleForwarder()
var f *ioset.SingleForwarder
var pr io.ReadCloser
var pw io.WriteCloser
if options.invokeConfig == nil {
pr = dockerCli.In()
} else {
f = ioset.NewSingleForwarder()
f.SetReader(dockerCli.In()) f.SetReader(dockerCli.In())
pr, pw = io.Pipe() pr, pw := io.Pipe()
f.SetWriter(pw, func() io.WriteCloser { f.SetWriter(pw, func() io.WriteCloser {
pw.Close() // propagate EOF pw.Close() // propagate EOF
logrus.Debug("propagating stdin close") logrus.Debug("propagating stdin close")
return nil return nil
}) })
}
ref, resp, err = c.Build(ctx, *opts, pr, printer) ref, resp, err = c.Build(ctx, *opts, pr, printer)
if err != nil { if err != nil {
@@ -448,14 +442,12 @@ func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *contro
} }
} }
if options.invokeConfig != nil {
if err := pw.Close(); err != nil { if err := pw.Close(); err != nil {
logrus.Debug("failed to close stdin pipe writer") logrus.Debug("failed to close stdin pipe writer")
} }
if err := pr.Close(); err != nil { if err := pr.Close(); err != nil {
logrus.Debug("failed to close stdin pipe reader") logrus.Debug("failed to close stdin pipe reader")
} }
}
if options.invokeConfig != nil && options.invokeConfig.needsDebug(retErr) { if options.invokeConfig != nil && options.invokeConfig.needsDebug(retErr) {
// Print errors before launching monitor // Print errors before launching monitor
@@ -583,7 +575,7 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugConfig *debug.D
flags.StringVarP(&options.dockerfileName, "file", "f", "", `Name of the Dockerfile (default: "PATH/Dockerfile")`) flags.StringVarP(&options.dockerfileName, "file", "f", "", `Name of the Dockerfile (default: "PATH/Dockerfile")`)
flags.SetAnnotation("file", annotation.ExternalURL, []string{"https://docs.docker.com/reference/cli/docker/image/build/#file"}) flags.SetAnnotation("file", annotation.ExternalURL, []string{"https://docs.docker.com/reference/cli/docker/image/build/#file"})
flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to a file") flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file")
flags.StringArrayVar(&options.labels, "label", []string{}, "Set metadata for an image") flags.StringArrayVar(&options.labels, "label", []string{}, "Set metadata for an image")
@@ -597,7 +589,7 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugConfig *debug.D
flags.StringArrayVar(&options.platforms, "platform", platformsDefault, "Set target platform for build") flags.StringArrayVar(&options.platforms, "platform", platformsDefault, "Set target platform for build")
if confutil.IsExperimental() { if isExperimental() {
flags.StringVar(&options.printFunc, "print", "", "Print result of information request (e.g., outline, targets)") flags.StringVar(&options.printFunc, "print", "", "Print result of information request (e.g., outline, targets)")
cobrautil.MarkFlagsExperimental(flags, "print") cobrautil.MarkFlagsExperimental(flags, "print")
} }
@@ -625,7 +617,7 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugConfig *debug.D
flags.StringVar(&options.sbom, "sbom", "", `Shorthand for "--attest=type=sbom"`) flags.StringVar(&options.sbom, "sbom", "", `Shorthand for "--attest=type=sbom"`)
flags.StringVar(&options.provenance, "provenance", "", `Shorthand for "--attest=type=provenance"`) flags.StringVar(&options.provenance, "provenance", "", `Shorthand for "--attest=type=provenance"`)
if confutil.IsExperimental() { if isExperimental() {
// TODO: move this to debug command if needed // TODO: move this to debug command if needed
flags.StringVar(&options.Root, "root", "", "Specify root directory of server to connect") flags.StringVar(&options.Root, "root", "", "Specify root directory of server to connect")
flags.BoolVar(&options.Detach, "detach", false, "Detach buildx server (supported only on linux)") flags.BoolVar(&options.Detach, "detach", false, "Detach buildx server (supported only on linux)")
@@ -698,7 +690,7 @@ func commonBuildFlags(options *commonFlags, flags *pflag.FlagSet) {
options.noCache = flags.Bool("no-cache", false, "Do not use cache when building the image") 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.StringVar(&options.progress, "progress", "auto", `Set type of progress output ("auto", "plain", "tty"). Use plain to show container output`)
options.pull = flags.Bool("pull", false, "Always attempt to pull all referenced images") options.pull = flags.Bool("pull", false, "Always attempt to pull all referenced images")
flags.StringVar(&options.metadataFile, "metadata-file", "", "Write build result metadata to a file") flags.StringVar(&options.metadataFile, "metadata-file", "", "Write build result metadata to the file")
} }
func checkWarnedFlags(f *pflag.Flag) { func checkWarnedFlags(f *pflag.Flag) {
@@ -770,6 +762,14 @@ func (w *wrapped) Unwrap() error {
return w.err return w.err
} }
func isExperimental() bool {
if v, ok := os.LookupEnv("BUILDX_EXPERIMENTAL"); ok {
vv, _ := strconv.ParseBool(v)
return vv
}
return false
}
func updateLastActivity(dockerCli command.Cli, ng *store.NodeGroup) error { func updateLastActivity(dockerCli command.Cli, ng *store.NodeGroup) error {
txn, release, err := storeutil.GetStore(dockerCli) txn, release, err := storeutil.GetStore(dockerCli)
if err != nil { if err != nil {
@@ -862,22 +862,13 @@ func printResult(f *controllerapi.PrintFunc, res map[string]string) error {
return printValue(targets.PrintTargets, targets.SubrequestsTargetsDefinition.Version, f.Format, res) return printValue(targets.PrintTargets, targets.SubrequestsTargetsDefinition.Version, f.Format, res)
case "subrequests.describe": case "subrequests.describe":
return printValue(subrequests.PrintDescribe, subrequests.SubrequestsDescribeDefinition.Version, f.Format, res) return printValue(subrequests.PrintDescribe, subrequests.SubrequestsDescribeDefinition.Version, f.Format, res)
case "lint":
return printValue(lint.PrintLintViolations, lint.SubrequestLintDefinition.Version, f.Format, res)
default: default:
if dt, ok := res["result.json"]; ok && f.Format == "json" { if dt, ok := res["result.txt"]; ok {
fmt.Println(dt)
} else if dt, ok := res["result.txt"]; ok {
fmt.Print(dt) fmt.Print(dt)
} else { } else {
log.Printf("%s %+v", f, res) log.Printf("%s %+v", f, res)
} }
} }
if v, ok := res["result.statuscode"]; !f.IgnoreStatus && ok {
if n, err := strconv.Atoi(v); err == nil && n != 0 {
os.Exit(n)
}
}
return nil return nil
} }

View File

@@ -7,7 +7,6 @@ import (
imagetoolscmd "github.com/docker/buildx/commands/imagetools" imagetoolscmd "github.com/docker/buildx/commands/imagetools"
"github.com/docker/buildx/controller/remote" "github.com/docker/buildx/controller/remote"
"github.com/docker/buildx/util/cobrautil/completion" "github.com/docker/buildx/util/cobrautil/completion"
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/logutil" "github.com/docker/buildx/util/logutil"
"github.com/docker/cli-docs-tool/annotation" "github.com/docker/cli-docs-tool/annotation"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
@@ -64,7 +63,7 @@ func NewRootCmd(name string, isPlugin bool, dockerCli command.Cli) *cobra.Comman
"using default config store", "using default config store",
)) ))
if !confutil.IsExperimental() { if !isExperimental() {
cmd.SetHelpTemplate(cmd.HelpTemplate() + "\nExperimental commands and flags are hidden. Set BUILDX_EXPERIMENTAL=1 to show them.\n") cmd.SetHelpTemplate(cmd.HelpTemplate() + "\nExperimental commands and flags are hidden. Set BUILDX_EXPERIMENTAL=1 to show them.\n")
} }
@@ -97,7 +96,7 @@ func addCommands(cmd *cobra.Command, dockerCli command.Cli) {
duCmd(dockerCli, opts), duCmd(dockerCli, opts),
imagetoolscmd.RootCmd(dockerCli, imagetoolscmd.RootOptions{Builder: &opts.builder}), imagetoolscmd.RootCmd(dockerCli, imagetoolscmd.RootOptions{Builder: &opts.builder}),
) )
if confutil.IsExperimental() { if isExperimental() {
cmd.AddCommand(debugcmd.RootCmd(dockerCli, cmd.AddCommand(debugcmd.RootCmd(dockerCli,
newDebuggableBuild(dockerCli, opts), newDebuggableBuild(dockerCli, opts),
)) ))

View File

@@ -1,22 +1,17 @@
package commands package commands
import ( import (
"bufio"
"context" "context"
"fmt"
"io" "io"
"os"
"runtime"
"strings"
"github.com/docker/cli/cli/streams" "github.com/docker/cli/cli/command"
) )
func prompt(ctx context.Context, ins io.Reader, out io.Writer, msg string) (bool, error) { func prompt(ctx context.Context, ins io.Reader, out io.Writer, msg string) (bool, error) {
done := make(chan struct{}) done := make(chan struct{})
var ok bool var ok bool
go func() { go func() {
ok = promptForConfirmation(ins, out, msg) ok = command.PromptForConfirmation(ins, out, msg)
close(done) close(done)
}() }()
select { select {
@@ -26,32 +21,3 @@ func prompt(ctx context.Context, ins io.Reader, out io.Writer, msg string) (bool
return ok, nil return ok, nil
} }
} }
// promptForConfirmation requests and checks confirmation from user.
// This will display the provided message followed by ' [y/N] '. If
// the user input 'y' or 'Y' it returns true other false. If no
// message is provided "Are you sure you want to proceed? [y/N] "
// will be used instead.
//
// Copied from github.com/docker/cli since the upstream version changed
// recently with an incompatible change.
//
// See https://github.com/docker/buildx/pull/2359#discussion_r1544736494
// for discussion on the issue.
func promptForConfirmation(ins io.Reader, outs io.Writer, message string) bool {
if message == "" {
message = "Are you sure you want to proceed?"
}
message += " [y/N] "
_, _ = fmt.Fprint(outs, message)
// On Windows, force the use of the regular OS stdin stream.
if runtime.GOOS == "windows" {
ins = streams.NewIn(os.Stdin)
}
reader := bufio.NewReader(ins)
answer, _, _ := reader.ReadLine()
return strings.ToLower(string(answer)) == "y"
}

View File

@@ -67,7 +67,6 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in controllerapi.Build
Target: in.Target, Target: in.Target,
Ulimits: controllerUlimitOpt2DockerUlimit(in.Ulimits), Ulimits: controllerUlimitOpt2DockerUlimit(in.Ulimits),
GroupRef: in.GroupRef, GroupRef: in.GroupRef,
WithProvenanceResponse: in.WithProvenanceResponse,
} }
platforms, err := platformutil.Parse(in.Platforms) platforms, err := platformutil.Parse(in.Platforms)
@@ -163,7 +162,6 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in controllerapi.Build
opts.PrintFunc = &build.PrintFunc{ opts.PrintFunc = &build.PrintFunc{
Name: in.PrintFunc.Name, Name: in.PrintFunc.Name,
Format: in.PrintFunc.Format, Format: in.PrintFunc.Format,
IgnoreStatus: in.PrintFunc.IgnoreStatus,
} }
} }

View File

@@ -302,7 +302,6 @@ type BuildOptions struct {
Ref string `protobuf:"bytes,29,opt,name=Ref,proto3" json:"Ref,omitempty"` Ref string `protobuf:"bytes,29,opt,name=Ref,proto3" json:"Ref,omitempty"`
GroupRef string `protobuf:"bytes,30,opt,name=GroupRef,proto3" json:"GroupRef,omitempty"` GroupRef string `protobuf:"bytes,30,opt,name=GroupRef,proto3" json:"GroupRef,omitempty"`
Annotations []string `protobuf:"bytes,31,rep,name=Annotations,proto3" json:"Annotations,omitempty"` Annotations []string `protobuf:"bytes,31,rep,name=Annotations,proto3" json:"Annotations,omitempty"`
WithProvenanceResponse bool `protobuf:"varint,32,opt,name=WithProvenanceResponse,proto3" json:"WithProvenanceResponse,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`
@@ -549,13 +548,6 @@ func (m *BuildOptions) GetAnnotations() []string {
return nil return nil
} }
func (m *BuildOptions) GetWithProvenanceResponse() bool {
if m != nil {
return m.WithProvenanceResponse
}
return false
}
type ExportEntry struct { type ExportEntry struct {
Type string `protobuf:"bytes,1,opt,name=Type,proto3" json:"Type,omitempty"` Type string `protobuf:"bytes,1,opt,name=Type,proto3" json:"Type,omitempty"`
Attrs map[string]string `protobuf:"bytes,2,rep,name=Attrs,proto3" json:"Attrs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Attrs map[string]string `protobuf:"bytes,2,rep,name=Attrs,proto3" json:"Attrs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
@@ -813,7 +805,6 @@ func (m *Secret) GetEnv() string {
type PrintFunc struct { type PrintFunc struct {
Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"` Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"`
Format string `protobuf:"bytes,2,opt,name=Format,proto3" json:"Format,omitempty"` Format string `protobuf:"bytes,2,opt,name=Format,proto3" json:"Format,omitempty"`
IgnoreStatus bool `protobuf:"varint,3,opt,name=IgnoreStatus,proto3" json:"IgnoreStatus,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`
@@ -857,13 +848,6 @@ func (m *PrintFunc) GetFormat() string {
return "" return ""
} }
func (m *PrintFunc) GetIgnoreStatus() bool {
if m != nil {
return m.IgnoreStatus
}
return false
}
type InspectRequest struct { type InspectRequest struct {
Ref string `protobuf:"bytes,1,opt,name=Ref,proto3" json:"Ref,omitempty"` Ref string `protobuf:"bytes,1,opt,name=Ref,proto3" json:"Ref,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
@@ -2094,130 +2078,128 @@ func init() {
func init() { proto.RegisterFile("controller.proto", fileDescriptor_ed7f10298fa1d90f) } func init() { proto.RegisterFile("controller.proto", fileDescriptor_ed7f10298fa1d90f) }
var fileDescriptor_ed7f10298fa1d90f = []byte{ var fileDescriptor_ed7f10298fa1d90f = []byte{
// 1960 bytes of a gzipped FileDescriptorProto // 1922 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x58, 0x5f, 0x73, 0x1b, 0x49, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x58, 0x5f, 0x73, 0x1b, 0x49,
0x11, 0x67, 0x25, 0x59, 0x7f, 0x5a, 0x96, 0xcf, 0x19, 0x9c, 0x30, 0xd9, 0xe4, 0x12, 0x67, 0x93, 0x11, 0x67, 0x25, 0x59, 0x7f, 0x5a, 0x96, 0xcf, 0x19, 0x9c, 0x30, 0xd9, 0xe4, 0x12, 0x67, 0x93,
0x1c, 0x2a, 0x42, 0xc9, 0x77, 0x3e, 0x72, 0xb9, 0x5c, 0xee, 0xaa, 0xb0, 0x65, 0x0b, 0xfb, 0x2a, 0x1c, 0x2a, 0x42, 0xc9, 0x77, 0x3e, 0x82, 0x2f, 0x97, 0xbb, 0x2a, 0x6c, 0xd9, 0xc2, 0xbe, 0x4a,
0xb1, 0x5d, 0x23, 0x27, 0x29, 0xb8, 0x2a, 0xae, 0x56, 0xd2, 0x58, 0xde, 0xd2, 0x6a, 0x47, 0xec, 0x6c, 0xd7, 0xca, 0xc9, 0x15, 0x50, 0xc5, 0xd5, 0x5a, 0x1a, 0xcb, 0x5b, 0x5a, 0xed, 0x88, 0x9d,
0x8c, 0x64, 0x8b, 0x27, 0x1e, 0xe0, 0x8d, 0xe2, 0x7b, 0x50, 0x7c, 0x04, 0x9e, 0x78, 0xe3, 0xe3, 0x91, 0x6d, 0xf1, 0xc4, 0x03, 0xbc, 0x51, 0x14, 0x5f, 0x83, 0xe2, 0x23, 0xf0, 0xc4, 0x37, 0xe2,
0xf0, 0x11, 0xa8, 0xf9, 0xb3, 0xab, 0x5d, 0x4b, 0x2b, 0xdb, 0xf0, 0xa4, 0xe9, 0x9e, 0x5f, 0x77, 0x23, 0x50, 0xd3, 0x33, 0xbb, 0x5a, 0x59, 0x5a, 0xd9, 0x86, 0x27, 0x4d, 0xf7, 0xfe, 0xba, 0x7b,
0x4f, 0xf7, 0xf6, 0x74, 0xf7, 0x08, 0xd6, 0xbb, 0x2c, 0x10, 0x21, 0xf3, 0x7d, 0x1a, 0x36, 0x46, 0xba, 0xa7, 0xa7, 0xbb, 0x47, 0xb0, 0xda, 0xe1, 0xa1, 0x8c, 0x78, 0x10, 0xb0, 0xa8, 0x31, 0x8c,
0x21, 0x13, 0x0c, 0x6d, 0x74, 0xc6, 0x9e, 0xdf, 0xbb, 0x6c, 0x24, 0x36, 0x26, 0x5f, 0xd8, 0x6f, 0xb8, 0xe4, 0x64, 0xed, 0x74, 0xe4, 0x07, 0xdd, 0xab, 0x46, 0xea, 0xc3, 0xc5, 0x17, 0xf6, 0xdb,
0xfa, 0x9e, 0x38, 0x1f, 0x77, 0x1a, 0x5d, 0x36, 0xdc, 0x1a, 0xb2, 0xce, 0x74, 0x4b, 0xa1, 0x06, 0x9e, 0x2f, 0xcf, 0x47, 0xa7, 0x8d, 0x0e, 0x1f, 0x6c, 0x0c, 0xf8, 0xe9, 0x78, 0x03, 0x51, 0x7d,
0x9e, 0xd8, 0x72, 0x47, 0xde, 0x16, 0xa7, 0xe1, 0xc4, 0xeb, 0x52, 0xbe, 0x65, 0x84, 0xa2, 0x5f, 0x5f, 0x6e, 0x78, 0x43, 0x7f, 0x43, 0xb0, 0xe8, 0xc2, 0xef, 0x30, 0xb1, 0x61, 0x84, 0xe2, 0x5f,
0xad, 0xd2, 0x7e, 0x99, 0x29, 0xcc, 0xd9, 0x38, 0xec, 0xd2, 0x11, 0xf3, 0xbd, 0xee, 0x74, 0x6b, 0xad, 0xd2, 0x7e, 0x9d, 0x29, 0x2c, 0xf8, 0x28, 0xea, 0xb0, 0x21, 0x0f, 0xfc, 0xce, 0x78, 0x63,
0xd4, 0xd9, 0xd2, 0x2b, 0x2d, 0xe6, 0xd4, 0x61, 0xe3, 0xad, 0xc7, 0xc5, 0x49, 0xc8, 0xba, 0x94, 0x78, 0xba, 0xa1, 0x57, 0x5a, 0xcc, 0xa9, 0xc3, 0xda, 0x3b, 0x5f, 0xc8, 0xe3, 0x88, 0x77, 0x98,
0x73, 0xca, 0x09, 0xfd, 0xc3, 0x98, 0x72, 0x81, 0xd6, 0x21, 0x4f, 0xe8, 0x19, 0xb6, 0x36, 0xad, 0x10, 0x4c, 0xb8, 0xec, 0x0f, 0x23, 0x26, 0x24, 0x59, 0x85, 0xbc, 0xcb, 0xce, 0xa8, 0xb5, 0x6e,
0x7a, 0x85, 0xc8, 0xa5, 0x73, 0x02, 0x77, 0xaf, 0x20, 0xf9, 0x88, 0x05, 0x9c, 0xa2, 0x57, 0xb0, 0xd5, 0x2b, 0xae, 0x5a, 0x3a, 0xc7, 0x70, 0xff, 0x1a, 0x52, 0x0c, 0x79, 0x28, 0x18, 0xd9, 0x82,
0x72, 0x18, 0x9c, 0x31, 0x8e, 0xad, 0xcd, 0x7c, 0xbd, 0xba, 0xfd, 0xa4, 0xb1, 0xc8, 0xb9, 0x86, 0xa5, 0x83, 0xf0, 0x8c, 0x0b, 0x6a, 0xad, 0xe7, 0xeb, 0xd5, 0xcd, 0x67, 0x8d, 0x79, 0xce, 0x35,
0x91, 0x93, 0x48, 0xa2, 0xf1, 0x0e, 0x87, 0x6a, 0x82, 0x8b, 0x1e, 0x42, 0x25, 0x22, 0xf7, 0x8c, 0x8c, 0x9c, 0x42, 0xba, 0x1a, 0xef, 0x08, 0xa8, 0xa6, 0xb8, 0xe4, 0x31, 0x54, 0x62, 0x72, 0xd7,
0xe1, 0x19, 0x03, 0xb5, 0x60, 0xf5, 0x30, 0x98, 0xb0, 0x01, 0x6d, 0xb2, 0xe0, 0xcc, 0xeb, 0xe3, 0x18, 0x9e, 0x30, 0x48, 0x0b, 0x96, 0x0f, 0xc2, 0x0b, 0xde, 0x67, 0x4d, 0x1e, 0x9e, 0xf9, 0x3d,
0xdc, 0xa6, 0x55, 0xaf, 0x6e, 0x3b, 0x8b, 0x8d, 0x25, 0x91, 0x24, 0x25, 0xe7, 0x7c, 0x0f, 0x78, 0x9a, 0x5b, 0xb7, 0xea, 0xd5, 0x4d, 0x67, 0xbe, 0xb1, 0x34, 0xd2, 0x9d, 0x92, 0x73, 0xbe, 0x03,
0xcf, 0xe3, 0x5d, 0x16, 0x04, 0xb4, 0x1b, 0x39, 0x93, 0xe9, 0x74, 0xfa, 0x4c, 0xb9, 0x2b, 0x67, 0xba, 0xeb, 0x8b, 0x0e, 0x0f, 0x43, 0xd6, 0x89, 0x9d, 0xc9, 0x74, 0x7a, 0x7a, 0x4f, 0xb9, 0x6b,
0x72, 0x1e, 0xc0, 0xfd, 0x05, 0xba, 0x74, 0x58, 0x9c, 0xdf, 0xc3, 0xea, 0xae, 0x3c, 0x5b, 0xb6, 0x7b, 0x72, 0x1e, 0xc1, 0xc3, 0x39, 0xba, 0x74, 0x58, 0x9c, 0xdf, 0xc3, 0xf2, 0x8e, 0xda, 0x5b,
0xf2, 0x6f, 0xa1, 0x74, 0x3c, 0x12, 0x1e, 0x0b, 0xf8, 0x72, 0x6f, 0x94, 0x1a, 0x83, 0x24, 0x91, 0xb6, 0xf2, 0x6f, 0xa0, 0x74, 0x34, 0x94, 0x3e, 0x0f, 0xc5, 0x62, 0x6f, 0x50, 0x8d, 0x41, 0xba,
0x88, 0xf3, 0xef, 0x55, 0x63, 0xc0, 0x30, 0xd0, 0x26, 0x54, 0x9b, 0x2c, 0x10, 0xf4, 0x52, 0x9c, 0xb1, 0x88, 0xf3, 0xf7, 0x65, 0x63, 0xc0, 0x30, 0xc8, 0x3a, 0x54, 0x9b, 0x3c, 0x94, 0xec, 0x4a,
0xb8, 0xe2, 0xdc, 0x18, 0x4a, 0xb2, 0xd0, 0x67, 0xb0, 0xb6, 0xc7, 0xba, 0x03, 0x1a, 0x9e, 0x79, 0x1e, 0x7b, 0xf2, 0xdc, 0x18, 0x4a, 0xb3, 0xc8, 0x67, 0xb0, 0xb2, 0xcb, 0x3b, 0x7d, 0x16, 0x9d,
0x3e, 0x3d, 0x72, 0x87, 0xd4, 0xb8, 0x74, 0x85, 0x8b, 0xbe, 0x93, 0x5e, 0x7b, 0x81, 0x68, 0x8d, 0xf9, 0x01, 0x3b, 0xf4, 0x06, 0xcc, 0xb8, 0x74, 0x8d, 0x4b, 0xbe, 0x55, 0x5e, 0xfb, 0xa1, 0x6c,
0x83, 0x2e, 0xce, 0xab, 0xa3, 0x3d, 0xce, 0xfa, 0xaa, 0x06, 0x46, 0x66, 0x12, 0xe8, 0x07, 0xa8, 0x8d, 0xc2, 0x0e, 0xcd, 0xe3, 0xd6, 0x9e, 0x66, 0x9d, 0xaa, 0x81, 0xb9, 0x13, 0x09, 0xf2, 0x3b,
0x49, 0x35, 0x3d, 0x63, 0x9a, 0xe3, 0x82, 0x4a, 0x8c, 0x97, 0xd7, 0x7b, 0xd7, 0x48, 0xc9, 0xed, 0xa8, 0x29, 0x35, 0x5d, 0x63, 0x5a, 0xd0, 0x02, 0x26, 0xc6, 0xeb, 0x9b, 0xbd, 0x6b, 0x4c, 0xc9,
0x07, 0x22, 0x9c, 0x92, 0xb4, 0x2e, 0xb4, 0x01, 0x2b, 0x3b, 0xbe, 0xcf, 0x2e, 0xf0, 0xca, 0x66, 0xed, 0x85, 0x32, 0x1a, 0xbb, 0xd3, 0xba, 0xc8, 0x1a, 0x2c, 0x6d, 0x07, 0x01, 0xbf, 0xa4, 0x4b,
0xbe, 0x5e, 0x21, 0x9a, 0x40, 0x5f, 0x41, 0x69, 0x47, 0x08, 0xca, 0x05, 0xc7, 0x45, 0x65, 0xec, 0xeb, 0xf9, 0x7a, 0xc5, 0xd5, 0x04, 0xf9, 0x25, 0x94, 0xb6, 0xa5, 0x64, 0x42, 0x0a, 0x5a, 0x44,
0xe1, 0x62, 0x63, 0x1a, 0x44, 0x22, 0x30, 0x3a, 0x86, 0x8a, 0xb2, 0xbf, 0x13, 0xf6, 0x39, 0x2e, 0x63, 0x8f, 0xe7, 0x1b, 0xd3, 0x20, 0x37, 0x06, 0x93, 0x23, 0xa8, 0xa0, 0xfd, 0xed, 0xa8, 0x27,
0x29, 0xc9, 0x2f, 0x6e, 0x70, 0xcc, 0x58, 0x46, 0x1f, 0x71, 0xa6, 0x03, 0xed, 0x43, 0xa5, 0xe9, 0x68, 0x09, 0x25, 0xbf, 0xb8, 0xc5, 0x36, 0x13, 0x19, 0xbd, 0xc5, 0x89, 0x0e, 0xb2, 0x07, 0x95,
0x76, 0xcf, 0x69, 0x2b, 0x64, 0x43, 0x5c, 0x56, 0x0a, 0x7f, 0xbe, 0x58, 0xa1, 0x82, 0x19, 0x85, 0xa6, 0xd7, 0x39, 0x67, 0xad, 0x88, 0x0f, 0x68, 0x19, 0x15, 0xfe, 0x74, 0xbe, 0x42, 0x84, 0x19,
0x46, 0x4d, 0x2c, 0x89, 0x76, 0xa0, 0xa4, 0x88, 0x53, 0x86, 0x2b, 0xb7, 0x53, 0x12, 0xc9, 0x21, 0x85, 0x46, 0x4d, 0x22, 0x49, 0xb6, 0xa1, 0x84, 0xc4, 0x09, 0xa7, 0x95, 0xbb, 0x29, 0x89, 0xe5,
0x07, 0x56, 0x9b, 0xfd, 0x90, 0x8d, 0x47, 0x27, 0x6e, 0x48, 0x03, 0x81, 0x41, 0x7d, 0xea, 0x14, 0x88, 0x03, 0xcb, 0xcd, 0x5e, 0xc4, 0x47, 0xc3, 0x63, 0x2f, 0x62, 0xa1, 0xa4, 0x80, 0x47, 0x3d,
0x0f, 0xbd, 0x81, 0xd2, 0xfe, 0xe5, 0x88, 0x85, 0x82, 0xe3, 0xea, 0xb2, 0xcb, 0xab, 0x41, 0xc6, 0xc5, 0x23, 0x6f, 0xa1, 0xb4, 0x77, 0x35, 0xe4, 0x91, 0x14, 0xb4, 0xba, 0xe8, 0xf2, 0x6a, 0x90,
0x80, 0x91, 0x40, 0x8f, 0x00, 0xf6, 0x2f, 0x45, 0xe8, 0x1e, 0x30, 0x19, 0xf6, 0x55, 0xf5, 0x39, 0x31, 0x60, 0x24, 0xc8, 0x13, 0x80, 0xbd, 0x2b, 0x19, 0x79, 0xfb, 0x5c, 0x85, 0x7d, 0x19, 0x8f,
0x12, 0x1c, 0xd4, 0x82, 0xe2, 0x5b, 0xb7, 0x43, 0x7d, 0x8e, 0x6b, 0x4a, 0x77, 0xe3, 0x06, 0x81, 0x23, 0xc5, 0x21, 0x2d, 0x28, 0xbe, 0xf3, 0x4e, 0x59, 0x20, 0x68, 0x0d, 0x75, 0x37, 0x6e, 0x11,
0xd5, 0x02, 0xda, 0x90, 0x91, 0x96, 0x79, 0x7d, 0x44, 0xc5, 0x05, 0x0b, 0x07, 0xef, 0x58, 0x8f, 0x58, 0x2d, 0xa0, 0x0d, 0x19, 0x69, 0x95, 0xd7, 0x87, 0x4c, 0x5e, 0xf2, 0xa8, 0xff, 0x9e, 0x77,
0xe2, 0x35, 0x9d, 0xd7, 0x09, 0x16, 0x7a, 0x06, 0xb5, 0x23, 0xa6, 0x83, 0xe7, 0xf9, 0x82, 0x86, 0x19, 0x5d, 0xd1, 0x79, 0x9d, 0x62, 0x91, 0x17, 0x50, 0x3b, 0xe4, 0x3a, 0x78, 0x7e, 0x20, 0x59,
0xf8, 0x13, 0x75, 0x98, 0x34, 0x53, 0xdd, 0x65, 0xdf, 0x15, 0x67, 0x2c, 0x1c, 0x72, 0xbc, 0xae, 0x44, 0x3f, 0xc1, 0xcd, 0x4c, 0x33, 0xf1, 0x2e, 0x07, 0x9e, 0x3c, 0xe3, 0xd1, 0x40, 0xd0, 0x55,
0x10, 0x33, 0x86, 0xcc, 0xa0, 0x36, 0xed, 0x86, 0x54, 0x70, 0x7c, 0x67, 0x59, 0x06, 0x69, 0x10, 0x44, 0x4c, 0x18, 0x2a, 0x83, 0xda, 0xac, 0x13, 0x31, 0x29, 0xe8, 0xbd, 0x45, 0x19, 0xa4, 0x41,
0x89, 0xc0, 0x08, 0x43, 0xa9, 0x7d, 0x3e, 0x6c, 0x7b, 0x7f, 0xa4, 0x18, 0x6d, 0x5a, 0xf5, 0x3c, 0x6e, 0x0c, 0x26, 0x14, 0x4a, 0xed, 0xf3, 0x41, 0xdb, 0xff, 0x23, 0xa3, 0x64, 0xdd, 0xaa, 0xe7,
0x89, 0x48, 0xf4, 0x02, 0xf2, 0xed, 0xf6, 0x01, 0xfe, 0xa9, 0xd2, 0x76, 0x3f, 0x43, 0x5b, 0xfb, 0xdd, 0x98, 0x24, 0xaf, 0x20, 0xdf, 0x6e, 0xef, 0xd3, 0x1f, 0xa3, 0xb6, 0x87, 0x19, 0xda, 0xda,
0x80, 0x48, 0x14, 0x42, 0x50, 0x38, 0x75, 0xfb, 0x1c, 0x6f, 0xa8, 0x73, 0xa9, 0x35, 0xba, 0x07, 0xfb, 0xae, 0x42, 0x11, 0x02, 0x85, 0x13, 0xaf, 0x27, 0xe8, 0x1a, 0xee, 0x0b, 0xd7, 0xe4, 0x01,
0xc5, 0x53, 0x37, 0xec, 0x53, 0x81, 0xef, 0x2a, 0x9f, 0x0d, 0x85, 0x5e, 0x43, 0xe9, 0xbd, 0xef, 0x14, 0x4f, 0xbc, 0xa8, 0xc7, 0x24, 0xbd, 0x8f, 0x3e, 0x1b, 0x8a, 0xbc, 0x81, 0xd2, 0x87, 0xc0,
0x0d, 0x3d, 0xc1, 0xf1, 0xbd, 0x65, 0x97, 0x53, 0x83, 0x8e, 0x47, 0x82, 0x44, 0x78, 0x79, 0x5a, 0x1f, 0xf8, 0x52, 0xd0, 0x07, 0x8b, 0x2e, 0xa7, 0x06, 0x1d, 0x0d, 0xa5, 0x1b, 0xe3, 0xd5, 0x6e,
0x15, 0x6f, 0x1a, 0xe2, 0x9f, 0x29, 0x9d, 0x11, 0x29, 0x77, 0x4c, 0xb8, 0x30, 0xde, 0xb4, 0xea, 0x31, 0xde, 0x2c, 0xa2, 0x3f, 0x41, 0x9d, 0x31, 0xa9, 0xbe, 0x98, 0x70, 0x51, 0xba, 0x6e, 0xd5,
0x65, 0x12, 0x91, 0xf2, 0x68, 0x27, 0x63, 0xdf, 0xc7, 0xf7, 0x15, 0x5b, 0xad, 0xf5, 0xb7, 0x97, 0xcb, 0x6e, 0x4c, 0xaa, 0xad, 0x1d, 0x8f, 0x82, 0x80, 0x3e, 0x44, 0x36, 0xae, 0xf5, 0xd9, 0xab,
0x69, 0x70, 0x32, 0xe6, 0xe7, 0xd8, 0x56, 0x3b, 0x09, 0xce, 0x6c, 0xff, 0x2d, 0x73, 0x7b, 0xf8, 0x34, 0x38, 0x1e, 0x89, 0x73, 0x6a, 0xe3, 0x97, 0x14, 0x67, 0xf2, 0xfd, 0x1d, 0xf7, 0xba, 0xf4,
0x41, 0x72, 0x5f, 0x72, 0xd0, 0x21, 0xac, 0xb6, 0x55, 0x5b, 0x3a, 0x51, 0xcd, 0x08, 0x3f, 0x54, 0x51, 0xfa, 0xbb, 0xe2, 0x90, 0x03, 0x58, 0x6e, 0x63, 0x5b, 0x3a, 0xc6, 0x66, 0x44, 0x1f, 0xa3,
0x7e, 0x3c, 0x6f, 0xc8, 0xce, 0xd5, 0x88, 0x3a, 0x97, 0xf4, 0x21, 0xd9, 0xbc, 0x1a, 0x1a, 0x4c, 0x1f, 0x2f, 0x1b, 0xaa, 0x73, 0x35, 0xe2, 0xce, 0xa5, 0x7c, 0x48, 0x37, 0xaf, 0x86, 0x06, 0xbb,
0x52, 0xa2, 0x51, 0x5d, 0xfd, 0x74, 0x56, 0x57, 0x6d, 0x28, 0xff, 0x46, 0x26, 0xb9, 0x64, 0x3f, 0x53, 0xa2, 0x71, 0x5d, 0xfd, 0x74, 0x52, 0x57, 0x6d, 0x28, 0xff, 0x5a, 0x25, 0xb9, 0x62, 0x3f,
0x52, 0xec, 0x98, 0x96, 0xc9, 0xb4, 0x13, 0x04, 0x4c, 0xb8, 0xba, 0xee, 0x3e, 0x56, 0xe1, 0x4e, 0x41, 0x76, 0x42, 0xab, 0x64, 0xda, 0x0e, 0x43, 0x2e, 0x3d, 0x5d, 0x77, 0x9f, 0x62, 0xb8, 0xd3,
0xb2, 0xd0, 0x57, 0x70, 0xef, 0xa3, 0x27, 0xce, 0x4f, 0x42, 0x36, 0xa1, 0x81, 0x1b, 0x74, 0x69, 0x2c, 0xfb, 0x57, 0x40, 0x66, 0xab, 0x90, 0xb2, 0xd2, 0x67, 0xe3, 0xb8, 0x7a, 0xf7, 0xd9, 0x58,
0x54, 0xd1, 0xf1, 0xa6, 0x72, 0x23, 0x63, 0xd7, 0xfe, 0x35, 0xa0, 0xf9, 0xea, 0x25, 0x4f, 0x37, 0x15, 0xa2, 0x0b, 0x2f, 0x18, 0xc5, 0x35, 0x54, 0x13, 0x5f, 0xe7, 0xbe, 0xb2, 0xec, 0x6f, 0x60,
0xa0, 0xd3, 0xa8, 0xea, 0x0f, 0xe8, 0x54, 0x16, 0xb0, 0x89, 0xeb, 0x8f, 0xa3, 0xda, 0xab, 0x89, 0x65, 0xba, 0x40, 0xdc, 0x49, 0xfa, 0x0d, 0x54, 0x53, 0xb7, 0xe0, 0x2e, 0xa2, 0xce, 0xbf, 0x2d,
0x6f, 0x72, 0x5f, 0x5b, 0xf6, 0xb7, 0xb0, 0x96, 0x2e, 0x2c, 0xb7, 0x92, 0x7e, 0x0d, 0xd5, 0xc4, 0xa8, 0xa6, 0xae, 0x2a, 0x26, 0xd5, 0x78, 0xc8, 0x8c, 0x30, 0xae, 0xc9, 0x0e, 0x2c, 0x6d, 0x4b,
0xed, 0xb9, 0x8d, 0xa8, 0xf3, 0x2f, 0x0b, 0xaa, 0x89, 0x2b, 0xae, 0x92, 0x71, 0x3a, 0xa2, 0x46, 0x19, 0xa9, 0x96, 0xa3, 0xf2, 0xf2, 0xe7, 0x37, 0x5e, 0xf8, 0x06, 0xc2, 0xf5, 0x95, 0xd4, 0xa2,
0x58, 0xad, 0xd1, 0x2e, 0xac, 0xec, 0x08, 0x11, 0xca, 0x56, 0x25, 0xf3, 0xf9, 0x97, 0xd7, 0x16, 0x2a, 0x88, 0xbb, 0x4c, 0x48, 0x3f, 0xc4, 0x90, 0x61, 0x87, 0xa8, 0xb8, 0x69, 0x96, 0xfd, 0x15,
0x8a, 0x86, 0x82, 0xeb, 0xab, 0xac, 0x45, 0x65, 0xf0, 0xf7, 0x28, 0x17, 0x5e, 0xa0, 0x42, 0xad, 0xc0, 0x44, 0xec, 0x4e, 0x3e, 0xfc, 0xd3, 0x82, 0x7b, 0x33, 0x55, 0x6d, 0xae, 0x27, 0xfb, 0xd3,
0x3a, 0x4b, 0x85, 0x24, 0x59, 0xf6, 0xd7, 0x00, 0x33, 0xb1, 0x5b, 0xf9, 0xf0, 0x0f, 0x0b, 0xee, 0x9e, 0x6c, 0xde, 0xb2, 0x42, 0xce, 0xfa, 0xf3, 0x7f, 0xec, 0xf6, 0x10, 0x8a, 0xba, 0x95, 0xcc,
0xcc, 0x55, 0xc3, 0x85, 0x9e, 0x1c, 0xa4, 0x3d, 0xd9, 0xbe, 0x61, 0x65, 0x9d, 0xf7, 0xe7, 0xff, 0xdd, 0xa1, 0x0d, 0xe5, 0x5d, 0x5f, 0x78, 0xa7, 0x01, 0xeb, 0xa2, 0x68, 0xd9, 0x4d, 0x68, 0xec,
0x38, 0xed, 0x11, 0x14, 0x75, 0x0b, 0x5a, 0x78, 0x42, 0x1b, 0xca, 0x7b, 0x1e, 0x77, 0x3b, 0x3e, 0x63, 0xb8, 0x7b, 0x1d, 0x3d, 0x4d, 0x38, 0xba, 0x66, 0x90, 0x15, 0xc8, 0x25, 0x33, 0x50, 0xee,
0xed, 0x29, 0xd1, 0x32, 0x89, 0x69, 0xd5, 0xff, 0xd4, 0xe9, 0x75, 0xf4, 0x34, 0xe1, 0xe8, 0x5a, 0x60, 0x57, 0x81, 0x55, 0x03, 0xd7, 0xae, 0x56, 0x5c, 0x4d, 0x38, 0x2d, 0x28, 0xea, 0x2a, 0x34,
0x83, 0xd6, 0x20, 0x17, 0xcf, 0x4e, 0xb9, 0xc3, 0x3d, 0x09, 0x96, 0x8d, 0x5f, 0xbb, 0x5a, 0x21, 0x83, 0xb7, 0xa1, 0xdc, 0xf2, 0x03, 0x86, 0x73, 0x80, 0xde, 0x73, 0x42, 0x2b, 0xf7, 0xf6, 0xc2,
0x9a, 0x70, 0x5a, 0x50, 0xd4, 0xd5, 0x6b, 0x0e, 0x6f, 0x43, 0xb9, 0xe5, 0xf9, 0x54, 0xcd, 0x0f, 0x0b, 0x63, 0x56, 0x2d, 0x9d, 0xad, 0x54, 0xbb, 0x57, 0x7e, 0xe0, 0x64, 0x60, 0xfc, 0xc0, 0x79,
0xfa, 0xcc, 0x31, 0x2d, 0xdd, 0xdb, 0x0f, 0x26, 0xc6, 0xac, 0x5c, 0x3a, 0x3f, 0x24, 0xc6, 0x04, 0xe0, 0x01, 0x14, 0x5b, 0x3c, 0x1a, 0x78, 0xd2, 0x28, 0x33, 0x94, 0xe3, 0xc0, 0xca, 0x41, 0x28,
0xe9, 0x87, 0x9a, 0x28, 0x8c, 0x1f, 0x6a, 0x8e, 0xb8, 0x07, 0xc5, 0x16, 0x0b, 0x87, 0xae, 0x30, 0x86, 0xac, 0x23, 0xb3, 0xc7, 0xc6, 0x23, 0xf8, 0x24, 0xc1, 0x98, 0x81, 0x31, 0x35, 0xf7, 0x58,
0xca, 0x0c, 0x25, 0x5b, 0xd3, 0x61, 0x3f, 0x60, 0x21, 0x6d, 0x0b, 0x57, 0x8c, 0xb5, 0x2b, 0x65, 0x77, 0x9f, 0x7b, 0xfe, 0x61, 0x41, 0x25, 0xa9, 0x6c, 0xa4, 0x09, 0x45, 0x3c, 0x8d, 0x78, 0xfa,
0x92, 0xe2, 0x39, 0x0e, 0xac, 0x1d, 0x06, 0x7c, 0x44, 0xbb, 0x22, 0x7b, 0x24, 0x3d, 0x86, 0x4f, 0x7c, 0x75, 0x43, 0x29, 0x6c, 0x7c, 0x44, 0xb4, 0xe9, 0x30, 0x5a, 0xd4, 0xfe, 0x1e, 0xaa, 0x29,
0x62, 0x8c, 0x19, 0x46, 0x13, 0x33, 0x95, 0x75, 0xfb, 0x99, 0xea, 0xef, 0x16, 0x54, 0xe2, 0xaa, 0xf6, 0x9c, 0x04, 0xd8, 0x4c, 0x27, 0x40, 0x66, 0x6b, 0xd0, 0x46, 0xd2, 0xe9, 0xb1, 0x0b, 0x45,
0x89, 0x9a, 0x50, 0x54, 0x5f, 0x2c, 0x9a, 0x6c, 0x5f, 0x5c, 0x53, 0x66, 0x1b, 0x1f, 0x14, 0xda, 0xcd, 0x9c, 0x1b, 0x56, 0x02, 0x85, 0x7d, 0x2f, 0xd2, 0xa9, 0x91, 0x77, 0x71, 0xad, 0x78, 0x6d,
0x74, 0x2f, 0x2d, 0x6a, 0x7f, 0x84, 0x6a, 0x82, 0xbd, 0x20, 0x49, 0xb6, 0x93, 0x49, 0x92, 0xd9, 0x7e, 0x26, 0xf1, 0x78, 0xf2, 0x2e, 0xae, 0x9d, 0x7f, 0x59, 0x50, 0x33, 0xa3, 0xa4, 0x89, 0x20,
0x76, 0xb4, 0x91, 0x64, 0x0a, 0xed, 0x41, 0x51, 0x33, 0x17, 0x86, 0x1e, 0x41, 0xe1, 0xc0, 0x0d, 0x83, 0x55, 0x7d, 0x43, 0x59, 0x14, 0xf3, 0x8c, 0xff, 0x6f, 0x16, 0x84, 0x32, 0x86, 0x36, 0xae,
0x75, 0xfa, 0xe4, 0x89, 0x5a, 0x4b, 0x5e, 0x9b, 0x9d, 0x09, 0x15, 0xee, 0x3c, 0x51, 0x6b, 0xe7, 0xcb, 0xea, 0x68, 0xcc, 0xa8, 0xb4, 0x9b, 0x70, 0x7f, 0x2e, 0xf4, 0x4e, 0x57, 0xe4, 0x25, 0xdc,
0x9f, 0x16, 0xd4, 0xcc, 0x98, 0x6a, 0x22, 0x48, 0x61, 0x5d, 0xdf, 0x62, 0x1a, 0xc6, 0x95, 0x4f, 0x9b, 0x0c, 0xc9, 0xd9, 0x79, 0xb2, 0x06, 0x24, 0x0d, 0x33, 0x43, 0xf4, 0x53, 0xa8, 0xaa, 0x47,
0xfb, 0xff, 0x7a, 0x49, 0x28, 0x23, 0x68, 0xe3, 0xaa, 0xac, 0x8e, 0xc6, 0x9c, 0x4a, 0xbb, 0x09, 0x47, 0xb6, 0x98, 0x03, 0xcb, 0x1a, 0x60, 0x22, 0x43, 0xa0, 0xd0, 0x67, 0x63, 0x9d, 0x0d, 0x15,
0x77, 0x17, 0x42, 0x6f, 0x75, 0x8d, 0x9e, 0xc3, 0x9d, 0xd9, 0x00, 0x9e, 0x9d, 0x27, 0x1b, 0x80, 0x17, 0xd7, 0xce, 0xdf, 0x2c, 0xf5, 0x76, 0x18, 0x8e, 0xe4, 0x7b, 0x26, 0x84, 0xd7, 0x53, 0x09,
0x92, 0x30, 0x33, 0xa0, 0x3f, 0x86, 0xaa, 0x7c, 0xd0, 0x64, 0x8b, 0x39, 0xb0, 0xaa, 0x01, 0x26, 0x58, 0x38, 0x08, 0x7d, 0x69, 0xb2, 0xef, 0xb3, 0xac, 0x37, 0xc4, 0x70, 0x24, 0x15, 0xcc, 0x48,
0x32, 0x08, 0x0a, 0x03, 0x3a, 0xd5, 0xd9, 0x50, 0x21, 0x6a, 0xed, 0xfc, 0xcd, 0x92, 0xef, 0x92, 0xed, 0xff, 0xc8, 0x45, 0x29, 0xb2, 0x05, 0x85, 0x5d, 0x4f, 0x7a, 0x26, 0x17, 0x32, 0x26, 0x26,
0xd1, 0x58, 0xbc, 0xa3, 0x9c, 0xbb, 0x7d, 0x99, 0x80, 0x85, 0xc3, 0xc0, 0x13, 0x26, 0xfb, 0x3e, 0x85, 0x48, 0x09, 0x2a, 0x72, 0xa7, 0xa4, 0x1e, 0x4a, 0xc3, 0x91, 0x74, 0x5e, 0xc0, 0xea, 0x75,
0xcb, 0x7a, 0x9f, 0x8c, 0xc6, 0x42, 0xc2, 0x8c, 0xd4, 0xc1, 0x4f, 0x88, 0x92, 0x42, 0xaf, 0xa0, 0xed, 0x73, 0x5c, 0xfb, 0x12, 0xaa, 0x29, 0x2d, 0x78, 0x6f, 0x8f, 0x5a, 0x08, 0x28, 0xbb, 0x6a,
0xb0, 0xe7, 0x0a, 0xd7, 0xe4, 0x42, 0xc6, 0x34, 0x26, 0x11, 0x09, 0x41, 0x49, 0xee, 0x96, 0xe4, 0xa9, 0x7c, 0x4d, 0x36, 0xb2, 0xac, 0x6d, 0x38, 0x9f, 0x40, 0x0d, 0x55, 0x27, 0x11, 0xfc, 0x53,
0x23, 0x6c, 0x34, 0x16, 0xce, 0x33, 0x58, 0xbf, 0xaa, 0x7d, 0x81, 0x6b, 0x5f, 0x42, 0x35, 0xa1, 0x0e, 0x4a, 0xb1, 0x8a, 0xad, 0x29, 0xbf, 0x9f, 0x65, 0xf9, 0x3d, 0xeb, 0xf2, 0x6b, 0x28, 0xa8,
0x45, 0xdd, 0xed, 0xe3, 0x96, 0x02, 0x94, 0x89, 0x5c, 0x4a, 0x5f, 0xe3, 0x83, 0xac, 0x6a, 0x1b, 0xfa, 0x61, 0x5c, 0xce, 0x18, 0x37, 0x5a, 0xdd, 0x94, 0x98, 0x82, 0x93, 0x6f, 0xa1, 0xe8, 0x32,
0xce, 0x27, 0x50, 0x53, 0xaa, 0xe3, 0x08, 0xfe, 0x29, 0x07, 0xa5, 0x48, 0xc5, 0xab, 0x94, 0xdf, 0xa1, 0x46, 0x23, 0xfd, 0x88, 0x78, 0x3e, 0x5f, 0x50, 0x63, 0x26, 0xc2, 0x46, 0x48, 0x89, 0xb7,
0x4f, 0xb2, 0xfc, 0x9e, 0x77, 0xf9, 0x25, 0x14, 0x64, 0x8d, 0x31, 0x2e, 0x67, 0x8c, 0x32, 0xad, 0xfd, 0x5e, 0xe8, 0x05, 0xb4, 0xb0, 0x48, 0x5c, 0x63, 0x52, 0xe2, 0x9a, 0x31, 0x09, 0xf7, 0x5f,
0x5e, 0x42, 0x4c, 0xc2, 0xd1, 0x77, 0x50, 0x24, 0x94, 0xcb, 0xb1, 0x4b, 0x3f, 0x50, 0x9e, 0x2e, 0x2c, 0xa8, 0x2e, 0x0c, 0xf5, 0xe2, 0x67, 0xde, 0xcc, 0xd3, 0x33, 0xff, 0x3f, 0x3e, 0x3d, 0xff,
0x16, 0xd4, 0x98, 0x99, 0xb0, 0x11, 0x92, 0xe2, 0x6d, 0xaf, 0x1f, 0xb8, 0x3e, 0x2e, 0x2c, 0x13, 0x9c, 0x9b, 0x56, 0x84, 0x53, 0x92, 0xba, 0x4f, 0x43, 0xee, 0x87, 0xd2, 0xa4, 0x6c, 0x8a, 0xa3,
0xd7, 0x98, 0x84, 0xb8, 0x66, 0xcc, 0xc2, 0xfd, 0x17, 0x0b, 0xaa, 0x4b, 0x43, 0xbd, 0xfc, 0x09, 0x36, 0xda, 0x1c, 0x74, 0x4d, 0xd1, 0x57, 0x4b, 0x75, 0xcd, 0x0e, 0xb9, 0xe2, 0x55, 0x31, 0x0d,
0x39, 0xf7, 0xac, 0xcd, 0xff, 0x8f, 0xcf, 0xda, 0x3f, 0xe7, 0xd2, 0x8a, 0xd4, 0x04, 0x26, 0xef, 0x34, 0x31, 0x29, 0xe9, 0x79, 0x53, 0xd2, 0x55, 0x6a, 0x7c, 0x10, 0x2c, 0xc2, 0xc0, 0x55, 0x5c,
0xd3, 0x88, 0x79, 0x81, 0x30, 0x29, 0x9b, 0xe0, 0xc8, 0x83, 0x36, 0x87, 0x3d, 0xd3, 0x18, 0xe4, 0x5c, 0xab, 0x2a, 0x7e, 0xc8, 0x91, 0xbb, 0x84, 0xc2, 0x86, 0x42, 0x2b, 0x97, 0x5d, 0x5a, 0xd4,
0x52, 0x5e, 0xb3, 0x23, 0x26, 0x79, 0x55, 0x95, 0x06, 0x9a, 0x98, 0x95, 0xfd, 0xbc, 0x29, 0xfb, 0xe1, 0x68, 0x5e, 0xc6, 0x56, 0x2e, 0xbb, 0xb4, 0x94, 0x58, 0xb9, 0x44, 0x2b, 0x27, 0x72, 0x4c,
0x32, 0x35, 0xde, 0x73, 0x1a, 0xaa, 0xc0, 0x55, 0x88, 0x5a, 0xcb, 0x4a, 0x7f, 0xc4, 0x14, 0x77, 0xcb, 0x3a, 0x01, 0x4f, 0xe4, 0x58, 0xb5, 0x19, 0x97, 0x07, 0xc1, 0xa9, 0xd7, 0xe9, 0xd3, 0x8a,
0x45, 0x09, 0x1b, 0x4a, 0x59, 0xb9, 0xe8, 0xe1, 0xa2, 0x0e, 0x47, 0xf3, 0x22, 0xb2, 0x72, 0xd1, 0xee, 0x6f, 0x31, 0xad, 0xe6, 0x49, 0x15, 0x73, 0xdf, 0x0b, 0xf0, 0xe5, 0x51, 0x76, 0x63, 0xd2,
0xc3, 0xa5, 0xd8, 0xca, 0x85, 0xb2, 0x72, 0x2a, 0xa6, 0xb8, 0xac, 0x13, 0xf0, 0x54, 0x4c, 0x65, 0xd9, 0x86, 0x4a, 0x92, 0x2a, 0xaa, 0x73, 0xb5, 0xba, 0x78, 0x14, 0x35, 0x37, 0xd7, 0xea, 0xc6,
0x2b, 0x22, 0xcc, 0xf7, 0x3b, 0x6e, 0x77, 0x80, 0x2b, 0xba, 0x07, 0x46, 0xb4, 0x9c, 0x55, 0x65, 0x59, 0x9e, 0x9b, 0xcd, 0xf2, 0x7c, 0x2a, 0xcb, 0xb7, 0xa0, 0x36, 0x95, 0x34, 0x0a, 0xe4, 0xf2,
0xcc, 0x3d, 0xd7, 0x57, 0xaf, 0x9a, 0x32, 0x89, 0x48, 0x67, 0x07, 0x2a, 0x71, 0xaa, 0xc8, 0xee, 0x4b, 0x61, 0x14, 0xe1, 0x5a, 0xf1, 0x9a, 0x3c, 0xd0, 0x6f, 0xeb, 0x9a, 0x8b, 0x6b, 0xe7, 0x39,
0xd6, 0xea, 0xa9, 0x4f, 0x51, 0x23, 0xb9, 0x56, 0x2f, 0xca, 0xf2, 0xdc, 0x7c, 0x96, 0xe7, 0x13, 0xd4, 0xa6, 0xd2, 0x65, 0x5e, 0x5d, 0x76, 0x9e, 0x41, 0xad, 0x2d, 0x3d, 0x39, 0x5a, 0xf0, 0x67,
0x59, 0xfe, 0x0a, 0x6a, 0xa9, 0xa4, 0x91, 0x20, 0xc2, 0x2e, 0xb8, 0x51, 0xa4, 0xd6, 0x92, 0xd7, 0xc8, 0x7f, 0x2c, 0x58, 0x89, 0x31, 0xa6, 0xf2, 0xfc, 0x02, 0xca, 0x17, 0x2c, 0x92, 0xec, 0x2a,
0x64, 0xbe, 0x7e, 0xb7, 0xd7, 0x88, 0x5a, 0x3b, 0x4f, 0xa1, 0x96, 0x4a, 0x97, 0x45, 0x75, 0xd9, 0xe9, 0x45, 0x74, 0x76, 0x9c, 0xfd, 0x88, 0x08, 0x37, 0x41, 0x92, 0xaf, 0xa1, 0x2c, 0x50, 0x0f,
0x79, 0x02, 0x35, 0xdd, 0xe0, 0xb2, 0xcb, 0xce, 0x7f, 0x2c, 0x58, 0x8b, 0x30, 0xa6, 0xf2, 0xfc, 0x8b, 0xe7, 0x98, 0x27, 0x59, 0x52, 0xc6, 0x5e, 0x82, 0x27, 0x1b, 0x50, 0x08, 0x78, 0x4f, 0xe0,
0x0a, 0xca, 0x13, 0x1a, 0x0a, 0x7a, 0x19, 0xf7, 0x22, 0x3c, 0x3f, 0x2a, 0x7f, 0x50, 0x08, 0x12, 0xb9, 0x57, 0x37, 0x1f, 0x65, 0xc9, 0xbd, 0xe3, 0x3d, 0x17, 0x81, 0xe4, 0x2d, 0x94, 0x2f, 0xbd,
0x23, 0xd1, 0x37, 0x50, 0xe6, 0x4a, 0x0f, 0x8d, 0x66, 0x9d, 0x47, 0x59, 0x52, 0xc6, 0x5e, 0x8c, 0x28, 0xf4, 0xc3, 0x5e, 0xfc, 0x26, 0x7f, 0x9a, 0x25, 0xf4, 0xbd, 0xc6, 0xb9, 0x89, 0x80, 0x53,
0x47, 0x5b, 0x50, 0xf0, 0x59, 0x9f, 0xab, 0xef, 0x5e, 0xdd, 0x7e, 0x90, 0x25, 0xf7, 0x96, 0xf5, 0x53, 0x97, 0xe8, 0x8c, 0x9b, 0x98, 0x38, 0xbf, 0x51, 0xb9, 0xac, 0x48, 0xe3, 0xfe, 0x01, 0xd4,
0x89, 0x02, 0xa2, 0x37, 0x50, 0xbe, 0x70, 0xc3, 0xc0, 0x0b, 0xfa, 0xd1, 0x7b, 0xff, 0x71, 0x96, 0xf4, 0x7d, 0xf8, 0xc8, 0x22, 0xa1, 0xa6, 0x42, 0x6b, 0xd1, 0x9d, 0xdd, 0x49, 0x43, 0xdd, 0x69,
0xd0, 0x47, 0x8d, 0x23, 0xb1, 0x80, 0x53, 0x93, 0x97, 0xe8, 0x8c, 0x99, 0x98, 0x38, 0xbf, 0x95, 0x49, 0xe7, 0x07, 0xd3, 0xee, 0x62, 0x86, 0xca, 0xa5, 0xa1, 0xd7, 0xe9, 0x7b, 0xbd, 0xf8, 0x9c,
0xb9, 0x2c, 0x49, 0xe3, 0xfe, 0x21, 0xd4, 0xf4, 0x7d, 0xf8, 0x40, 0x43, 0x2e, 0x27, 0x47, 0x6b, 0x62, 0x52, 0x7d, 0xb9, 0x30, 0xf6, 0xf4, 0xb5, 0x8d, 0x49, 0x95, 0x9b, 0x11, 0xbb, 0xf0, 0xc5,
0xd9, 0x9d, 0xdd, 0x4d, 0x42, 0x49, 0x5a, 0xd2, 0xf9, 0xd1, 0xb4, 0xbb, 0x88, 0x21, 0x73, 0x69, 0x64, 0x40, 0x4d, 0xe8, 0xcd, 0xbf, 0x96, 0x00, 0x9a, 0xc9, 0x7e, 0xc8, 0x31, 0x2c, 0xa1, 0x3d,
0xe4, 0x76, 0x07, 0x6e, 0x3f, 0xfa, 0x4e, 0x11, 0x29, 0x77, 0x26, 0xc6, 0x9e, 0xbe, 0xb6, 0x11, 0xe2, 0x2c, 0x6c, 0x9e, 0xe8, 0xb7, 0xfd, 0xfc, 0x16, 0x0d, 0x96, 0x7c, 0x54, 0xc9, 0x8f, 0x43,
0x29, 0x73, 0x33, 0xa4, 0x13, 0x8f, 0xcf, 0x86, 0xd8, 0x98, 0xde, 0xfe, 0x6b, 0x09, 0xa0, 0x19, 0x0f, 0x79, 0x91, 0x55, 0x26, 0xd2, 0x73, 0x93, 0xfd, 0xf2, 0x06, 0x94, 0xd1, 0xfb, 0x01, 0x8a,
0x9f, 0x07, 0x9d, 0xc0, 0x8a, 0xb2, 0x87, 0x9c, 0xa5, 0xcd, 0x53, 0xf9, 0x6d, 0x3f, 0xbd, 0x41, 0x3a, 0x0b, 0x48, 0x56, 0x2d, 0x4c, 0xe7, 0xad, 0xfd, 0x62, 0x31, 0x48, 0x2b, 0xfd, 0xdc, 0x22,
0x83, 0x45, 0x1f, 0x64, 0xf2, 0xab, 0xa1, 0x07, 0x3d, 0xcb, 0x2a, 0x13, 0xc9, 0xb9, 0xc9, 0x7e, 0xae, 0xa9, 0x94, 0xc4, 0x59, 0xd0, 0x0a, 0xcd, 0x8d, 0xc9, 0x0a, 0xc0, 0x54, 0xd7, 0xa9, 0x5b,
0x7e, 0x0d, 0xca, 0xe8, 0x7d, 0x0f, 0x45, 0x9d, 0x05, 0x28, 0xab, 0x16, 0x26, 0xf3, 0xd6, 0x7e, 0xe4, 0x3b, 0x28, 0xea, 0x5a, 0x47, 0x3e, 0x9d, 0x2f, 0x10, 0xeb, 0x5b, 0xfc, 0xb9, 0x6e, 0x7d,
0xb6, 0x1c, 0xa4, 0x95, 0x7e, 0x6e, 0x21, 0x62, 0x2a, 0x25, 0x72, 0x96, 0xb4, 0x42, 0x73, 0x63, 0x6e, 0x91, 0xf7, 0x50, 0x50, 0x4d, 0x9e, 0x64, 0x74, 0xac, 0xd4, 0x84, 0x60, 0x3b, 0x8b, 0x20,
0xb2, 0x02, 0x90, 0xea, 0x3a, 0x75, 0x0b, 0x7d, 0x0f, 0x45, 0x5d, 0xeb, 0xd0, 0xa7, 0x8b, 0x05, 0x26, 0x8a, 0x3f, 0x00, 0x4c, 0x46, 0x0d, 0x92, 0xf1, 0xcf, 0xca, 0xcc, 0xcc, 0x62, 0xd7, 0x6f,
0x22, 0x7d, 0xcb, 0xb7, 0xeb, 0xd6, 0xe7, 0x16, 0x7a, 0x07, 0x05, 0xd9, 0xe4, 0x51, 0x46, 0xc7, 0x06, 0x1a, 0x03, 0xef, 0x55, 0x9f, 0x3d, 0xe3, 0x24, 0xb3, 0xc3, 0x26, 0xd7, 0xc8, 0x76, 0x16,
0x4a, 0x4c, 0x08, 0xb6, 0xb3, 0x0c, 0x62, 0xa2, 0xf8, 0x23, 0xc0, 0x6c, 0xd4, 0x40, 0x19, 0xff, 0x41, 0x8c, 0xba, 0x73, 0xa8, 0x4d, 0xfd, 0xf3, 0x4a, 0x7e, 0x96, 0xed, 0xe4, 0xf5, 0x3f, 0x72,
0xda, 0xcc, 0xcd, 0x2c, 0x76, 0xfd, 0x7a, 0xa0, 0x31, 0xf0, 0x4e, 0xf6, 0xd9, 0x33, 0x86, 0x32, 0xed, 0x57, 0xb7, 0xc2, 0x1a, 0x4b, 0x32, 0x3d, 0xab, 0x99, 0xcf, 0xa4, 0x71, 0x93, 0xdf, 0xd3,
0x3b, 0x6c, 0x7c, 0x8d, 0x6c, 0x67, 0x19, 0xc4, 0xa8, 0x3b, 0x87, 0x5a, 0xea, 0x5f, 0x5d, 0xf4, 0xff, 0xa2, 0xda, 0x1b, 0xb7, 0xc6, 0x6b, 0xab, 0x3b, 0x85, 0xdf, 0xe6, 0x86, 0xa7, 0xa7, 0x45,
0x8b, 0x6c, 0x27, 0xaf, 0xfe, 0x49, 0x6c, 0xbf, 0xb8, 0x11, 0xd6, 0x58, 0x12, 0xc9, 0x59, 0xcd, 0xfc, 0x43, 0xfa, 0xcb, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0xe3, 0x77, 0x0e, 0x2f, 0x2e, 0x17,
0x6c, 0xa3, 0xc6, 0x75, 0x7e, 0xa7, 0xff, 0xa1, 0xb5, 0xb7, 0x6e, 0x8c, 0xd7, 0x56, 0x77, 0x0b, 0x00, 0x00,
0xbf, 0xcb, 0x8d, 0x3a, 0x9d, 0xa2, 0xfa, 0xb3, 0xfb, 0xcb, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff,
0xc1, 0x07, 0x8b, 0x2b, 0x8a, 0x17, 0x00, 0x00,
} }
// Reference imports to suppress errors if they are not otherwise used. // Reference imports to suppress errors if they are not otherwise used.

View File

@@ -80,7 +80,6 @@ message BuildOptions {
string Ref = 29; string Ref = 29;
string GroupRef = 30; string GroupRef = 30;
repeated string Annotations = 31; repeated string Annotations = 31;
bool WithProvenanceResponse = 32;
} }
message ExportEntry { message ExportEntry {
@@ -114,7 +113,6 @@ message Secret {
message PrintFunc { message PrintFunc {
string Name = 1; string Name = 1;
string Format = 2; string Format = 2;
bool IgnoreStatus = 3;
} }
message InspectRequest { message InspectRequest {

View File

@@ -180,9 +180,6 @@ variable "HTTPS_PROXY" {
variable "NO_PROXY" { variable "NO_PROXY" {
default = "" default = ""
} }
variable "TEST_BUILDKIT_TAG" {
default = null
}
target "integration-test-base" { target "integration-test-base" {
inherits = ["_common"] inherits = ["_common"]
@@ -190,7 +187,6 @@ target "integration-test-base" {
HTTP_PROXY = HTTP_PROXY HTTP_PROXY = HTTP_PROXY
HTTPS_PROXY = HTTPS_PROXY HTTPS_PROXY = HTTPS_PROXY
NO_PROXY = NO_PROXY NO_PROXY = NO_PROXY
BUILDKIT_VERSION = TEST_BUILDKIT_TAG
} }
target = "integration-test-base" target = "integration-test-base"
output = ["type=cacheonly"] output = ["type=cacheonly"]

View File

@@ -14,11 +14,11 @@ Build from a file
### Options ### Options
| Name | Type | Default | Description | | Name | Type | Default | Description |
|:------------------------------------|:--------------|:--------|:-----------------------------------------------------------------------------------------| |:---------------------------------|:--------------|:--------|:-----------------------------------------------------------------------------------------|
| [`--builder`](#builder) | `string` | | Override the configured builder instance | | [`--builder`](#builder) | `string` | | Override the configured builder instance |
| [`-f`](#file), [`--file`](#file) | `stringArray` | | Build definition file | | [`-f`](#file), [`--file`](#file) | `stringArray` | | Build definition file |
| `--load` | | | Shorthand for `--set=*.output=type=docker` | | `--load` | | | Shorthand for `--set=*.output=type=docker` |
| [`--metadata-file`](#metadata-file) | `string` | | Write build result metadata to a file | | `--metadata-file` | `string` | | Write build result metadata to the file |
| [`--no-cache`](#no-cache) | | | Do not use cache when building the image | | [`--no-cache`](#no-cache) | | | Do not use cache when building the image |
| [`--print`](#print) | | | Print the options without building | | [`--print`](#print) | | | Print the options without building |
| [`--progress`](#progress) | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output | | [`--progress`](#progress) | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output |
@@ -90,77 +90,6 @@ $ docker buildx bake -f docker-bake.dev.hcl db webapp-release
See the [Bake file reference](https://docs.docker.com/build/bake/reference/) See the [Bake file reference](https://docs.docker.com/build/bake/reference/)
for more details. for more details.
### <a name="metadata-file"></a> Write build results metadata to a file (--metadata-file)
Similar to [`buildx build --metadata-file`](buildx_build.md#metadata-file) but
writes a map of results for each target such as:
```hcl
# docker-bake.hcl
group "default" {
targets = ["db", "webapp-dev"]
}
target "db" {
dockerfile = "Dockerfile.db"
tags = ["docker.io/username/db"]
}
target "webapp-dev" {
dockerfile = "Dockerfile.webapp"
tags = ["docker.io/username/webapp"]
}
```
```console
$ docker buildx bake --load --metadata-file metadata.json .
$ cat metadata.json
```
```json
{
"db": {
"buildx.build.provenance": {},
"buildx.build.ref": "mybuilder/mybuilder0/0fjb6ubs52xx3vygf6fgdl611",
"containerimage.config.digest": "sha256:2937f66a9722f7f4a2df583de2f8cb97fc9196059a410e7f00072fc918930e66",
"containerimage.descriptor": {
"annotations": {
"config.digest": "sha256:2937f66a9722f7f4a2df583de2f8cb97fc9196059a410e7f00072fc918930e66",
"org.opencontainers.image.created": "2022-02-08T21:28:03Z"
},
"digest": "sha256:19ffeab6f8bc9293ac2c3fdf94ebe28396254c993aea0b5a542cfb02e0883fa3",
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 506
},
"containerimage.digest": "sha256:19ffeab6f8bc9293ac2c3fdf94ebe28396254c993aea0b5a542cfb02e0883fa3"
},
"webapp-dev": {
"buildx.build.provenance": {},
"buildx.build.ref": "mybuilder/mybuilder0/kamngmcgyzebqxwu98b4lfv3n",
"containerimage.config.digest": "sha256:9651cc2b3c508f697c9c43b67b64c8359c2865c019e680aac1c11f4b875b67e0",
"containerimage.descriptor": {
"annotations": {
"config.digest": "sha256:9651cc2b3c508f697c9c43b67b64c8359c2865c019e680aac1c11f4b875b67e0",
"org.opencontainers.image.created": "2022-02-08T21:28:15Z"
},
"digest": "sha256:6d9ac9237a84afe1516540f40a0fafdc86859b2141954b4d643af7066d598b74",
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 506
},
"containerimage.digest": "sha256:6d9ac9237a84afe1516540f40a0fafdc86859b2141954b4d643af7066d598b74"
}
}
```
> **Note**
>
> Build record [provenance](https://docs.docker.com/build/attestations/slsa-provenance/#provenance-attestation-example)
> (`buildx.build.provenance`) includes minimal provenance by default. Set the
> `BUILDX_METADATA_PROVENANCE` environment variable to customize this behavior:
> * `min` sets minimal provenance (default).
> * `max` sets full provenance.
> * `disabled`, `false` or `0` does not set any provenance.
### <a name="no-cache"></a> Don't use cache when building the image (--no-cache) ### <a name="no-cache"></a> Don't use cache when building the image (--no-cache)
Same as `build --no-cache`. Don't use cache when building the image. Same as `build --no-cache`. Don't use cache when building the image.
@@ -233,7 +162,6 @@ You can override the following fields:
* `context` * `context`
* `dockerfile` * `dockerfile`
* `labels` * `labels`
* `load`
* `no-cache` * `no-cache`
* `no-cache-filter` * `no-cache-filter`
* `output` * `output`

View File

@@ -27,10 +27,10 @@ Start a build
| [`--cgroup-parent`](https://docs.docker.com/reference/cli/docker/image/build/#cgroup-parent) | `string` | | Set the parent cgroup for the `RUN` instructions during build | | [`--cgroup-parent`](https://docs.docker.com/reference/cli/docker/image/build/#cgroup-parent) | `string` | | Set the parent cgroup for the `RUN` instructions during build |
| `--detach` | | | Detach buildx server (supported only on linux) (EXPERIMENTAL) | | `--detach` | | | Detach buildx server (supported only on linux) (EXPERIMENTAL) |
| [`-f`](https://docs.docker.com/reference/cli/docker/image/build/#file), [`--file`](https://docs.docker.com/reference/cli/docker/image/build/#file) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) | | [`-f`](https://docs.docker.com/reference/cli/docker/image/build/#file), [`--file`](https://docs.docker.com/reference/cli/docker/image/build/#file) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
| `--iidfile` | `string` | | Write the image ID to a file | | `--iidfile` | `string` | | Write the image ID to the file |
| `--label` | `stringArray` | | Set metadata for an image | | `--label` | `stringArray` | | Set metadata for an image |
| [`--load`](#load) | | | Shorthand for `--output=type=docker` | | [`--load`](#load) | | | Shorthand for `--output=type=docker` |
| [`--metadata-file`](#metadata-file) | `string` | | Write build result metadata to a file | | [`--metadata-file`](#metadata-file) | `string` | | Write build result metadata to the file |
| `--network` | `string` | `default` | Set the networking mode for the `RUN` instructions during build | | `--network` | `string` | `default` | Set the networking mode for the `RUN` instructions during build |
| `--no-cache` | | | Do not use cache when building the image | | `--no-cache` | | | Do not use cache when building the image |
| [`--no-cache-filter`](#no-cache-filter) | `stringArray` | | Do not cache specified stages | | [`--no-cache-filter`](#no-cache-filter) | `stringArray` | | Do not cache specified stages |
@@ -152,7 +152,7 @@ Allow extra privileged entitlement. List of entitlements:
- `network.host` - Allows executions with host networking. - `network.host` - Allows executions with host networking.
- `security.insecure` - Allows executions without sandbox. See - `security.insecure` - Allows executions without sandbox. See
[related Dockerfile extensions](https://docs.docker.com/reference/dockerfile/#run---security). [related Dockerfile extensions](https://docs.docker.com/reference/dockerfile/#run---securitysandbox).
For entitlements to be enabled, the BuildKit daemon also needs to allow them For entitlements to be enabled, the BuildKit daemon also needs to allow them
with `--allow-insecure-entitlement` (see [`create --buildkitd-flags`](buildx_create.md#buildkitd-flags)). with `--allow-insecure-entitlement` (see [`create --buildkitd-flags`](buildx_create.md#buildkitd-flags)).
@@ -314,7 +314,7 @@ More info about cache exporters and available attributes: https://github.com/mob
Shorthand for [`--output=type=docker`](#docker). Will automatically load the Shorthand for [`--output=type=docker`](#docker). Will automatically load the
single-platform build result to `docker images`. single-platform build result to `docker images`.
### <a name="metadata-file"></a> Write build result metadata to a file (--metadata-file) ### <a name="metadata-file"></a> Write build result metadata to the file (--metadata-file)
To output build metadata such as the image digest, pass the `--metadata-file` flag. To output build metadata such as the image digest, pass the `--metadata-file` flag.
The metadata will be written as a JSON object to the specified file. The The metadata will be written as a JSON object to the specified file. The
@@ -327,7 +327,6 @@ $ cat metadata.json
```json ```json
{ {
"buildx.build.provenance": {},
"buildx.build.ref": "mybuilder/mybuilder0/0fjb6ubs52xx3vygf6fgdl611", "buildx.build.ref": "mybuilder/mybuilder0/0fjb6ubs52xx3vygf6fgdl611",
"containerimage.config.digest": "sha256:2937f66a9722f7f4a2df583de2f8cb97fc9196059a410e7f00072fc918930e66", "containerimage.config.digest": "sha256:2937f66a9722f7f4a2df583de2f8cb97fc9196059a410e7f00072fc918930e66",
"containerimage.descriptor": { "containerimage.descriptor": {
@@ -343,15 +342,6 @@ $ cat metadata.json
} }
``` ```
> **Note**
>
> Build record [provenance](https://docs.docker.com/build/attestations/slsa-provenance/#provenance-attestation-example)
> (`buildx.build.provenance`) includes minimal provenance by default. Set the
> `BUILDX_METADATA_PROVENANCE` environment variable to customize this behavior:
> * `min` sets minimal provenance (default).
> * `max` sets full provenance.
> * `disabled`, `false` or `0` does not set any provenance.
### <a name="no-cache-filter"></a> Ignore build cache for specific stages (--no-cache-filter) ### <a name="no-cache-filter"></a> Ignore build cache for specific stages (--no-cache-filter)
The `--no-cache-filter` lets you specify one or more stages of a multi-stage The `--no-cache-filter` lets you specify one or more stages of a multi-stage
@@ -428,7 +418,7 @@ exporter and write to `stdout`.
```console ```console
$ docker buildx build -o . . $ docker buildx build -o . .
$ docker buildx build -o outdir . $ docker buildx build -o outdir .
$ docker buildx build -o - . > out.tar $ docker buildx build -o - - > out.tar
$ docker buildx build -o type=docker . $ docker buildx build -o type=docker .
$ docker buildx build -o type=docker,dest=- . > myimage.tar $ docker buildx build -o type=docker,dest=- . > myimage.tar
$ docker buildx build -t tonistiigi/foo -o type=registry $ docker buildx build -t tonistiigi/foo -o type=registry
@@ -625,18 +615,10 @@ For more information, see [here](https://docs.docker.com/build/attestations/sbom
--secret=[type=TYPE[,KEY=VALUE] --secret=[type=TYPE[,KEY=VALUE]
``` ```
Exposes secrets (authentication credentials, tokens) to the build. Exposes secret to the build. The secret can be used by the build using
A secret can be mounted into the build using a `RUN --mount=type=secret` mount in the [`RUN --mount=type=secret` mount](https://docs.docker.com/reference/dockerfile/#run---mounttypesecret).
[Dockerfile](https://docs.docker.com/reference/dockerfile/#run---mounttypesecret).
For more information about how to use build secrets, see
[Build secrets](https://docs.docker.com/build/building/secrets/).
Supported types are: If `type` is unset it will be detected. Supported types are:
- [`file`](#file)
- [`env`](#env)
Buildx attempts to detect the `type` automatically if unset.
#### `file` #### `file`

View File

@@ -23,10 +23,10 @@ Start a build
| [`--cgroup-parent`](https://docs.docker.com/reference/cli/docker/image/build/#cgroup-parent) | `string` | | Set the parent cgroup for the `RUN` instructions during build | | [`--cgroup-parent`](https://docs.docker.com/reference/cli/docker/image/build/#cgroup-parent) | `string` | | Set the parent cgroup for the `RUN` instructions during build |
| `--detach` | | | Detach buildx server (supported only on linux) (EXPERIMENTAL) | | `--detach` | | | Detach buildx server (supported only on linux) (EXPERIMENTAL) |
| [`-f`](https://docs.docker.com/reference/cli/docker/image/build/#file), [`--file`](https://docs.docker.com/reference/cli/docker/image/build/#file) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) | | [`-f`](https://docs.docker.com/reference/cli/docker/image/build/#file), [`--file`](https://docs.docker.com/reference/cli/docker/image/build/#file) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
| `--iidfile` | `string` | | Write the image ID to a file | | `--iidfile` | `string` | | Write the image ID to the file |
| `--label` | `stringArray` | | Set metadata for an image | | `--label` | `stringArray` | | Set metadata for an image |
| `--load` | | | Shorthand for `--output=type=docker` | | `--load` | | | Shorthand for `--output=type=docker` |
| `--metadata-file` | `string` | | Write build result metadata to a file | | `--metadata-file` | `string` | | Write build result metadata to the file |
| `--network` | `string` | `default` | Set the networking mode for the `RUN` instructions during build | | `--network` | `string` | `default` | Set the networking mode for the `RUN` instructions during build |
| `--no-cache` | | | Do not use cache when building the image | | `--no-cache` | | | Do not use cache when building the image |
| `--no-cache-filter` | `stringArray` | | Do not cache specified stages | | `--no-cache-filter` | `stringArray` | | Do not cache specified stages |

View File

@@ -30,6 +30,7 @@ import (
"github.com/docker/docker/pkg/idtools" "github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/stdcopy"
"github.com/moby/buildkit/client" "github.com/moby/buildkit/client"
"github.com/moby/buildkit/util/tracing/detect"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@@ -56,7 +57,6 @@ type Driver struct {
cgroupParent string cgroupParent string
restartPolicy container.RestartPolicy restartPolicy container.RestartPolicy
env []string env []string
defaultLoad bool
} }
func (d *Driver) IsMobyDriver() bool { func (d *Driver) IsMobyDriver() bool {
@@ -396,21 +396,28 @@ func (d *Driver) Dial(ctx context.Context) (net.Conn, error) {
return conn, nil return conn, nil
} }
func (d *Driver) Client(ctx context.Context, opts ...client.ClientOpt) (*client.Client, error) { func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
conn, err := d.Dial(ctx) conn, err := d.Dial(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
exp, _, err := detect.Exporter()
if err != nil {
return nil, err
}
var opts []client.ClientOpt
var counter int64 var counter int64
opts = append([]client.ClientOpt{ opts = append(opts, client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
if atomic.AddInt64(&counter, 1) > 1 { if atomic.AddInt64(&counter, 1) > 1 {
return nil, net.ErrClosed return nil, net.ErrClosed
} }
return conn, nil return conn, nil
}), }))
}, opts...) if td, ok := exp.(client.TracerDelegate); ok {
opts = append(opts, client.WithTracerDelegate(td))
}
return client.New(ctx, "", opts...) return client.New(ctx, "", opts...)
} }
@@ -424,7 +431,6 @@ func (d *Driver) Features(ctx context.Context) map[driver.Feature]bool {
driver.DockerExporter: true, driver.DockerExporter: true,
driver.CacheExport: true, driver.CacheExport: true,
driver.MultiPlatform: true, driver.MultiPlatform: true,
driver.DefaultLoad: d.defaultLoad,
} }
} }

View File

@@ -94,11 +94,6 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
if err != nil { if err != nil {
return nil, err return nil, err
} }
case k == "default-load":
d.defaultLoad, err = strconv.ParseBool(v)
if err != nil {
return nil, err
}
case strings.HasPrefix(k, "env."): case strings.HasPrefix(k, "env."):
envName := strings.TrimPrefix(k, "env.") envName := strings.TrimPrefix(k, "env.")
if envName == "" { if envName == "" {

View File

@@ -9,6 +9,7 @@ import (
"github.com/docker/buildx/driver" "github.com/docker/buildx/driver"
"github.com/docker/buildx/util/progress" "github.com/docker/buildx/util/progress"
"github.com/moby/buildkit/client" "github.com/moby/buildkit/client"
"github.com/moby/buildkit/util/tracing/detect"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@@ -60,14 +61,22 @@ func (d *Driver) Dial(ctx context.Context) (net.Conn, error) {
return d.DockerAPI.DialHijack(ctx, "/grpc", "h2c", d.DialMeta) return d.DockerAPI.DialHijack(ctx, "/grpc", "h2c", d.DialMeta)
} }
func (d *Driver) Client(ctx context.Context, opts ...client.ClientOpt) (*client.Client, error) { func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
opts = append([]client.ClientOpt{ opts := []client.ClientOpt{
client.WithContextDialer(func(context.Context, string) (net.Conn, error) { client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return d.Dial(ctx) return d.Dial(ctx)
}), client.WithSessionDialer(func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) { }), client.WithSessionDialer(func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
return d.DockerAPI.DialHijack(ctx, "/session", proto, meta) return d.DockerAPI.DialHijack(ctx, "/session", proto, meta)
}), }),
}, opts...) }
exp, _, err := detect.Exporter()
if err != nil {
return nil, err
}
if td, ok := exp.(client.TracerDelegate); ok {
opts = append(opts, client.WithTracerDelegate(td))
}
return client.New(ctx, "", opts...) return client.New(ctx, "", opts...)
} }
@@ -93,7 +102,6 @@ func (d *Driver) Features(ctx context.Context) map[driver.Feature]bool {
driver.DockerExporter: useContainerdSnapshotter, driver.DockerExporter: useContainerdSnapshotter,
driver.CacheExport: useContainerdSnapshotter, driver.CacheExport: useContainerdSnapshotter,
driver.MultiPlatform: useContainerdSnapshotter, driver.MultiPlatform: useContainerdSnapshotter,
driver.DefaultLoad: true,
} }
}) })
return d.features.list return d.features.list

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"io" "io"
"net" "net"
"strings"
"github.com/docker/buildx/store" "github.com/docker/buildx/store"
"github.com/docker/buildx/util/progress" "github.com/docker/buildx/util/progress"
@@ -61,33 +60,15 @@ type Driver interface {
Stop(ctx context.Context, force bool) error Stop(ctx context.Context, force bool) error
Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error
Dial(ctx context.Context) (net.Conn, error) Dial(ctx context.Context) (net.Conn, error)
Client(ctx context.Context, opts ...client.ClientOpt) (*client.Client, error) Client(ctx context.Context) (*client.Client, error)
Features(ctx context.Context) map[Feature]bool Features(ctx context.Context) map[Feature]bool
HostGatewayIP(ctx context.Context) (net.IP, error) HostGatewayIP(ctx context.Context) (net.IP, error)
IsMobyDriver() bool IsMobyDriver() bool
Config() InitConfig Config() InitConfig
} }
const builderNamePrefix = "buildx_buildkit_"
func BuilderName(name string) string {
return builderNamePrefix + name
}
func ParseBuilderName(name string) (string, error) {
if !strings.HasPrefix(name, builderNamePrefix) {
return "", errors.Errorf("invalid builder name %q, must have %q prefix", name, builderNamePrefix)
}
return strings.TrimPrefix(name, builderNamePrefix), nil
}
func Boot(ctx, clientContext context.Context, d *DriverHandle, pw progress.Writer) (*client.Client, error) { func Boot(ctx, clientContext context.Context, d *DriverHandle, pw progress.Writer) (*client.Client, error) {
try := 0 try := 0
logger := discardLogger
if pw != nil {
logger = pw.Write
}
for { for {
info, err := d.Info(ctx) info, err := d.Info(ctx)
if err != nil { if err != nil {
@@ -98,7 +79,7 @@ func Boot(ctx, clientContext context.Context, d *DriverHandle, pw progress.Write
if try > 2 { if try > 2 {
return nil, errors.Errorf("failed to bootstrap %T driver in attempts", d) return nil, errors.Errorf("failed to bootstrap %T driver in attempts", d)
} }
if err := d.Bootstrap(ctx, logger); err != nil { if err := d.Bootstrap(ctx, pw.Write); err != nil {
return nil, err return nil, err
} }
} }
@@ -114,8 +95,6 @@ func Boot(ctx, clientContext context.Context, d *DriverHandle, pw progress.Write
} }
} }
func discardLogger(*client.SolveStatus) {}
func historyAPISupported(ctx context.Context, c *client.Client) bool { func historyAPISupported(ctx context.Context, c *client.Client) bool {
cl, err := c.ControlClient().ListenBuildHistory(ctx, &controlapi.BuildHistoryRequest{ cl, err := c.ControlClient().ListenBuildHistory(ctx, &controlapi.BuildHistoryRequest{
ActiveOnly: true, ActiveOnly: true,

View File

@@ -7,5 +7,3 @@ const DockerExporter Feature = "Docker exporter"
const CacheExport Feature = "Cache export" const CacheExport Feature = "Cache export"
const MultiPlatform Feature = "Multi-platform build" const MultiPlatform Feature = "Multi-platform build"
const DefaultLoad Feature = "Automatically load images to the Docker Engine image store"

View File

@@ -15,6 +15,7 @@ import (
"github.com/docker/buildx/util/platformutil" "github.com/docker/buildx/util/platformutil"
"github.com/docker/buildx/util/progress" "github.com/docker/buildx/util/progress"
"github.com/moby/buildkit/client" "github.com/moby/buildkit/client"
"github.com/moby/buildkit/util/tracing/detect"
"github.com/pkg/errors" "github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@@ -49,7 +50,6 @@ type Driver struct {
podClient clientcorev1.PodInterface podClient clientcorev1.PodInterface
configMapClient clientcorev1.ConfigMapInterface configMapClient clientcorev1.ConfigMapInterface
podChooser podchooser.PodChooser podChooser podchooser.PodChooser
defaultLoad bool
} }
func (d *Driver) IsMobyDriver() bool { func (d *Driver) IsMobyDriver() bool {
@@ -211,12 +211,19 @@ func (d *Driver) Dial(ctx context.Context) (net.Conn, error) {
return conn, nil return conn, nil
} }
func (d *Driver) Client(ctx context.Context, opts ...client.ClientOpt) (*client.Client, error) { func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
opts = append([]client.ClientOpt{ exp, _, err := detect.Exporter()
client.WithContextDialer(func(context.Context, string) (net.Conn, error) { if err != nil {
return nil, err
}
var opts []client.ClientOpt
opts = append(opts, client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return d.Dial(ctx) return d.Dial(ctx)
}), }))
}, opts...) if td, ok := exp.(client.TracerDelegate); ok {
opts = append(opts, client.WithTracerDelegate(td))
}
return client.New(ctx, "", opts...) return client.New(ctx, "", opts...)
} }
@@ -230,7 +237,6 @@ func (d *Driver) Features(_ context.Context) map[driver.Feature]bool {
driver.DockerExporter: d.DockerAPI != nil, driver.DockerExporter: d.DockerAPI != nil,
driver.CacheExport: true, driver.CacheExport: true,
driver.MultiPlatform: true, // Untested (needs multiple Driver instances) driver.MultiPlatform: true, // Untested (needs multiple Driver instances)
driver.DefaultLoad: d.defaultLoad,
} }
} }

View File

@@ -68,13 +68,11 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
clientset: clientset, clientset: clientset,
} }
deploymentOpt, loadbalance, namespace, defaultLoad, err := f.processDriverOpts(deploymentName, namespace, cfg) deploymentOpt, loadbalance, namespace, err := f.processDriverOpts(deploymentName, namespace, cfg)
if nil != err { if nil != err {
return nil, err return nil, err
} }
d.defaultLoad = defaultLoad
d.deployment, d.configMaps, err = manifest.NewDeployment(deploymentOpt) d.deployment, d.configMaps, err = manifest.NewDeployment(deploymentOpt)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -102,7 +100,7 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
return d, nil return d, nil
} }
func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg driver.InitConfig) (*manifest.DeploymentOpt, string, string, bool, error) { func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg driver.InitConfig) (*manifest.DeploymentOpt, string, string, error) {
deploymentOpt := &manifest.DeploymentOpt{ deploymentOpt := &manifest.DeploymentOpt{
Name: deploymentName, Name: deploymentName,
Image: bkimage.DefaultImage, Image: bkimage.DefaultImage,
@@ -113,8 +111,6 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg
ConfigFiles: cfg.Files, ConfigFiles: cfg.Files,
} }
defaultLoad := false
deploymentOpt.Qemu.Image = bkimage.QemuImage deploymentOpt.Qemu.Image = bkimage.QemuImage
loadbalance := LoadbalanceSticky loadbalance := LoadbalanceSticky
@@ -131,46 +127,40 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg
case "replicas": case "replicas":
deploymentOpt.Replicas, err = strconv.Atoi(v) deploymentOpt.Replicas, err = strconv.Atoi(v)
if err != nil { if err != nil {
return nil, "", "", false, err return nil, "", "", err
} }
case "requests.cpu": case "requests.cpu":
deploymentOpt.RequestsCPU = v deploymentOpt.RequestsCPU = v
case "requests.memory": case "requests.memory":
deploymentOpt.RequestsMemory = v deploymentOpt.RequestsMemory = v
case "requests.ephemeral-storage":
deploymentOpt.RequestsEphemeralStorage = v
case "limits.cpu": case "limits.cpu":
deploymentOpt.LimitsCPU = v deploymentOpt.LimitsCPU = v
case "limits.memory": case "limits.memory":
deploymentOpt.LimitsMemory = v deploymentOpt.LimitsMemory = v
case "limits.ephemeral-storage":
deploymentOpt.LimitsEphemeralStorage = v
case "rootless": case "rootless":
deploymentOpt.Rootless, err = strconv.ParseBool(v) deploymentOpt.Rootless, err = strconv.ParseBool(v)
if err != nil { if err != nil {
return nil, "", "", false, err return nil, "", "", err
} }
if _, isImage := cfg.DriverOpts["image"]; !isImage { if _, isImage := cfg.DriverOpts["image"]; !isImage {
deploymentOpt.Image = bkimage.DefaultRootlessImage deploymentOpt.Image = bkimage.DefaultRootlessImage
} }
case "schedulername":
deploymentOpt.SchedulerName = v
case "serviceaccount": case "serviceaccount":
deploymentOpt.ServiceAccountName = v deploymentOpt.ServiceAccountName = v
case "nodeselector": case "nodeselector":
deploymentOpt.NodeSelector, err = splitMultiValues(v, ",", "=") deploymentOpt.NodeSelector, err = splitMultiValues(v, ",", "=")
if err != nil { if err != nil {
return nil, "", "", false, errors.Wrap(err, "cannot parse node selector") return nil, "", "", errors.Wrap(err, "cannot parse node selector")
} }
case "annotations": case "annotations":
deploymentOpt.CustomAnnotations, err = splitMultiValues(v, ",", "=") deploymentOpt.CustomAnnotations, err = splitMultiValues(v, ",", "=")
if err != nil { if err != nil {
return nil, "", "", false, errors.Wrap(err, "cannot parse annotations") return nil, "", "", errors.Wrap(err, "cannot parse annotations")
} }
case "labels": case "labels":
deploymentOpt.CustomLabels, err = splitMultiValues(v, ",", "=") deploymentOpt.CustomLabels, err = splitMultiValues(v, ",", "=")
if err != nil { if err != nil {
return nil, "", "", false, errors.Wrap(err, "cannot parse labels") return nil, "", "", errors.Wrap(err, "cannot parse labels")
} }
case "tolerations": case "tolerations":
ts := strings.Split(v, ";") ts := strings.Split(v, ";")
@@ -195,12 +185,12 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg
case "tolerationSeconds": case "tolerationSeconds":
c, err := strconv.Atoi(kv[1]) c, err := strconv.Atoi(kv[1])
if nil != err { if nil != err {
return nil, "", "", false, err return nil, "", "", err
} }
c64 := int64(c) c64 := int64(c)
t.TolerationSeconds = &c64 t.TolerationSeconds = &c64
default: default:
return nil, "", "", false, errors.Errorf("invalid tolaration %q", v) return nil, "", "", errors.Errorf("invalid tolaration %q", v)
} }
} }
} }
@@ -212,29 +202,24 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg
case LoadbalanceSticky: case LoadbalanceSticky:
case LoadbalanceRandom: case LoadbalanceRandom:
default: default:
return nil, "", "", false, errors.Errorf("invalid loadbalance %q", v) return nil, "", "", errors.Errorf("invalid loadbalance %q", v)
} }
loadbalance = v loadbalance = v
case "qemu.install": case "qemu.install":
deploymentOpt.Qemu.Install, err = strconv.ParseBool(v) deploymentOpt.Qemu.Install, err = strconv.ParseBool(v)
if err != nil { if err != nil {
return nil, "", "", false, err return nil, "", "", err
} }
case "qemu.image": case "qemu.image":
if v != "" { if v != "" {
deploymentOpt.Qemu.Image = v deploymentOpt.Qemu.Image = v
} }
case "default-load":
defaultLoad, err = strconv.ParseBool(v)
if err != nil {
return nil, "", "", false, err
}
default: default:
return nil, "", "", false, errors.Errorf("invalid driver option %s for driver %s", k, DriverName) return nil, "", "", errors.Errorf("invalid driver option %s for driver %s", k, DriverName)
} }
} }
return deploymentOpt, loadbalance, namespace, defaultLoad, nil return deploymentOpt, loadbalance, namespace, nil
} }
func splitMultiValues(in string, itemsep string, kvsep string) (map[string]string, error) { func splitMultiValues(in string, itemsep string, kvsep string) (map[string]string, error) {
@@ -259,10 +244,10 @@ func (f *factory) AllowsInstances() bool {
// eg. "buildx_buildkit_loving_mendeleev0" -> "loving-mendeleev0" // eg. "buildx_buildkit_loving_mendeleev0" -> "loving-mendeleev0"
func buildxNameToDeploymentName(bx string) (string, error) { func buildxNameToDeploymentName(bx string) (string, error) {
// TODO: commands.util.go should not pass "buildx_buildkit_" prefix to drivers // TODO: commands.util.go should not pass "buildx_buildkit_" prefix to drivers
s, err := driver.ParseBuilderName(bx) if !strings.HasPrefix(bx, "buildx_buildkit_") {
if err != nil { return "", errors.Errorf("expected a string with \"buildx_buildkit_\", got %q", bx)
return "", err
} }
s := strings.TrimPrefix(bx, "buildx_buildkit_")
s = strings.ReplaceAll(s, "_", "-") s = strings.ReplaceAll(s, "_", "-")
return s, nil return s, nil
} }

View File

@@ -29,7 +29,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
} }
cfg := driver.InitConfig{ cfg := driver.InitConfig{
Name: driver.BuilderName("test"), Name: "buildx_buildkit_test",
KubeClientConfig: &kcc, KubeClientConfig: &kcc,
} }
f := factory{} f := factory{}
@@ -52,9 +52,8 @@ func TestFactory_processDriverOpts(t *testing.T) {
"loadbalance": "random", "loadbalance": "random",
"qemu.install": "true", "qemu.install": "true",
"qemu.image": "qemu:latest", "qemu.image": "qemu:latest",
"default-load": "true",
} }
r, loadbalance, ns, defaultLoad, err := f.processDriverOpts(cfg.Name, "test", cfg) r, loadbalance, ns, err := f.processDriverOpts(cfg.Name, "test", cfg)
nodeSelectors := map[string]string{ nodeSelectors := map[string]string{
"selector1": "value1", "selector1": "value1",
@@ -103,7 +102,6 @@ func TestFactory_processDriverOpts(t *testing.T) {
require.Equal(t, LoadbalanceRandom, loadbalance) require.Equal(t, LoadbalanceRandom, loadbalance)
require.True(t, r.Qemu.Install) require.True(t, r.Qemu.Install)
require.Equal(t, "qemu:latest", r.Qemu.Image) require.Equal(t, "qemu:latest", r.Qemu.Image)
require.True(t, defaultLoad)
}, },
) )
@@ -111,7 +109,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
"NoOptions", func(t *testing.T) { "NoOptions", func(t *testing.T) {
cfg.DriverOpts = map[string]string{} cfg.DriverOpts = map[string]string{}
r, loadbalance, ns, defaultLoad, err := f.processDriverOpts(cfg.Name, "test", cfg) r, loadbalance, ns, err := f.processDriverOpts(cfg.Name, "test", cfg)
require.NoError(t, err) require.NoError(t, err)
@@ -130,7 +128,6 @@ func TestFactory_processDriverOpts(t *testing.T) {
require.Equal(t, LoadbalanceSticky, loadbalance) require.Equal(t, LoadbalanceSticky, loadbalance)
require.False(t, r.Qemu.Install) require.False(t, r.Qemu.Install)
require.Equal(t, bkimage.QemuImage, r.Qemu.Image) require.Equal(t, bkimage.QemuImage, r.Qemu.Image)
require.False(t, defaultLoad)
}, },
) )
@@ -141,7 +138,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
"loadbalance": "sticky", "loadbalance": "sticky",
} }
r, loadbalance, ns, defaultLoad, err := f.processDriverOpts(cfg.Name, "test", cfg) r, loadbalance, ns, err := f.processDriverOpts(cfg.Name, "test", cfg)
require.NoError(t, err) require.NoError(t, err)
@@ -160,7 +157,6 @@ func TestFactory_processDriverOpts(t *testing.T) {
require.Equal(t, LoadbalanceSticky, loadbalance) require.Equal(t, LoadbalanceSticky, loadbalance)
require.False(t, r.Qemu.Install) require.False(t, r.Qemu.Install)
require.Equal(t, bkimage.QemuImage, r.Qemu.Image) require.Equal(t, bkimage.QemuImage, r.Qemu.Image)
require.False(t, defaultLoad)
}, },
) )
@@ -169,7 +165,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
cfg.DriverOpts = map[string]string{ cfg.DriverOpts = map[string]string{
"replicas": "invalid", "replicas": "invalid",
} }
_, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
require.Error(t, err) require.Error(t, err)
}, },
) )
@@ -179,7 +175,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
cfg.DriverOpts = map[string]string{ cfg.DriverOpts = map[string]string{
"rootless": "invalid", "rootless": "invalid",
} }
_, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
require.Error(t, err) require.Error(t, err)
}, },
) )
@@ -189,7 +185,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
cfg.DriverOpts = map[string]string{ cfg.DriverOpts = map[string]string{
"tolerations": "key=foo,value=bar,invalid=foo2", "tolerations": "key=foo,value=bar,invalid=foo2",
} }
_, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
require.Error(t, err) require.Error(t, err)
}, },
) )
@@ -199,7 +195,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
cfg.DriverOpts = map[string]string{ cfg.DriverOpts = map[string]string{
"tolerations": "key=foo,value=bar,tolerationSeconds=invalid", "tolerations": "key=foo,value=bar,tolerationSeconds=invalid",
} }
_, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
require.Error(t, err) require.Error(t, err)
}, },
) )
@@ -209,7 +205,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
cfg.DriverOpts = map[string]string{ cfg.DriverOpts = map[string]string{
"annotations": "key,value", "annotations": "key,value",
} }
_, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
require.Error(t, err) require.Error(t, err)
}, },
) )
@@ -219,7 +215,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
cfg.DriverOpts = map[string]string{ cfg.DriverOpts = map[string]string{
"labels": "key=value=foo", "labels": "key=value=foo",
} }
_, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
require.Error(t, err) require.Error(t, err)
}, },
) )
@@ -229,7 +225,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
cfg.DriverOpts = map[string]string{ cfg.DriverOpts = map[string]string{
"loadbalance": "invalid", "loadbalance": "invalid",
} }
_, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
require.Error(t, err) require.Error(t, err)
}, },
) )
@@ -239,7 +235,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
cfg.DriverOpts = map[string]string{ cfg.DriverOpts = map[string]string{
"qemu.install": "invalid", "qemu.install": "invalid",
} }
_, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
require.Error(t, err) require.Error(t, err)
}, },
) )
@@ -249,7 +245,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
cfg.DriverOpts = map[string]string{ cfg.DriverOpts = map[string]string{
"invalid": "foo", "invalid": "foo",
} }
_, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
require.Error(t, err) require.Error(t, err)
}, },
) )

View File

@@ -20,7 +20,6 @@ type DeploymentOpt struct {
Image string Image string
Replicas int Replicas int
ServiceAccountName string ServiceAccountName string
SchedulerName string
// Qemu // Qemu
Qemu struct { Qemu struct {
@@ -40,10 +39,8 @@ type DeploymentOpt struct {
Tolerations []corev1.Toleration Tolerations []corev1.Toleration
RequestsCPU string RequestsCPU string
RequestsMemory string RequestsMemory string
RequestsEphemeralStorage string
LimitsCPU string LimitsCPU string
LimitsMemory string LimitsMemory string
LimitsEphemeralStorage string
Platforms []v1.Platform Platforms []v1.Platform
} }
@@ -108,7 +105,6 @@ func NewDeployment(opt *DeploymentOpt) (d *appsv1.Deployment, c []*corev1.Config
}, },
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
ServiceAccountName: opt.ServiceAccountName, ServiceAccountName: opt.ServiceAccountName,
SchedulerName: opt.SchedulerName,
Containers: []corev1.Container{ Containers: []corev1.Container{
{ {
Name: containerName, Name: containerName,
@@ -209,14 +205,6 @@ func NewDeployment(opt *DeploymentOpt) (d *appsv1.Deployment, c []*corev1.Config
d.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceMemory] = reqMemory d.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceMemory] = reqMemory
} }
if opt.RequestsEphemeralStorage != "" {
reqEphemeralStorage, err := resource.ParseQuantity(opt.RequestsEphemeralStorage)
if err != nil {
return nil, nil, err
}
d.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceEphemeralStorage] = reqEphemeralStorage
}
if opt.LimitsCPU != "" { if opt.LimitsCPU != "" {
limCPU, err := resource.ParseQuantity(opt.LimitsCPU) limCPU, err := resource.ParseQuantity(opt.LimitsCPU)
if err != nil { if err != nil {
@@ -233,14 +221,6 @@ func NewDeployment(opt *DeploymentOpt) (d *appsv1.Deployment, c []*corev1.Config
d.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceMemory] = limMemory d.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceMemory] = limMemory
} }
if opt.LimitsEphemeralStorage != "" {
limEphemeralStorage, err := resource.ParseQuantity(opt.LimitsEphemeralStorage)
if err != nil {
return nil, nil, err
}
d.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceEphemeralStorage] = limEphemeralStorage
}
return return
} }

View File

@@ -9,7 +9,6 @@ import (
dockerclient "github.com/docker/docker/client" dockerclient "github.com/docker/docker/client"
"github.com/moby/buildkit/client" "github.com/moby/buildkit/client"
"github.com/moby/buildkit/util/tracing/detect"
specs "github.com/opencontainers/image-spec/specs-go/v1" specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
@@ -157,28 +156,11 @@ type DriverHandle struct {
func (d *DriverHandle) Client(ctx context.Context) (*client.Client, error) { func (d *DriverHandle) Client(ctx context.Context) (*client.Client, error) {
d.once.Do(func() { d.once.Do(func() {
opts, err := d.getClientOptions() d.client, d.err = d.Driver.Client(ctx)
if err != nil {
d.err = err
return
}
d.client, d.err = d.Driver.Client(ctx, opts...)
}) })
return d.client, d.err return d.client, d.err
} }
func (d *DriverHandle) getClientOptions() ([]client.ClientOpt, error) {
exp, _, err := detect.Exporter()
if err != nil {
return nil, err
} else if td, ok := exp.(client.TracerDelegate); ok {
return []client.ClientOpt{
client.WithTracerDelegate(td),
}, nil
}
return nil, nil
}
func (d *DriverHandle) HistoryAPISupported(ctx context.Context) bool { func (d *DriverHandle) HistoryAPISupported(ctx context.Context) bool {
d.historyAPISupportedOnce.Do(func() { d.historyAPISupportedOnce.Do(func() {
if c, err := d.Client(ctx); err == nil { if c, err := d.Client(ctx); err == nil {

View File

@@ -13,7 +13,7 @@ import (
util "github.com/docker/buildx/driver/remote/util" util "github.com/docker/buildx/driver/remote/util"
"github.com/docker/buildx/util/progress" "github.com/docker/buildx/util/progress"
"github.com/moby/buildkit/client" "github.com/moby/buildkit/client"
"github.com/moby/buildkit/client/connhelper" "github.com/moby/buildkit/util/tracing/detect"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@@ -24,7 +24,6 @@ type Driver struct {
// if you add fields, remember to update docs: // if you add fields, remember to update docs:
// https://github.com/docker/docs/blob/main/content/build/drivers/remote.md // https://github.com/docker/docs/blob/main/content/build/drivers/remote.md
*tlsOpts *tlsOpts
defaultLoad bool
} }
type tlsOpts struct { type tlsOpts struct {
@@ -77,26 +76,26 @@ func (d *Driver) Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error {
return nil return nil
} }
func (d *Driver) Client(ctx context.Context, opts ...client.ClientOpt) (*client.Client, error) { func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
opts = append([]client.ClientOpt{ opts := []client.ClientOpt{}
client.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) {
exp, _, err := detect.Exporter()
if err != nil {
return nil, err
}
if td, ok := exp.(client.TracerDelegate); ok {
opts = append(opts, client.WithTracerDelegate(td))
}
opts = append(opts, client.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) {
return d.Dial(ctx) return d.Dial(ctx)
}), }))
}, opts...)
return client.New(ctx, "", opts...) return client.New(ctx, "", opts...)
} }
func (d *Driver) Dial(ctx context.Context) (net.Conn, error) { func (d *Driver) Dial(ctx context.Context) (net.Conn, error) {
addr := d.InitConfig.EndpointAddr network, addr, ok := strings.Cut(d.InitConfig.EndpointAddr, "://")
ch, err := connhelper.GetConnectionHelper(addr)
if err != nil {
return nil, err
}
if ch != nil {
return ch.ContextDialer(ctx, addr)
}
network, addr, ok := strings.Cut(addr, "://")
if !ok { if !ok {
return nil, errors.Errorf("invalid endpoint address: %s", d.InitConfig.EndpointAddr) return nil, errors.Errorf("invalid endpoint address: %s", d.InitConfig.EndpointAddr)
} }
@@ -150,7 +149,6 @@ func (d *Driver) Features(ctx context.Context) map[driver.Feature]bool {
driver.DockerExporter: true, driver.DockerExporter: true,
driver.CacheExport: true, driver.CacheExport: true,
driver.MultiPlatform: true, driver.MultiPlatform: true,
driver.DefaultLoad: d.defaultLoad,
} }
} }

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"net/url" "net/url"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
// import connhelpers for special url schemes // import connhelpers for special url schemes
@@ -81,12 +80,6 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
} }
tls.key = v tls.key = v
tlsEnabled = true tlsEnabled = true
case "default-load":
parsed, err := strconv.ParseBool(v)
if err != nil {
return nil, err
}
d.defaultLoad = parsed
default: default:
return nil, errors.Errorf("invalid driver option %s for remote driver", k) return nil, errors.Errorf("invalid driver option %s for remote driver", k)
} }

41
go.mod
View File

@@ -6,27 +6,26 @@ require (
github.com/Masterminds/semver/v3 v3.2.1 github.com/Masterminds/semver/v3 v3.2.1
github.com/Microsoft/go-winio v0.6.1 github.com/Microsoft/go-winio v0.6.1
github.com/aws/aws-sdk-go-v2/config v1.26.6 github.com/aws/aws-sdk-go-v2/config v1.26.6
github.com/compose-spec/compose-go/v2 v2.0.2 github.com/compose-spec/compose-go/v2 v2.0.0-rc.8
github.com/containerd/console v1.0.4 github.com/containerd/console v1.0.4
github.com/containerd/containerd v1.7.15 github.com/containerd/containerd v1.7.13
github.com/containerd/continuity v0.4.3 github.com/containerd/continuity v0.4.3
github.com/containerd/log v0.1.0 github.com/containerd/log v0.1.0
github.com/containerd/typeurl/v2 v2.1.1 github.com/containerd/typeurl/v2 v2.1.1
github.com/creack/pty v1.1.18 github.com/creack/pty v1.1.18
github.com/distribution/reference v0.5.0 github.com/distribution/reference v0.5.0
github.com/docker/cli v26.1.3+incompatible github.com/docker/cli v26.0.0-rc1+incompatible
github.com/docker/cli-docs-tool v0.7.0 github.com/docker/cli-docs-tool v0.7.0
github.com/docker/docker v26.0.0+incompatible github.com/docker/docker v26.0.0-rc1+incompatible
github.com/docker/go-units v0.5.0 github.com/docker/go-units v0.5.0
github.com/gofrs/flock v0.8.1 github.com/gofrs/flock v0.8.1
github.com/gogo/protobuf v1.3.2 github.com/gogo/protobuf v1.3.2
github.com/golang/protobuf v1.5.4 github.com/golang/protobuf v1.5.3
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/hashicorp/go-cty-funcs v0.0.0-20230405223818-a090f58aa992 github.com/hashicorp/go-cty-funcs v0.0.0-20230405223818-a090f58aa992
github.com/hashicorp/hcl/v2 v2.20.1 github.com/hashicorp/hcl/v2 v2.19.1
github.com/in-toto/in-toto-golang v0.5.0 github.com/moby/buildkit v0.13.0
github.com/moby/buildkit v0.13.0-rc3.0.20240417151852-71f99c52a669 // v0.14.0-dev
github.com/moby/sys/mountinfo v0.7.1 github.com/moby/sys/mountinfo v0.7.1
github.com/moby/sys/signal v0.7.0 github.com/moby/sys/signal v0.7.0
github.com/morikuni/aec v1.0.0 github.com/morikuni/aec v1.0.0
@@ -39,15 +38,18 @@ require (
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
github.com/tonistiigi/fsutil v0.0.0-20240301111122-7525a1af2bb5 github.com/zclconf/go-cty v1.14.1
github.com/zclconf/go-cty v1.14.4
go.opentelemetry.io/otel v1.21.0 go.opentelemetry.io/otel v1.21.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.42.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.42.0
go.opentelemetry.io/otel/metric v1.21.0 go.opentelemetry.io/otel/metric v1.21.0
go.opentelemetry.io/otel/sdk v1.21.0
go.opentelemetry.io/otel/sdk/metric v1.21.0
go.opentelemetry.io/otel/trace v1.21.0 go.opentelemetry.io/otel/trace v1.21.0
golang.org/x/mod v0.14.0 golang.org/x/mod v0.14.0
golang.org/x/sync v0.6.0 golang.org/x/sync v0.6.0
golang.org/x/sys v0.18.0 golang.org/x/sys v0.16.0
golang.org/x/term v0.18.0 golang.org/x/term v0.16.0
google.golang.org/grpc v1.59.0 google.golang.org/grpc v1.59.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.29.2 k8s.io/api v0.29.2
@@ -79,7 +81,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/containerd/ttrpc v1.2.3 // indirect github.com/containerd/ttrpc v1.2.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.0 // indirect github.com/docker/docker-credential-helpers v0.8.0 // indirect
@@ -100,9 +102,11 @@ require (
github.com/google/gofuzz v1.2.0 // indirect github.com/google/gofuzz v1.2.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/imdario/mergo v0.3.16 // indirect github.com/imdario/mergo v0.3.16 // indirect
github.com/in-toto/in-toto-golang v0.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
@@ -136,6 +140,7 @@ require (
github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/theupdateframework/notary v0.7.0 // indirect github.com/theupdateframework/notary v0.7.0 // indirect
github.com/tonistiigi/fsutil v0.0.0-20240301111122-7525a1af2bb5 // indirect
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531 // indirect github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
@@ -145,18 +150,14 @@ require (
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.42.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.21.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.21.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect
golang.org/x/crypto v0.21.0 // indirect golang.org/x/crypto v0.18.0 // indirect
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect
golang.org/x/net v0.23.0 // indirect golang.org/x/net v0.20.0 // indirect
golang.org/x/oauth2 v0.11.0 // indirect golang.org/x/oauth2 v0.11.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect golang.org/x/time v0.3.0 // indirect
@@ -165,7 +166,7 @@ require (
google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/protobuf v1.33.0 // indirect google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.110.1 // indirect k8s.io/klog/v2 v2.110.1 // indirect

116
go.sum
View File

@@ -1,3 +1,4 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME= cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME=
cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0= cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0=
cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78=
@@ -76,22 +77,25 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXe
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e85keuznYcH5rqI438v41pKcBl4ZxQ= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e85keuznYcH5rqI438v41pKcBl4ZxQ=
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
github.com/compose-spec/compose-go/v2 v2.0.2 h1:zhXMV7VWI00Su0LdKt8/sxeXxcjLWhmGmpEyw+ZYznI= github.com/compose-spec/compose-go/v2 v2.0.0-rc.8 h1:b7l+GqFF+2W4M4kLQUDRTGhqmTiRwT3bYd9X7xrxp5Q=
github.com/compose-spec/compose-go/v2 v2.0.2/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc= github.com/compose-spec/compose-go/v2 v2.0.0-rc.8/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc=
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= github.com/containerd/containerd v1.7.13 h1:wPYKIeGMN8vaggSKuV1X0wZulpMz4CrgEsZdaCyB6Is=
github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= github.com/containerd/containerd v1.7.13/go.mod h1:zT3up6yTRfEUa6+GsITYIJNgSVL9NQ4x4h1RPzk0Wu4=
github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8=
github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY=
@@ -103,8 +107,8 @@ github.com/containerd/nydus-snapshotter v0.13.7/go.mod h1:VPVKQ3jmHFIcUIV2yiQ1kI
github.com/containerd/stargz-snapshotter v0.15.1 h1:fpsP4kf/Z4n2EYnU0WT8ZCE3eiKDwikDhL6VwxIlgeA= github.com/containerd/stargz-snapshotter v0.15.1 h1:fpsP4kf/Z4n2EYnU0WT8ZCE3eiKDwikDhL6VwxIlgeA=
github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU= github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU=
github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk= github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk=
github.com/containerd/ttrpc v1.2.3 h1:4jlhbXIGvijRtNC8F/5CpuJZ7yKOBFGFOOXg1bkISz0= github.com/containerd/ttrpc v1.2.2 h1:9vqZr0pxwOF5koz6N0N3kJ0zDHokrcPxIR/ZR2YFtOs=
github.com/containerd/ttrpc v1.2.3/go.mod h1:ieWsXucbb8Mj9PH0rXCw1i8IunRbbAiDkpXkbfflWBM= github.com/containerd/ttrpc v1.2.2/go.mod h1:sIT6l32Ph/H9cvnJsfXM5drIVzTr5A2flTf1G5tYZak=
github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4=
github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@@ -117,15 +121,15 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v26.1.3+incompatible h1:bUpXT/N0kDE3VUHI2r5VMsYQgi38kYuoC0oL9yt3lqc= github.com/docker/cli v26.0.0-rc1+incompatible h1:PVxv2ySd8iZHoNfoAoKcnWSC/hKP2qMb806PWM34v50=
github.com/docker/cli v26.1.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v26.0.0-rc1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli-docs-tool v0.7.0 h1:M2Da98Unz2kz3A5d4yeSGbhyOge2mfYSNjAFt01Rw0M= github.com/docker/cli-docs-tool v0.7.0 h1:M2Da98Unz2kz3A5d4yeSGbhyOge2mfYSNjAFt01Rw0M=
github.com/docker/cli-docs-tool v0.7.0/go.mod h1:zMjqTFCU361PRh8apiXzeAZ1Q/xupbIwTusYpzCXS/o= github.com/docker/cli-docs-tool v0.7.0/go.mod h1:zMjqTFCU361PRh8apiXzeAZ1Q/xupbIwTusYpzCXS/o=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v26.0.0+incompatible h1:Ng2qi+gdKADUa/VM+6b6YaY2nlZhk/lVJiKR/2bMudU= github.com/docker/docker v26.0.0-rc1+incompatible h1:8Q4f+KSdA4hFc5SXcxthv1vR9HIoRwwjhBnMZL6IpkU=
github.com/docker/docker v26.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v26.0.0-rc1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8= github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8=
github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40= github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
@@ -145,6 +149,10 @@ github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNE
github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA=
github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
@@ -154,6 +162,7 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fvbommel/sortorder v1.0.1 h1:dSnXLt4mJYH25uDDGa3biZNQsozaUWDSWeKJ0qqFfzE= github.com/fvbommel/sortorder v1.0.1 h1:dSnXLt4mJYH25uDDGa3biZNQsozaUWDSWeKJ0qqFfzE=
github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -183,24 +192,29 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93 h1:jc2UWq7CbdszqeH6qu1ougXMIUBfSy8Pbh/anURYbGI= github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93 h1:jc2UWq7CbdszqeH6qu1ougXMIUBfSy8Pbh/anURYbGI=
github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
@@ -221,6 +235,8 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
@@ -229,8 +245,8 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-cty-funcs v0.0.0-20230405223818-a090f58aa992 h1:fYOrSfO5C9PmFGtmRWSYGqq52SOoE2dXMtAn2Xzh1LQ= github.com/hashicorp/go-cty-funcs v0.0.0-20230405223818-a090f58aa992 h1:fYOrSfO5C9PmFGtmRWSYGqq52SOoE2dXMtAn2Xzh1LQ=
github.com/hashicorp/go-cty-funcs v0.0.0-20230405223818-a090f58aa992/go.mod h1:Abjk0jbRkDaNCzsRhOv2iDCofYpX1eVsjozoiK63qLA= github.com/hashicorp/go-cty-funcs v0.0.0-20230405223818-a090f58aa992/go.mod h1:Abjk0jbRkDaNCzsRhOv2iDCofYpX1eVsjozoiK63qLA=
github.com/hashicorp/hcl/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc= github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI=
github.com/hashicorp/hcl/v2 v2.20.1/go.mod h1:TZDqQ4kNKCbh1iJp99FdPiUaVDDUPivbqxZulxDYqL4= github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
@@ -267,6 +283,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/magiconair/properties v1.5.3 h1:C8fxWnhYyME3n0klPOhVM7PtYUB3eV1W3DeFmN3j53Y= github.com/magiconair/properties v1.5.3 h1:C8fxWnhYyME3n0klPOhVM7PtYUB3eV1W3DeFmN3j53Y=
github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@@ -293,8 +311,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/buildkit v0.13.0-rc3.0.20240417151852-71f99c52a669 h1:DnnuoY7BDEXoW4qbDHBvWy2lCzus6AO4CJGaEq94e7M= github.com/moby/buildkit v0.13.0 h1:reVR1Y+rbNIUQ9jf0Q1YZVH5a/nhOixZsl+HJ9qQEGI=
github.com/moby/buildkit v0.13.0-rc3.0.20240417151852-71f99c52a669/go.mod h1:iqJg3dy9wLt5maCeC8WBbDISnLvuSX+R9jVNzg2zACU= github.com/moby/buildkit v0.13.0/go.mod h1:aNmNQKLBFYAOFuzQjR3VA27/FijlvtBD1pjNwTSN37k=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
@@ -366,6 +384,7 @@ github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+L
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
@@ -377,6 +396,7 @@ github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
@@ -386,6 +406,8 @@ github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncj
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/secure-systems-lab/go-securesystemslib v0.4.0 h1:b23VGrQhTA8cN2CbBw7/FulN9fTtqYUdS5+Oxzt+DUE= github.com/secure-systems-lab/go-securesystemslib v0.4.0 h1:b23VGrQhTA8cN2CbBw7/FulN9fTtqYUdS5+Oxzt+DUE=
github.com/secure-systems-lab/go-securesystemslib v0.4.0/go.mod h1:FGBZgq2tXWICsxWQW1msNf49F0Pf2Op5Htayx335Qbs= github.com/secure-systems-lab/go-securesystemslib v0.4.0/go.mod h1:FGBZgq2tXWICsxWQW1msNf49F0Pf2Op5Htayx335Qbs=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b h1:h+3JX2VoWTFuyQEo87pStk/a99dzIO1mM9KxIyLPGTU= github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b h1:h+3JX2VoWTFuyQEo87pStk/a99dzIO1mM9KxIyLPGTU=
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc= github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc=
github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI=
@@ -393,6 +415,8 @@ github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spdx/tools-golang v0.5.3 h1:ialnHeEYUC4+hkm5vJm4qz2x+oEJbS0mAMFrNXdQraY= github.com/spdx/tools-golang v0.5.3 h1:ialnHeEYUC4+hkm5vJm4qz2x+oEJbS0mAMFrNXdQraY=
@@ -445,10 +469,8 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zclconf/go-cty v1.4.0/go.mod h1:nHzOclRkoj++EU9ZjSrZvRG0BXIWt8c7loYc0qXAFGQ= github.com/zclconf/go-cty v1.4.0/go.mod h1:nHzOclRkoj++EU9ZjSrZvRG0BXIWt8c7loYc0qXAFGQ=
github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= github.com/zclconf/go-cty v1.14.1 h1:t9fyA35fwjjUMcmL5hLER+e/rEPqrbCK1/OSE4SI9KA=
github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty v1.14.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI=
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE=
@@ -483,8 +505,11 @@ go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -493,24 +518,33 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o=
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
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/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/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/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -519,27 +553,31 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -548,6 +586,10 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
@@ -558,8 +600,13 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 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/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA=
google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI=
google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k=
@@ -567,12 +614,19 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo= google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc=
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/cenkalti/backoff.v2 v2.2.1 h1:eJ9UAg01/HIHG987TwxvnzK2MgxXq97YY6rYDpY9aII= gopkg.in/cenkalti/backoff.v2 v2.2.1 h1:eJ9UAg01/HIHG987TwxvnzK2MgxXq97YY6rYDpY9aII=
@@ -600,6 +654,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
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=
k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A= k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A=
k8s.io/api v0.29.2/go.mod h1:sdIaaKuU7P44aoyyLlikSLayT6Vb7bvJNCX105xZXY0= k8s.io/api v0.29.2/go.mod h1:sdIaaKuU7P44aoyyLlikSLayT6Vb7bvJNCX105xZXY0=
k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8=

View File

@@ -10,9 +10,7 @@ set -eu -o pipefail
: "${TEST_REPORT_SUFFIX=}" : "${TEST_REPORT_SUFFIX=}"
: "${TEST_KEEP_CACHE=}" : "${TEST_KEEP_CACHE=}"
: "${TEST_DOCKERD=}" : "${TEST_DOCKERD=}"
: "${TEST_BUILDKIT_TAG=}"
: "${TEST_BUILDKIT_IMAGE=}" : "${TEST_BUILDKIT_IMAGE=}"
: "${TEST_BUILDX_EXPERIMENTAL=}"
if [ "$TEST_IMAGE_BUILD" = "1" ]; then if [ "$TEST_IMAGE_BUILD" = "1" ]; then
${BUILDX_CMD} bake integration-test --set "*.output=type=docker,name=$TEST_IMAGE_ID" ${BUILDX_CMD} bake integration-test --set "*.output=type=docker,name=$TEST_IMAGE_ID"
@@ -26,11 +24,11 @@ gotestArgs="-mod=vendor -coverprofile=/testreports/coverage-report$TEST_REPORT_S
cacheVolume="buildx-test-cache" cacheVolume="buildx-test-cache"
if ! docker container inspect "$cacheVolume" >/dev/null 2>/dev/null; then if ! docker container inspect "$cacheVolume" >/dev/null 2>/dev/null; then
docker create -v /root/.cache -v /root/.cache/registry -v /root/.cache/undock -v /go/pkg/mod --name "$cacheVolume" alpine docker create -v /root/.cache -v /root/.cache/registry -v /go/pkg/mod --name "$cacheVolume" alpine
fi fi
if [ "$TEST_KEEP_CACHE" != "1" ]; then if [ "$TEST_KEEP_CACHE" != "1" ]; then
trap 'docker rm -v $cacheVolume' EXIT trap 'docker rm -v $cacheVolume' EXIT
fi fi
cid=$(docker create --rm -v /tmp $testReportsVol --volumes-from=$cacheVolume -e GITHUB_REF -e TEST_DOCKERD -e TEST_BUILDKIT_IMAGE -e TEST_BUILDKIT_TAG -e TEST_BUILDX_EXPERIMENTAL -e SKIP_INTEGRATION_TESTS -e GOTESTSUM_FORMAT ${BUILDKIT_INTEGRATION_SNAPSHOTTER:+"-eBUILDKIT_INTEGRATION_SNAPSHOTTER"} -e BUILDKIT_REGISTRY_MIRROR_DIR=/root/.cache/registry --privileged $TEST_IMAGE_ID gotestsum $gotestsumArgs --packages="${TESTPKGS:-./...}" -- $gotestArgs ${TESTFLAGS:--v}) cid=$(docker create --rm -v /tmp $testReportsVol --volumes-from=$cacheVolume -e GITHUB_REF -e TEST_DOCKERD -e TEST_BUILDKIT_IMAGE -e SKIP_INTEGRATION_TESTS -e GOTESTSUM_FORMAT ${BUILDKIT_INTEGRATION_SNAPSHOTTER:+"-eBUILDKIT_INTEGRATION_SNAPSHOTTER"} -e BUILDKIT_REGISTRY_MIRROR_DIR=/root/.cache/registry --privileged $TEST_IMAGE_ID gotestsum $gotestsumArgs --packages="${TESTPKGS:-./...}" -- $gotestArgs ${TESTFLAGS:--v})
docker start -a -i $cid docker start -a -i $cid

View File

@@ -5,12 +5,12 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"github.com/containerd/continuity/fs/fstest" "github.com/containerd/continuity/fs/fstest"
"github.com/docker/buildx/util/gitutil" "github.com/docker/buildx/util/gitutil"
"github.com/moby/buildkit/identity" "github.com/moby/buildkit/identity"
provenancetypes "github.com/moby/buildkit/solver/llbsolver/provenance/types"
"github.com/moby/buildkit/util/contentutil" "github.com/moby/buildkit/util/contentutil"
"github.com/moby/buildkit/util/testutil" "github.com/moby/buildkit/util/testutil"
"github.com/moby/buildkit/util/testutil/integration" "github.com/moby/buildkit/util/testutil/integration"
@@ -29,7 +29,6 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){
testBakeLocal, testBakeLocal,
testBakeLocalMulti, testBakeLocalMulti,
testBakeRemote, testBakeRemote,
testBakeRemoteAuth,
testBakeRemoteCmdContext, testBakeRemoteCmdContext,
testBakeRemoteLocalOverride, testBakeRemoteLocalOverride,
testBakeLocalCwdOverride, testBakeLocalCwdOverride,
@@ -42,7 +41,7 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){
testBakeEmpty, testBakeEmpty,
testBakeShmSize, testBakeShmSize,
testBakeUlimits, testBakeUlimits,
testBakeMetadata, testBakeRefs,
testBakeMultiExporters, testBakeMultiExporters,
testBakeLoadPush, testBakeLoadPush,
} }
@@ -145,41 +144,6 @@ EOT
require.FileExists(t, filepath.Join(dirDest, "foo")) require.FileExists(t, filepath.Join(dirDest, "foo"))
} }
func testBakeRemoteAuth(t *testing.T, sb integration.Sandbox) {
bakefile := []byte(`
target "default" {
dockerfile-inline = <<EOT
FROM scratch
COPY foo /foo
EOT
}
`)
dir := tmpdir(
t,
fstest.CreateFile("docker-bake.hcl", bakefile, 0600),
fstest.CreateFile("foo", []byte("foo"), 0600),
)
dirDest := t.TempDir()
git, err := gitutil.New(gitutil.WithWorkingDir(dir))
require.NoError(t, err)
gitutil.GitInit(git, t)
gitutil.GitAdd(git, t, "docker-bake.hcl", "foo")
gitutil.GitCommit(git, t, "initial commit")
token := identity.NewID()
addr := gitutil.GitServeHTTP(git, t, gitutil.WithAccessToken(token))
out, err := bakeCmd(sb, withDir(dir),
withEnv("BUILDX_BAKE_GIT_AUTH_TOKEN="+token),
withArgs(addr, "--set", "*.output=type=local,dest="+dirDest),
)
require.NoError(t, err, out)
require.FileExists(t, filepath.Join(dirDest, "foo"))
}
func testBakeRemoteLocalOverride(t *testing.T, sb integration.Sandbox) { func testBakeRemoteLocalOverride(t *testing.T, sb integration.Sandbox) {
remoteBakefile := []byte(` remoteBakefile := []byte(`
target "default" { target "default" {
@@ -633,19 +597,7 @@ target "default" {
require.Contains(t, string(dt), `1024`) require.Contains(t, string(dt), `1024`)
} }
func testBakeMetadata(t *testing.T, sb integration.Sandbox) { func testBakeRefs(t *testing.T, sb integration.Sandbox) {
t.Run("max", func(t *testing.T) {
bakeMetadata(t, sb, "max")
})
t.Run("min", func(t *testing.T) {
bakeMetadata(t, sb, "min")
})
t.Run("disabled", func(t *testing.T) {
bakeMetadata(t, sb, "disabled")
})
}
func bakeMetadata(t *testing.T, sb integration.Sandbox, metadataMode string) {
dockerfile := []byte(` dockerfile := []byte(`
FROM scratch FROM scratch
COPY foo /foo COPY foo /foo
@@ -669,12 +621,7 @@ target "default" {
outFlag += ",dest=" + dirDest + "/image.tar" outFlag += ",dest=" + dirDest + "/image.tar"
} }
cmd := buildxCmd( cmd := buildxCmd(sb, withDir(dir), withArgs("bake", "--metadata-file", filepath.Join(dirDest, "md.json"), "--set", outFlag))
sb,
withDir(dir),
withArgs("bake", "--metadata-file", filepath.Join(dirDest, "md.json"), "--set", outFlag),
withEnv("BUILDX_METADATA_PROVENANCE="+metadataMode),
)
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
require.NoError(t, err, out) require.NoError(t, err, out)
@@ -684,7 +631,6 @@ target "default" {
type mdT struct { type mdT struct {
Default struct { Default struct {
BuildRef string `json:"buildx.build.ref"` BuildRef string `json:"buildx.build.ref"`
BuildProvenance map[string]interface{} `json:"buildx.build.provenance"`
} `json:"default"` } `json:"default"`
} }
var md mdT var md mdT
@@ -692,25 +638,12 @@ target "default" {
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, md.Default.BuildRef) require.NotEmpty(t, md.Default.BuildRef)
if metadataMode == "disabled" {
require.Empty(t, md.Default.BuildProvenance)
return
}
require.NotEmpty(t, md.Default.BuildProvenance)
dtprv, err := json.Marshal(md.Default.BuildProvenance)
require.NoError(t, err)
var prv provenancetypes.ProvenancePredicate
require.NoError(t, json.Unmarshal(dtprv, &prv))
require.Equal(t, provenancetypes.BuildKitBuildType, prv.BuildType)
} }
func testBakeMultiExporters(t *testing.T, sb integration.Sandbox) { func testBakeMultiExporters(t *testing.T, sb integration.Sandbox) {
if !isDockerContainerWorker(sb) { if sb.Name() != "docker" {
t.Skip("only testing with docker-container worker") t.Skip("skipping test for non-docker workers")
} }
skipNoCompatBuildKit(t, sb, ">= 0.13.0-0", "multi exporters")
registry, err := sb.NewRegistry() registry, err := sb.NewRegistry()
if errors.Is(err, integration.ErrRequirements) { if errors.Is(err, integration.ErrRequirements) {
@@ -721,12 +654,30 @@ func testBakeMultiExporters(t *testing.T, sb integration.Sandbox) {
targetReg := registry + "/buildx/registry:latest" targetReg := registry + "/buildx/registry:latest"
targetStore := "buildx:local-" + identity.NewID() targetStore := "buildx:local-" + identity.NewID()
var builderName string
t.Cleanup(func() { t.Cleanup(func() {
if builderName == "" {
return
}
cmd := dockerCmd(sb, withArgs("image", "rm", targetStore)) cmd := dockerCmd(sb, withArgs("image", "rm", targetStore))
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
require.NoError(t, cmd.Run()) require.NoError(t, cmd.Run())
out, err := rmCmd(sb, withArgs(builderName))
require.NoError(t, err, out)
}) })
// TODO: use stable buildkit image when v0.13.0 released
out, err := createCmd(sb, withArgs(
"--driver", "docker-container",
"--buildkitd-flags=--allow-insecure-entitlement=network.host",
"--driver-opt", "network=host",
"--driver-opt", "image=moby/buildkit:v0.13.0-rc3",
))
require.NoError(t, err, out)
builderName = strings.TrimSpace(out)
dockerfile := []byte(` dockerfile := []byte(`
FROM scratch FROM scratch
COPY foo /foo COPY foo /foo
@@ -748,6 +699,7 @@ target "default" {
"--set", fmt.Sprintf("*.output=type=oci,dest=%s/result", dir), "--set", fmt.Sprintf("*.output=type=oci,dest=%s/result", dir),
} }
cmd := buildxCmd(sb, withDir(dir), withArgs("bake"), withArgs(outputs...)) cmd := buildxCmd(sb, withDir(dir), withArgs("bake"), withArgs(outputs...))
cmd.Env = append(cmd.Env, "BUILDX_BUILDER="+builderName)
outb, err := cmd.CombinedOutput() outb, err := cmd.CombinedOutput()
require.NoError(t, err, string(outb)) require.NoError(t, err, string(outb))
@@ -770,10 +722,9 @@ target "default" {
} }
func testBakeLoadPush(t *testing.T, sb integration.Sandbox) { func testBakeLoadPush(t *testing.T, sb integration.Sandbox) {
if !isDockerContainerWorker(sb) { if sb.Name() != "docker" {
t.Skip("only testing with docker-container worker") t.Skip("skipping test for non-docker workers")
} }
skipNoCompatBuildKit(t, sb, ">= 0.13.0-0", "multi exporters")
registry, err := sb.NewRegistry() registry, err := sb.NewRegistry()
if errors.Is(err, integration.ErrRequirements) { if errors.Is(err, integration.ErrRequirements) {
@@ -783,12 +734,30 @@ func testBakeLoadPush(t *testing.T, sb integration.Sandbox) {
target := registry + "/buildx/registry:" + identity.NewID() target := registry + "/buildx/registry:" + identity.NewID()
var builderName string
t.Cleanup(func() { t.Cleanup(func() {
if builderName == "" {
return
}
cmd := dockerCmd(sb, withArgs("image", "rm", target)) cmd := dockerCmd(sb, withArgs("image", "rm", target))
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
require.NoError(t, cmd.Run()) require.NoError(t, cmd.Run())
out, err := rmCmd(sb, withArgs(builderName))
require.NoError(t, err, out)
}) })
// TODO: use stable buildkit image when v0.13.0 released
out, err := createCmd(sb, withArgs(
"--driver", "docker-container",
"--buildkitd-flags=--allow-insecure-entitlement=network.host",
"--driver-opt", "network=host",
"--driver-opt", "image=moby/buildkit:v0.13.0-rc3",
))
require.NoError(t, err, out)
builderName = strings.TrimSpace(out)
dockerfile := []byte(` dockerfile := []byte(`
FROM scratch FROM scratch
COPY foo /foo COPY foo /foo
@@ -805,6 +774,7 @@ target "default" {
) )
cmd := buildxCmd(sb, withDir(dir), withArgs("bake", "--push", "--load", fmt.Sprintf("--set=*.tags=%s", target))) cmd := buildxCmd(sb, withDir(dir), withArgs("bake", "--push", "--load", fmt.Sprintf("--set=*.tags=%s", target)))
cmd.Env = append(cmd.Env, "BUILDX_BUILDER="+builderName)
outb, err := cmd.CombinedOutput() outb, err := cmd.CombinedOutput()
require.NoError(t, err, string(outb)) require.NoError(t, err, string(outb))

View File

@@ -17,7 +17,6 @@ import (
"github.com/containerd/continuity/fs/fstest" "github.com/containerd/continuity/fs/fstest"
"github.com/creack/pty" "github.com/creack/pty"
"github.com/moby/buildkit/identity" "github.com/moby/buildkit/identity"
provenancetypes "github.com/moby/buildkit/solver/llbsolver/provenance/types"
"github.com/moby/buildkit/util/appdefaults" "github.com/moby/buildkit/util/appdefaults"
"github.com/moby/buildkit/util/contentutil" "github.com/moby/buildkit/util/contentutil"
"github.com/moby/buildkit/util/testutil" "github.com/moby/buildkit/util/testutil"
@@ -37,7 +36,6 @@ func buildCmd(sb integration.Sandbox, opts ...cmdOpt) (string, error) {
var buildTests = []func(t *testing.T, sb integration.Sandbox){ var buildTests = []func(t *testing.T, sb integration.Sandbox){
testBuild, testBuild,
testBuildStdin,
testImageIDOutput, testImageIDOutput,
testBuildLocalExport, testBuildLocalExport,
testBuildRegistryExport, testBuildRegistryExport,
@@ -56,11 +54,9 @@ var buildTests = []func(t *testing.T, sb integration.Sandbox){
testBuildNetworkModeBridge, testBuildNetworkModeBridge,
testBuildShmSize, testBuildShmSize,
testBuildUlimit, testBuildUlimit,
testBuildMetadata, testBuildRef,
testBuildMultiExporters, testBuildMultiExporters,
testBuildLoadPush, testBuildLoadPush,
testBuildSecret,
testBuildDefaultLoad,
} }
func testBuild(t *testing.T, sb integration.Sandbox) { func testBuild(t *testing.T, sb integration.Sandbox) {
@@ -69,26 +65,6 @@ func testBuild(t *testing.T, sb integration.Sandbox) {
require.NoError(t, err, string(out)) require.NoError(t, err, string(out))
} }
func testBuildStdin(t *testing.T, sb integration.Sandbox) {
dockerfile := []byte(`
FROM busybox:latest AS base
COPY foo /etc/foo
RUN cp /etc/foo /etc/bar
FROM scratch
COPY --from=base /etc/bar /bar
`)
dir := tmpdir(
t,
fstest.CreateFile("foo", []byte("foo"), 0600),
)
cmd := buildxCmd(sb, withDir(dir), withArgs("build", "--progress=quiet", "-f-", dir))
cmd.Stdin = bytes.NewReader(dockerfile)
out, err := cmd.CombinedOutput()
require.NoError(t, err, string(out))
}
func testBuildLocalExport(t *testing.T, sb integration.Sandbox) { func testBuildLocalExport(t *testing.T, sb integration.Sandbox) {
dir := createTestProject(t) dir := createTestProject(t)
out, err := buildCmd(sb, withArgs(fmt.Sprintf("--output=type=local,dest=%s/result", dir), dir)) out, err := buildCmd(sb, withArgs(fmt.Sprintf("--output=type=local,dest=%s/result", dir), dir))
@@ -149,14 +125,10 @@ func testBuildRegistryExportAttestations(t *testing.T, sb integration.Sandbox) {
target := registry + "/buildx/registry:latest" target := registry + "/buildx/registry:latest"
out, err := buildCmd(sb, withArgs(fmt.Sprintf("--output=type=image,name=%s,push=true", target), "--provenance=true", dir)) out, err := buildCmd(sb, withArgs(fmt.Sprintf("--output=type=image,name=%s,push=true", target), "--provenance=true", dir))
if isMobyWorker(sb) { if sb.Name() == "docker" {
require.Error(t, err) require.Error(t, err)
require.Contains(t, out, "Attestation is not supported") require.Contains(t, out, "Attestation is not supported")
return return
} else if !isMobyContainerdSnapWorker(sb) && !matchesBuildKitVersion(t, sb, ">= 0.11.0-0") {
require.Error(t, err)
require.Contains(t, out, "Attestations are not supported by the current BuildKit daemon")
return
} }
require.NoError(t, err, string(out)) require.NoError(t, err, string(out))
@@ -228,7 +200,7 @@ func testImageIDOutput(t *testing.T, sb integration.Sandbox) {
func testBuildMobyFromLocalImage(t *testing.T, sb integration.Sandbox) { func testBuildMobyFromLocalImage(t *testing.T, sb integration.Sandbox) {
if !isDockerWorker(sb) { if !isDockerWorker(sb) {
t.Skip("only testing with docker workers") t.Skip("skipping test for non-docker workers")
} }
// pull image // pull image
@@ -275,7 +247,6 @@ RUN busybox | head -1 | grep v1.36.1
} }
func testBuildDetailsLink(t *testing.T, sb integration.Sandbox) { func testBuildDetailsLink(t *testing.T, sb integration.Sandbox) {
skipNoCompatBuildKit(t, sb, ">= 0.11.0-0", "build details link")
buildDetailsPattern := regexp.MustCompile(`(?m)^View build details: docker-desktop://dashboard/build/[^/]+/[^/]+/[^/]+\n$`) buildDetailsPattern := regexp.MustCompile(`(?m)^View build details: docker-desktop://dashboard/build/[^/]+/[^/]+/[^/]+\n$`)
// build simple dockerfile // build simple dockerfile
@@ -307,11 +278,6 @@ RUN echo foo > /bar`)
require.NoError(t, err, string(out)) require.NoError(t, err, string(out))
require.True(t, buildDetailsPattern.MatchString(string(out)), fmt.Sprintf("expected build details link in output, got %q", out)) require.True(t, buildDetailsPattern.MatchString(string(out)), fmt.Sprintf("expected build details link in output, got %q", out))
if isExperimental() {
// FIXME: https://github.com/docker/buildx/issues/2382
t.Skip("build details link not displayed in experimental mode when build fails: https://github.com/docker/buildx/issues/2382")
}
// build erroneous dockerfile // build erroneous dockerfile
dockerfile = []byte(`FROM busybox:latest dockerfile = []byte(`FROM busybox:latest
RUN exit 1`) RUN exit 1`)
@@ -324,7 +290,7 @@ RUN exit 1`)
func testBuildProgress(t *testing.T, sb integration.Sandbox) { func testBuildProgress(t *testing.T, sb integration.Sandbox) {
dir := createTestProject(t) dir := createTestProject(t)
sbDriver, _ := driverName(sb.Name()) driver, _, _ := strings.Cut(sb.Name(), "+")
name := sb.Address() name := sb.Address()
// progress=tty // progress=tty
@@ -335,7 +301,7 @@ func testBuildProgress(t *testing.T, sb integration.Sandbox) {
io.Copy(buf, f) io.Copy(buf, f)
ttyOutput := buf.String() ttyOutput := buf.String()
require.Contains(t, ttyOutput, "[+] Building") require.Contains(t, ttyOutput, "[+] Building")
require.Contains(t, ttyOutput, fmt.Sprintf("%s:%s", sbDriver, name)) require.Contains(t, ttyOutput, fmt.Sprintf("%s:%s", driver, name))
require.Contains(t, ttyOutput, "=> [internal] load build definition from Dockerfile") require.Contains(t, ttyOutput, "=> [internal] load build definition from Dockerfile")
require.Contains(t, ttyOutput, "=> [base 1/3] FROM docker.io/library/busybox:latest") require.Contains(t, ttyOutput, "=> [base 1/3] FROM docker.io/library/busybox:latest")
@@ -343,16 +309,15 @@ func testBuildProgress(t *testing.T, sb integration.Sandbox) {
cmd = buildxCmd(sb, withArgs("build", "--progress=plain", "--output=type=cacheonly", dir)) cmd = buildxCmd(sb, withArgs("build", "--progress=plain", "--output=type=cacheonly", dir))
plainOutput, err := cmd.CombinedOutput() plainOutput, err := cmd.CombinedOutput()
require.NoError(t, err) require.NoError(t, err)
require.Contains(t, string(plainOutput), fmt.Sprintf(`#0 building with "%s" instance using %s driver`, name, sbDriver)) require.Contains(t, string(plainOutput), fmt.Sprintf(`#0 building with "%s" instance using %s driver`, name, driver))
require.Contains(t, string(plainOutput), "[internal] load build definition from Dockerfile") require.Contains(t, string(plainOutput), "[internal] load build definition from Dockerfile")
require.Contains(t, string(plainOutput), "[base 1/3] FROM docker.io/library/busybox:latest") require.Contains(t, string(plainOutput), "[base 1/3] FROM docker.io/library/busybox:latest")
} }
func testBuildAnnotations(t *testing.T, sb integration.Sandbox) { func testBuildAnnotations(t *testing.T, sb integration.Sandbox) {
if isMobyWorker(sb) { if sb.Name() == "docker" {
t.Skip("annotations not supported on docker worker") t.Skip("annotations not supported on docker worker")
} }
skipNoCompatBuildKit(t, sb, ">= 0.11.0-0", "annotations")
dir := createTestProject(t) dir := createTestProject(t)
@@ -409,8 +374,8 @@ func testBuildLabelNoKey(t *testing.T, sb integration.Sandbox) {
} }
func testBuildCacheExportNotSupported(t *testing.T, sb integration.Sandbox) { func testBuildCacheExportNotSupported(t *testing.T, sb integration.Sandbox) {
if !isMobyWorker(sb) { if sb.Name() != "docker" {
t.Skip("only testing with docker worker") t.Skip("skipping test for non-docker workers")
} }
dir := createTestProject(t) dir := createTestProject(t)
@@ -421,8 +386,8 @@ func testBuildCacheExportNotSupported(t *testing.T, sb integration.Sandbox) {
} }
func testBuildOCIExportNotSupported(t *testing.T, sb integration.Sandbox) { func testBuildOCIExportNotSupported(t *testing.T, sb integration.Sandbox) {
if !isMobyWorker(sb) { if sb.Name() != "docker" {
t.Skip("only testing with docker worker") t.Skip("skipping test for non-docker workers")
} }
dir := createTestProject(t) dir := createTestProject(t)
@@ -433,8 +398,8 @@ func testBuildOCIExportNotSupported(t *testing.T, sb integration.Sandbox) {
} }
func testBuildMultiPlatformNotSupported(t *testing.T, sb integration.Sandbox) { func testBuildMultiPlatformNotSupported(t *testing.T, sb integration.Sandbox) {
if !isMobyWorker(sb) { if sb.Name() != "docker" {
t.Skip("only testing with docker worker") t.Skip("skipping test for non-docker workers")
} }
dir := createTestProject(t) dir := createTestProject(t)
@@ -461,10 +426,9 @@ RUN ping -c 1 buildx.host-gateway-ip.local
} }
func testBuildNetworkModeBridge(t *testing.T, sb integration.Sandbox) { func testBuildNetworkModeBridge(t *testing.T, sb integration.Sandbox) {
if !isDockerContainerWorker(sb) { if sb.Name() != "docker" {
t.Skip("only testing with docker-container worker") t.Skip("skipping test for non-docker workers")
} }
skipNoCompatBuildKit(t, sb, ">= 0.13.0-0", "network bridge")
var builderName string var builderName string
t.Cleanup(func() { t.Cleanup(func() {
@@ -475,10 +439,8 @@ func testBuildNetworkModeBridge(t *testing.T, sb integration.Sandbox) {
require.NoError(t, err, out) require.NoError(t, err, out)
}) })
out, err := createCmd(sb, withArgs( // TODO: use stable buildkit image when v0.13.0 released
"--driver", "docker-container", out, err := createCmd(sb, withArgs("--driver", "docker-container", "--buildkitd-flags=--oci-worker-net=bridge --allow-insecure-entitlement=network.host", "--driver-opt", "image=moby/buildkit:v0.13.0-rc3"))
"--buildkitd-flags=--oci-worker-net=bridge --allow-insecure-entitlement=network.host",
))
require.NoError(t, err, out) require.NoError(t, err, out)
builderName = strings.TrimSpace(out) builderName = strings.TrimSpace(out)
@@ -556,19 +518,7 @@ COPY --from=build /ulimit /
require.Contains(t, string(dt), `1024`) require.Contains(t, string(dt), `1024`)
} }
func testBuildMetadata(t *testing.T, sb integration.Sandbox) { func testBuildRef(t *testing.T, sb integration.Sandbox) {
t.Run("max", func(t *testing.T) {
buildMetadata(t, sb, "max")
})
t.Run("min", func(t *testing.T) {
buildMetadata(t, sb, "min")
})
t.Run("disabled", func(t *testing.T) {
buildMetadata(t, sb, "disabled")
})
}
func buildMetadata(t *testing.T, sb integration.Sandbox, metadataMode string) {
dir := createTestProject(t) dir := createTestProject(t)
dirDest := t.TempDir() dirDest := t.TempDir()
@@ -578,11 +528,7 @@ func buildMetadata(t *testing.T, sb integration.Sandbox, metadataMode string) {
outFlag += ",dest=" + dirDest + "/image.tar" outFlag += ",dest=" + dirDest + "/image.tar"
} }
cmd := buildxCmd( cmd := buildxCmd(sb, withArgs("build", outFlag, "--metadata-file", filepath.Join(dirDest, "md.json"), dir))
sb,
withArgs("build", outFlag, "--metadata-file", filepath.Join(dirDest, "md.json"), dir),
withEnv("BUILDX_METADATA_PROVENANCE="+metadataMode),
)
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
require.NoError(t, err, string(out)) require.NoError(t, err, string(out))
@@ -591,32 +537,18 @@ func buildMetadata(t *testing.T, sb integration.Sandbox, metadataMode string) {
type mdT struct { type mdT struct {
BuildRef string `json:"buildx.build.ref"` BuildRef string `json:"buildx.build.ref"`
BuildProvenance map[string]interface{} `json:"buildx.build.provenance"`
} }
var md mdT var md mdT
err = json.Unmarshal(dt, &md) err = json.Unmarshal(dt, &md)
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, md.BuildRef) require.NotEmpty(t, md.BuildRef)
if metadataMode == "disabled" {
require.Empty(t, md.BuildProvenance)
return
}
require.NotEmpty(t, md.BuildProvenance)
dtprv, err := json.Marshal(md.BuildProvenance)
require.NoError(t, err)
var prv provenancetypes.ProvenancePredicate
require.NoError(t, json.Unmarshal(dtprv, &prv))
require.Equal(t, provenancetypes.BuildKitBuildType, prv.BuildType)
} }
func testBuildMultiExporters(t *testing.T, sb integration.Sandbox) { func testBuildMultiExporters(t *testing.T, sb integration.Sandbox) {
if !isDockerContainerWorker(sb) { if sb.Name() != "docker" {
t.Skip("only testing with docker-container worker") t.Skip("skipping test for non-docker workers")
} }
skipNoCompatBuildKit(t, sb, ">= 0.13.0-0", "multi exporters")
registry, err := sb.NewRegistry() registry, err := sb.NewRegistry()
if errors.Is(err, integration.ErrRequirements) { if errors.Is(err, integration.ErrRequirements) {
@@ -627,12 +559,30 @@ func testBuildMultiExporters(t *testing.T, sb integration.Sandbox) {
targetReg := registry + "/buildx/registry:latest" targetReg := registry + "/buildx/registry:latest"
targetStore := "buildx:local-" + identity.NewID() targetStore := "buildx:local-" + identity.NewID()
var builderName string
t.Cleanup(func() { t.Cleanup(func() {
if builderName == "" {
return
}
cmd := dockerCmd(sb, withArgs("image", "rm", targetStore)) cmd := dockerCmd(sb, withArgs("image", "rm", targetStore))
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
require.NoError(t, cmd.Run()) require.NoError(t, cmd.Run())
out, err := rmCmd(sb, withArgs(builderName))
require.NoError(t, err, out)
}) })
// TODO: use stable buildkit image when v0.13.0 released
out, err := createCmd(sb, withArgs(
"--driver", "docker-container",
"--buildkitd-flags=--allow-insecure-entitlement=network.host",
"--driver-opt", "network=host",
"--driver-opt", "image=moby/buildkit:v0.13.0-rc3",
))
require.NoError(t, err, out)
builderName = strings.TrimSpace(out)
dir := createTestProject(t) dir := createTestProject(t)
outputs := []string{ outputs := []string{
@@ -641,6 +591,7 @@ func testBuildMultiExporters(t *testing.T, sb integration.Sandbox) {
"--output", fmt.Sprintf("type=oci,dest=%s/result", dir), "--output", fmt.Sprintf("type=oci,dest=%s/result", dir),
} }
cmd := buildxCmd(sb, withArgs("build"), withArgs(outputs...), withArgs(dir)) cmd := buildxCmd(sb, withArgs("build"), withArgs(outputs...), withArgs(dir))
cmd.Env = append(cmd.Env, "BUILDX_BUILDER="+builderName)
outb, err := cmd.CombinedOutput() outb, err := cmd.CombinedOutput()
require.NoError(t, err, string(outb)) require.NoError(t, err, string(outb))
@@ -663,10 +614,9 @@ func testBuildMultiExporters(t *testing.T, sb integration.Sandbox) {
} }
func testBuildLoadPush(t *testing.T, sb integration.Sandbox) { func testBuildLoadPush(t *testing.T, sb integration.Sandbox) {
if !isDockerContainerWorker(sb) { if sb.Name() != "docker" {
t.Skip("only testing with docker-container worker") t.Skip("skipping test for non-docker workers")
} }
skipNoCompatBuildKit(t, sb, ">= 0.13.0-0", "multi exporters")
registry, err := sb.NewRegistry() registry, err := sb.NewRegistry()
if errors.Is(err, integration.ErrRequirements) { if errors.Is(err, integration.ErrRequirements) {
@@ -676,12 +626,30 @@ func testBuildLoadPush(t *testing.T, sb integration.Sandbox) {
target := registry + "/buildx/registry:" + identity.NewID() target := registry + "/buildx/registry:" + identity.NewID()
var builderName string
t.Cleanup(func() { t.Cleanup(func() {
if builderName == "" {
return
}
cmd := dockerCmd(sb, withArgs("image", "rm", target)) cmd := dockerCmd(sb, withArgs("image", "rm", target))
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
require.NoError(t, cmd.Run()) require.NoError(t, cmd.Run())
out, err := rmCmd(sb, withArgs(builderName))
require.NoError(t, err, out)
}) })
// TODO: use stable buildkit image when v0.13.0 released
out, err := createCmd(sb, withArgs(
"--driver", "docker-container",
"--buildkitd-flags=--allow-insecure-entitlement=network.host",
"--driver-opt", "network=host",
"--driver-opt", "image=moby/buildkit:v0.13.0-rc3",
))
require.NoError(t, err, out)
builderName = strings.TrimSpace(out)
dir := createTestProject(t) dir := createTestProject(t)
cmd := buildxCmd(sb, withArgs( cmd := buildxCmd(sb, withArgs(
@@ -689,6 +657,7 @@ func testBuildLoadPush(t *testing.T, sb integration.Sandbox) {
fmt.Sprintf("-t=%s", target), fmt.Sprintf("-t=%s", target),
dir, dir,
)) ))
cmd.Env = append(cmd.Env, "BUILDX_BUILDER="+builderName)
outb, err := cmd.CombinedOutput() outb, err := cmd.CombinedOutput()
require.NoError(t, err, string(outb)) require.NoError(t, err, string(outb))
@@ -706,93 +675,6 @@ func testBuildLoadPush(t *testing.T, sb integration.Sandbox) {
// TODO: test metadata file when supported by multi exporters https://github.com/docker/buildx/issues/2181 // TODO: test metadata file when supported by multi exporters https://github.com/docker/buildx/issues/2181
} }
func testBuildSecret(t *testing.T, sb integration.Sandbox) {
token := "abcd1234"
dockerfile := []byte(`
FROM busybox AS build
RUN --mount=type=secret,id=token cat /run/secrets/token | tee /token
FROM scratch
COPY --from=build /token /
`)
dir := tmpdir(
t,
fstest.CreateFile("Dockerfile", dockerfile, 0600),
fstest.CreateFile("tokenfile", []byte(token), 0600),
)
t.Run("env", func(t *testing.T) {
t.Cleanup(func() {
_ = os.Remove(filepath.Join(dir, "token"))
})
cmd := buildxCmd(sb, withEnv("TOKEN="+token), withArgs("build", "--secret=id=token,env=TOKEN", fmt.Sprintf("--output=type=local,dest=%s", dir), dir))
out, err := cmd.CombinedOutput()
require.NoError(t, err, string(out))
dt, err := os.ReadFile(filepath.Join(dir, "token"))
require.NoError(t, err)
require.Equal(t, token, string(dt))
})
t.Run("file", func(t *testing.T) {
t.Cleanup(func() {
_ = os.Remove(filepath.Join(dir, "token"))
})
cmd := buildxCmd(sb, withArgs("build", "--secret=id=token,src="+path.Join(dir, "tokenfile"), fmt.Sprintf("--output=type=local,dest=%s", dir), dir))
out, err := cmd.CombinedOutput()
require.NoError(t, err, string(out))
dt, err := os.ReadFile(filepath.Join(dir, "token"))
require.NoError(t, err)
require.Equal(t, token, string(dt))
})
}
func testBuildDefaultLoad(t *testing.T, sb integration.Sandbox) {
if !isDockerWorker(sb) {
t.Skip("only testing with docker workers")
}
tag := "buildx/build:" + identity.NewID()
var builderName string
t.Cleanup(func() {
if builderName == "" {
return
}
cmd := dockerCmd(sb, withArgs("image", "rm", tag))
cmd.Stderr = os.Stderr
require.NoError(t, cmd.Run())
out, err := rmCmd(sb, withArgs(builderName))
require.NoError(t, err, out)
})
out, err := createCmd(sb, withArgs(
"--driver", "docker-container",
"--driver-opt", "default-load=true",
))
require.NoError(t, err, out)
builderName = strings.TrimSpace(out)
dir := createTestProject(t)
cmd := buildxCmd(sb, withArgs(
"build",
fmt.Sprintf("-t=%s", tag),
dir,
))
cmd.Env = append(cmd.Env, "BUILDX_BUILDER="+builderName)
outb, err := cmd.CombinedOutput()
require.NoError(t, err, string(outb))
cmd = dockerCmd(sb, withArgs("image", "inspect", tag))
cmd.Stderr = os.Stderr
require.NoError(t, cmd.Run())
}
func createTestProject(t *testing.T) string { func createTestProject(t *testing.T) string {
dockerfile := []byte(` dockerfile := []byte(`
FROM busybox:latest AS base FROM busybox:latest AS base

View File

@@ -1,13 +1,9 @@
package tests package tests
import ( import (
"fmt"
"os"
"strings" "strings"
"testing" "testing"
"github.com/docker/buildx/driver"
"github.com/moby/buildkit/identity"
"github.com/moby/buildkit/util/testutil/integration" "github.com/moby/buildkit/util/testutil/integration"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -22,12 +18,11 @@ func createCmd(sb integration.Sandbox, opts ...cmdOpt) (string, error) {
var createTests = []func(t *testing.T, sb integration.Sandbox){ var createTests = []func(t *testing.T, sb integration.Sandbox){
testCreateMemoryLimit, testCreateMemoryLimit,
testCreateRestartAlways, testCreateRestartAlways,
testCreateRemoteContainer,
} }
func testCreateMemoryLimit(t *testing.T, sb integration.Sandbox) { func testCreateMemoryLimit(t *testing.T, sb integration.Sandbox) {
if !isDockerContainerWorker(sb) { if sb.Name() != "docker-container" {
t.Skip("only testing with docker-container worker") t.Skip("only testing for docker-container driver")
} }
var builderName string var builderName string
@@ -45,8 +40,8 @@ func testCreateMemoryLimit(t *testing.T, sb integration.Sandbox) {
} }
func testCreateRestartAlways(t *testing.T, sb integration.Sandbox) { func testCreateRestartAlways(t *testing.T, sb integration.Sandbox) {
if !isDockerContainerWorker(sb) { if sb.Name() != "docker-container" {
t.Skip("only testing with docker-container worker") t.Skip("only testing for docker-container driver")
} }
var builderName string var builderName string
@@ -62,49 +57,3 @@ func testCreateRestartAlways(t *testing.T, sb integration.Sandbox) {
require.NoError(t, err, out) require.NoError(t, err, out)
builderName = strings.TrimSpace(out) builderName = strings.TrimSpace(out)
} }
func testCreateRemoteContainer(t *testing.T, sb integration.Sandbox) {
if !isDockerWorker(sb) {
t.Skip("only testing with docker workers")
}
ctnBuilderName := "ctn-builder-" + identity.NewID()
remoteBuilderName := "remote-builder-" + identity.NewID()
var hasCtnBuilder, hasRemoteBuilder bool
t.Cleanup(func() {
if hasCtnBuilder {
out, err := rmCmd(sb, withArgs(ctnBuilderName))
require.NoError(t, err, out)
}
if hasRemoteBuilder {
out, err := rmCmd(sb, withArgs(remoteBuilderName))
require.NoError(t, err, out)
}
})
out, err := createCmd(sb, withArgs("--driver", "docker-container", "--name", ctnBuilderName))
require.NoError(t, err, out)
hasCtnBuilder = true
out, err = inspectCmd(sb, withArgs("--bootstrap", ctnBuilderName))
require.NoError(t, err, out)
cmd := dockerCmd(sb, withArgs("container", "inspect", fmt.Sprintf("%s0", driver.BuilderName(ctnBuilderName))))
cmd.Stderr = os.Stderr
require.NoError(t, cmd.Run())
out, err = createCmd(sb, withArgs("--driver", "remote", "--name", remoteBuilderName, fmt.Sprintf("docker-container://%s0", driver.BuilderName(ctnBuilderName))))
require.NoError(t, err, out)
hasRemoteBuilder = true
out, err = inspectCmd(sb, withArgs(remoteBuilderName))
require.NoError(t, err, out)
for _, line := range strings.Split(out, "\n") {
if v, ok := strings.CutPrefix(line, "Status:"); ok {
require.Equal(t, strings.TrimSpace(v), "running")
return
}
}
require.Fail(t, "remote builder is not running")
}

View File

@@ -61,7 +61,6 @@ func testDialStdio(t *testing.T, sb integration.Sandbox) {
} }
}() }()
skipNoCompatBuildKit(t, sb, ">= 0.11.0-0", "unknown method Info for service moby.buildkit.v1.Control")
_, err = c.Info(sb.Context()) _, err = c.Info(sb.Context())
require.NoError(t, err) require.NoError(t, err)

View File

@@ -22,8 +22,8 @@ var imagetoolsTests = []func(t *testing.T, sb integration.Sandbox){
} }
func testImagetoolsCopyManifest(t *testing.T, sb integration.Sandbox) { func testImagetoolsCopyManifest(t *testing.T, sb integration.Sandbox) {
if !isDockerContainerWorker(sb) { if sb.Name() != "docker-container" {
t.Skip("only testing with docker-container worker, imagetools only runs on docker-container") t.Skip("imagetools tests are not driver specific and only run on docker-container")
} }
dir := createDockerfile(t) dir := createDockerfile(t)
@@ -81,8 +81,8 @@ func testImagetoolsCopyManifest(t *testing.T, sb integration.Sandbox) {
} }
func testImagetoolsCopyIndex(t *testing.T, sb integration.Sandbox) { func testImagetoolsCopyIndex(t *testing.T, sb integration.Sandbox) {
if !isDockerContainerWorker(sb) { if sb.Name() != "docker-container" {
t.Skip("only testing with docker-container worker, imagetools only runs on docker-container") t.Skip("imagetools tests are not driver specific and only run on docker-container")
} }
dir := createDockerfile(t) dir := createDockerfile(t)
@@ -130,8 +130,8 @@ func testImagetoolsCopyIndex(t *testing.T, sb integration.Sandbox) {
} }
func testImagetoolsInspectAndFilter(t *testing.T, sb integration.Sandbox) { func testImagetoolsInspectAndFilter(t *testing.T, sb integration.Sandbox) {
if !isDockerContainerWorker(sb) { if sb.Name() != "docker-container" {
t.Skip("only testing with docker-container worker, imagetools only runs on docker-container") t.Skip("imagetools tests are not driver specific and only run on docker-container")
} }
dir := createDockerfile(t) dir := createDockerfile(t)
@@ -181,8 +181,8 @@ func testImagetoolsInspectAndFilter(t *testing.T, sb integration.Sandbox) {
} }
func testImagetoolsAnnotation(t *testing.T, sb integration.Sandbox) { func testImagetoolsAnnotation(t *testing.T, sb integration.Sandbox) {
if !isDockerContainerWorker(sb) { if sb.Name() != "docker-container" {
t.Skip("only testing with docker-container worker, imagetools only runs on docker-container") t.Skip("imagetools tests are not driver specific and only run on docker-container")
} }
dir := createDockerfile(t) dir := createDockerfile(t)

View File

@@ -41,7 +41,7 @@ func testInspect(t *testing.T, sb integration.Sandbox) {
} }
require.Equal(t, sb.Address(), name) require.Equal(t, sb.Address(), name)
sbDriver, _ := driverName(sb.Name()) sbDriver, _, _ := strings.Cut(sb.Name(), "+")
require.Equal(t, sbDriver, driver) require.Equal(t, sbDriver, driver)
if isDockerWorker(sb) { if isDockerWorker(sb) {
require.NotEmpty(t, hostGatewayIP, "host-gateway-ip worker label should be set with docker driver") require.NotEmpty(t, hostGatewayIP, "host-gateway-ip worker label should be set with docker driver")
@@ -51,8 +51,8 @@ func testInspect(t *testing.T, sb integration.Sandbox) {
} }
func testInspectBuildkitdFlags(t *testing.T, sb integration.Sandbox) { func testInspectBuildkitdFlags(t *testing.T, sb integration.Sandbox) {
if !isDockerContainerWorker(sb) { if sb.Name() != "docker-container" {
t.Skip("only testing with docker-container worker") t.Skip("only testing for docker-container driver")
} }
var builderName string var builderName string
@@ -81,8 +81,8 @@ func testInspectBuildkitdFlags(t *testing.T, sb integration.Sandbox) {
} }
func testInspectNetworkHostEntitlement(t *testing.T, sb integration.Sandbox) { func testInspectNetworkHostEntitlement(t *testing.T, sb integration.Sandbox) {
if !isDockerContainerWorker(sb) { if sb.Name() != "docker-container" {
t.Skip("only testing with docker-container worker") t.Skip("only testing for docker-container driver")
} }
var builderName string var builderName string

View File

@@ -3,22 +3,14 @@ package tests
import ( import (
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"strconv"
"strings" "strings"
"sync"
"testing" "testing"
"github.com/Masterminds/semver/v3"
"github.com/containerd/continuity/fs/fstest" "github.com/containerd/continuity/fs/fstest"
"github.com/moby/buildkit/util/testutil/integration" "github.com/moby/buildkit/util/testutil/integration"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
const defaultBuildKitTag = "buildx-stable-1"
var buildkitImage string
func tmpdir(t *testing.T, appliers ...fstest.Applier) string { func tmpdir(t *testing.T, appliers ...fstest.Applier) string {
t.Helper() t.Helper()
tmpdir := t.TempDir() tmpdir := t.TempDir()
@@ -63,9 +55,6 @@ func buildxCmd(sb integration.Sandbox, opts ...cmdOpt) *exec.Cmd {
if context := sb.DockerAddress(); context != "" { if context := sb.DockerAddress(); context != "" {
cmd.Env = append(cmd.Env, "DOCKER_CONTEXT="+context) cmd.Env = append(cmd.Env, "DOCKER_CONTEXT="+context)
} }
if isExperimental() {
cmd.Env = append(cmd.Env, "BUILDX_EXPERIMENTAL=1")
}
return cmd return cmd
} }
@@ -82,118 +71,7 @@ func dockerCmd(sb integration.Sandbox, opts ...cmdOpt) *exec.Cmd {
return cmd return cmd
} }
func isMobyWorker(sb integration.Sandbox) bool {
name, hasFeature := driverName(sb.Name())
return name == "docker" && !hasFeature
}
func isMobyContainerdSnapWorker(sb integration.Sandbox) bool {
name, hasFeature := driverName(sb.Name())
return name == "docker" && hasFeature
}
func isDockerWorker(sb integration.Sandbox) bool { func isDockerWorker(sb integration.Sandbox) bool {
name, _ := driverName(sb.Name()) sbDriver, _, _ := strings.Cut(sb.Name(), "+")
return name == "docker" return sbDriver == "docker"
}
func isDockerContainerWorker(sb integration.Sandbox) bool {
name, _ := driverName(sb.Name())
return name == "docker-container"
}
func driverName(sbName string) (string, bool) {
name := sbName
var hasFeature bool
if b, _, ok := strings.Cut(name, "+"); ok {
name = b
hasFeature = true
}
return name, hasFeature
}
func isExperimental() bool {
if v, ok := os.LookupEnv("TEST_BUILDX_EXPERIMENTAL"); ok {
vv, _ := strconv.ParseBool(v)
return vv
}
return false
}
func buildkitTag() string {
if v := os.Getenv("TEST_BUILDKIT_TAG"); v != "" {
return v
}
return defaultBuildKitTag
}
var (
bkvers map[string]string
bkversMu sync.Mutex
)
func buildkitVersion(t *testing.T, sb integration.Sandbox) string {
bkversMu.Lock()
defer bkversMu.Unlock()
if bkvers == nil {
bkvers = make(map[string]string)
}
ver, ok := bkvers[sb.Name()]
if !ok {
out, err := inspectCmd(sb, withArgs(sb.Address()))
require.NoError(t, err, out)
for _, line := range strings.Split(out, "\n") {
if v, ok := strings.CutPrefix(line, "BuildKit version:"); ok {
ver = strings.TrimSpace(v)
bkvers[sb.Name()] = ver
}
}
if ver == "" {
t.Logf("BuildKit version not found in inspect output, extract it from the image.\n%s", out)
undockBin, err := exec.LookPath("undock")
require.NoError(t, err, "undock not found")
destDir := t.TempDir()
t.Cleanup(func() {
os.RemoveAll(destDir)
})
cmd := exec.Command(undockBin, "--cachedir", "/root/.cache/undock", "--include", "/usr/bin/buildkitd", "--rm-dist", buildkitImage, destDir)
require.NoErrorf(t, cmd.Run(), "failed to extract buildkitd binary from %q", buildkitImage)
cmd = exec.Command(filepath.Join(destDir, "usr", "bin", "buildkitd"), "--version")
out, err := cmd.CombinedOutput()
require.NoErrorf(t, err, "failed to get BuildKit version from %q: %s", buildkitImage, string(out))
v := strings.Fields(strings.TrimSpace(string(out)))
if len(v) != 4 {
require.Fail(t, "unexpected version format: "+strings.TrimSpace(string(out)))
}
ver = v[2]
bkvers[sb.Name()] = ver
}
}
return ver
}
func matchesBuildKitVersion(t *testing.T, sb integration.Sandbox, constraint string) bool {
c, err := semver.NewConstraint(constraint)
if err != nil {
return false
}
v, err := semver.NewVersion(buildkitVersion(t, sb))
if err != nil {
// if the version is not a valid semver, we assume it matches (master)
return true
}
return c.Check(v)
}
func skipNoCompatBuildKit(t *testing.T, sb integration.Sandbox, constraint string, msg string) {
if !matchesBuildKitVersion(t, sb, constraint) {
t.Skipf("buildkit version %s does not match %s constraint (%s)", buildkitVersion(t, sb), constraint, msg)
}
} }

View File

@@ -35,7 +35,7 @@ func TestIntegration(t *testing.T) {
func testIntegration(t *testing.T, funcs ...func(t *testing.T, sb integration.Sandbox)) { func testIntegration(t *testing.T, funcs ...func(t *testing.T, sb integration.Sandbox)) {
mirroredImages := integration.OfficialImages("busybox:latest", "alpine:latest") mirroredImages := integration.OfficialImages("busybox:latest", "alpine:latest")
buildkitImage = "docker.io/moby/buildkit:" + buildkitTag() buildkitImage := "docker.io/moby/buildkit:buildx-stable-1"
if bkworkers.IsTestDockerd() { if bkworkers.IsTestDockerd() {
if img, ok := os.LookupEnv("TEST_BUILDKIT_IMAGE"); ok { if img, ok := os.LookupEnv("TEST_BUILDKIT_IMAGE"); ok {
ref, err := reference.ParseNormalizedNamed(img) ref, err := reference.ParseNormalizedNamed(img)

View File

@@ -34,7 +34,7 @@ func testLs(t *testing.T, sb integration.Sandbox) {
}, },
} }
sbDriver, _ := driverName(sb.Name()) sbDriver, _, _ := strings.Cut(sb.Name(), "+")
for _, tt := range tests { for _, tt := range tests {
tt := tt tt := tt
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@@ -21,8 +21,8 @@ var rmTests = []func(t *testing.T, sb integration.Sandbox){
} }
func testRm(t *testing.T, sb integration.Sandbox) { func testRm(t *testing.T, sb integration.Sandbox) {
if !isDockerContainerWorker(sb) { if sb.Name() != "docker-container" {
t.Skip("only testing with docker-container worker") t.Skip("only testing for docker-container driver")
} }
out, err := rmCmd(sb, withArgs("default")) out, err := rmCmd(sb, withArgs("default"))
@@ -40,8 +40,8 @@ func testRm(t *testing.T, sb integration.Sandbox) {
} }
func testRmMulti(t *testing.T, sb integration.Sandbox) { func testRmMulti(t *testing.T, sb integration.Sandbox) {
if !isDockerContainerWorker(sb) { if sb.Name() != "docker-container" {
t.Skip("only testing with docker-container worker") t.Skip("only testing for docker-container driver")
} }
var builderNames []string var builderNames []string

View File

@@ -39,10 +39,6 @@ func (s *backend) NetNSDetached() bool {
return false return false
} }
func (s *backend) ExtraEnv() []string {
return nil
}
func (s backend) Supports(feature string) bool { func (s backend) Supports(feature string) bool {
if enabledFeatures := os.Getenv("BUILDKIT_TEST_ENABLE_FEATURES"); enabledFeatures != "" { if enabledFeatures := os.Getenv("BUILDKIT_TEST_ENABLE_FEATURES"); enabledFeatures != "" {
for _, enabledFeature := range strings.Split(enabledFeatures, ",") { for _, enabledFeature := range strings.Split(enabledFeatures, ",") {

View File

@@ -2,7 +2,6 @@ package buildflags
import ( import (
"encoding/csv" "encoding/csv"
"strconv"
"strings" "strings"
controllerapi "github.com/docker/buildx/controller/pb" controllerapi "github.com/docker/buildx/controller/pb"
@@ -22,16 +21,9 @@ func ParsePrintFunc(str string) (*controllerapi.PrintFunc, error) {
for _, field := range fields { for _, field := range fields {
parts := strings.SplitN(field, "=", 2) parts := strings.SplitN(field, "=", 2)
if len(parts) == 2 { if len(parts) == 2 {
switch parts[0] { if parts[0] == "format" {
case "format":
f.Format = parts[1] f.Format = parts[1]
case "ignorestatus": } else {
v, err := strconv.ParseBool(parts[1])
if err != nil {
return nil, errors.Wrapf(err, "invalid ignorestatus print value: %s", parts[1])
}
f.IgnoreStatus = v
default:
return nil, errors.Errorf("invalid print field: %s", field) return nil, errors.Errorf("invalid print field: %s", field)
} }
} else { } else {

View File

@@ -1,35 +0,0 @@
package confutil
import (
"os"
"strconv"
)
// MetadataProvenanceMode is the type for setting provenance in the metdata file
type MetadataProvenanceMode int
const (
// MetadataProvenanceModeMin sets minimal provenance (default)
MetadataProvenanceModeMin MetadataProvenanceMode = iota
// MetadataProvenanceModeMax sets full provenance
MetadataProvenanceModeMax
// MetadataProvenanceModeDisabled doesn't set provenance
MetadataProvenanceModeDisabled
)
// MetadataProvenance returns the provenance mode to set in the metadata file
func MetadataProvenance() MetadataProvenanceMode {
bmp := os.Getenv("BUILDX_METADATA_PROVENANCE")
switch bmp {
case "min":
return MetadataProvenanceModeMin
case "max":
return MetadataProvenanceModeMax
case "disabled":
return MetadataProvenanceModeDisabled
}
if ok, err := strconv.ParseBool(bmp); err == nil && !ok {
return MetadataProvenanceModeDisabled
}
return MetadataProvenanceModeMin
}

View File

@@ -10,28 +10,11 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
type gitServe struct { func GitServeHTTP(c *Git, t testing.TB) (url string) {
token string
}
type GitServeOpt func(*gitServe)
func WithAccessToken(token string) GitServeOpt {
return func(s *gitServe) {
s.token = token
}
}
func GitServeHTTP(c *Git, t testing.TB, opts ...GitServeOpt) (url string) {
t.Helper() t.Helper()
gitUpdateServerInfo(c, t) gitUpdateServerInfo(c, t)
ctx, cancel := context.WithCancel(context.TODO()) ctx, cancel := context.WithCancel(context.TODO())
gs := &gitServe{}
for _, opt := range opts {
opt(gs)
}
ready := make(chan struct{}) ready := make(chan struct{})
done := make(chan struct{}) done := make(chan struct{})
@@ -45,25 +28,7 @@ func GitServeHTTP(c *Git, t testing.TB, opts ...GitServeOpt) (url string) {
go func() { go func() {
mux := http.NewServeMux() mux := http.NewServeMux()
prefix := fmt.Sprintf("/%s/", name) prefix := fmt.Sprintf("/%s/", name)
mux.Handle(prefix, http.StripPrefix(prefix, http.FileServer(http.Dir(dir))))
handler := func(next http.Handler) http.Handler {
var tokenChecked bool
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if gs.token != "" && !tokenChecked {
t.Logf("git access token to check: %q", gs.token)
user, pass, _ := r.BasicAuth()
t.Logf("basic auth: user=%q pass=%q", user, pass)
if pass != gs.token {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
tokenChecked = true
}
next.ServeHTTP(w, r)
})
}
mux.Handle(prefix, handler(http.StripPrefix(prefix, http.FileServer(http.Dir(dir)))))
l, err := net.Listen("tcp", "localhost:0") l, err := net.Listen("tcp", "localhost:0")
if err != nil { if err != nil {
panic(err) panic(err)

View File

@@ -1,399 +0,0 @@
package imagetools
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"strings"
"github.com/containerd/containerd/remotes"
intoto "github.com/in-toto/in-toto-golang/in_toto"
slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
)
type attestationType int
const (
plainSpdx attestationType = 0
dsseEmbeded attestationType = 1
plainSpdxAndDSSEEmbed attestationType = 2
)
type mockFetcher struct {
}
type mockResolver struct {
fetcher remotes.Fetcher
pusher remotes.Pusher
}
var manifests = make(map[digest.Digest]manifest)
var indexes = make(map[digest.Digest]index)
func (f mockFetcher) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error) {
switch desc.MediaType {
case ocispec.MediaTypeImageIndex:
reader := io.NopCloser(strings.NewReader(indexes[desc.Digest].desc.Annotations["test_content"]))
return reader, nil
case ocispec.MediaTypeImageManifest:
reader := io.NopCloser(strings.NewReader(manifests[desc.Digest].desc.Annotations["test_content"]))
return reader, nil
default:
reader := io.NopCloser(strings.NewReader(desc.Annotations["test_content"]))
return reader, nil
}
}
func (r mockResolver) Resolve(ctx context.Context, ref string) (name string, desc ocispec.Descriptor, err error) {
d := digest.Digest(strings.ReplaceAll(ref, "docker.io/library/test@", ""))
return string(d), indexes[d].desc, nil
}
func (r mockResolver) Fetcher(ctx context.Context, ref string) (remotes.Fetcher, error) {
return r.fetcher, nil
}
func (r mockResolver) Pusher(ctx context.Context, ref string) (remotes.Pusher, error) {
return r.pusher, nil
}
func getMockResolver() remotes.Resolver {
resolver := mockResolver{
fetcher: mockFetcher{},
}
return resolver
}
func getImageNoAttestation() *result {
return getImageFromManifests(getBaseManifests())
}
func getImageWithAttestation(t attestationType) *result {
manifestList := getBaseManifests()
objManifest := ocispec.Manifest{
MediaType: v1.MediaTypeImageManifest,
Layers: getAttestationLayers(t),
Annotations: map[string]string{
"platform": "linux/amd64",
},
}
jsonContent, _ := json.Marshal(objManifest)
jsonString := string(jsonContent)
d := digest.FromString(jsonString)
manifestList[d] = manifest{
desc: ocispec.Descriptor{
MediaType: v1.MediaTypeImageManifest,
Digest: d,
Size: int64(len(jsonString)),
Annotations: map[string]string{
"vnd.docker.reference.digest": string(getManifestDigestForArch(manifestList, "linux", "amd64")),
"vnd.docker.reference.type": "attestation-manifest",
"test_content": jsonString,
},
Platform: &v1.Platform{
Architecture: "unknown",
OS: "unknown",
},
},
manifest: objManifest,
}
objManifest = ocispec.Manifest{
MediaType: v1.MediaTypeImageManifest,
Layers: getAttestationLayers(t),
Annotations: map[string]string{
"platform": "linux/arm64",
},
}
jsonContent, _ = json.Marshal(objManifest)
jsonString = string(jsonContent)
d = digest.FromString(jsonString)
manifestList[d] = manifest{
desc: ocispec.Descriptor{
MediaType: v1.MediaTypeImageManifest,
Digest: d,
Size: int64(len(jsonString)),
Annotations: map[string]string{
"vnd.docker.reference.digest": string(getManifestDigestForArch(manifestList, "linux", "arm64")),
"vnd.docker.reference.type": "attestation-manifest",
"test_content": jsonString,
},
Platform: &v1.Platform{
Architecture: "unknown",
OS: "unknown",
},
},
}
return getImageFromManifests(manifestList)
}
func getImageFromManifests(manifests map[digest.Digest]manifest) *result {
r := &result{
indexes: make(map[digest.Digest]index),
manifests: manifests,
images: make(map[string]digest.Digest),
refs: make(map[digest.Digest][]digest.Digest),
assets: make(map[string]asset),
}
r.images["linux/amd64"] = getManifestDigestForArch(manifests, "linux", "amd64")
r.images["linux/arm64"] = getManifestDigestForArch(manifests, "linux", "arm64")
manifestsDesc := []v1.Descriptor{}
for _, val := range manifests {
manifestsDesc = append(manifestsDesc, val.desc)
}
objIndex := v1.Index{
MediaType: v1.MediaTypeImageIndex,
Manifests: manifestsDesc,
}
jsonContent, _ := json.Marshal(objIndex)
jsonString := string(jsonContent)
d := digest.FromString(jsonString)
if _, ok := indexes[d]; !ok {
indexes[d] = index{
desc: ocispec.Descriptor{
MediaType: v1.MediaTypeImageIndex,
Digest: d,
Size: int64(len(jsonString)),
Annotations: map[string]string{
"test_content": jsonString,
},
},
index: objIndex,
}
}
r.indexes[d] = indexes[d]
return r
}
func getManifestDigestForArch(manifests map[digest.Digest]manifest, os string, arch string) digest.Digest {
for d, m := range manifests {
if m.desc.Platform.OS == os && m.desc.Platform.Architecture == arch {
return d
}
}
return digest.Digest("")
}
func getBaseManifests() map[digest.Digest]manifest {
if len(manifests) == 0 {
config := getConfig()
content := "amd64-content"
objManifest := ocispec.Manifest{
MediaType: v1.MediaTypeImageManifest,
Config: config,
Layers: []v1.Descriptor{
{
MediaType: v1.MediaTypeImageLayerGzip,
Digest: digest.FromString(content),
Size: int64(len(content)),
},
},
}
jsonContent, _ := json.Marshal(objManifest)
jsonString := string(jsonContent)
d := digest.FromString(jsonString)
manifests[d] = manifest{
desc: ocispec.Descriptor{
MediaType: v1.MediaTypeImageManifest,
Digest: d,
Size: int64(len(jsonString)),
Platform: &v1.Platform{
Architecture: "amd64",
OS: "linux",
},
Annotations: map[string]string{
"test_content": jsonString,
},
},
manifest: objManifest,
}
content = "arm64-content"
objManifest = ocispec.Manifest{
MediaType: v1.MediaTypeImageManifest,
Config: config,
Layers: []v1.Descriptor{
{
MediaType: v1.MediaTypeImageLayerGzip,
Digest: digest.FromString(content),
Size: int64(len(content)),
},
},
}
jsonContent, _ = json.Marshal(objManifest)
jsonString = string(jsonContent)
d = digest.FromString(jsonString)
manifests[d] = manifest{
desc: ocispec.Descriptor{
MediaType: v1.MediaTypeImageManifest,
Digest: d,
Size: int64(len(jsonString)),
Platform: &v1.Platform{
Architecture: "arm64",
OS: "linux",
},
Annotations: map[string]string{
"test_content": jsonString,
},
},
manifest: objManifest,
}
}
return manifests
}
func getConfig() v1.Descriptor {
config := v1.ImageConfig{
Env: []string{
"config",
},
}
jsonContent, _ := json.Marshal(config)
jsonString := string(jsonContent)
d := digest.FromString(jsonString)
return v1.Descriptor{
MediaType: ocispec.MediaTypeImageConfig,
Digest: d,
Size: int64(len(jsonString)),
Annotations: map[string]string{
"test_content": jsonString,
},
}
}
func getAttestationLayers(t attestationType) []v1.Descriptor {
layers := []v1.Descriptor{}
if t == plainSpdx || t == plainSpdxAndDSSEEmbed {
layers = append(layers, v1.Descriptor{
MediaType: inTotoGenericMime,
Digest: digest.FromString(attestationContent),
Size: int64(len(attestationContent)),
Annotations: map[string]string{
"in-toto.io/predicate-type": intoto.PredicateSPDX,
"test_content": attestationContent,
},
})
layers = append(layers, v1.Descriptor{
MediaType: inTotoGenericMime,
Digest: digest.FromString(provenanceContent),
Size: int64(len(provenanceContent)),
Annotations: map[string]string{
"in-toto.io/predicate-type": slsa02.PredicateSLSAProvenance,
"test_content": provenanceContent,
},
})
}
if t == dsseEmbeded || t == plainSpdxAndDSSEEmbed {
dsseAttestation := fmt.Sprintf("{\"payload\":\"%s\"}", base64.StdEncoding.EncodeToString([]byte(attestationContent)))
dsseProvenance := fmt.Sprintf("{\"payload\":\"%s\"}", base64.StdEncoding.EncodeToString([]byte(provenanceContent)))
layers = append(layers, v1.Descriptor{
MediaType: inTotoSPDXDSSEMime,
Digest: digest.FromString(dsseAttestation),
Size: int64(len(dsseAttestation)),
Annotations: map[string]string{
"in-toto.io/predicate-type": intoto.PredicateSPDX,
"test_content": dsseAttestation,
},
})
layers = append(layers, v1.Descriptor{
MediaType: inTotoProvenanceDSSEMime,
Digest: digest.FromString(dsseProvenance),
Size: int64(len(dsseProvenance)),
Annotations: map[string]string{
"in-toto.io/predicate-type": slsa02.PredicateSLSAProvenance,
"test_content": dsseProvenance,
},
})
}
return layers
}
const attestationContent = `
{
"_type": "https://in-toto.io/Statement/v0.1",
"predicateType": "https://spdx.dev/Document",
"predicate": {
"name": "sbom",
"spdxVersion": "SPDX-2.3",
"SPDXID": "SPDXRef-DOCUMENT",
"creationInfo": {
"created": "2024-01-31T16:09:05Z",
"creators": [
"Tool: buildkit-v0.11.0"
],
"licenseListVersion": "3.22"
},
"dataLicense": "CC0-1.0",
"documentNamespace": "https://example.com",
"packages": [
{
"name": "sbom",
"SPDXID": "SPDXRef-DocumentRoot-Directory-sbom",
"copyrightText": "",
"downloadLocation": "NOASSERTION",
"primaryPackagePurpose": "FILE",
"supplier": "NOASSERTION"
}
],
"relationships": [
{
"relatedSpdxElement": "SPDXRef-DocumentRoot-Directory-sbom",
"relationshipType": "DESCRIBES",
"spdxElementId": "SPDXRef-DOCUMENT"
}
]
}
}
`
const provenanceContent = `
{
"_type": "https://in-toto.io/Statement/v0.1",
"predicateType": "https://slsa.dev/provenance/v0.2",
"predicate": {
"buildType": "https://example.com/Makefile",
"builder": {
"id": "mailto:person@example.com"
},
"invocation": {
"configSource": {
"uri": "https://example.com/example-1.2.3.tar.gz",
"digest": {"sha256": ""},
"entryPoint": "src:foo"
},
"parameters": {
"CFLAGS": "-O3"
},
"materials": [
{
"uri": "https://example.com/example-1.2.3.tar.gz",
"digest": {"sha256": ""}
}
]
}
}
}
`

View File

@@ -16,7 +16,6 @@ import (
"github.com/containerd/containerd/platforms" "github.com/containerd/containerd/platforms"
"github.com/containerd/containerd/remotes" "github.com/containerd/containerd/remotes"
"github.com/distribution/reference" "github.com/distribution/reference"
intoto "github.com/in-toto/in-toto-golang/in_toto"
"github.com/moby/buildkit/util/contentutil" "github.com/moby/buildkit/util/contentutil"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@@ -293,7 +292,7 @@ func (l *loader) scanSBOM(ctx context.Context, fetcher remotes.Fetcher, r *resul
} }
for _, layer := range mfst.manifest.Layers { for _, layer := range mfst.manifest.Layers {
if (layer.MediaType == inTotoGenericMime || isInTotoDSSE(layer.MediaType)) && if (layer.MediaType == inTotoGenericMime || isInTotoDSSE(layer.MediaType)) &&
layer.Annotations["in-toto.io/predicate-type"] == intoto.PredicateSPDX { layer.Annotations["in-toto.io/predicate-type"] == "https://spdx.dev/Document" {
_, err := remotes.FetchHandler(l.cache, fetcher)(ctx, layer) _, err := remotes.FetchHandler(l.cache, fetcher)(ctx, layer)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -1,193 +0,0 @@
package imagetools
import (
"context"
"encoding/base64"
"fmt"
"reflect"
"testing"
"github.com/opencontainers/go-digest"
"github.com/stretchr/testify/assert"
)
func TestLoad(t *testing.T) {
loader := newLoader(getMockResolver())
ctx := context.Background()
r := getImageNoAttestation()
indexDigest := reflect.ValueOf(r.indexes).MapKeys()[0].String()
result, err := loader.Load(ctx, fmt.Sprintf("test@%s", indexDigest))
assert.NoError(t, err)
if err == nil {
assert.Equal(t, 1, len(result.indexes))
assert.Equal(t, 2, len(result.images))
assert.Equal(t, 2, len(result.platforms))
assert.Equal(t, 2, len(result.manifests))
assert.Equal(t, 2, len(result.assets))
assert.Equal(t, 0, len(result.refs))
}
r = getImageWithAttestation(plainSpdx)
indexDigest = reflect.ValueOf(r.indexes).MapKeys()[0].String()
result, err = loader.Load(ctx, fmt.Sprintf("test@%s", indexDigest))
assert.NoError(t, err)
if err == nil {
assert.Equal(t, 1, len(result.indexes))
assert.Equal(t, 2, len(result.images))
assert.Equal(t, 2, len(result.platforms))
assert.Equal(t, 4, len(result.manifests))
assert.Equal(t, 2, len(result.assets))
assert.Equal(t, 2, len(result.refs))
for d1, m := range r.manifests {
if _, ok := m.desc.Annotations["vnd.docker.reference.digest"]; ok {
d2 := digest.Digest(m.desc.Annotations["vnd.docker.reference.digest"])
assert.Equal(t, d1, result.refs[d2][0])
}
}
}
}
func TestSBOM(t *testing.T) {
tests := []struct {
name string
contentType attestationType
}{
{
name: "Plain SPDX",
contentType: plainSpdx,
},
{
name: "SPDX in DSSE envelope",
contentType: dsseEmbeded,
},
{
name: "Plain SPDX and SPDX in DSSE envelope",
contentType: plainSpdxAndDSSEEmbed,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
loader := newLoader(getMockResolver())
ctx := context.Background()
fetcher, _ := loader.resolver.Fetcher(ctx, "")
r := getImageWithAttestation(test.contentType)
imageDigest := r.images["linux/amd64"]
// Manual mapping
for d, m := range r.manifests {
if m.desc.Annotations["vnd.docker.reference.digest"] == string(imageDigest) {
r.refs[imageDigest] = []digest.Digest{
d,
}
}
}
a := asset{}
loader.scanSBOM(ctx, fetcher, r, r.refs[imageDigest], &a)
r.assets["linux/amd64"] = a
actual, err := r.SBOM()
assert.NoError(t, err)
assert.Equal(t, 1, len(actual))
})
}
}
func TestProvenance(t *testing.T) {
tests := []struct {
name string
contentType attestationType
}{
{
name: "Plain SPDX",
contentType: plainSpdx,
},
{
name: "SPDX in DSSE envelope",
contentType: dsseEmbeded,
},
{
name: "Plain SPDX and SPDX in DSSE envelope",
contentType: plainSpdxAndDSSEEmbed,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
loader := newLoader(getMockResolver())
ctx := context.Background()
fetcher, _ := loader.resolver.Fetcher(ctx, "")
r := getImageWithAttestation(test.contentType)
imageDigest := r.images["linux/amd64"]
// Manual mapping
for d, m := range r.manifests {
if m.desc.Annotations["vnd.docker.reference.digest"] == string(imageDigest) {
r.refs[imageDigest] = []digest.Digest{
d,
}
}
}
a := asset{}
loader.scanProvenance(ctx, fetcher, r, r.refs[imageDigest], &a)
r.assets["linux/amd64"] = a
actual, err := r.Provenance()
assert.NoError(t, err)
assert.Equal(t, 1, len(actual))
})
}
}
func Test_isInTotoDSSE(t *testing.T) {
tests := []struct {
mime string
expected bool
}{
{
mime: "application/vnd.in-toto.spdx+dsse",
expected: true,
},
{
mime: "application/vnd.in-toto.provenance+dsse",
expected: true,
},
{
mime: "application/vnd.in-toto+json",
expected: false,
},
}
for _, test := range tests {
t.Run(test.mime, func(t *testing.T) {
assert.Equal(t, isInTotoDSSE(test.mime), test.expected)
})
}
}
func Test_decodeDSSE(t *testing.T) {
// Returns input when mime isn't a DSSE type
actual, err := decodeDSSE([]byte("foobar"), "application/vnd.in-toto+json")
assert.NoError(t, err)
assert.Equal(t, []byte("foobar"), actual)
// Returns the base64 decoded payload if is a DSSE
payload := base64.StdEncoding.EncodeToString([]byte("hello world"))
envelope := fmt.Sprintf("{\"payload\":\"%s\"}", payload)
actual, err = decodeDSSE([]byte(envelope), "application/vnd.in-toto.spdx+dsse")
assert.NoError(t, err)
assert.Equal(t, "hello world", string(actual))
_, err = decodeDSSE([]byte("not a json"), "application/vnd.in-toto.spdx+dsse")
assert.Error(t, err)
_, err = decodeDSSE([]byte("{\"payload\": \"not base64\"}"), "application/vnd.in-toto.spdx+dsse")
assert.Error(t, err)
}

View File

@@ -1,10 +1,216 @@
package metricutil package metricutil
import ( import (
"context"
"fmt"
"net/url"
"path"
"time"
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/version" "github.com/docker/buildx/version"
"github.com/docker/cli/cli/command"
"github.com/pkg/errors"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/noop"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"golang.org/x/sync/errgroup"
) )
const (
otelConfigFieldName = "otel"
reportTimeout = 2 * time.Second
)
// MeterProvider holds a MeterProvider for metric generation and the configured
// exporters for reporting metrics from the CLI.
type MeterProvider struct {
metric.MeterProvider
reader *sdkmetric.ManualReader
exporters []sdkmetric.Exporter
}
// NewMeterProvider configures a MeterProvider from the CLI context.
func NewMeterProvider(ctx context.Context, cli command.Cli) (*MeterProvider, error) {
var exps []sdkmetric.Exporter
if exp, err := dockerOtelExporter(cli); err != nil {
return nil, err
} else if exp != nil {
exps = append(exps, exp)
}
if confutil.IsExperimental() {
// Expose the user-facing metric exporter only if the experimental flag is set.
if exp, err := detectOtlpExporter(ctx); err != nil {
return nil, err
} else if exp != nil {
exps = append(exps, exp)
}
}
if len(exps) == 0 {
// No exporters are configured so use a noop provider.
return &MeterProvider{
MeterProvider: noop.NewMeterProvider(),
}, nil
}
reader := sdkmetric.NewManualReader(
sdkmetric.WithTemporalitySelector(deltaTemporality),
)
mp := sdkmetric.NewMeterProvider(
sdkmetric.WithResource(Resource()),
sdkmetric.WithReader(reader),
)
return &MeterProvider{
MeterProvider: mp,
reader: reader,
exporters: exps,
}, nil
}
// Report exports metrics to the configured exporter. This should be done before the CLI
// exits.
func (m *MeterProvider) Report(ctx context.Context) {
if m.reader == nil {
// Not configured.
return
}
ctx, cancel := context.WithTimeout(ctx, reportTimeout)
defer cancel()
var rm metricdata.ResourceMetrics
if err := m.reader.Collect(ctx, &rm); err != nil {
// Error when collecting metrics. Do not send any.
otel.Handle(err)
return
}
var eg errgroup.Group
for _, exp := range m.exporters {
exp := exp
eg.Go(func() error {
if err := exp.Export(ctx, &rm); err != nil {
otel.Handle(err)
}
_ = exp.Shutdown(ctx)
return nil
})
}
// Can't report an error because we don't allow it to.
_ = eg.Wait()
}
// dockerOtelExporter reads the CLI metadata to determine an OTLP exporter
// endpoint for docker metrics to be sent.
//
// This location, configuration, and usage is hard-coded as part of
// sending usage statistics so this metric reporting is not meant to be
// user facing.
func dockerOtelExporter(cli command.Cli) (sdkmetric.Exporter, error) {
endpoint, err := otelExporterOtlpEndpoint(cli)
if endpoint == "" || err != nil {
return nil, err
}
// Parse the endpoint. The docker config expects the endpoint to be
// in the form of a URL to match the environment variable, but this
// option doesn't correspond directly to WithEndpoint.
//
// We pretend we're the same as the environment reader.
u, err := url.Parse(endpoint)
if err != nil {
return nil, errors.Errorf("docker otel endpoint is invalid: %s", err)
}
var opts []otlpmetricgrpc.Option
switch u.Scheme {
case "unix":
// Unix sockets are a bit weird. OTEL seems to imply they
// can be used as an environment variable and are handled properly,
// but they don't seem to be as the behavior of the environment variable
// is to strip the scheme from the endpoint, but the underlying implementation
// needs the scheme to use the correct resolver.
//
// We'll just handle this in a special way and add the unix:// back to the endpoint.
opts = []otlpmetricgrpc.Option{
otlpmetricgrpc.WithEndpoint(fmt.Sprintf("unix://%s", path.Join(u.Host, u.Path))),
otlpmetricgrpc.WithInsecure(),
}
case "http":
opts = []otlpmetricgrpc.Option{
// Omit the scheme when using http or https.
otlpmetricgrpc.WithEndpoint(path.Join(u.Host, u.Path)),
otlpmetricgrpc.WithInsecure(),
}
default:
opts = []otlpmetricgrpc.Option{
// Omit the scheme when using http or https.
otlpmetricgrpc.WithEndpoint(path.Join(u.Host, u.Path)),
}
}
// Hardcoded endpoint from the endpoint.
exp, err := otlpmetricgrpc.New(context.Background(), opts...)
if err != nil {
return nil, err
}
return exp, nil
}
// otelExporterOtlpEndpoint retrieves the OTLP endpoint used for the docker reporter
// from the current context.
func otelExporterOtlpEndpoint(cli command.Cli) (string, error) {
meta, err := cli.ContextStore().GetMetadata(cli.CurrentContext())
if err != nil {
return "", err
}
var otelCfg interface{}
switch m := meta.Metadata.(type) {
case command.DockerContext:
otelCfg = m.AdditionalFields[otelConfigFieldName]
case map[string]interface{}:
otelCfg = m[otelConfigFieldName]
}
if otelCfg == nil {
return "", nil
}
otelMap, ok := otelCfg.(map[string]interface{})
if !ok {
return "", errors.Errorf(
"unexpected type for field %q: %T (expected: %T)",
otelConfigFieldName,
otelCfg,
otelMap,
)
}
// keys from https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/
endpoint, _ := otelMap["OTEL_EXPORTER_OTLP_ENDPOINT"].(string)
return endpoint, nil
}
// deltaTemporality sets the Temporality of every instrument to delta.
//
// This isn't really needed since we create a unique resource on each invocation,
// but it can help with cardinality concerns for downstream processors since they can
// perform aggregation for a time interval and then discard the data once that time
// period has passed. Cumulative temporality would imply to the downstream processor
// that they might receive a successive point and they may unnecessarily keep state
// they really shouldn't.
func deltaTemporality(_ sdkmetric.InstrumentKind) metricdata.Temporality {
return metricdata.DeltaTemporality
}
// Meter returns a Meter from the MetricProvider that indicates the measurement // Meter returns a Meter from the MetricProvider that indicates the measurement
// comes from buildx with the appropriate version. // comes from buildx with the appropriate version.
func Meter(mp metric.MeterProvider) metric.Meter { func Meter(mp metric.MeterProvider) metric.Meter {

45
util/metricutil/otlp.go Normal file
View File

@@ -0,0 +1,45 @@
package metricutil
import (
"context"
"os"
"github.com/pkg/errors"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
)
// detectOtlpExporter configures a metrics exporter based on environment variables.
// This is similar to the version of this in buildkit, but we need direct access
// to the exporter and the prometheus exporter doesn't work at all in a CLI context.
//
// There's some duplication here which I hope to remove when the detect package
// is refactored or extracted from buildkit so it can be utilized here.
//
// This version of the exporter is public facing in contrast to the
// docker otel collector.
func detectOtlpExporter(ctx context.Context) (sdkmetric.Exporter, error) {
set := os.Getenv("OTEL_METRICS_EXPORTER") == "otlp" || os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT") != "" || os.Getenv("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT") != ""
if !set {
return nil, nil
}
proto := os.Getenv("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL")
if proto == "" {
proto = os.Getenv("OTEL_EXPORTER_OTLP_PROTOCOL")
}
if proto == "" {
proto = "grpc"
}
switch proto {
case "grpc":
return otlpmetricgrpc.New(ctx)
case "http/protobuf":
return otlpmetrichttp.New(ctx)
// case "http/json": // unsupported by library
default:
return nil, errors.Errorf("unsupported otlp protocol %v", proto)
}
}

View File

@@ -0,0 +1,53 @@
package metricutil
import (
"context"
"os"
"path/filepath"
"sync"
"github.com/google/uuid"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
)
var (
res *resource.Resource
resOnce sync.Once
)
// Resource retrieves the OTEL resource for the buildx CLI.
func Resource() *resource.Resource {
resOnce.Do(func() {
var err error
res, err = resource.New(context.Background(),
resource.WithDetectors(serviceNameDetector{}),
resource.WithAttributes(
// Use a unique instance id so OTEL knows that each invocation
// of the CLI is its own instance. Without this, downstream
// OTEL processors may think the same process is restarting
// continuously and reset the metric counters.
semconv.ServiceInstanceID(uuid.New().String()),
),
resource.WithFromEnv(),
resource.WithTelemetrySDK(),
)
if err != nil {
otel.Handle(err)
}
})
return res
}
type serviceNameDetector struct{}
func (serviceNameDetector) Detect(ctx context.Context) (*resource.Resource, error) {
return resource.StringDetector(
semconv.SchemaURL,
semconv.ServiceNameKey,
func() (string, error) {
return filepath.Base(os.Args[0]), nil
},
).Detect(ctx)
}

View File

@@ -0,0 +1,33 @@
package metricutil
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel"
)
func TestResource(t *testing.T) {
setErrorHandler(t)
// Ensure resource creation doesn't result in an error.
// This is because the schema urls for the various attributes need to be
// the same, but it's really easy to import the wrong package when upgrading
// otel to anew version and the buildx CLI swallows any visible errors.
res := Resource()
// Ensure an attribute is present.
assert.True(t, res.Set().HasValue("telemetry.sdk.version"), "resource attribute missing")
}
func setErrorHandler(tb testing.TB) {
tb.Helper()
errorHandler := otel.GetErrorHandler()
otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) {
tb.Errorf("otel error: %s", err)
}))
tb.Cleanup(func() {
otel.SetErrorHandler(errorHandler)
})
}

View File

@@ -3,9 +3,7 @@ package progress
import ( import (
"context" "context"
"regexp" "regexp"
"sort"
"strings" "strings"
"sync"
"time" "time"
"github.com/docker/buildx/util/metricutil" "github.com/docker/buildx/util/metricutil"
@@ -25,10 +23,6 @@ func newMetrics(mp metric.MeterProvider, attrs attribute.Set) *metricWriter {
return &metricWriter{ return &metricWriter{
recorders: []metricRecorder{ recorders: []metricRecorder{
newLocalSourceTransferMetricRecorder(meter, attrs), newLocalSourceTransferMetricRecorder(meter, attrs),
newImageSourceTransferMetricRecorder(meter, attrs),
newExecMetricRecorder(meter, attrs),
newExportImageMetricRecorder(meter, attrs),
newIdleMetricRecorder(meter, attrs),
}, },
attrs: attrs, attrs: attrs,
} }
@@ -155,274 +149,3 @@ func detectLocalSourceType(vertexName string) attribute.KeyValue {
// No matches found. // No matches found.
return attribute.KeyValue{} return attribute.KeyValue{}
} }
type (
imageSourceMetricRecorder struct {
// BaseAttributes holds the set of base attributes for all metrics produced.
BaseAttributes attribute.Set
// State holds the state for an individual digest. It is mostly used to check
// if a status belongs to an image source since this recorder doesn't maintain
// individual digest state.
State map[digest.Digest]struct{}
// TransferSize holds the counter for the transfer size.
TransferSize metric.Int64Counter
// TransferDuration holds the counter for the transfer duration.
TransferDuration metric.Float64Counter
// ExtractDuration holds the counter for the duration of image extraction.
ExtractDuration metric.Float64Counter
}
)
func newImageSourceTransferMetricRecorder(meter metric.Meter, attrs attribute.Set) *imageSourceMetricRecorder {
mr := &imageSourceMetricRecorder{
BaseAttributes: attrs,
State: make(map[digest.Digest]struct{}),
}
mr.TransferSize, _ = meter.Int64Counter("source.image.transfer.io",
metric.WithDescription("Measures the number of bytes transferred for image content."),
metric.WithUnit("By"))
mr.TransferDuration, _ = meter.Float64Counter("source.image.transfer.time",
metric.WithDescription("Measures the length of time spent transferring image content."),
metric.WithUnit("ms"))
mr.ExtractDuration, _ = meter.Float64Counter("source.image.extract.time",
metric.WithDescription("Measures the length of time spent extracting image content."),
metric.WithUnit("ms"))
return mr
}
func (mr *imageSourceMetricRecorder) Record(ss *client.SolveStatus) {
for _, v := range ss.Vertexes {
if _, ok := mr.State[v.Digest]; !ok {
if !detectImageSourceType(v.Name) {
continue
}
mr.State[v.Digest] = struct{}{}
}
}
for _, status := range ss.Statuses {
// For this image type, we're only interested in completed statuses.
if status.Completed == nil {
continue
}
if status.Name == "extracting" {
dur := float64(status.Completed.Sub(*status.Started)) / float64(time.Millisecond)
mr.ExtractDuration.Add(context.Background(), dur,
metric.WithAttributeSet(mr.BaseAttributes),
)
continue
}
// Remaining statuses will be associated with the from node.
if _, ok := mr.State[status.Vertex]; !ok {
continue
}
if strings.HasPrefix(status.ID, "sha256:") {
// Signals a transfer. Record the duration and the size.
dur := float64(status.Completed.Sub(*status.Started)) / float64(time.Millisecond)
mr.TransferDuration.Add(context.Background(), dur,
metric.WithAttributeSet(mr.BaseAttributes),
)
mr.TransferSize.Add(context.Background(), status.Total,
metric.WithAttributeSet(mr.BaseAttributes),
)
}
}
}
var reImageSourceType = regexp.MustCompile(`^\[.*] FROM `)
func detectImageSourceType(vertexName string) bool {
return reImageSourceType.MatchString(vertexName)
}
type (
execMetricRecorder struct {
// Attributes holds the attributes for this metric recorder.
Attributes attribute.Set
// Duration tracks the duration of exec statements.
Duration metric.Float64Counter
}
)
func newExecMetricRecorder(meter metric.Meter, attrs attribute.Set) *execMetricRecorder {
mr := &execMetricRecorder{
Attributes: attrs,
}
mr.Duration, _ = meter.Float64Counter("exec.command.time",
metric.WithDescription("Measures the length of time spent executing run statements."),
metric.WithUnit("ms"))
return mr
}
func (mr *execMetricRecorder) Record(ss *client.SolveStatus) {
for _, v := range ss.Vertexes {
if v.Started == nil || v.Completed == nil || !detectExecType(v.Name) {
continue
}
dur := float64(v.Completed.Sub(*v.Started)) / float64(time.Millisecond)
mr.Duration.Add(context.Background(), dur, metric.WithAttributeSet(mr.Attributes))
}
}
var reExecType = regexp.MustCompile(`^\[.*] RUN `)
func detectExecType(vertexName string) bool {
return reExecType.MatchString(vertexName)
}
type (
exportImageMetricRecorder struct {
// Attributes holds the attributes for the export image metric.
Attributes attribute.Set
// Duration tracks the duration of image exporting.
Duration metric.Float64Counter
}
)
func newExportImageMetricRecorder(meter metric.Meter, attrs attribute.Set) *exportImageMetricRecorder {
mr := &exportImageMetricRecorder{
Attributes: attrs,
}
mr.Duration, _ = meter.Float64Counter("export.image.time",
metric.WithDescription("Measures the length of time spent exporting the image."),
metric.WithUnit("ms"))
return mr
}
func (mr *exportImageMetricRecorder) Record(ss *client.SolveStatus) {
for _, v := range ss.Vertexes {
if v.Started == nil || v.Completed == nil {
continue
}
format := detectExportImageType(v.Name)
if format == "" {
continue
}
dur := float64(v.Completed.Sub(*v.Started)) / float64(time.Millisecond)
mr.Duration.Add(context.Background(), dur,
metric.WithAttributeSet(mr.Attributes),
metric.WithAttributes(
attribute.String("image.format", format),
),
)
}
}
var reExportImageType = regexp.MustCompile(`^exporting to (image|(?P<format>\w+) image format)$`)
func detectExportImageType(vertexName string) string {
m := reExportImageType.FindStringSubmatch(vertexName)
if m == nil {
return ""
}
format := "docker"
if m[2] != "" {
format = m[2]
}
return format
}
type idleMetricRecorder struct {
// Attributes holds the set of base attributes for all metrics produced.
Attributes attribute.Set
// Duration tracks the amount of time spent idle during this build.
Duration metric.Float64ObservableGauge
// Started stores the set of times when tasks were started.
Started []time.Time
// Completed stores the set of times when tasks were completed.
Completed []time.Time
mu sync.Mutex
}
func newIdleMetricRecorder(meter metric.Meter, attrs attribute.Set) *idleMetricRecorder {
mr := &idleMetricRecorder{
Attributes: attrs,
}
mr.Duration, _ = meter.Float64ObservableGauge("builder.idle.time",
metric.WithDescription("Measures the length of time the builder spends idle."),
metric.WithUnit("ms"),
metric.WithFloat64Callback(mr.calculateIdleTime))
return mr
}
func (mr *idleMetricRecorder) Record(ss *client.SolveStatus) {
mr.mu.Lock()
defer mr.mu.Unlock()
for _, v := range ss.Vertexes {
if v.Started == nil || v.Completed == nil {
continue
}
mr.Started = append(mr.Started, *v.Started)
mr.Completed = append(mr.Completed, *v.Completed)
}
}
// calculateIdleTime will use the recorded vertices that have been completed to determine the
// amount of time spent idle.
//
// This calculation isn't accurate until the build itself is completed. At the moment,
// metrics are only ever sent when a build is completed. If that changes, this calculation
// will likely be inaccurate.
func (mr *idleMetricRecorder) calculateIdleTime(_ context.Context, o metric.Float64Observer) error {
mr.mu.Lock()
defer mr.mu.Unlock()
dur := calculateIdleTime(mr.Started, mr.Completed)
o.Observe(float64(dur)/float64(time.Millisecond), metric.WithAttributeSet(mr.Attributes))
return nil
}
func calculateIdleTime(started, completed []time.Time) time.Duration {
sort.Slice(started, func(i, j int) bool {
return started[i].Before(started[j])
})
sort.Slice(completed, func(i, j int) bool {
return completed[i].Before(completed[j])
})
if len(started) == 0 {
return 0
}
var (
idleStart time.Time
elapsed time.Duration
)
for active := 0; len(started) > 0 && len(completed) > 0; {
if started[0].Before(completed[0]) {
if active == 0 && !idleStart.IsZero() {
elapsed += started[0].Sub(idleStart)
}
active++
started = started[1:]
continue
}
active--
if active == 0 {
idleStart = completed[0]
}
completed = completed[1:]
}
return elapsed
}

View File

@@ -1,46 +0,0 @@
package progress
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestCalculateIdleTime(t *testing.T) {
for _, tt := range []struct {
started []int64
completed []int64
ms int64
}{
{
started: []int64{0, 1, 3},
completed: []int64{2, 10, 5},
ms: 0,
},
{
started: []int64{0, 3},
completed: []int64{2, 5},
ms: 1,
},
{
started: []int64{3, 0, 7},
completed: []int64{5, 2, 10},
ms: 3,
},
} {
started := unixMillis(tt.started...)
completed := unixMillis(tt.completed...)
actual := int64(calculateIdleTime(started, completed) / time.Millisecond)
assert.Equal(t, tt.ms, actual)
}
}
func unixMillis(ts ...int64) []time.Time {
times := make([]time.Time, len(ts))
for i, ms := range ts {
times[i] = time.UnixMilli(ms)
}
return times
}

View File

@@ -6,6 +6,7 @@ import (
"sync" "sync"
"github.com/containerd/console" "github.com/containerd/console"
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/logutil" "github.com/docker/buildx/util/logutil"
"github.com/moby/buildkit/client" "github.com/moby/buildkit/client"
"github.com/moby/buildkit/util/progress/progressui" "github.com/moby/buildkit/util/progress/progressui"
@@ -175,8 +176,10 @@ func WithDesc(text string, console string) PrinterOpt {
func WithMetrics(mp metric.MeterProvider, attrs attribute.Set) PrinterOpt { func WithMetrics(mp metric.MeterProvider, attrs attribute.Set) PrinterOpt {
return func(opt *printerOpts) { return func(opt *printerOpts) {
if confutil.IsExperimental() {
opt.mw = newMetrics(mp, attrs) opt.mw = newMetrics(mp, attrs)
} }
}
} }
func WithOnClose(onclose func()) PrinterOpt { func WithOnClose(onclose func()) PrinterOpt {

View File

@@ -15,7 +15,7 @@ type Writer interface {
ClearLogSource(interface{}) ClearLogSource(interface{})
} }
func Write(w Writer, name string, f func() error) error { func Write(w Writer, name string, f func() error) {
dgst := digest.FromBytes([]byte(identity.NewID())) dgst := digest.FromBytes([]byte(identity.NewID()))
tm := time.Now() tm := time.Now()
@@ -40,8 +40,6 @@ func Write(w Writer, name string, f func() error) error {
w.Write(&client.SolveStatus{ w.Write(&client.SolveStatus{
Vertexes: []*client.Vertex{&vtx2}, Vertexes: []*client.Vertex{&vtx2},
}) })
return err
} }
func WriteBuildRef(w Writer, target string, ref string) { func WriteBuildRef(w Writer, target string, ref string) {

View File

@@ -217,10 +217,7 @@ func WithLoadOptions(loadOptions ...func(*loader.Options)) ProjectOptionsFn {
// profiles specified via the COMPOSE_PROFILES environment variable otherwise. // profiles specified via the COMPOSE_PROFILES environment variable otherwise.
func WithDefaultProfiles(profile ...string) ProjectOptionsFn { func WithDefaultProfiles(profile ...string) ProjectOptionsFn {
if len(profile) == 0 { if len(profile) == 0 {
for _, s := range strings.Split(os.Getenv(consts.ComposeProfiles), ",") { profile = strings.Split(os.Getenv(consts.ComposeProfiles), ",")
profile = append(profile, strings.TrimSpace(s))
}
} }
return WithProfiles(profile) return WithProfiles(profile)
} }
@@ -382,7 +379,7 @@ var DefaultFileNames = []string{"compose.yaml", "compose.yml", "docker-compose.y
// DefaultOverrideFileNames defines the Compose override file names for auto-discovery (in order of preference) // DefaultOverrideFileNames defines the Compose override file names for auto-discovery (in order of preference)
var DefaultOverrideFileNames = []string{"compose.override.yml", "compose.override.yaml", "docker-compose.override.yml", "docker-compose.override.yaml"} var DefaultOverrideFileNames = []string{"compose.override.yml", "compose.override.yaml", "docker-compose.override.yml", "docker-compose.override.yaml"}
func (o *ProjectOptions) GetWorkingDir() (string, error) { func (o ProjectOptions) GetWorkingDir() (string, error) {
if o.WorkingDir != "" { if o.WorkingDir != "" {
return filepath.Abs(o.WorkingDir) return filepath.Abs(o.WorkingDir)
} }
@@ -398,7 +395,7 @@ func (o *ProjectOptions) GetWorkingDir() (string, error) {
return os.Getwd() return os.Getwd()
} }
func (o *ProjectOptions) GeConfigFiles() ([]types.ConfigFile, error) { func (o ProjectOptions) GeConfigFiles() ([]types.ConfigFile, error) {
configPaths, err := o.getConfigPaths() configPaths, err := o.getConfigPaths()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -430,66 +427,39 @@ func (o *ProjectOptions) GeConfigFiles() ([]types.ConfigFile, error) {
return configs, err return configs, err
} }
// LoadProject loads compose file according to options and bind to types.Project go structs // ProjectFromOptions load a compose project based on command line options
func (o *ProjectOptions) LoadProject(ctx context.Context) (*types.Project, error) { func ProjectFromOptions(ctx context.Context, options *ProjectOptions) (*types.Project, error) {
configDetails, err := o.prepare() configs, err := options.GeConfigFiles()
if err != nil { if err != nil {
return nil, err return nil, err
} }
project, err := loader.LoadWithContext(ctx, configDetails, o.loadOptions...) workingDir, err := options.GetWorkingDir()
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, config := range configDetails.ConfigFiles { options.loadOptions = append(options.loadOptions,
withNamePrecedenceLoad(workingDir, options),
withConvertWindowsPaths(options),
withListeners(options))
project, err := loader.LoadWithContext(ctx, types.ConfigDetails{
ConfigFiles: configs,
WorkingDir: workingDir,
Environment: options.Environment,
}, options.loadOptions...)
if err != nil {
return nil, err
}
for _, config := range configs {
project.ComposeFiles = append(project.ComposeFiles, config.Filename) project.ComposeFiles = append(project.ComposeFiles, config.Filename)
} }
return project, nil return project, nil
} }
// LoadModel loads compose file according to options and returns a raw (yaml tree) model
func (o *ProjectOptions) LoadModel(ctx context.Context) (map[string]any, error) {
configDetails, err := o.prepare()
if err != nil {
return nil, err
}
return loader.LoadModelWithContext(ctx, configDetails, o.loadOptions...)
}
// prepare converts ProjectOptions into loader's types.ConfigDetails and configures default load options
func (o *ProjectOptions) prepare() (types.ConfigDetails, error) {
configs, err := o.GeConfigFiles()
if err != nil {
return types.ConfigDetails{}, err
}
workingDir, err := o.GetWorkingDir()
if err != nil {
return types.ConfigDetails{}, err
}
configDetails := types.ConfigDetails{
ConfigFiles: configs,
WorkingDir: workingDir,
Environment: o.Environment,
}
o.loadOptions = append(o.loadOptions,
withNamePrecedenceLoad(workingDir, o),
withConvertWindowsPaths(o),
withListeners(o))
return configDetails, nil
}
// ProjectFromOptions load a compose project based on command line options
// Deprecated: use ProjectOptions.LoadProject or ProjectOptions.LoadModel
func ProjectFromOptions(ctx context.Context, options *ProjectOptions) (*types.Project, error) {
return options.LoadProject(ctx)
}
func withNamePrecedenceLoad(absWorkingDir string, options *ProjectOptions) func(*loader.Options) { func withNamePrecedenceLoad(absWorkingDir string, options *ProjectOptions) func(*loader.Options) {
return func(opts *loader.Options) { return func(opts *loader.Options) {
if options.Name != "" { if options.Name != "" {

View File

@@ -30,9 +30,6 @@ var (
// ErrIncompatible is returned when a compose project uses an incompatible attribute // ErrIncompatible is returned when a compose project uses an incompatible attribute
ErrIncompatible = errors.New("incompatible attribute") ErrIncompatible = errors.New("incompatible attribute")
// ErrDisabled is returned when a resource was found in model but is disabled
ErrDisabled = errors.New("disabled")
) )
// IsNotFoundError returns true if the unwrapped error is ErrNotFound // IsNotFoundError returns true if the unwrapped error is ErrNotFound

View File

@@ -1,63 +0,0 @@
/*
Copyright 2020 The Compose Specification Authors.
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 graph
import (
"fmt"
"strings"
"github.com/compose-spec/compose-go/v2/types"
"github.com/compose-spec/compose-go/v2/utils"
"golang.org/x/exp/slices"
)
// CheckCycle analyze project's depends_on relation and report an error on cycle detection
func CheckCycle(project *types.Project) error {
g, err := newGraph(project)
if err != nil {
return err
}
return g.checkCycle()
}
func (g *graph[T]) checkCycle() error {
// iterate on vertices in a name-order to render a predicable error message
// this is required by tests and enforce command reproducibility by user, which otherwise could be confusing
names := utils.MapKeys(g.vertices)
for _, name := range names {
err := searchCycle([]string{name}, g.vertices[name])
if err != nil {
return err
}
}
return nil
}
func searchCycle[T any](path []string, v *vertex[T]) error {
names := utils.MapKeys(v.children)
for _, name := range names {
if i := slices.Index(path, name); i > 0 {
return fmt.Errorf("dependency cycle detected: %s", strings.Join(path[i:], " -> "))
}
ch := v.children[name]
err := searchCycle(append(path, name), ch)
if err != nil {
return err
}
}
return nil
}

View File

@@ -16,6 +16,14 @@
package graph package graph
import (
"fmt"
"strings"
"github.com/compose-spec/compose-go/v2/utils"
"golang.org/x/exp/slices"
)
// graph represents project as service dependencies // graph represents project as service dependencies
type graph[T any] struct { type graph[T any] struct {
vertices map[string]*vertex[T] vertices map[string]*vertex[T]
@@ -64,6 +72,34 @@ func (g *graph[T]) leaves() []*vertex[T] {
return res return res
} }
func (g *graph[T]) checkCycle() error {
// iterate on vertices in a name-order to render a predicable error message
// this is required by tests and enforce command reproducibility by user, which otherwise could be confusing
names := utils.MapKeys(g.vertices)
for _, name := range names {
err := searchCycle([]string{name}, g.vertices[name])
if err != nil {
return err
}
}
return nil
}
func searchCycle[T any](path []string, v *vertex[T]) error {
names := utils.MapKeys(v.children)
for _, name := range names {
if i := slices.Index(path, name); i > 0 {
return fmt.Errorf("dependency cycle detected: %s", strings.Join(path[i:], " -> "))
}
ch := v.children[name]
err := searchCycle(append(path, name), ch)
if err != nil {
return err
}
}
return nil
}
// descendents return all descendents for a vertex, might contain duplicates // descendents return all descendents for a vertex, might contain duplicates
func (v *vertex[T]) descendents() []string { func (v *vertex[T]) descendents() []string {
var vx []string var vx []string

View File

@@ -20,6 +20,7 @@ import (
"context" "context"
"fmt" "fmt"
"path/filepath" "path/filepath"
"strings"
"github.com/compose-spec/compose-go/v2/consts" "github.com/compose-spec/compose-go/v2/consts"
"github.com/compose-spec/compose-go/v2/override" "github.com/compose-spec/compose-go/v2/override"
@@ -105,6 +106,11 @@ func applyServiceExtends(ctx context.Context, name string, services map[string]a
} }
source := deepClone(base).(map[string]any) source := deepClone(base).(map[string]any)
err = validateExtendSource(source, ref)
if err != nil {
return nil, err
}
for _, processor := range post { for _, processor := range post {
processor.Apply(map[string]any{ processor.Apply(map[string]any{
"services": map[string]any{ "services": map[string]any{
@@ -121,6 +127,30 @@ func applyServiceExtends(ctx context.Context, name string, services map[string]a
return merged, nil return merged, nil
} }
// validateExtendSource check the source for `extends` doesn't refer to another container/service
func validateExtendSource(source map[string]any, ref string) error {
forbidden := []string{"links", "volumes_from", "depends_on"}
for _, key := range forbidden {
if _, ok := source[key]; ok {
return fmt.Errorf("service %q can't be used with `extends` as it declare `%s`", ref, key)
}
}
sharedNamespace := []string{"network_mode", "ipc", "pid", "net", "cgroup", "userns_mode", "uts"}
for _, key := range sharedNamespace {
if v, ok := source[key]; ok {
val := v.(string)
if strings.HasPrefix(val, types.ContainerPrefix) {
return fmt.Errorf("service %q can't be used with `extends` as it shares `%s` with another container", ref, key)
}
if strings.HasPrefix(val, types.ServicePrefix) {
return fmt.Errorf("service %q can't be used with `extends` as it shares `%s` with another service", ref, key)
}
}
}
return nil
}
func getExtendsBaseFromFile(ctx context.Context, name string, path string, opts *Options, ct *cycleTracker) (map[string]any, error) { func getExtendsBaseFromFile(ctx context.Context, name string, path string, opts *Options, ct *cycleTracker) (map[string]any, error) {
for _, loader := range opts.ResourceLoaders { for _, loader := range opts.ResourceLoaders {
if !loader.Accept(path) { if !loader.Accept(path) {

View File

@@ -60,41 +60,43 @@ func ApplyInclude(ctx context.Context, configDetails types.ConfigDetails, model
}) })
} }
var relworkingdir string
for i, p := range r.Path { for i, p := range r.Path {
for _, loader := range options.ResourceLoaders { for _, loader := range options.ResourceLoaders {
if !loader.Accept(p) { if loader.Accept(p) {
continue
}
path, err := loader.Load(ctx, p) path, err := loader.Load(ctx, p)
if err != nil { if err != nil {
return err return err
} }
p = path p = path
break
if i == 0 { // This is the "main" file, used to define project-directory. Others are overrides
relworkingdir = loader.Dir(path)
if r.ProjectDirectory == "" {
r.ProjectDirectory = filepath.Dir(path)
}
for _, f := range included {
if f == path {
included = append(included, path)
return fmt.Errorf("include cycle detected:\n%s\n include %s", included[0], strings.Join(included[1:], "\n include "))
}
}
} }
} }
r.Path[i] = p r.Path[i] = p
} }
mainFile := r.Path[0]
for _, f := range included {
if f == mainFile {
included = append(included, mainFile)
return fmt.Errorf("include cycle detected:\n%s\n include %s", included[0], strings.Join(included[1:], "\n include "))
}
}
if r.ProjectDirectory == "" {
r.ProjectDirectory = filepath.Dir(mainFile)
}
relworkingdir, err := filepath.Rel(configDetails.WorkingDir, r.ProjectDirectory)
if err != nil {
// included file path is not inside project working directory => use absolute path
relworkingdir = r.ProjectDirectory
}
loadOptions := options.clone() loadOptions := options.clone()
loadOptions.ResolvePaths = true loadOptions.ResolvePaths = true
loadOptions.SkipNormalization = true loadOptions.SkipNormalization = true
loadOptions.SkipConsistencyCheck = true loadOptions.SkipConsistencyCheck = true
loadOptions.ResourceLoaders = append(loadOptions.RemoteResourceLoaders(), localResourceLoader{ loadOptions.ResourceLoaders = append(loadOptions.RemoteResourceLoaders(), localResourceLoader{
WorkingDir: r.ProjectDirectory, WorkingDir: relworkingdir,
}) })
if len(r.EnvFile) == 0 { if len(r.EnvFile) == 0 {

View File

@@ -41,7 +41,6 @@ import (
"github.com/compose-spec/compose-go/v2/validation" "github.com/compose-spec/compose-go/v2/validation"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/exp/slices"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -85,15 +84,6 @@ type Options struct {
Listeners []Listener Listeners []Listener
} }
var versionWarning []string
func (o *Options) warnObsoleteVersion(file string) {
if !slices.Contains(versionWarning, file) {
logrus.Warning(fmt.Sprintf("%s: `version` is obsolete", file))
}
versionWarning = append(versionWarning, file)
}
type Listener = func(event string, metadata map[string]any) type Listener = func(event string, metadata map[string]any)
// Invoke all listeners for an event // Invoke all listeners for an event
@@ -295,45 +285,12 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
return LoadWithContext(context.Background(), configDetails, options...) return LoadWithContext(context.Background(), configDetails, options...)
} }
// LoadWithContext reads a ConfigDetails and returns a fully loaded configuration as a compose-go Project // LoadWithContext reads a ConfigDetails and returns a fully loaded configuration
func LoadWithContext(ctx context.Context, configDetails types.ConfigDetails, options ...func(*Options)) (*types.Project, error) { func LoadWithContext(ctx context.Context, configDetails types.ConfigDetails, options ...func(*Options)) (*types.Project, error) {
opts := toOptions(&configDetails, options)
dict, err := loadModelWithContext(ctx, &configDetails, opts)
if err != nil {
return nil, err
}
return modelToProject(dict, opts, configDetails)
}
// LoadModelWithContext reads a ConfigDetails and returns a fully loaded configuration as a yaml dictionary
func LoadModelWithContext(ctx context.Context, configDetails types.ConfigDetails, options ...func(*Options)) (map[string]any, error) {
opts := toOptions(&configDetails, options)
return loadModelWithContext(ctx, &configDetails, opts)
}
// LoadModelWithContext reads a ConfigDetails and returns a fully loaded configuration as a yaml dictionary
func loadModelWithContext(ctx context.Context, configDetails *types.ConfigDetails, opts *Options) (map[string]any, error) {
if len(configDetails.ConfigFiles) < 1 { if len(configDetails.ConfigFiles) < 1 {
return nil, errors.New("No files specified") return nil, errors.New("No files specified")
} }
err := projectName(*configDetails, opts)
if err != nil {
return nil, err
}
// TODO(milas): this should probably ALWAYS set (overriding any existing)
if _, ok := configDetails.Environment[consts.ComposeProjectName]; !ok && opts.projectName != "" {
if configDetails.Environment == nil {
configDetails.Environment = map[string]string{}
}
configDetails.Environment[consts.ComposeProjectName] = opts.projectName
}
return load(ctx, *configDetails, opts, nil)
}
func toOptions(configDetails *types.ConfigDetails, options []func(*Options)) *Options {
opts := &Options{ opts := &Options{
Interpolate: &interp.Options{ Interpolate: &interp.Options{
Substitute: template.Substitute, Substitute: template.Substitute,
@@ -347,7 +304,21 @@ func toOptions(configDetails *types.ConfigDetails, options []func(*Options)) *Op
op(opts) op(opts)
} }
opts.ResourceLoaders = append(opts.ResourceLoaders, localResourceLoader{configDetails.WorkingDir}) opts.ResourceLoaders = append(opts.ResourceLoaders, localResourceLoader{configDetails.WorkingDir})
return opts
err := projectName(configDetails, opts)
if err != nil {
return nil, err
}
// TODO(milas): this should probably ALWAYS set (overriding any existing)
if _, ok := configDetails.Environment[consts.ComposeProjectName]; !ok && opts.projectName != "" {
if configDetails.Environment == nil {
configDetails.Environment = map[string]string{}
}
configDetails.Environment[consts.ComposeProjectName] = opts.projectName
}
return load(ctx, configDetails, opts, nil)
} }
func loadYamlModel(ctx context.Context, config types.ConfigDetails, opts *Options, ct *cycleTracker, included []string) (map[string]interface{}, error) { func loadYamlModel(ctx context.Context, config types.ConfigDetails, opts *Options, ct *cycleTracker, included []string) (map[string]interface{}, error) {
@@ -419,10 +390,6 @@ func loadYamlModel(ctx context.Context, config types.ConfigDetails, opts *Option
if err := schema.Validate(dict); err != nil { if err := schema.Validate(dict); err != nil {
return fmt.Errorf("validating %s: %w", file.Filename, err) return fmt.Errorf("validating %s: %w", file.Filename, err)
} }
if _, ok := dict["version"]; ok {
opts.warnObsoleteVersion(file.Filename)
delete(dict, "version")
}
} }
return err return err
@@ -491,7 +458,7 @@ func loadYamlModel(ctx context.Context, config types.ConfigDetails, opts *Option
return dict, nil return dict, nil
} }
func load(ctx context.Context, configDetails types.ConfigDetails, opts *Options, loaded []string) (map[string]interface{}, error) { func load(ctx context.Context, configDetails types.ConfigDetails, opts *Options, loaded []string) (*types.Project, error) {
mainFile := configDetails.ConfigFiles[0].Filename mainFile := configDetails.ConfigFiles[0].Filename
for _, f := range loaded { for _, f := range loaded {
if f == mainFile { if f == mainFile {
@@ -514,19 +481,6 @@ func load(ctx context.Context, configDetails types.ConfigDetails, opts *Options,
return nil, errors.New("project name must not be empty") return nil, errors.New("project name must not be empty")
} }
if !opts.SkipNormalization {
dict["name"] = opts.projectName
dict, err = Normalize(dict, configDetails.Environment)
if err != nil {
return nil, err
}
}
return dict, nil
}
// modelToProject binds a canonical yaml dict into compose-go structs
func modelToProject(dict map[string]interface{}, opts *Options, configDetails types.ConfigDetails) (*types.Project, error) {
project := &types.Project{ project := &types.Project{
Name: opts.projectName, Name: opts.projectName,
WorkingDir: configDetails.WorkingDir, WorkingDir: configDetails.WorkingDir,
@@ -534,7 +488,6 @@ func modelToProject(dict map[string]interface{}, opts *Options, configDetails ty
} }
delete(dict, "name") // project name set by yaml must be identified by caller as opts.projectName delete(dict, "name") // project name set by yaml must be identified by caller as opts.projectName
var err error
dict, err = processExtensions(dict, tree.NewPath(), opts.KnownExtensions) dict, err = processExtensions(dict, tree.NewPath(), opts.KnownExtensions)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -545,6 +498,13 @@ func modelToProject(dict map[string]interface{}, opts *Options, configDetails ty
return nil, err return nil, err
} }
if !opts.SkipNormalization {
err := Normalize(project)
if err != nil {
return nil, err
}
}
if opts.ConvertWindowsPaths { if opts.ConvertWindowsPaths {
for i, service := range project.Services { for i, service := range project.Services {
for j, volume := range service.Volumes { for j, volume := range service.Volumes {
@@ -554,10 +514,6 @@ func modelToProject(dict map[string]interface{}, opts *Options, configDetails ty
} }
} }
if project, err = project.WithProfiles(opts.Profiles); err != nil {
return nil, err
}
if !opts.SkipConsistencyCheck { if !opts.SkipConsistencyCheck {
err := checkConsistency(project) err := checkConsistency(project)
if err != nil { if err != nil {
@@ -565,12 +521,17 @@ func modelToProject(dict map[string]interface{}, opts *Options, configDetails ty
} }
} }
if project, err = project.WithProfiles(opts.Profiles); err != nil {
return nil, err
}
if !opts.SkipResolveEnvironment { if !opts.SkipResolveEnvironment {
project, err = project.WithServicesEnvironmentResolved(opts.discardEnvFiles) project, err = project.WithServicesEnvironmentResolved(opts.discardEnvFiles)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
return project, nil return project, nil
} }

View File

@@ -18,202 +18,266 @@ package loader
import ( import (
"fmt" "fmt"
"strconv"
"strings" "strings"
"github.com/compose-spec/compose-go/v2/errdefs"
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
"github.com/sirupsen/logrus"
) )
// Normalize compose project by moving deprecated attributes to their canonical position and injecting implicit defaults // Normalize compose project by moving deprecated attributes to their canonical position and injecting implicit defaults
func Normalize(dict map[string]any, env types.Mapping) (map[string]any, error) { func Normalize(project *types.Project) error {
dict["networks"] = normalizeNetworks(dict) if project.Networks == nil {
project.Networks = make(map[string]types.NetworkConfig)
if d, ok := dict["services"]; ok {
services := d.(map[string]any)
for name, s := range services {
service := s.(map[string]any)
_, hasNetworks := service["networks"]
_, hasNetworkMode := service["network_mode"]
if !hasNetworks && !hasNetworkMode {
// Service without explicit network attachment are implicitly exposed on default network
service["networks"] = map[string]any{"default": nil}
} }
if service["pull_policy"] == types.PullPolicyIfNotPresent { // If not declared explicitly, Compose model involves an implicit "default" network
service["pull_policy"] = types.PullPolicyMissing if _, ok := project.Networks["default"]; !ok {
project.Networks["default"] = types.NetworkConfig{}
}
for name, s := range project.Services {
if len(s.Networks) == 0 && s.NetworkMode == "" {
// Service without explicit network attachment are implicitly exposed on default network
s.Networks = map[string]*types.ServiceNetworkConfig{"default": nil}
}
if s.PullPolicy == types.PullPolicyIfNotPresent {
s.PullPolicy = types.PullPolicyMissing
} }
fn := func(s string) (string, bool) { fn := func(s string) (string, bool) {
v, ok := env[s] v, ok := project.Environment[s]
return v, ok return v, ok
} }
if b, ok := service["build"]; ok { if s.Build != nil {
build := b.(map[string]any) if s.Build.Context == "" {
if build["context"] == nil { s.Build.Context = "."
build["context"] = "."
} }
if build["dockerfile"] == nil && build["dockerfile_inline"] == nil { if s.Build.Dockerfile == "" && s.Build.DockerfileInline == "" {
build["dockerfile"] = "Dockerfile" s.Build.Dockerfile = "Dockerfile"
} }
s.Build.Args = s.Build.Args.Resolve(fn)
}
s.Environment = s.Environment.Resolve(fn)
if a, ok := build["args"]; ok { for _, link := range s.Links {
build["args"], _ = resolve(a, fn)
}
service["build"] = build
}
if e, ok := service["environment"]; ok {
service["environment"], _ = resolve(e, fn)
}
var dependsOn map[string]any
if d, ok := service["depends_on"]; ok {
dependsOn = d.(map[string]any)
} else {
dependsOn = map[string]any{}
}
if l, ok := service["links"]; ok {
links := l.([]any)
for _, e := range links {
link := e.(string)
parts := strings.Split(link, ":") parts := strings.Split(link, ":")
if len(parts) == 2 { if len(parts) == 2 {
link = parts[0] link = parts[0]
} }
if _, ok := dependsOn[link]; !ok { s.DependsOn = setIfMissing(s.DependsOn, link, types.ServiceDependency{
dependsOn[link] = map[string]any{ Condition: types.ServiceConditionStarted,
"condition": types.ServiceConditionStarted, Restart: true,
"restart": true, Required: true,
"required": true, })
}
} }
for _, namespace := range []string{s.NetworkMode, s.Ipc, s.Pid, s.Uts, s.Cgroup} {
if strings.HasPrefix(namespace, types.ServicePrefix) {
name := namespace[len(types.ServicePrefix):]
s.DependsOn = setIfMissing(s.DependsOn, name, types.ServiceDependency{
Condition: types.ServiceConditionStarted,
Restart: true,
Required: true,
})
} }
} }
for _, namespace := range []string{"network_mode", "ipc", "pid", "uts", "cgroup"} { for _, vol := range s.VolumesFrom {
if n, ok := service[namespace]; ok {
ref := n.(string)
if strings.HasPrefix(ref, types.ServicePrefix) {
shared := ref[len(types.ServicePrefix):]
if _, ok := dependsOn[shared]; !ok {
dependsOn[shared] = map[string]any{
"condition": types.ServiceConditionStarted,
"restart": true,
"required": true,
}
}
}
}
}
if n, ok := service["volumes_from"]; ok {
volumesFrom := n.([]any)
for _, v := range volumesFrom {
vol := v.(string)
if !strings.HasPrefix(vol, types.ContainerPrefix) { if !strings.HasPrefix(vol, types.ContainerPrefix) {
spec := strings.Split(vol, ":") spec := strings.Split(vol, ":")
if _, ok := dependsOn[spec[0]]; !ok { s.DependsOn = setIfMissing(s.DependsOn, spec[0], types.ServiceDependency{
dependsOn[spec[0]] = map[string]any{ Condition: types.ServiceConditionStarted,
"condition": types.ServiceConditionStarted, Restart: false,
"restart": false, Required: true,
"required": true, })
} }
} }
}
}
}
if len(dependsOn) > 0 {
service["depends_on"] = dependsOn
}
services[name] = service
}
dict["services"] = services
}
setNameFromKey(dict) err := relocateLogDriver(&s)
if err != nil {
return err
}
return dict, nil err = relocateLogOpt(&s)
if err != nil {
return err
}
err = relocateDockerfile(&s)
if err != nil {
return err
}
inferImplicitDependencies(&s)
project.Services[name] = s
}
setNameFromKey(project)
return nil
} }
func normalizeNetworks(dict map[string]any) map[string]any { // IsServiceDependency check the relation set by ref refers to a service
var networks map[string]any func IsServiceDependency(ref string) (string, bool) {
if n, ok := dict["networks"]; ok { if strings.HasPrefix(
networks = n.(map[string]any) ref,
} else { types.ServicePrefix,
networks = map[string]any{} ) {
} return ref[len(types.ServicePrefix):], true
if _, ok := networks["default"]; !ok {
// If not declared explicitly, Compose model involves an implicit "default" network
networks["default"] = nil
}
return networks
}
func resolve(a any, fn func(s string) (string, bool)) (any, bool) {
switch v := a.(type) {
case []any:
var resolved []any
for _, val := range v {
if r, ok := resolve(val, fn); ok {
resolved = append(resolved, r)
}
}
return resolved, true
case map[string]any:
resolved := map[string]any{}
for key, val := range v {
if val != nil {
resolved[key] = val
continue
}
if s, ok := fn(key); ok {
resolved[key] = s
}
}
return resolved, true
case string:
if !strings.Contains(v, "=") {
if val, ok := fn(v); ok {
return fmt.Sprintf("%s=%s", v, val), true
} }
return "", false return "", false
}
func inferImplicitDependencies(service *types.ServiceConfig) {
var dependencies []string
maybeReferences := []string{
service.NetworkMode,
service.Ipc,
service.Pid,
service.Uts,
service.Cgroup,
} }
return v, true for _, ref := range maybeReferences {
default: if dep, ok := IsServiceDependency(ref); ok {
return v, false dependencies = append(dependencies, dep)
} }
}
for _, vol := range service.VolumesFrom {
spec := strings.Split(vol, ":")
if len(spec) == 0 {
continue
}
if spec[0] == "container" {
continue
}
dependencies = append(dependencies, spec[0])
}
for _, link := range service.Links {
dependencies = append(dependencies, strings.Split(link, ":")[0])
}
if len(dependencies) > 0 && service.DependsOn == nil {
service.DependsOn = make(types.DependsOnConfig)
}
for _, d := range dependencies {
if _, ok := service.DependsOn[d]; !ok {
service.DependsOn[d] = types.ServiceDependency{
Condition: types.ServiceConditionStarted,
Required: true,
}
}
}
}
// setIfMissing adds a ServiceDependency for service if not already defined
func setIfMissing(d types.DependsOnConfig, service string, dep types.ServiceDependency) types.DependsOnConfig {
if d == nil {
d = types.DependsOnConfig{}
}
if _, ok := d[service]; !ok {
d[service] = dep
}
return d
} }
// Resources with no explicit name are actually named by their key in map // Resources with no explicit name are actually named by their key in map
func setNameFromKey(dict map[string]any) { func setNameFromKey(project *types.Project) {
for _, r := range []string{"networks", "volumes", "configs", "secrets"} { for key, n := range project.Networks {
a, ok := dict[r] if n.Name == "" {
if !ok { if n.External {
continue n.Name = key
}
toplevel := a.(map[string]any)
for key, r := range toplevel {
var resource map[string]any
if r != nil {
resource = r.(map[string]any)
} else { } else {
resource = map[string]any{} n.Name = fmt.Sprintf("%s_%s", project.Name, key)
} }
if resource["name"] == nil { project.Networks[key] = n
if x, ok := resource["external"]; ok && isTrue(x) { }
resource["name"] = key }
for key, v := range project.Volumes {
if v.Name == "" {
if v.External {
v.Name = key
} else { } else {
resource["name"] = fmt.Sprintf("%s_%s", dict["name"], key) v.Name = fmt.Sprintf("%s_%s", project.Name, key)
}
project.Volumes[key] = v
} }
} }
toplevel[key] = resource
for key, c := range project.Configs {
if c.Name == "" {
if c.External {
c.Name = key
} else {
c.Name = fmt.Sprintf("%s_%s", project.Name, key)
}
project.Configs[key] = c
}
}
for key, s := range project.Secrets {
if s.Name == "" {
if s.External {
s.Name = key
} else {
s.Name = fmt.Sprintf("%s_%s", project.Name, key)
}
project.Secrets[key] = s
} }
} }
} }
func isTrue(x any) bool { func relocateLogOpt(s *types.ServiceConfig) error {
parseBool, _ := strconv.ParseBool(fmt.Sprint(x)) if len(s.LogOpt) != 0 {
return parseBool logrus.Warn("`log_opts` is deprecated. Use the `logging` element")
if s.Logging == nil {
s.Logging = &types.LoggingConfig{}
}
for k, v := range s.LogOpt {
if _, ok := s.Logging.Options[k]; !ok {
s.Logging.Options[k] = v
} else {
return fmt.Errorf("can't use both 'log_opt' (deprecated) and 'logging.options': %w", errdefs.ErrInvalid)
}
}
}
return nil
}
func relocateLogDriver(s *types.ServiceConfig) error {
if s.LogDriver != "" {
logrus.Warn("`log_driver` is deprecated. Use the `logging` element")
if s.Logging == nil {
s.Logging = &types.LoggingConfig{}
}
if s.Logging.Driver == "" {
s.Logging.Driver = s.LogDriver
} else {
return fmt.Errorf("can't use both 'log_driver' (deprecated) and 'logging.driver': %w", errdefs.ErrInvalid)
}
}
return nil
}
func relocateDockerfile(s *types.ServiceConfig) error {
if s.Dockerfile != "" {
logrus.Warn("`dockerfile` is deprecated. Use the `build` element")
if s.Build == nil {
s.Build = &types.BuildConfig{}
}
if s.Dockerfile == "" {
s.Build.Dockerfile = s.Dockerfile
} else {
return fmt.Errorf("can't use both 'dockerfile' (deprecated) and 'build.dockerfile': %w", errdefs.ErrInvalid)
}
}
return nil
} }

View File

@@ -17,6 +17,7 @@
package loader package loader
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
@@ -70,13 +71,16 @@ func checkConsistency(project *types.Project) error {
} }
} }
for dependedService, cfg := range s.DependsOn { for dependedService := range s.DependsOn {
if _, err := project.GetService(dependedService); err != nil { if _, err := project.GetService(dependedService); err != nil {
if errors.Is(err, errdefs.ErrDisabled) && !cfg.Required { return fmt.Errorf("service %q depends on undefined service %s: %w", s.Name, dependedService, errdefs.ErrInvalid)
continue
} }
return fmt.Errorf("service %q depends on undefined service %q: %w", s.Name, dependedService, errdefs.ErrInvalid)
} }
// Check there isn't a cycle in depends_on declarations
if err := graph.InDependencyOrder(context.Background(), project, func(ctx context.Context, s string, config types.ServiceConfig) error {
return nil
}); err != nil {
return err
} }
if strings.HasPrefix(s.NetworkMode, types.ServicePrefix) { if strings.HasPrefix(s.NetworkMode, types.ServicePrefix) {
@@ -120,31 +124,6 @@ func checkConsistency(project *types.Project) error {
s.Deploy.Replicas = s.Scale s.Deploy.Replicas = s.Scale
} }
if s.CPUS != 0 && s.Deploy != nil {
if s.Deploy.Resources.Limits != nil && s.Deploy.Resources.Limits.NanoCPUs.Value() != s.CPUS {
return fmt.Errorf("services.%s: can't set distinct values on 'cpus' and 'deploy.resources.limits.cpus': %w",
s.Name, errdefs.ErrInvalid)
}
}
if s.MemLimit != 0 && s.Deploy != nil {
if s.Deploy.Resources.Limits != nil && s.Deploy.Resources.Limits.MemoryBytes != s.MemLimit {
return fmt.Errorf("services.%s: can't set distinct values on 'mem_limit' and 'deploy.resources.limits.memory': %w",
s.Name, errdefs.ErrInvalid)
}
}
if s.MemReservation != 0 && s.Deploy != nil {
if s.Deploy.Resources.Reservations != nil && s.Deploy.Resources.Reservations.MemoryBytes != s.MemReservation {
return fmt.Errorf("services.%s: can't set distinct values on 'mem_reservation' and 'deploy.resources.reservations.memory': %w",
s.Name, errdefs.ErrInvalid)
}
}
if s.PidsLimit != 0 && s.Deploy != nil {
if s.Deploy.Resources.Limits != nil && s.Deploy.Resources.Limits.Pids != s.PidsLimit {
return fmt.Errorf("services.%s: can't set distinct values on 'pids_limit' and 'deploy.resources.limits.pids': %w",
s.Name, errdefs.ErrInvalid)
}
}
if s.ContainerName != "" { if s.ContainerName != "" {
if existing, ok := containerNames[s.ContainerName]; ok { if existing, ok := containerNames[s.ContainerName]; ok {
return fmt.Errorf(`"services.%s": container name "%s" is already in use by "services.%s": %w`, s.Name, s.ContainerName, existing, errdefs.ErrInvalid) return fmt.Errorf(`"services.%s": container name "%s" is already in use by "services.%s": %w`, s.Name, s.ContainerName, existing, errdefs.ErrInvalid)
@@ -180,5 +159,5 @@ func checkConsistency(project *types.Project) error {
} }
} }
return graph.CheckCycle(project) return nil
} }

View File

@@ -322,13 +322,11 @@
{ {
"type": "object", "type": "object",
"properties": { "properties": {
"name": {"type": "string"},
"mode": {"type": "string"}, "mode": {"type": "string"},
"host_ip": {"type": "string"}, "host_ip": {"type": "string"},
"target": {"type": "integer"}, "target": {"type": "integer"},
"published": {"type": ["string", "integer"]}, "published": {"type": ["string", "integer"]},
"protocol": {"type": "string"}, "protocol": {"type": "string"}
"app_protocol": {"type": "string"}
}, },
"additionalProperties": false, "additionalProperties": false,
"patternProperties": {"^x-": {}} "patternProperties": {"^x-": {}}
@@ -391,8 +389,7 @@
"volume": { "volume": {
"type": "object", "type": "object",
"properties": { "properties": {
"nocopy": {"type": "boolean"}, "nocopy": {"type": "boolean"}
"subpath": {"type": "string"}
}, },
"additionalProperties": false, "additionalProperties": false,
"patternProperties": {"^x-": {}} "patternProperties": {"^x-": {}}

View File

@@ -44,7 +44,7 @@ var patternString = fmt.Sprintf(
groupInvalid, groupInvalid,
) )
var DefaultPattern = regexp.MustCompile(patternString) var defaultPattern = regexp.MustCompile(patternString)
// InvalidTemplateError is returned when a variable template is not in a valid // InvalidTemplateError is returned when a variable template is not in a valid
// format // format
@@ -121,7 +121,7 @@ func SubstituteWithOptions(template string, mapping Mapping, options ...Option)
var returnErr error var returnErr error
cfg := &Config{ cfg := &Config{
pattern: DefaultPattern, pattern: defaultPattern,
replacementFunc: DefaultReplacementFunc, replacementFunc: DefaultReplacementFunc,
logging: true, logging: true,
} }
@@ -268,14 +268,14 @@ func getFirstBraceClosingIndex(s string) int {
// Substitute variables in the string with their values // Substitute variables in the string with their values
func Substitute(template string, mapping Mapping) (string, error) { func Substitute(template string, mapping Mapping) (string, error) {
return SubstituteWith(template, mapping, DefaultPattern) return SubstituteWith(template, mapping, defaultPattern)
} }
// ExtractVariables returns a map of all the variables defined in the specified // ExtractVariables returns a map of all the variables defined in the specified
// composefile (dict representation) and their default value if any. // composefile (dict representation) and their default value if any.
func ExtractVariables(configDict map[string]interface{}, pattern *regexp.Regexp) map[string]Variable { func ExtractVariables(configDict map[string]interface{}, pattern *regexp.Regexp) map[string]Variable {
if pattern == nil { if pattern == nil {
pattern = DefaultPattern pattern = defaultPattern
} }
return recurseExtract(configDict, pattern) return recurseExtract(configDict, pattern)
} }

View File

@@ -18,7 +18,6 @@ package types
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
@@ -26,7 +25,6 @@ import (
"sort" "sort"
"github.com/compose-spec/compose-go/v2/dotenv" "github.com/compose-spec/compose-go/v2/dotenv"
"github.com/compose-spec/compose-go/v2/errdefs"
"github.com/compose-spec/compose-go/v2/utils" "github.com/compose-spec/compose-go/v2/utils"
"github.com/distribution/reference" "github.com/distribution/reference"
"github.com/mitchellh/copystructure" "github.com/mitchellh/copystructure"
@@ -217,9 +215,9 @@ func (p *Project) GetService(name string) (ServiceConfig, error) {
if !ok { if !ok {
_, ok := p.DisabledServices[name] _, ok := p.DisabledServices[name]
if ok { if ok {
return ServiceConfig{}, fmt.Errorf("no such service: %s: %w", name, errdefs.ErrDisabled) return ServiceConfig{}, fmt.Errorf("service %s is disabled", name)
} }
return ServiceConfig{}, fmt.Errorf("no such service: %s: %w", name, errdefs.ErrNotFound) return ServiceConfig{}, fmt.Errorf("no such service: %s", name)
} }
return service, nil return service, nil
} }
@@ -333,9 +331,6 @@ func (s ServiceConfig) HasProfile(profiles []string) bool {
return true return true
} }
for _, p := range profiles { for _, p := range profiles {
if p == "*" {
return true
}
for _, sp := range s.Profiles { for _, sp := range s.Profiles {
if sp == p { if sp == p {
return true return true
@@ -349,6 +344,11 @@ func (s ServiceConfig) HasProfile(profiles []string) bool {
// It returns a new Project instance with the changes and keep the original Project unchanged // It returns a new Project instance with the changes and keep the original Project unchanged
func (p *Project) WithProfiles(profiles []string) (*Project, error) { func (p *Project) WithProfiles(profiles []string) (*Project, error) {
newProject := p.deepCopy() newProject := p.deepCopy()
for _, p := range profiles {
if p == "*" {
return newProject, nil
}
}
enabled := Services{} enabled := Services{}
disabled := Services{} disabled := Services{}
for name, service := range newProject.AllServices() { for name, service := range newProject.AllServices() {
@@ -536,29 +536,39 @@ func (p *Project) WithServicesDisabled(names ...string) *Project {
// WithImagesResolved updates services images to include digest computed by a resolver function // WithImagesResolved updates services images to include digest computed by a resolver function
// It returns a new Project instance with the changes and keep the original Project unchanged // It returns a new Project instance with the changes and keep the original Project unchanged
func (p *Project) WithImagesResolved(resolver func(named reference.Named) (godigest.Digest, error)) (*Project, error) { func (p *Project) WithImagesResolved(resolver func(named reference.Named) (godigest.Digest, error)) (*Project, error) {
return p.WithServicesTransform(func(name string, service ServiceConfig) (ServiceConfig, error) { newProject := p.deepCopy()
eg := errgroup.Group{}
for i, s := range newProject.Services {
idx := i
service := s
if service.Image == "" { if service.Image == "" {
return service, nil continue
} }
eg.Go(func() error {
named, err := reference.ParseDockerRef(service.Image) named, err := reference.ParseDockerRef(service.Image)
if err != nil { if err != nil {
return service, err return err
} }
if _, ok := named.(reference.Canonical); !ok { if _, ok := named.(reference.Canonical); !ok {
// image is named but not digested reference // image is named but not digested reference
digest, err := resolver(named) digest, err := resolver(named)
if err != nil { if err != nil {
return service, err return err
} }
named, err = reference.WithDigest(named, digest) named, err = reference.WithDigest(named, digest)
if err != nil { if err != nil {
return service, err return err
} }
} }
service.Image = named.String() service.Image = named.String()
return service, nil newProject.Services[idx] = service
return nil
}) })
}
return newProject, eg.Wait()
} }
// MarshalYAML marshal Project into a yaml tree // MarshalYAML marshal Project into a yaml tree
@@ -596,7 +606,7 @@ func (p *Project) MarshalJSON() ([]byte, error) {
for k, v := range p.Extensions { for k, v := range p.Extensions {
m[k] = v m[k] = v
} }
return json.MarshalIndent(m, "", " ") return json.Marshal(m)
} }
// WithServicesEnvironmentResolved parses env_files set for services to resolve the actual environment map for services // WithServicesEnvironmentResolved parses env_files set for services to resolve the actual environment map for services
@@ -652,47 +662,3 @@ func (p *Project) deepCopy() *Project {
} }
return instance.(*Project) return instance.(*Project)
} }
// WithServicesTransform applies a transformation to project services and return a new project with transformation results
func (p *Project) WithServicesTransform(fn func(name string, s ServiceConfig) (ServiceConfig, error)) (*Project, error) {
type result struct {
name string
service ServiceConfig
}
resultCh := make(chan result)
newProject := p.deepCopy()
eg, ctx := errgroup.WithContext(context.Background())
eg.Go(func() error {
expect := len(newProject.Services)
s := Services{}
for expect > 0 {
select {
case <-ctx.Done():
// interrupted as some goroutine returned an error
return nil
case r := <-resultCh:
s[r.name] = r.service
expect--
}
}
newProject.Services = s
return nil
})
for n, s := range newProject.Services {
name := n
service := s
eg.Go(func() error {
updated, err := fn(name, service)
if err != nil {
return err
}
resultCh <- result{
name: name,
service: updated,
}
return nil
})
}
return newProject, eg.Wait()
}

View File

@@ -20,7 +20,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"sort" "sort"
"strconv"
"strings" "strings"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
@@ -366,7 +365,7 @@ type Resources struct {
// Resource is a resource to be limited or reserved // Resource is a resource to be limited or reserved
type Resource struct { type Resource struct {
// TODO: types to convert from units and ratios // TODO: types to convert from units and ratios
NanoCPUs NanoCPUs `yaml:"cpus,omitempty" json:"cpus,omitempty"` NanoCPUs string `yaml:"cpus,omitempty" json:"cpus,omitempty"`
MemoryBytes UnitBytes `yaml:"memory,omitempty" json:"memory,omitempty"` MemoryBytes UnitBytes `yaml:"memory,omitempty" json:"memory,omitempty"`
Pids int64 `yaml:"pids,omitempty" json:"pids,omitempty"` Pids int64 `yaml:"pids,omitempty" json:"pids,omitempty"`
Devices []DeviceRequest `yaml:"devices,omitempty" json:"devices,omitempty"` Devices []DeviceRequest `yaml:"devices,omitempty" json:"devices,omitempty"`
@@ -375,30 +374,6 @@ type Resource struct {
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
} }
type NanoCPUs float32
func (n *NanoCPUs) DecodeMapstructure(a any) error {
switch v := a.(type) {
case string:
f, err := strconv.ParseFloat(v, 64)
if err != nil {
return err
}
*n = NanoCPUs(f)
case float32:
*n = NanoCPUs(v)
case float64:
*n = NanoCPUs(v)
default:
return fmt.Errorf("unexpected value type %T for cpus", v)
}
return nil
}
func (n *NanoCPUs) Value() float32 {
return float32(*n)
}
// GenericResource represents a "user defined" resource which can // GenericResource represents a "user defined" resource which can
// only be an integer (e.g: SSD=3) for a service // only be an integer (e.g: SSD=3) for a service
type GenericResource struct { type GenericResource struct {
@@ -458,13 +433,11 @@ type ServiceNetworkConfig struct {
// ServicePortConfig is the port configuration for a service // ServicePortConfig is the port configuration for a service
type ServicePortConfig struct { type ServicePortConfig struct {
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Mode string `yaml:"mode,omitempty" json:"mode,omitempty"` Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`
HostIP string `yaml:"host_ip,omitempty" json:"host_ip,omitempty"` HostIP string `yaml:"host_ip,omitempty" json:"host_ip,omitempty"`
Target uint32 `yaml:"target,omitempty" json:"target,omitempty"` Target uint32 `yaml:"target,omitempty" json:"target,omitempty"`
Published string `yaml:"published,omitempty" json:"published,omitempty"` Published string `yaml:"published,omitempty" json:"published,omitempty"`
Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"` Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"`
AppProtocol string `yaml:"app_protocol,omitempty" json:"app_protocol,omitempty"`
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
} }
@@ -538,9 +511,6 @@ func (s ServiceVolumeConfig) String() string {
if s.Volume != nil && s.Volume.NoCopy { if s.Volume != nil && s.Volume.NoCopy {
options = append(options, "nocopy") options = append(options, "nocopy")
} }
if s.Volume != nil && s.Volume.Subpath != "" {
options = append(options, s.Volume.Subpath)
}
return fmt.Sprintf("%s:%s:%s", s.Source, s.Target, strings.Join(options, ",")) return fmt.Sprintf("%s:%s:%s", s.Source, s.Target, strings.Join(options, ","))
} }
@@ -598,7 +568,6 @@ const (
// ServiceVolumeVolume are options for a service volume of type volume // ServiceVolumeVolume are options for a service volume of type volume
type ServiceVolumeVolume struct { type ServiceVolumeVolume struct {
NoCopy bool `yaml:"nocopy,omitempty" json:"nocopy,omitempty"` NoCopy bool `yaml:"nocopy,omitempty" json:"nocopy,omitempty"`
Subpath string `yaml:"subpath,omitempty" json:"subpath,omitempty"`
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
} }

View File

@@ -31,6 +31,9 @@ import (
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
) )
// maxResets is the no.of times the Copy() method can tolerate a reset of the body
const maxResets = 5
var ErrReset = errors.New("writer has been reset") var ErrReset = errors.New("writer has been reset")
var bufPool = sync.Pool{ var bufPool = sync.Pool{
@@ -157,7 +160,7 @@ func Copy(ctx context.Context, cw Writer, or io.Reader, size int64, expected dig
} }
} }
for i := 0; ; i++ { for i := 0; i < maxResets; i++ {
if i >= 1 { if i >= 1 {
log.G(ctx).WithField("digest", expected).Debugf("retrying copy due to reset") log.G(ctx).WithField("digest", expected).Debugf("retrying copy due to reset")
} }
@@ -198,6 +201,9 @@ func Copy(ctx context.Context, cw Writer, or io.Reader, size int64, expected dig
} }
return nil return nil
} }
log.G(ctx).WithField("digest", expected).Errorf("failed to copy after %d retries", maxResets)
return fmt.Errorf("failed to copy after %d retries", maxResets)
} }
// CopyReaderAt copies to a writer from a given reader at for the given // CopyReaderAt copies to a writer from a given reader at for the given

View File

@@ -471,7 +471,6 @@ func ociIndexRecord(manifests []ocispec.Descriptor) tarRecord {
Versioned: ocispecs.Versioned{ Versioned: ocispecs.Versioned{
SchemaVersion: 2, SchemaVersion: 2,
}, },
MediaType: ocispec.MediaTypeImageIndex,
Manifests: manifests, Manifests: manifests,
} }

View File

@@ -284,7 +284,7 @@ func (p dockerPusher) push(ctx context.Context, desc ocispec.Descriptor, ref str
req.body = func() (io.ReadCloser, error) { req.body = func() (io.ReadCloser, error) {
pr, pw := io.Pipe() pr, pw := io.Pipe()
pushw.setPipe(pw) pushw.setPipe(pw)
return pr, nil return io.NopCloser(pr), nil
} }
req.size = desc.Size req.size = desc.Size
@@ -292,6 +292,7 @@ func (p dockerPusher) push(ctx context.Context, desc ocispec.Descriptor, ref str
resp, err := req.doWithRetries(ctx, nil) resp, err := req.doWithRetries(ctx, nil)
if err != nil { if err != nil {
pushw.setError(err) pushw.setError(err)
pushw.Close()
return return
} }
@@ -301,7 +302,7 @@ func (p dockerPusher) push(ctx context.Context, desc ocispec.Descriptor, ref str
err := remoteserrors.NewUnexpectedStatusErr(resp) err := remoteserrors.NewUnexpectedStatusErr(resp)
log.G(ctx).WithField("resp", resp).WithField("body", string(err.(remoteserrors.ErrUnexpectedStatus).Body)).Debug("unexpected response") log.G(ctx).WithField("resp", resp).WithField("body", string(err.(remoteserrors.ErrUnexpectedStatus).Body)).Debug("unexpected response")
pushw.setError(err) pushw.setError(err)
return pushw.Close()
} }
pushw.setResponse(resp) pushw.setResponse(resp)
}() }()
@@ -334,11 +335,9 @@ type pushWriter struct {
pipe *io.PipeWriter pipe *io.PipeWriter
done chan struct{}
closeOnce sync.Once
pipeC chan *io.PipeWriter pipeC chan *io.PipeWriter
respC chan *http.Response respC chan *http.Response
closeOnce sync.Once
errC chan error errC chan error
isManifest bool isManifest bool
@@ -357,51 +356,19 @@ func newPushWriter(db *dockerBase, ref string, expected digest.Digest, tracker S
pipeC: make(chan *io.PipeWriter, 1), pipeC: make(chan *io.PipeWriter, 1),
respC: make(chan *http.Response, 1), respC: make(chan *http.Response, 1),
errC: make(chan error, 1), errC: make(chan error, 1),
done: make(chan struct{}),
isManifest: isManifest, isManifest: isManifest,
} }
} }
func (pw *pushWriter) setPipe(p *io.PipeWriter) { func (pw *pushWriter) setPipe(p *io.PipeWriter) {
select { pw.pipeC <- p
case <-pw.done:
case pw.pipeC <- p:
}
} }
func (pw *pushWriter) setError(err error) { func (pw *pushWriter) setError(err error) {
select { pw.errC <- err
case <-pw.done:
case pw.errC <- err:
}
} }
func (pw *pushWriter) setResponse(resp *http.Response) { func (pw *pushWriter) setResponse(resp *http.Response) {
select { pw.respC <- resp
case <-pw.done:
case pw.respC <- resp:
}
}
func (pw *pushWriter) replacePipe(p *io.PipeWriter) error {
if pw.pipe == nil {
pw.pipe = p
return nil
}
pw.pipe.CloseWithError(content.ErrReset)
pw.pipe = p
// If content has already been written, the bytes
// cannot be written again and the caller must reset
status, err := pw.tracker.GetStatus(pw.ref)
if err != nil {
return err
}
status.Offset = 0
status.UpdatedAt = time.Now()
pw.tracker.SetStatus(pw.ref, status)
return content.ErrReset
} }
func (pw *pushWriter) Write(p []byte) (n int, err error) { func (pw *pushWriter) Write(p []byte) (n int, err error) {
@@ -411,18 +378,26 @@ func (pw *pushWriter) Write(p []byte) (n int, err error) {
} }
if pw.pipe == nil { if pw.pipe == nil {
select { p, ok := <-pw.pipeC
case <-pw.done: if !ok {
return 0, io.ErrClosedPipe return 0, io.ErrClosedPipe
case p := <-pw.pipeC:
pw.replacePipe(p)
} }
pw.pipe = p
} else { } else {
select { select {
case <-pw.done: case p, ok := <-pw.pipeC:
if !ok {
return 0, io.ErrClosedPipe return 0, io.ErrClosedPipe
case p := <-pw.pipeC: }
return 0, pw.replacePipe(p) pw.pipe.CloseWithError(content.ErrReset)
pw.pipe = p
// If content has already been written, the bytes
// cannot be written and the caller must reset
status.Offset = 0
status.UpdatedAt = time.Now()
pw.tracker.SetStatus(pw.ref, status)
return 0, content.ErrReset
default: default:
} }
} }
@@ -432,13 +407,9 @@ func (pw *pushWriter) Write(p []byte) (n int, err error) {
// if the pipe is closed, we might have the original error on the error // if the pipe is closed, we might have the original error on the error
// channel - so we should try and get it // channel - so we should try and get it
select { select {
case <-pw.done: case err2 := <-pw.errC:
case err = <-pw.errC: err = err2
pw.Close() default:
case p := <-pw.pipeC:
return 0, pw.replacePipe(p)
case resp := <-pw.respC:
pw.setResponse(resp)
} }
} }
status.Offset += int64(n) status.Offset += int64(n)
@@ -451,7 +422,7 @@ func (pw *pushWriter) Close() error {
// Ensure pipeC is closed but handle `Close()` being // Ensure pipeC is closed but handle `Close()` being
// called multiple times without panicking // called multiple times without panicking
pw.closeOnce.Do(func() { pw.closeOnce.Do(func() {
close(pw.done) close(pw.pipeC)
}) })
if pw.pipe != nil { if pw.pipe != nil {
status, err := pw.tracker.GetStatus(pw.ref) status, err := pw.tracker.GetStatus(pw.ref)
@@ -491,18 +462,30 @@ func (pw *pushWriter) Commit(ctx context.Context, size int64, expected digest.Di
// TODO: timeout waiting for response // TODO: timeout waiting for response
var resp *http.Response var resp *http.Response
select { select {
case <-pw.done:
return io.ErrClosedPipe
case err := <-pw.errC: case err := <-pw.errC:
pw.Close()
return err return err
case resp = <-pw.respC: case resp = <-pw.respC:
defer resp.Body.Close() defer resp.Body.Close()
case p := <-pw.pipeC: case p, ok := <-pw.pipeC:
// check whether the pipe has changed in the commit, because sometimes Write // check whether the pipe has changed in the commit, because sometimes Write
// can complete successfully, but the pipe may have changed. In that case, the // can complete successfully, but the pipe may have changed. In that case, the
// content needs to be reset. // content needs to be reset.
return pw.replacePipe(p) if !ok {
return io.ErrClosedPipe
}
pw.pipe.CloseWithError(content.ErrReset)
pw.pipe = p
// If content has already been written, the bytes
// cannot be written again and the caller must reset
status, err := pw.tracker.GetStatus(pw.ref)
if err != nil {
return err
}
status.Offset = 0
status.UpdatedAt = time.Now()
pw.tracker.SetStatus(pw.ref, status)
return content.ErrReset
} }
// 201 is specified return status, some registries return // 201 is specified return status, some registries return

View File

@@ -23,7 +23,7 @@ var (
Package = "github.com/containerd/containerd" Package = "github.com/containerd/containerd"
// Version holds the complete version number. Filled in at linking time. // Version holds the complete version number. Filled in at linking time.
Version = "1.7.15+unknown" Version = "1.7.13+unknown"
// Revision is filled with the VCS (e.g. git) revision being used to build // Revision is filled with the VCS (e.g. git) revision being used to build
// the program at linking time. // the program at linking time.

View File

@@ -71,42 +71,6 @@ func WithUnaryClientInterceptor(i UnaryClientInterceptor) ClientOpts {
} }
} }
// WithChainUnaryClientInterceptor sets the provided chain of client interceptors
func WithChainUnaryClientInterceptor(interceptors ...UnaryClientInterceptor) ClientOpts {
return func(c *Client) {
if len(interceptors) == 0 {
return
}
if c.interceptor != nil {
interceptors = append([]UnaryClientInterceptor{c.interceptor}, interceptors...)
}
c.interceptor = func(
ctx context.Context,
req *Request,
reply *Response,
info *UnaryClientInfo,
final Invoker,
) error {
return interceptors[0](ctx, req, reply, info,
chainUnaryInterceptors(interceptors[1:], final, info))
}
}
}
func chainUnaryInterceptors(interceptors []UnaryClientInterceptor, final Invoker, info *UnaryClientInfo) Invoker {
if len(interceptors) == 0 {
return final
}
return func(
ctx context.Context,
req *Request,
reply *Response,
) error {
return interceptors[0](ctx, req, reply, info,
chainUnaryInterceptors(interceptors[1:], final, info))
}
}
// NewClient creates a new ttrpc client using the given connection // NewClient creates a new ttrpc client using the given connection
func NewClient(conn net.Conn, opts ...ClientOpts) *Client { func NewClient(conn net.Conn, opts ...ClientOpts) *Client {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@@ -121,16 +85,13 @@ func NewClient(conn net.Conn, opts ...ClientOpts) *Client {
ctx: ctx, ctx: ctx,
userCloseFunc: func() {}, userCloseFunc: func() {},
userCloseWaitCh: make(chan struct{}), userCloseWaitCh: make(chan struct{}),
interceptor: defaultClientInterceptor,
} }
for _, o := range opts { for _, o := range opts {
o(c) o(c)
} }
if c.interceptor == nil {
c.interceptor = defaultClientInterceptor
}
go c.run() go c.run()
return c return c
} }
@@ -325,7 +286,7 @@ func (c *Client) Close() error {
return nil return nil
} }
// UserOnCloseWait is used to block until the user's on-close callback // UserOnCloseWait is used to blocks untils the user's on-close callback
// finishes. // finishes.
func (c *Client) UserOnCloseWait(ctx context.Context) error { func (c *Client) UserOnCloseWait(ctx context.Context) error {
select { select {

View File

@@ -16,10 +16,7 @@
package ttrpc package ttrpc
import ( import "errors"
"context"
"errors"
)
type serverConfig struct { type serverConfig struct {
handshaker Handshaker handshaker Handshaker
@@ -47,40 +44,9 @@ func WithServerHandshaker(handshaker Handshaker) ServerOpt {
func WithUnaryServerInterceptor(i UnaryServerInterceptor) ServerOpt { func WithUnaryServerInterceptor(i UnaryServerInterceptor) ServerOpt {
return func(c *serverConfig) error { return func(c *serverConfig) error {
if c.interceptor != nil { if c.interceptor != nil {
return errors.New("only one unchained interceptor allowed per server") return errors.New("only one interceptor allowed per server")
} }
c.interceptor = i c.interceptor = i
return nil return nil
} }
} }
// WithChainUnaryServerInterceptor sets the provided chain of server interceptors
func WithChainUnaryServerInterceptor(interceptors ...UnaryServerInterceptor) ServerOpt {
return func(c *serverConfig) error {
if len(interceptors) == 0 {
return nil
}
if c.interceptor != nil {
interceptors = append([]UnaryServerInterceptor{c.interceptor}, interceptors...)
}
c.interceptor = func(
ctx context.Context,
unmarshal Unmarshaler,
info *UnaryServerInfo,
method Method) (interface{}, error) {
return interceptors[0](ctx, unmarshal, info,
chainUnaryServerInterceptors(info, method, interceptors[1:]))
}
return nil
}
}
func chainUnaryServerInterceptors(info *UnaryServerInfo, method Method, interceptors []UnaryServerInterceptor) Method {
if len(interceptors) == 0 {
return method
}
return func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) {
return interceptors[0](ctx, unmarshal, info,
chainUnaryServerInterceptors(info, method, interceptors[1:]))
}
}

View File

@@ -140,11 +140,7 @@ func (s *serviceSet) handle(ctx context.Context, req *Request, respond func(*sta
respond(st, p, stream.StreamingServer, true) respond(st, p, stream.StreamingServer, true)
}() }()
// Empty proto messages serialized to 0 payloads, if req.Payload != nil {
// so signatures like: rpc Stream(google.protobuf.Empty) returns (stream Data);
// don't get invoked here, which causes hang on client side.
// See https://github.com/containerd/ttrpc/issues/126
if req.Payload != nil || !info.StreamingClient {
unmarshal := func(obj interface{}) error { unmarshal := func(obj interface{}) error {
return protoUnmarshal(req.Payload, obj) return protoUnmarshal(req.Payload, obj)
} }

View File

@@ -1,18 +0,0 @@
package hooks
import (
"fmt"
"io"
"github.com/morikuni/aec"
)
func PrintNextSteps(out io.Writer, messages []string) {
if len(messages) == 0 {
return
}
fmt.Fprintln(out, aec.Bold.Apply("\nWhat's next:"))
for _, n := range messages {
_, _ = fmt.Fprintf(out, " %s\n", n)
}
}

View File

@@ -1,116 +0,0 @@
package hooks
import (
"bytes"
"errors"
"fmt"
"strconv"
"strings"
"text/template"
"github.com/spf13/cobra"
)
type HookType int
const (
NextSteps = iota
)
// HookMessage represents a plugin hook response. Plugins
// declaring support for CLI hooks need to print a json
// representation of this type when their hook subcommand
// is invoked.
type HookMessage struct {
Type HookType
Template string
}
// TemplateReplaceSubcommandName returns a hook template string
// that will be replaced by the CLI subcommand being executed
//
// Example:
//
// "you ran the subcommand: " + TemplateReplaceSubcommandName()
//
// when being executed after the command:
// `docker run --name "my-container" alpine`
// will result in the message:
// `you ran the subcommand: run`
func TemplateReplaceSubcommandName() string {
return hookTemplateCommandName
}
// TemplateReplaceFlagValue returns a hook template string
// that will be replaced by the flags value.
//
// Example:
//
// "you ran a container named: " + TemplateReplaceFlagValue("name")
//
// when being executed after the command:
// `docker run --name "my-container" alpine`
// will result in the message:
// `you ran a container named: my-container`
func TemplateReplaceFlagValue(flag string) string {
return fmt.Sprintf(hookTemplateFlagValue, flag)
}
// TemplateReplaceArg takes an index i and returns a hook
// template string that the CLI will replace the template with
// the ith argument, after processing the passed flags.
//
// Example:
//
// "run this image with `docker run " + TemplateReplaceArg(0) + "`"
//
// when being executed after the command:
// `docker pull alpine`
// will result in the message:
// "Run this image with `docker run alpine`"
func TemplateReplaceArg(i int) string {
return fmt.Sprintf(hookTemplateArg, strconv.Itoa(i))
}
func ParseTemplate(hookTemplate string, cmd *cobra.Command) ([]string, error) {
tmpl := template.New("").Funcs(commandFunctions)
tmpl, err := tmpl.Parse(hookTemplate)
if err != nil {
return nil, err
}
b := bytes.Buffer{}
err = tmpl.Execute(&b, cmd)
if err != nil {
return nil, err
}
return strings.Split(b.String(), "\n"), nil
}
var ErrHookTemplateParse = errors.New("failed to parse hook template")
const (
hookTemplateCommandName = "{{.Name}}"
hookTemplateFlagValue = `{{flag . "%s"}}`
hookTemplateArg = "{{arg . %s}}"
)
var commandFunctions = template.FuncMap{
"flag": getFlagValue,
"arg": getArgValue,
}
func getFlagValue(cmd *cobra.Command, flag string) (string, error) {
cmdFlag := cmd.Flag(flag)
if cmdFlag == nil {
return "", ErrHookTemplateParse
}
return cmdFlag.Value.String(), nil
}
func getArgValue(cmd *cobra.Command, i int) (string, error) {
flags := cmd.Flags()
if flags == nil {
return "", ErrHookTemplateParse
}
return flags.Arg(i), nil
}

View File

@@ -2,14 +2,11 @@ package manager
import ( import (
"fmt" "fmt"
"net/url"
"os" "os"
"strings"
"sync" "sync"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.opentelemetry.io/otel/attribute"
) )
const ( const (
@@ -33,10 +30,6 @@ const (
// is, one which failed it's candidate test) and contains the // is, one which failed it's candidate test) and contains the
// reason for the failure. // reason for the failure.
CommandAnnotationPluginInvalid = "com.docker.cli.plugin-invalid" CommandAnnotationPluginInvalid = "com.docker.cli.plugin-invalid"
// CommandAnnotationPluginCommandPath is added to overwrite the
// command path for a plugin invocation.
CommandAnnotationPluginCommandPath = "com.docker.cli.plugin.command_path"
) )
var pluginCommandStubsOnce sync.Once var pluginCommandStubsOnce sync.Once
@@ -105,44 +98,3 @@ func AddPluginCommandStubs(dockerCli command.Cli, rootCmd *cobra.Command) (err e
}) })
return err return err
} }
const (
dockerCliAttributePrefix = attribute.Key("docker.cli")
cobraCommandPath = attribute.Key("cobra.command_path")
)
func getPluginResourceAttributes(cmd *cobra.Command, plugin Plugin) attribute.Set {
commandPath := cmd.Annotations[CommandAnnotationPluginCommandPath]
if commandPath == "" {
commandPath = fmt.Sprintf("%s %s", cmd.CommandPath(), plugin.Name)
}
attrSet := attribute.NewSet(
cobraCommandPath.String(commandPath),
)
kvs := make([]attribute.KeyValue, 0, attrSet.Len())
for iter := attrSet.Iter(); iter.Next(); {
attr := iter.Attribute()
kvs = append(kvs, attribute.KeyValue{
Key: dockerCliAttributePrefix + "." + attr.Key,
Value: attr.Value,
})
}
return attribute.NewSet(kvs...)
}
func appendPluginResourceAttributesEnvvar(env []string, cmd *cobra.Command, plugin Plugin) []string {
if attrs := getPluginResourceAttributes(cmd, plugin); attrs.Len() > 0 {
// values in environment variables need to be in baggage format
// otel/baggage package can be used after update to v1.22, currently it encodes incorrectly
attrsSlice := make([]string, attrs.Len())
for iter := attrs.Iter(); iter.Next(); {
i, v := iter.IndexedAttribute()
attrsSlice[i] = string(v.Key) + "=" + url.PathEscape(v.Value.AsString())
}
env = append(env, ResourceAttributesEnvvar+"="+strings.Join(attrsSlice, ","))
}
return env
}

View File

@@ -41,9 +41,6 @@ func (e *pluginError) MarshalText() (text []byte, err error) {
// wrapAsPluginError wraps an error in a pluginError with an // wrapAsPluginError wraps an error in a pluginError with an
// additional message, analogous to errors.Wrapf. // additional message, analogous to errors.Wrapf.
func wrapAsPluginError(err error, msg string) error { func wrapAsPluginError(err error, msg string) error {
if err == nil {
return nil
}
return &pluginError{cause: errors.Wrap(err, msg)} return &pluginError{cause: errors.Wrap(err, msg)}
} }

View File

@@ -1,166 +0,0 @@
package manager
import (
"encoding/json"
"strings"
"github.com/docker/cli/cli-plugins/hooks"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// HookPluginData is the type representing the information
// that plugins declaring support for hooks get passed when
// being invoked following a CLI command execution.
type HookPluginData struct {
// RootCmd is a string representing the matching hook configuration
// which is currently being invoked. If a hook for `docker context` is
// configured and the user executes `docker context ls`, the plugin will
// be invoked with `context`.
RootCmd string
Flags map[string]string
CommandError string
}
// RunCLICommandHooks is the entrypoint into the hooks execution flow after
// a main CLI command was executed. It calls the hook subcommand for all
// present CLI plugins that declare support for hooks in their metadata and
// parses/prints their responses.
func RunCLICommandHooks(dockerCli command.Cli, rootCmd, subCommand *cobra.Command, cmdErrorMessage string) {
commandName := strings.TrimPrefix(subCommand.CommandPath(), rootCmd.Name()+" ")
flags := getCommandFlags(subCommand)
runHooks(dockerCli, rootCmd, subCommand, commandName, flags, cmdErrorMessage)
}
// RunPluginHooks is the entrypoint for the hooks execution flow
// after a plugin command was just executed by the CLI.
func RunPluginHooks(dockerCli command.Cli, rootCmd, subCommand *cobra.Command, args []string) {
commandName := strings.Join(args, " ")
flags := getNaiveFlags(args)
runHooks(dockerCli, rootCmd, subCommand, commandName, flags, "")
}
func runHooks(dockerCli command.Cli, rootCmd, subCommand *cobra.Command, invokedCommand string, flags map[string]string, cmdErrorMessage string) {
nextSteps := invokeAndCollectHooks(dockerCli, rootCmd, subCommand, invokedCommand, flags, cmdErrorMessage)
hooks.PrintNextSteps(dockerCli.Err(), nextSteps)
}
func invokeAndCollectHooks(dockerCli command.Cli, rootCmd, subCmd *cobra.Command, subCmdStr string, flags map[string]string, cmdErrorMessage string) []string {
pluginsCfg := dockerCli.ConfigFile().Plugins
if pluginsCfg == nil {
return nil
}
nextSteps := make([]string, 0, len(pluginsCfg))
for pluginName, cfg := range pluginsCfg {
match, ok := pluginMatch(cfg, subCmdStr)
if !ok {
continue
}
p, err := GetPlugin(pluginName, dockerCli, rootCmd)
if err != nil {
continue
}
hookReturn, err := p.RunHook(HookPluginData{
RootCmd: match,
Flags: flags,
CommandError: cmdErrorMessage,
})
if err != nil {
// skip misbehaving plugins, but don't halt execution
continue
}
var hookMessageData hooks.HookMessage
err = json.Unmarshal(hookReturn, &hookMessageData)
if err != nil {
continue
}
// currently the only hook type
if hookMessageData.Type != hooks.NextSteps {
continue
}
processedHook, err := hooks.ParseTemplate(hookMessageData.Template, subCmd)
if err != nil {
continue
}
nextSteps = append(nextSteps, processedHook...)
}
return nextSteps
}
// pluginMatch takes a plugin configuration and a string representing the
// command being executed (such as 'image ls' the root 'docker' is omitted)
// and, if the configuration includes a hook for the invoked command, returns
// the configured hook string.
func pluginMatch(pluginCfg map[string]string, subCmd string) (string, bool) {
configuredPluginHooks, ok := pluginCfg["hooks"]
if !ok || configuredPluginHooks == "" {
return "", false
}
commands := strings.Split(configuredPluginHooks, ",")
for _, hookCmd := range commands {
if hookMatch(hookCmd, subCmd) {
return hookCmd, true
}
}
return "", false
}
func hookMatch(hookCmd, subCmd string) bool {
hookCmdTokens := strings.Split(hookCmd, " ")
subCmdTokens := strings.Split(subCmd, " ")
if len(hookCmdTokens) > len(subCmdTokens) {
return false
}
for i, v := range hookCmdTokens {
if v != subCmdTokens[i] {
return false
}
}
return true
}
func getCommandFlags(cmd *cobra.Command) map[string]string {
flags := make(map[string]string)
cmd.Flags().Visit(func(f *pflag.Flag) {
var fValue string
if f.Value.Type() == "bool" {
fValue = f.Value.String()
}
flags[f.Name] = fValue
})
return flags
}
// getNaiveFlags string-matches argv and parses them into a map.
// This is used when calling hooks after a plugin command, since
// in this case we can't rely on the cobra command tree to parse
// flags in this case. In this case, no values are ever passed,
// since we don't have enough information to process them.
func getNaiveFlags(args []string) map[string]string {
flags := make(map[string]string)
for _, arg := range args {
if strings.HasPrefix(arg, "--") {
flags[arg[2:]] = ""
continue
}
if strings.HasPrefix(arg, "-") {
flags[arg[1:]] = ""
}
}
return flags
}

View File

@@ -17,17 +17,11 @@ import (
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
const ( // ReexecEnvvar is the name of an ennvar which is set to the command
// ReexecEnvvar is the name of an ennvar which is set to the command // used to originally invoke the docker CLI when executing a
// used to originally invoke the docker CLI when executing a // plugin. Assuming $PATH and $CWD remain unchanged this should allow
// plugin. Assuming $PATH and $CWD remain unchanged this should allow // the plugin to re-execute the original CLI.
// the plugin to re-execute the original CLI. const ReexecEnvvar = "DOCKER_CLI_PLUGIN_ORIGINAL_CLI_COMMAND"
ReexecEnvvar = "DOCKER_CLI_PLUGIN_ORIGINAL_CLI_COMMAND"
// ResourceAttributesEnvvar is the name of the envvar that includes additional
// resource attributes for OTEL.
ResourceAttributesEnvvar = "OTEL_RESOURCE_ATTRIBUTES"
)
// errPluginNotFound is the error returned when a plugin could not be found. // errPluginNotFound is the error returned when a plugin could not be found.
type errPluginNotFound string type errPluginNotFound string
@@ -240,8 +234,8 @@ func PluginRunCommand(dockerCli command.Cli, name string, rootcmd *cobra.Command
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Env = append(cmd.Environ(), ReexecEnvvar+"="+os.Args[0]) cmd.Env = os.Environ()
cmd.Env = appendPluginResourceAttributesEnvvar(cmd.Env, rootcmd, plugin) cmd.Env = append(cmd.Env, ReexecEnvvar+"="+os.Args[0])
return cmd, nil return cmd, nil
} }

View File

@@ -8,11 +8,6 @@ const (
// which must be supported by every plugin and returns the // which must be supported by every plugin and returns the
// plugin metadata. // plugin metadata.
MetadataSubcommandName = "docker-cli-plugin-metadata" MetadataSubcommandName = "docker-cli-plugin-metadata"
// HookSubcommandName is the name of the plugin subcommand
// which must be implemented by plugins declaring support
// for hooks in their metadata.
HookSubcommandName = "docker-cli-plugin-hooks"
) )
// Metadata provided by the plugin. // Metadata provided by the plugin.

View File

@@ -2,8 +2,6 @@ package manager
import ( import (
"encoding/json" "encoding/json"
"os"
"os/exec"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
@@ -102,22 +100,3 @@ func newPlugin(c Candidate, cmds []*cobra.Command) (Plugin, error) {
} }
return p, nil return p, nil
} }
// RunHook executes the plugin's hooks command
// and returns its unprocessed output.
func (p *Plugin) RunHook(hookData HookPluginData) ([]byte, error) {
hDataBytes, err := json.Marshal(hookData)
if err != nil {
return nil, wrapAsPluginError(err, "failed to marshall hook data")
}
pCmd := exec.Command(p.Path, p.Name, HookSubcommandName, string(hDataBytes))
pCmd.Env = os.Environ()
pCmd.Env = append(pCmd.Env, ReexecEnvvar+"="+os.Args[0])
hookCmdOutput, err := pCmd.Output()
if err != nil {
return nil, wrapAsPluginError(err, "failed to execute plugin hook subcommand")
}
return hookCmdOutput, nil
}

Some files were not shown because too many files have changed in this diff Show More