Compare commits

..

28 Commits
v5.0.2 ... main

Author SHA1 Message Date
Sebastiaan van Stijn
f9828dfab9 modernize some code
Results of running the modernize command, with some minor changes
afterwards (removing the `contains` and `hasStatus` helper functions);

    go install golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest
    modernize -fix ./...

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-12 15:31:35 +01:00
Sebastiaan van Stijn
da691c7cc1 pkg/compose: un-export consts
These consts were added in b760afaf9f,
but are only user internal in the package. Given that the list of
consts may change over time, it's probably best to keep them internal
and not part of the module's API.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 22:26:15 +01:00
Sebastiaan van Stijn
fefdc95224 pkg/compose: defaultNetworkSettings: slight refactor
- use an intermediate serviceNetworks slice so that we don't have
  to call service.NetworksByPriority multiple times.
- shift the primary network from the slice (if any), so that
  we can drop some checks for "additional networks"
- group code related to setting up the primary network as first
  step, then append remaining networks.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 15:14:49 +01:00
Sebastiaan van Stijn
08c32e9033 pkg/compose: defaultNetworkSettings: return early for unsupported feature
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 15:14:49 +01:00
Sebastiaan van Stijn
bdb630fb89 pkg/compose: format layer push progress
Format layer progress details with minimal efforts as new UI does
not render individual layers.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 12:32:37 +01:00
Nicolas De Loof
3697b128a1 pkg/compose: format layer pull progress
Format layer progress details with minimal efforts as new UI
does not render individual layers

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 12:32:37 +01:00
Sebastiaan van Stijn
b285e48976 pkg/compose: remove unused consts
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 12:32:37 +01:00
Sebastiaan van Stijn
8193d86d2f pkg/bridge: remove uses of go-connections
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 12:32:37 +01:00
Sebastiaan van Stijn
bfb5511d0d go.mod: bump github.com/moby/moby/api v1.53.0, moby/client v0.2.2
Also update TestDefaultNetworkSettings:
Test that the network with the highest priority is returned as
"primary" network, and other networks as extra networks.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 12:32:37 +01:00
dependabot[bot]
7abaa06617 build(deps): bump go.yaml.in/yaml/v4 from 4.0.0-rc.3 to 4.0.0-rc.4
Bumps [go.yaml.in/yaml/v4](https://github.com/yaml/go-yaml) from 4.0.0-rc.3 to 4.0.0-rc.4.
- [Commits](https://github.com/yaml/go-yaml/compare/v4.0.0-rc.3...v4.0.0-rc.4)

---
updated-dependencies:
- dependency-name: go.yaml.in/yaml/v4
  dependency-version: 4.0.0-rc.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-09 10:43:48 +01:00
dependabot[bot]
3b0e8f538e build(deps): bump golang.org/x/sys from 0.40.0 to 0.41.0
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.40.0 to 0.41.0.
- [Commits](https://github.com/golang/sys/compare/v0.40.0...v0.41.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-version: 0.41.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-09 10:31:58 +01:00
Sebastiaan van Stijn
af376603c3 update to go1.25.7
go1.25.7 (released 2026-02-04) includes security fixes to the go command
and the crypto/tls package, as well as bug fixes to the compiler and the
crypto/x509 package. See the Go 1.25.7 milestone on our issue tracker for
details:
https://github.com/golang/go/issues?q=milestone%3AGo1.25.7+label%3ACherryPickApproved

full diff: https://github.com/golang/go/compare/go1.25.6...go1.25.7

From the security mailing list:

> Hello gophers,
>
> We have just released Go versions 1.25.7 and 1.24.13, minor point releases.
>
> These releases include 2 security fixes following the security policy:
>
> - cmd/cgo: remove user-content from doc strings in cgo ASTs
>
>   A discrepancy between how Go and C/C++ comments
>   were parsed allowed for code smuggling into the
>   resulting cgo binary.
>
>   To prevent this behavior, the cgo compiler
>   will no longer parse user-provided doc
>   comments.
>
>   Thank you to RyotaK (https://ryotak.net) of
>   GMO Flatt Security Inc. for reporting this issue.
>
>   This is CVE-2025-61732 and https://go.dev/issue/76697.
>
> - crypto/tls: unexpected session resumption when using Config.GetConfigForClient
>
>   Config.GetConfigForClient is documented to use the original Config's session
>   ticket keys unless explicitly overridden. This can cause unexpected behavior if
>   the returned Config modifies authentication parameters, like ClientCAs: a
>   connection initially established with the parent (or a sibling) Config can be
>   resumed, bypassing the modified authentication requirements.
>
>   If ClientAuth is VerifyClientCertIfGiven or RequireAndVerifyClientCert (on the
>   server) or InsecureSkipVerify is false (on the client), crypto/tls now checks
>   that the root of the previously-verified chain is still in ClientCAs/RootCAs
>   when resuming a connection.
>
>   Go 1.26 Release Candidate 2, Go 1.25.6, and Go 1.24.12 had fixed a similar issue
>   related to session ticket keys being implicitly shared by Config.Clone. Since
>   this fix is broader, the Config.Clone behavior change has been reverted.
>
>   Note that VerifyPeerCertificate still behaves as documented: it does not apply
>   to resumed connections. Applications that use Config.GetConfigForClient or
>   Config.Clone and do not wish to blindly resume connections established with the
>   original Config must use VerifyConnection instead (or SetSessionTicketKeys or
>   SessionTicketsDisabled).
>
>   Thanks to Coia Prant (github.com/rbqvq) for reporting this issue.
>
>   This updates CVE-2025-68121 and Go issue https://go.dev/issue/77217.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-09 09:21:32 +01:00
Michael Irwin
7f8814f4c5 Fix invalid path error when using OCI artifacts on Windows
When using OCI artifacts (e.g., `docker compose -f oci://dockersamples/welcome-to-docker up`)
on Windows, users encountered the following error:

  CreateFile C:\Users\username\oci:\dockersamples\.env: The filename, directory name,
  or volume label syntax is incorrect.

This issue was introduced between v5.0.0 and v5.0.1, specifically by commit 6c043929a
which fixed error handling in setEnvWithDotEnv. The bug existed in v5.0.0 but was
silently ignored due to improper error handling.

Root Cause:
-----------
The setEnvWithDotEnv function creates ProjectOptions without registering remote loaders.
Without remote loaders, the compose-go library doesn't recognize OCI paths as remote
resources. It falls through to filepath.Abs() which treats the OCI reference as a
relative path.

On Windows, filepath.Abs("oci://dockersamples/...") produces an invalid path like:
  C:\Users\username\oci:\dockersamples

Windows rejects this path because colons are only valid after drive letters.

Solution:
---------
Modified setEnvWithDotEnv to detect remote config paths and skip environment loading
for them. Instead of hardcoding string checks, the fix uses the actual remote loaders'
Accept() method to determine if a config path is remote. This is more maintainable
and consistent with how the compose-go library identifies remote resources.

The function now:
- Accepts a dockerCli parameter to access remote loaders
- Uses opts.remoteLoaders(dockerCli) to get loader instances
- Checks if any loader accepts the config path using loader.Accept()
- Skips .env loading for remote configs (happens later when loaders are initialized)
- Allows normal processing for local compose files

Testing:
--------
- Added tests for OCI artifacts, Git remotes, and local paths
- Verified fix works on Windows ARM64
- All existing tests pass

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Michael Irwin <mikesir87@gmail.com>
2026-02-09 09:15:37 +01:00
CrazyMax
af0029afe1 ci: use bin-image edge tag directly for e2e tests
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2026-02-03 15:11:49 +01:00
CrazyMax
b76feb66e1 ci: fix missing dependency on bin-image job
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2026-02-03 15:11:49 +01:00
CrazyMax
9dc7f1e70c ci: use docker/github-builder to build, sign and push bin image
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2026-02-03 14:19:21 +01:00
CrazyMax
03205124fe ci: use docker/github-builder to build, sign binaries
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2026-02-03 14:19:21 +01:00
Sebastiaan van Stijn
8b769bad6b pkg/compose: remove dependency on github.com/docker/buildx/driver
The driver.Auth interface was describing the configfile.GetAuthConfig
implementation; define a local interface instead of using buildx's
definition as an intermediate.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-29 15:59:03 +01:00
Nicolas De Loof
671507a8b3 fix panic
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-01-29 09:57:49 +01:00
ibrahim yapar
56ab28aef3 compose: recreate container when mounted image digest changes
Until now, mustRecreate logic only checked for divergence in TypeVolume
mounts but ignored TypeImage mounts. This inconsistency caused containers
to erroneously retain stale images even after the source image was rebuilt.
This commit updates ensureImagesExists to resolve image volume sources to
their digests using the official reference package. This enables ServiceHash
(config hash) to naturally detect underlying image digest changes,
triggering recreation via the standard convergence logic.
An E2E test case is added to verify this behavior.
Fixes #13547

Signed-off-by: ibrahim yapar <74625807+ibrahimypr@users.noreply.github.com>
2026-01-26 15:55:44 +01:00
Sebastiaan van Stijn
e7d870a106 update to go1.25.6
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-26 09:26:08 +01:00
Nepomuk Crhonek
d5bb3387ca Fix potential nil pointer dereference in container event monitoring
The condition for checking container restart state had incorrect operator
precedence. The expression:

  inspect.State != nil && inspect.State.Restarting || inspect.State.Running

is evaluated as:

  (inspect.State != nil && inspect.State.Restarting) || inspect.State.Running

This means if inspect.State is nil and inspect.State.Restarting is false
(which would trigger a panic), the code would attempt to access
inspect.State.Running, causing a nil pointer dereference.

This fix adds parentheses to ensure the nil check applies to both
state checks:

  inspect.State != nil && (inspect.State.Restarting || inspect.State.Running)

Signed-off-by: Nepomuk Crhonek <105591323+Nepomuk5665@users.noreply.github.com>
2026-01-25 21:15:37 +01:00
Salman Muin Kayser Chishti
d91fc63813 Upgrade GitHub Actions to latest versions
Signed-off-by: Salman Muin Kayser Chishti <13schishti@gmail.com>
2026-01-23 15:34:48 +01:00
Sebastiaan van Stijn
c51b1fea29 replace some uses of strings.Split(N) for strings.Cut
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-22 11:26:16 +01:00
Sebastiaan van Stijn
fa7549a851 Dockerfile: update golangci-lint to v2.8.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-21 10:11:02 +01:00
Mahesh Thakur
a061c17736 fix: emit container status events after network reconnection
Signed-off-by: Mahesh Thakur <maheshthakur9152@gmail.com>
2026-01-21 09:40:05 +01:00
Sebastiaan van Stijn
c5e7d9158c update to go1.24.12
This releases includes 6 security fixes following the security policy:

- archive/zip: denial of service when parsing arbitrary ZIP archives

    archive/zip used a super-linear file name indexing algorithm that is invoked the first time a file in an archive is opened. This can lead to a denial of service when consuming a maliciously constructed ZIP archive.

    Thanks to Thanks to Jakub Ciolek for reporting this issue.

    This is CVE-2025-61728 and Go issue https://go.dev/issue/77102.

- net/http: memory exhaustion in Request.ParseForm

    When parsing a URL-encoded form net/http may allocate an unexpected amount of
    memory when provided a large number of key-value pairs. This can result in a
    denial of service due to memory exhaustion.

    Thanks to jub0bs for reporting this issue.

    This is CVE-2025-61726 and Go issue https://go.dev/issue/77101.

- crypto/tls: Config.Clone copies automatically generated session ticket keys, session resumption does not account for the expiration of full certificate chain

    The Config.Clone methods allows cloning a Config which has already been passed
    to a TLS function, allowing it to be mutated and reused.

    If Config.SessionTicketKey has not been set, and Config.SetSessionTicketKeys has
    not been called, crypto/tls will generate random session ticket keys and
    automatically rotate them. Config.Clone would copy these automatically generated
    keys into the returned Config, meaning that the two Configs would share session
    ticket keys, allowing sessions created using one Config could be used to resume
    sessions with the other Config. This can allow clients to resume sessions even
    though the Config may be configured such that they should not be able to do so.

    Config.Clone no longer copies the automatically generated session ticket keys.
    Config.Clone still copies keys which are explicitly provided, either by setting
    Config.SessionTicketKey or by calling Config.SetSessionTicketKeys.

    This issue was discoverd by the Go Security team while investigating another
    issue reported by Coia Prant (github.com/rbqvq).

    Additionally, on the server side only the expiration of the leaf certificate, if
    one was provided during the initial handshake, was checked when considering if a
    session could be resumed. This allowed sessions to be resumed if an intermediate
    or root certificate in the chain had expired.

    Session resumption now takes into account of the full chain when determining if
    the session can be resumed.

    Thanks to Coia Prant (github.com/rbqvq) for reporting this issue.

    This is CVE-2025-68121 and Go issue https://go.dev/issue/77113.

- cmd/go: bypass of flag sanitization can lead to arbitrary code execution

    Usage of 'CgoPkgConfig' allowed execution of the pkg-config
    binary with flags that are not explicitly safe-listed.

    To prevent this behavior, compiler flags resulting from usage
    of 'CgoPkgConfig' are sanitized prior to invoking pkg-config.

    Thank you to RyotaK (https://ryotak.net) of GMO Flatt Security Inc.
    for reporting this issue.

    This is CVE-2025-61731 and go.dev/issue/77100.

- cmd/go: unexpected code execution when invoking toolchain

    The Go toolchain supports multiple VCS which are used retrieving modules and
    embedding build information into binaries.

    On systems with Mercurial installed (hg) downloading modules (e.g. via go get or
    go mod download) from non-standard sources (e.g. custom domains) can cause
    unexpected code execution due to how external VCS commands are constructed.

    On systems with Git installed, downloading and building modules with malicious
    version strings could allow an attacker to write to arbitrary files on the
    system the user has access to. This can only be triggered by explicitly
    providing the malicious version strings to the toolchain, and does not affect
    usage of @latest or bare module paths.

    The toolchain now uses safer VCS options to prevent misinterpretation of
    untrusted inputs. In addition, the toolchain now disallows module version
    strings prefixed with a "-" or "/" character.

    Thanks to splitline (@splitline) from DEVCORE Research Team for reporting this
    issue.

    This is CVE-2025-68119 and Go issue https://go.dev/issue/77099.

- crypto/tls: handshake messages may be processed at the incorrect encryption level

    During the TLS 1.3 handshake if multiple messages are sent in records that span
    encryption level boundaries (for instance the Client Hello and Encrypted
    Extensions messages), the subsequent messages may be processed before the
    encryption level changes. This can cause some minor information disclosure if a
    network-local attacker can inject messages during the handshake.

    Thanks to Coia Prant (github.com/rbqvq) for reporting this issue.

    This is CVE-2025-61730 and Go issue https://go.dev/issue/76443

View the release notes for more information:
https://go.dev/doc/devel/release#go1.24.12

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-21 09:27:04 +01:00
Nicolas De Loof
3783b8ada3 fsnotify is set in Dockerfile
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-01-21 08:47:40 +01:00
97 changed files with 2636 additions and 2596 deletions

View File

@@ -22,24 +22,6 @@ permissions:
contents: read # to fetch code (actions/checkout)
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.platforms.outputs.matrix }}
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Create matrix
id: platforms
run: |
echo matrix=$(docker buildx bake binary-cross --print | jq -cr '.target."binary-cross".platforms') >> $GITHUB_OUTPUT
-
name: Show matrix
run: |
echo ${{ steps.platforms.outputs.matrix }}
validate:
runs-on: ubuntu-latest
strategy:
@@ -63,63 +45,88 @@ jobs:
make ${{ matrix.target }}
binary:
uses: docker/github-builder/.github/workflows/bake.yml@v1
permissions:
contents: read # same as global permission
id-token: write # for signing attestation(s) with GitHub OIDC Token
with:
runner: amd64
artifact-name: compose
artifact-upload: true
cache: true
cache-scope: binary
target: release
output: local
sbom: true
sign: ${{ github.event_name != 'pull_request' }}
binary-finalize:
runs-on: ubuntu-latest
needs:
- prepare
strategy:
fail-fast: false
matrix:
platform: ${{ fromJson(needs.prepare.outputs.matrix) }}
- binary
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Prepare
run: |
platform=${MATRIX_PLATFORM}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
env:
MATRIX_PLATFORM: ${{ matrix.platform }}
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Build
uses: docker/bake-action@v6
name: Download artifacts
uses: actions/download-artifact@v7
with:
source: .
targets: release
provenance: mode=max
sbom: true
set: |
*.platform=${{ matrix.platform }}
*.cache-from=type=gha,scope=binary-${{ env.PLATFORM_PAIR }}
*.cache-to=type=gha,scope=binary-${{ env.PLATFORM_PAIR }},mode=max
path: /tmp/compose-output
name: ${{ needs.binary.outputs.artifact-name }}
-
name: Rename provenance and sbom
run: |
for pdir in /tmp/compose-output/*/; do
(
cd "$pdir"
binname=$(find . -name 'docker-compose-*')
filename=$(basename "${binname%.exe}")
mv "provenance.json" "${filename}.provenance.json"
mv "sbom-binary.spdx.json" "${filename}.sbom.json"
find . -name 'sbom*.json' -exec rm {} \;
if [ -f "provenance.sigstore.json" ]; then
mv "provenance.sigstore.json" "${filename}.sigstore.json"
fi
)
done
mkdir -p "./bin/release"
mv /tmp/compose-output/**/* "./bin/release/"
-
name: Create checksum file
working-directory: ./bin/release
run: |
binname=$(find . -name 'docker-compose-*')
filename=$(basename "$binname" | sed -E 's/\.exe$//')
mv "provenance.json" "${filename}.provenance.json"
mv "sbom-binary.spdx.json" "${filename}.sbom.json"
find . -name 'sbom*.json' -exec rm {} \;
-
name: List artifacts
run: |
tree -nh ./bin/release
find . -type f -print0 | sort -z | xargs -r0 shasum -a 256 -b | sed 's# \*\./# *#' > $RUNNER_TEMP/checksums.txt
shasum -a 256 -U -c $RUNNER_TEMP/checksums.txt
mv $RUNNER_TEMP/checksums.txt .
cat checksums.txt | while read sum file; do
if [[ "${file#\*}" == docker-compose-* && "${file#\*}" != *.provenance.json && "${file#\*}" != *.sbom.json && "${file#\*}" != *.sigstore.json ]]; then
echo "$sum $file" > ${file#\*}.sha256
fi
done
-
name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: compose-${{ env.PLATFORM_PAIR }}
path: ./bin/release
name: release
path: ./bin/release/*
if-no-files-found: error
bin-image-test:
if: github.event_name == 'pull_request'
uses: docker/github-builder/.github/workflows/bake.yml@v1
with:
runner: amd64
target: image-cross
cache: true
cache-scope: bin-image-test
output: image
push: false
sbom: true
set-meta-labels: true
meta-images: |
compose-bin
meta-tags: |
type=ref,event=pr
meta-bake-target: meta-helper
test:
runs-on: ubuntu-latest
steps:
@@ -147,6 +154,7 @@ jobs:
with:
paths: bin/coverage/unit/report.xml
if: always()
e2e:
runs-on: ubuntu-latest
name: e2e (${{ matrix.mode }}, ${{ matrix.channel }})
@@ -254,6 +262,7 @@ jobs:
with:
paths: /tmp/report/report.xml
if: always()
coverage:
runs-on: ubuntu-latest
needs:
@@ -290,40 +299,26 @@ jobs:
path: ./coverage.txt
if-no-files-found: error
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v5
with:
files: ./coverage.txt
release:
permissions:
contents: write # to create a release (ncipollo/release-action)
runs-on: ubuntu-latest
needs:
- binary
- binary-finalize
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Download artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
pattern: compose-*
path: ./bin/release
merge-multiple: true
-
name: Create checksums
working-directory: ./bin/release
run: |
find . -type f -print0 | sort -z | xargs -r0 shasum -a 256 -b | sed 's# \*\./# *#' > $RUNNER_TEMP/checksums.txt
shasum -a 256 -U -c $RUNNER_TEMP/checksums.txt
mv $RUNNER_TEMP/checksums.txt .
cat checksums.txt | while read sum file; do
if [[ "${file#\*}" == docker-compose-* && "${file#\*}" != *.provenance.json && "${file#\*}" != *.sbom.json ]]; then
echo "$sum $file" > ${file#\*}.sha256
fi
done
name: release
-
name: List artifacts
run: |

View File

@@ -74,63 +74,41 @@ jobs:
run: |
make e2e-compose-standalone
bin-image:
runs-on: ubuntu-22.04
bin-image-prepare:
runs-on: ubuntu-24.04
outputs:
digest: ${{ fromJSON(steps.bake.outputs.metadata).image-cross['containerimage.digest'] }}
repo-slug: ${{ env.REPO_SLUG }}
steps:
-
name: Free disk space
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1
with:
android: true
dotnet: true
haskell: true
large-packages: true
swap-storage: true
-
name: Checkout
uses: actions/checkout@v4
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
# FIXME: can't use env object in reusable workflow inputs: https://github.com/orgs/community/discussions/26671
- run: echo "Exposing env vars for reusable workflow"
bin-image:
uses: docker/github-builder/.github/workflows/bake.yml@v1
needs:
- bin-image-prepare
permissions:
contents: read # same as global permission
id-token: write # for signing attestation(s) with GitHub OIDC Token
with:
runner: amd64
target: image-cross
cache: true
cache-scope: bin-image
output: image
push: ${{ github.event_name != 'pull_request' }}
sbom: true
set-meta-labels: true
meta-images: |
${{ needs.bin-image-prepare.outputs.repo-slug }}
meta-tags: |
type=ref,event=tag
type=edge
meta-bake-target: meta-helper
secrets:
registry-auths: |
- registry: docker.io
username: ${{ secrets.DOCKERPUBLICBOT_USERNAME }}
password: ${{ secrets.DOCKERPUBLICBOT_WRITE_PAT }}
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.REPO_SLUG }}
tags: |
type=ref,event=tag
type=edge
bake-target: meta-helper
-
name: Build and push image
uses: docker/bake-action@v6
id: bake
with:
source: .
files: |
./docker-bake.hcl
${{ steps.meta.outputs.bake-file }}
targets: image-cross
push: ${{ github.event_name != 'pull_request' }}
sbom: true
provenance: mode=max
set: |
*.cache-from=type=gha,scope=bin-image
*.cache-to=type=gha,scope=bin-image,mode=max
desktop-edge-test:
runs-on: ubuntu-latest
@@ -158,6 +136,6 @@ jobs:
workflow_id: 'compose-edge-integration.yml',
ref: 'main',
inputs: {
"image-tag": "${{ needs.bin-image.outputs.digest }}"
"image-tag": "${{ env.REPO_SLUG }}:edge"
}
})

View File

@@ -1 +1 @@
1.24.11
1.25.7

View File

@@ -15,9 +15,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
ARG GO_VERSION=1.24.11
ARG GO_VERSION=1.25.7
ARG XX_VERSION=1.9.0
ARG GOLANGCI_LINT_VERSION=v2.6.2
ARG GOLANGCI_LINT_VERSION=v2.8.0
ARG ADDLICENSE_VERSION=v1.0.0
ARG BUILD_TAGS="e2e"

View File

@@ -29,10 +29,6 @@ ifeq ($(DETECTED_OS),Windows)
BINARY_EXT=.exe
endif
ifeq ($(DETECTED_OS),Darwin)
GO_BUILDTAGS += fsnotify
endif
BUILD_FLAGS?=
TEST_FLAGS?=
E2E_TEST?=
@@ -99,7 +95,7 @@ example-provider: ## build example provider for e2e tests
mocks:
mockgen --version >/dev/null 2>&1 || go install go.uber.org/mock/mockgen@v0.4.0
mockgen -destination pkg/mocks/mock_docker_cli.go -package mocks github.com/docker/cli/cli/command Cli
mockgen -destination pkg/mocks/mock_docker_api.go -package mocks github.com/docker/docker/client APIClient
mockgen -destination pkg/mocks/mock_docker_api.go -package mocks github.com/moby/moby/client APIClient
mockgen -destination pkg/mocks/mock_docker_compose_api.go -package mocks -source=./pkg/api/api.go Service
.PHONY: e2e

View File

@@ -19,6 +19,7 @@ package compatibility
import (
"fmt"
"os"
"slices"
"strings"
"github.com/docker/compose/v5/cmd/compose"
@@ -59,7 +60,7 @@ func Convert(args []string) []string {
ARGS:
for i := 0; i < l; i++ {
arg := args[i]
if contains(getCompletionCommands(), arg) {
if slices.Contains(getCompletionCommands(), arg) {
command = append([]string{arg}, command...)
continue
}
@@ -79,7 +80,7 @@ ARGS:
arg = "version"
}
if contains(getBoolFlags(), arg) {
if slices.Contains(getBoolFlags(), arg) {
rootFlags = append(rootFlags, arg)
continue
}
@@ -105,12 +106,3 @@ ARGS:
}
return append(rootFlags, command...)
}
func contains(array []string, needle string) bool {
for _, val := range array {
if val == needle {
return true
}
}
return false
}

View File

@@ -23,9 +23,9 @@ import (
"github.com/distribution/reference"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/go-units"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/client/pkg/stringid"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/cmd/formatter"

View File

@@ -35,7 +35,6 @@ import (
composepaths "github.com/compose-spec/compose-go/v2/paths"
"github.com/compose-spec/compose-go/v2/types"
composegoutils "github.com/compose-spec/compose-go/v2/utils"
"github.com/docker/buildx/util/logutil"
dockercli "github.com/docker/cli/cli"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli/command"
@@ -423,16 +422,6 @@ func (o *BackendOptions) Add(option compose.Option) {
// RootCommand returns the compose command with its child commands
func RootCommand(dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command { //nolint:gocyclo
// filter out useless commandConn.CloseWrite warning message that can occur
// when using a remote context that is unreachable: "commandConn.CloseWrite: commandconn: failed to wait: signal: killed"
// https://github.com/docker/cli/blob/e1f24d3c93df6752d3c27c8d61d18260f141310c/cli/connhelper/commandconn/commandconn.go#L203-L215
logrus.AddHook(logutil.NewFilter([]logrus.Level{
logrus.WarnLevel,
},
"commandConn.CloseWrite:",
"commandConn.CloseRead:",
))
opts := ProjectOptions{}
var (
ansi string
@@ -477,7 +466,7 @@ func RootCommand(dockerCli command.Cli, backendOptions *BackendOptions) *cobra.C
logrus.SetLevel(logrus.TraceLevel)
}
err := setEnvWithDotEnv(opts)
err := setEnvWithDotEnv(opts, dockerCli)
if err != nil {
return err
}
@@ -505,6 +494,7 @@ func RootCommand(dockerCli command.Cli, backendOptions *BackendOptions) *cobra.C
display.Mode = display.ModeTTY
}
detached, _ := cmd.Flags().GetBool("detach")
var ep api.EventProcessor
switch opts.Progress {
case "", display.ModeAuto:
@@ -513,7 +503,7 @@ func RootCommand(dockerCli command.Cli, backendOptions *BackendOptions) *cobra.C
display.Mode = display.ModePlain
ep = display.Plain(dockerCli.Err())
case dockerCli.Out().IsTerminal():
ep = display.Full(dockerCli.Err(), stdinfo(dockerCli))
ep = display.Full(dockerCli.Err(), stdinfo(dockerCli), detached)
default:
ep = display.Plain(dockerCli.Err())
}
@@ -522,7 +512,7 @@ func RootCommand(dockerCli command.Cli, backendOptions *BackendOptions) *cobra.C
return fmt.Errorf("can't use --progress tty while ANSI support is disabled")
}
display.Mode = display.ModeTTY
ep = display.Full(dockerCli.Err(), stdinfo(dockerCli))
ep = display.Full(dockerCli.Err(), stdinfo(dockerCli), detached)
case display.ModePlain:
if ansi == "always" {
@@ -676,7 +666,21 @@ func stdinfo(dockerCli command.Cli) io.Writer {
return dockerCli.Err()
}
func setEnvWithDotEnv(opts ProjectOptions) error {
func setEnvWithDotEnv(opts ProjectOptions, dockerCli command.Cli) error {
// Check if we're using a remote config (OCI or Git)
// If so, skip env loading as remote loaders haven't been initialized yet
// and trying to process the path would fail
remoteLoaders := opts.remoteLoaders(dockerCli)
for _, path := range opts.ConfigPaths {
for _, loader := range remoteLoaders {
if loader.Accept(path) {
// Remote config - skip env loading for now
// It will be loaded later when the project is fully initialized
return nil
}
}
}
options, err := cli.NewProjectOptions(opts.ConfigPaths,
cli.WithWorkingDirectory(opts.ProjectDir),
cli.WithOsEnv,

View File

@@ -0,0 +1,76 @@
/*
Copyright 2020 Docker Compose CLI 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 compose
import (
"testing"
"go.uber.org/mock/gomock"
"gotest.tools/v3/assert"
"github.com/docker/compose/v5/pkg/mocks"
)
func TestSetEnvWithDotEnv_WithOCIArtifact(t *testing.T) {
// Test that setEnvWithDotEnv doesn't fail when using OCI artifacts
ctrl := gomock.NewController(t)
defer ctrl.Finish()
cli := mocks.NewMockCli(ctrl)
opts := ProjectOptions{
ConfigPaths: []string{"oci://docker.io/dockersamples/welcome-to-docker"},
ProjectDir: "",
EnvFiles: []string{},
}
err := setEnvWithDotEnv(opts, cli)
assert.NilError(t, err, "setEnvWithDotEnv should not fail with OCI artifact path")
}
func TestSetEnvWithDotEnv_WithGitRemote(t *testing.T) {
// Test that setEnvWithDotEnv doesn't fail when using Git remotes
ctrl := gomock.NewController(t)
defer ctrl.Finish()
cli := mocks.NewMockCli(ctrl)
opts := ProjectOptions{
ConfigPaths: []string{"https://github.com/docker/compose.git"},
ProjectDir: "",
EnvFiles: []string{},
}
err := setEnvWithDotEnv(opts, cli)
assert.NilError(t, err, "setEnvWithDotEnv should not fail with Git remote path")
}
func TestSetEnvWithDotEnv_WithLocalPath(t *testing.T) {
// Test that setEnvWithDotEnv still works with local paths
// This will fail if the file doesn't exist, but it should not panic
// or produce invalid paths
ctrl := gomock.NewController(t)
defer ctrl.Finish()
cli := mocks.NewMockCli(ctrl)
opts := ProjectOptions{
ConfigPaths: []string{"compose.yaml"},
ProjectDir: "",
EnvFiles: []string{},
}
// This may error if files don't exist, but should not panic
_ = setEnvWithDotEnv(opts, cli)
}

View File

@@ -23,6 +23,7 @@ import (
"fmt"
"io"
"os"
"slices"
"sort"
"strings"
@@ -454,9 +455,7 @@ func runHash(ctx context.Context, dockerCli command.Cli, opts configOptions) err
}
sorted := services
sort.Slice(sorted, func(i, j int) bool {
return sorted[i] < sorted[j]
})
slices.Sort(sorted)
for _, name := range sorted {
s, err := project.GetService(name)

View File

@@ -198,12 +198,11 @@ func (opts createOptions) Apply(project *types.Project) error {
func applyScaleOpts(project *types.Project, opts []string) error {
for _, scale := range opts {
split := strings.Split(scale, "=")
if len(split) != 2 {
name, val, ok := strings.Cut(scale, "=")
if !ok || val == "" {
return fmt.Errorf("invalid --scale option %q. Should be SERVICE=NUM", scale)
}
name := split[0]
replicas, err := strconv.Atoi(split[1])
replicas, err := strconv.Atoi(val)
if err != nil {
return err
}

View File

@@ -27,8 +27,8 @@ import (
"github.com/containerd/platforms"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/go-units"
"github.com/moby/moby/client/pkg/stringid"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/cmd/formatter"

View File

@@ -18,12 +18,15 @@ package compose
import (
"context"
"errors"
"fmt"
"io"
"regexp"
"strings"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/cmd/formatter"
@@ -61,11 +64,32 @@ var acceptedListFilters = map[string]bool{
"name": true,
}
// match returns true if any of the values at key match the source string
func match(filters client.Filters, field, source string) bool {
if f, ok := filters[field]; ok && f[source] {
return true
}
fieldValues := filters[field]
for name2match := range fieldValues {
isMatch, err := regexp.MatchString(name2match, source)
if err != nil {
continue
}
if isMatch {
return true
}
}
return false
}
func runList(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, lsOpts lsOptions) error {
filters := lsOpts.Filter.Value()
err := filters.Validate(acceptedListFilters)
if err != nil {
return err
for filter := range filters {
if _, ok := acceptedListFilters[filter]; !ok {
return errors.New("invalid filter '" + filter + "'")
}
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
@@ -77,13 +101,12 @@ func runList(ctx context.Context, dockerCli command.Cli, backendOptions *Backend
return err
}
if filters.Len() > 0 {
if len(filters) > 0 {
var filtered []api.Stack
for _, s := range stackList {
if filters.Contains("name") && !filters.Match("name", s.Name) {
continue
if match(filters, "name", s.Name) {
filtered = append(filtered, s)
}
filtered = append(filtered, s)
}
stackList = filtered
}

View File

@@ -213,9 +213,9 @@ func extractEnvCLIDefined(cmdEnvs []string) map[string]string {
// Parse command-line environment variables
cmdEnvMap := make(map[string]string)
for _, env := range cmdEnvs {
parts := strings.SplitN(env, "=", 2)
if len(parts) == 2 {
cmdEnvMap[parts[0]] = parts[1]
key, val, ok := strings.Cut(env, "=")
if ok {
cmdEnvMap[key] = val
}
}
return cmdEnvMap

View File

@@ -50,19 +50,19 @@ func (p *psOptions) parseFilter() error {
if p.Filter == "" {
return nil
}
parts := strings.SplitN(p.Filter, "=", 2)
if len(parts) != 2 {
key, val, ok := strings.Cut(p.Filter, "=")
if !ok {
return errors.New("arguments to --filter should be in form KEY=VAL")
}
switch parts[0] {
switch key {
case "status":
p.Status = append(p.Status, parts[1])
p.Status = append(p.Status, val)
return nil
case "source":
return api.ErrNotImplemented
default:
return fmt.Errorf("unknown filter %s", parts[0])
return fmt.Errorf("unknown filter %s", key)
}
return nil
}
func psCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
@@ -167,18 +167,9 @@ func runPs(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOp
func filterByStatus(containers []api.ContainerSummary, statuses []string) []api.ContainerSummary {
var filtered []api.ContainerSummary
for _, c := range containers {
if hasStatus(c, statuses) {
if slices.Contains(statuses, string(c.State)) {
filtered = append(filtered, c)
}
}
return filtered
}
func hasStatus(c api.ContainerSummary, statuses []string) bool {
for _, status := range statuses {
if c.State == status {
return true
}
}
return false
}

View File

@@ -284,11 +284,11 @@ func runRun(ctx context.Context, backend api.Compose, project *types.Project, op
labels := types.Labels{}
for _, s := range options.labels {
parts := strings.SplitN(s, "=", 2)
if len(parts) != 2 {
key, val, ok := strings.Cut(s, "=")
if !ok {
return fmt.Errorf("label must be set as KEY=VALUE")
}
labels[parts[0]] = parts[1]
labels[key] = val
}
var buildForRun *api.BuildOptions

View File

@@ -22,7 +22,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/container"
"github.com/docker/docker/api/types/filters"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
@@ -67,18 +67,17 @@ func runStats(ctx context.Context, dockerCli command.Cli, opts statsOptions, ser
if err != nil {
return err
}
filter := []filters.KeyValuePair{
filters.Arg("label", fmt.Sprintf("%s=%s", api.ProjectLabel, name)),
}
f := client.Filters{}
f.Add("label", fmt.Sprintf("%s=%s", api.ProjectLabel, name))
if len(service) > 0 {
filter = append(filter, filters.Arg("label", fmt.Sprintf("%s=%s", api.ServiceLabel, service[0])))
f.Add("label", fmt.Sprintf("%s=%s", api.ServiceLabel, service[0]))
}
args := filters.NewArgs(filter...)
return container.RunStats(ctx, dockerCli, &container.StatsOptions{
All: opts.all,
NoStream: opts.noStream,
NoTrunc: opts.noTrunc,
Format: opts.format,
Filters: &args,
Filters: f,
})
}

View File

@@ -37,13 +37,14 @@ import (
// Full creates an EventProcessor that render advanced UI within a terminal.
// On Start, TUI lists task with a progress timer
func Full(out io.Writer, info io.Writer) api.EventProcessor {
func Full(out io.Writer, info io.Writer, detached bool) api.EventProcessor {
return &ttyWriter{
out: out,
info: info,
tasks: map[string]*task{},
done: make(chan bool),
mtx: &sync.Mutex{},
out: out,
info: info,
tasks: map[string]*task{},
done: make(chan bool),
mtx: &sync.Mutex{},
detached: detached,
}
}
@@ -60,6 +61,7 @@ type ttyWriter struct {
ticker *time.Ticker
suspended bool
info io.Writer
detached bool
}
type task struct {
@@ -190,7 +192,7 @@ func (w *ttyWriter) On(events ...api.Resource) {
continue
}
if w.operation != "start" && (e.Text == api.StatusStarted || e.Text == api.StatusStarting) {
if w.operation != "start" && (e.Text == api.StatusStarted || e.Text == api.StatusStarting) && !w.detached {
// skip those events to avoid mix with container logs
continue
}
@@ -315,10 +317,7 @@ func (w *ttyWriter) printWithDimensions(terminalWidth, terminalHeight int) {
allTasks := slices.Collect(w.parentTasks())
// Available lines: terminal height - 2 (header line + potential "more" line)
maxLines := terminalHeight - 2
if maxLines < 1 {
maxLines = 1
}
maxLines := max(terminalHeight-2, 1)
showMore := len(allTasks) > maxLines
tasksToShow := allTasks
@@ -352,10 +351,7 @@ func (w *ttyWriter) printWithDimensions(terminalWidth, terminalHeight int) {
if showMore {
moreCount := len(allTasks) - len(tasksToShow)
moreText := fmt.Sprintf(" ... %d more", moreCount)
pad := terminalWidth - len(moreText)
if pad < 0 {
pad = 0
}
pad := max(terminalWidth-len(moreText), 0)
_, _ = fmt.Fprintf(w.out, "%s%s\n", moreText, strings.Repeat(" ", pad))
numLines++
}
@@ -390,10 +386,7 @@ func (w *ttyWriter) applyPadding(lines []lineData, terminalWidth int, timerLen i
if l.details != "" {
lineLen += 1 + utf8.RuneCountInString(l.details)
}
l.timerPad = terminalWidth - lineLen - timerLen
if l.timerPad < 1 {
l.timerPad = 1
}
l.timerPad = max(terminalWidth-lineLen-timerLen, 1)
lines[i] = l
}
@@ -470,10 +463,7 @@ func truncateDetails(lines []lineData, overflow int) bool {
for i := range lines {
l := &lines[i]
if len(l.details) > 3 {
reduction := overflow
if reduction > len(l.details)-3 {
reduction = len(l.details) - 3
}
reduction := min(overflow, len(l.details)-3)
l.details = l.details[:len(l.details)-reduction-3] + "..."
return true
} else if l.details != "" {
@@ -502,10 +492,7 @@ func truncateLongestTaskID(lines []lineData, overflow, minIDLen int) bool {
l := &lines[longestIdx]
reduction := overflow + 3 // account for "..."
newLen := len(l.taskID) - reduction
if newLen < minIDLen-3 {
newLen = minIDLen - 3
}
newLen := max(len(l.taskID)-reduction, minIDLen-3)
if newLen > 0 {
l.taskID = l.taskID[:newLen] + "..."
}
@@ -544,10 +531,7 @@ func (w *ttyWriter) prepareLineData(t *task) lineData {
total += child.total
current += child.current
r := len(percentChars) - 1
p := child.percent
if p > 100 {
p = 100
}
p := min(child.percent, 100)
completion = append(completion, percentChars[r*p/100])
}
}

View File

@@ -186,7 +186,7 @@ func TestPrintWithDimensions_TaskWithProgress(t *testing.T) {
w.ids = append(w.ids, "Image nginx")
// Create child tasks to trigger progress display
for i := 0; i < 3; i++ {
for i := range 3 {
child := &task{
ID: "layer" + string(rune('a'+i)),
parents: map[string]struct{}{"Image nginx": {}},

View File

@@ -18,14 +18,15 @@ package formatter
import (
"fmt"
"net/netip"
"strconv"
"strings"
"time"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/go-units"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client/pkg/stringid"
"github.com/docker/compose/v5/pkg/api"
)
@@ -197,7 +198,7 @@ func (c *ContainerContext) ExitCode() int {
}
func (c *ContainerContext) State() string {
return c.c.State
return string(c.c.State)
}
func (c *ContainerContext) Status() string {
@@ -205,7 +206,7 @@ func (c *ContainerContext) Status() string {
}
func (c *ContainerContext) Health() string {
return c.c.Health
return string(c.c.Health)
}
func (c *ContainerContext) Publishers() api.PortPublishers {
@@ -213,10 +214,16 @@ func (c *ContainerContext) Publishers() api.PortPublishers {
}
func (c *ContainerContext) Ports() string {
var ports []container.Port
var ports []container.PortSummary
for _, publisher := range c.c.Publishers {
ports = append(ports, container.Port{
IP: publisher.URL,
var pIP netip.Addr
if publisher.URL != "" {
if p, err := netip.ParseAddr(publisher.URL); err == nil {
pIP = p
}
}
ports = append(ports, container.PortSummary{
IP: pIP,
PrivatePort: uint16(publisher.TargetPort),
PublicPort: uint16(publisher.PublishedPort),
Type: publisher.Protocol,

View File

@@ -26,7 +26,7 @@ import (
"time"
"github.com/buger/goterm"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/moby/moby/client/pkg/jsonmessage"
"github.com/docker/compose/v5/pkg/api"
)

View File

@@ -189,7 +189,7 @@ func (lk *LogKeyboard) clearNavigationMenu() {
saveCursor()
// clearLine()
for i := 0; i < height; i++ {
for range height {
moveCursorDown(1)
clearLine()
}
@@ -341,7 +341,7 @@ func (lk *LogKeyboard) EnableDetach(detach func()) {
}
func allocateSpace(lines int) {
for i := 0; i < lines; i++ {
for range lines {
clearLine()
newLine()
carriageReturn()

67
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/docker/compose/v5
go 1.24.3
go 1.25.0
require (
github.com/AlecAivazis/survey/v2 v2.3.7
@@ -14,11 +14,10 @@ require (
github.com/containerd/errdefs v1.0.0
github.com/containerd/platforms v1.0.0-rc.2
github.com/distribution/reference v0.6.0
github.com/docker/buildx v0.30.1
github.com/docker/cli v28.5.2+incompatible
github.com/docker/buildx v0.31.1
github.com/docker/cli v29.2.1+incompatible
github.com/docker/cli-docs-tool v0.11.0
github.com/docker/docker v28.5.2+incompatible
github.com/docker/go-connections v0.6.0
github.com/docker/go-units v0.5.0
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203
github.com/fsnotify/fsevents v0.2.0
@@ -29,8 +28,10 @@ require (
github.com/jonboulle/clockwork v0.5.0
github.com/mattn/go-shellwords v1.0.12
github.com/mitchellh/go-ps v1.0.0
github.com/moby/buildkit v0.26.3
github.com/moby/go-archive v0.1.0
github.com/moby/buildkit v0.27.1
github.com/moby/go-archive v0.2.0
github.com/moby/moby/api v1.53.0
github.com/moby/moby/client v0.2.2
github.com/moby/patternmatcher v0.6.0
github.com/moby/sys/atomicwriter v0.1.0
github.com/morikuni/aec v1.1.0
@@ -52,9 +53,9 @@ require (
go.opentelemetry.io/otel/trace v1.38.0
go.uber.org/goleak v1.3.0
go.uber.org/mock v0.6.0
go.yaml.in/yaml/v4 v4.0.0-rc.3
go.yaml.in/yaml/v4 v4.0.0-rc.4
golang.org/x/sync v0.19.0
golang.org/x/sys v0.40.0
golang.org/x/sys v0.41.0
google.golang.org/grpc v1.78.0
gotest.tools/v3 v3.5.2
tags.cncf.io/container-device-interface v1.1.0
@@ -62,9 +63,7 @@ require (
require (
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/containerd/containerd/api v1.10.0 // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
@@ -74,9 +73,8 @@ require (
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/docker-credential-helpers v0.9.5 // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fvbommel/sortorder v1.1.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
@@ -87,22 +85,19 @@ require (
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/in-toto/in-toto-golang v0.9.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/magiconair/properties v1.8.9 // indirect
github.com/klauspost/compress v1.18.3 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/sys/capability v0.4.0 // indirect
@@ -112,24 +107,20 @@ require (
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/otiai10/mint v1.6.3 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.9.1 // indirect
github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/theupdateframework/notary v0.7.0 // indirect
github.com/sigstore/sigstore v1.10.0 // indirect
github.com/sigstore/sigstore-go v1.1.4-0.20251124094504-b5fe07a5a7d7 // indirect
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f // indirect
github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f // indirect
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 // indirect
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect
@@ -142,16 +133,24 @@ require (
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/term v0.37.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/term v0.38.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
google.golang.org/protobuf v1.36.10 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
exclude (
// FIXME(thaJeztah): remove this once kubernetes updated their dependencies to no longer need this.
//
// For additional details, see this PR and links mentioned in that PR:
// https://github.com/kubernetes-sigs/kustomize/pull/5830#issuecomment-2569960859
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
)

352
go.sum
View File

@@ -1,11 +1,11 @@
cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8=
cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e h1:rd4bOvKmDIx0WeTv9Qz+hghsgyjikFiPrseXHlKepO0=
github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e/go.mod h1:blbwPQh4DTlCZEfk1BLU4oMIhLda2U+A840Uag9DsZw=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
@@ -14,36 +14,22 @@ github.com/Microsoft/hcsshim v0.14.0-rc.1 h1:qAPXKwGOkVn8LlqgBN8GS0bxZ83hOJpcjxz
github.com/Microsoft/hcsshim v0.14.0-rc.1/go.mod h1:hTKFGbnDtQb1wHiOWv4v0eN+7boSWAHyK/tNAaYZL0c=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d h1:hi6J4K6DKrR4/ljxn6SF6nURyu785wKMuQcjt7H3VCQ=
github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc=
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA=
github.com/beorn7/perks v0.0.0-20150223135152-b965b613227f/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/anchore/go-struct-converter v0.1.0 h1:2rDRssAl6mgKBSLNiVCMADgZRhoqtw9dedlWa0OhD30=
github.com/anchore/go-struct-converter v0.1.0/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENUpMkpg42fw=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY=
github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE=
github.com/bugsnag/bugsnag-go v1.0.5-0.20150529004307-13fd6b8acda0 h1:s7+5BfS4WFJoVF9pnB8kBk03S7pZXRdKamnV0FOl5Sc=
github.com/bugsnag/bugsnag-go v1.0.5-0.20150529004307-13fd6b8acda0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ=
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o=
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/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/compose-spec/compose-go/v2 v2.10.1 h1:mFbXobojGRFIVi1UknrvaDAZ+PkJfyjqkA1yseh+vAU=
@@ -66,8 +52,8 @@ github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY
github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/nydus-snapshotter v0.15.4 h1:l59kGRVMtwMLDLh322HsWhEsBCkRKMkGWYV5vBeLYCE=
github.com/containerd/nydus-snapshotter v0.15.4/go.mod h1:eRJqnxQDr48HNop15kZdLZpFF5B6vf6Q11Aq1K0E4Ms=
github.com/containerd/nydus-snapshotter v0.15.10 h1:hphjuKOqSHLGznNJiAvmsOWkdu4qFXjf4DzGrWSuIsM=
github.com/containerd/nydus-snapshotter v0.15.10/go.mod h1:EWRd/QJ0b6UKHAqYgiV5gHlqLC2qq5cQiSlXEdVovrA=
github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4=
github.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4=
github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y=
@@ -82,103 +68,123 @@ github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsx
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/cyphar/filepath-securejoin v0.5.1 h1:eYgfMq5yryL4fbWfkLpFFy2ukSELzaJOTaUTuh+oF48=
github.com/cyphar/filepath-securejoin v0.5.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 h1:uX1JmpONuD549D73r6cgnxyUu18Zb7yHAy5AYU0Pm4Q=
github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw=
github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is=
github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 h1:ge14PCmCvPjpMQMIAH7uKg0lrtNSOdpYsRXlwk3QbaE=
github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc=
github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 h1:lxmTCgmHE1GUYL7P0MlNa00M67axePTq+9nBSGddR8I=
github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7/go.mod h1:GvWntX9qiTlOud0WkQ6ewFm0LPy5JUR1Xo0Ngbd1w6Y=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/buildx v0.30.1 h1:3vthfaTQOLt5QfN2nl7rKuPLUvx69nL5ZikFIXp//c8=
github.com/docker/buildx v0.30.1/go.mod h1:8nwT0V6UNYNo9rXq6WO/BQd9KrJ0JYcY/QX6x0y1Oro=
github.com/docker/cli v28.5.2+incompatible h1:XmG99IHcBmIAoC1PPg9eLBZPlTrNUAijsHLm8PjhBlg=
github.com/docker/cli v28.5.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/buildx v0.31.1 h1:zbvbrb9nxBNVV8nnI33f2F+4aAZBA1gY+AmeBFflMqY=
github.com/docker/buildx v0.31.1/go.mod h1:SD+jYLnt3S4SXqohVtV+8z+dihnOgwMJ8t+bLQvsaCk=
github.com/docker/cli v29.2.1+incompatible h1:n3Jt0QVCN65eiVBoUTZQM9mcQICCJt3akW4pKAbKdJg=
github.com/docker/cli v29.2.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli-docs-tool v0.11.0 h1:7d8QARFb7QEobizqxmEM7fOteZEHwH/zWgHQtHZEcfE=
github.com/docker/cli-docs-tool v0.11.0/go.mod h1:ma8BKiisUo8D6W05XEYIh3oa1UbgrZhi1nowyKFJa8Q=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
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/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM=
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg=
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsevents v0.2.0 h1:BRlvlqjvNTfogHfeBOFvSC9N0Ddy+wzQCQukyoD7o/c=
github.com/fsnotify/fsevents v0.2.0/go.mod h1:B3eEk39i4hz8y1zaWS/wPrAP4O6wkIl7HQwKBr1qH/w=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw=
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
github.com/go-kit/kit v0.8.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.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.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sql-driver/mysql v1.3.0 h1:pgwjLi/dvffoP9aabwkT3AKpXQM93QARkjFhDDqC1UE=
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-openapi/analysis v0.24.1 h1:Xp+7Yn/KOnVWYG8d+hPksOYnCYImE3TieBa7rBOesYM=
github.com/go-openapi/analysis v0.24.1/go.mod h1:dU+qxX7QGU1rl7IYhBC8bIfmWQdX4Buoea4TGtxXY84=
github.com/go-openapi/errors v0.22.4 h1:oi2K9mHTOb5DPW2Zjdzs/NIvwi2N3fARKaTJLdNabaM=
github.com/go-openapi/errors v0.22.4/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk=
github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk=
github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM=
github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc=
github.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4=
github.com/go-openapi/loads v0.23.2 h1:rJXAcP7g1+lWyBHC7iTY+WAF0rprtM+pm8Jxv1uQJp4=
github.com/go-openapi/loads v0.23.2/go.mod h1:IEVw1GfRt/P2Pplkelxzj9BYFajiWOtY2nHZNj4UnWY=
github.com/go-openapi/runtime v0.29.2 h1:UmwSGWNmWQqKm1c2MGgXVpC2FTGwPDQeUsBMufc5Yj0=
github.com/go-openapi/runtime v0.29.2/go.mod h1:biq5kJXRJKBJxTDJXAa00DOTa/anflQPhT0/wmjuy+0=
github.com/go-openapi/spec v0.22.1 h1:beZMa5AVQzRspNjvhe5aG1/XyBSMeX1eEOs7dMoXh/k=
github.com/go-openapi/spec v0.22.1/go.mod h1:c7aeIQT175dVowfp7FeCvXXnjN/MrpaONStibD2WtDA=
github.com/go-openapi/strfmt v0.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ=
github.com/go-openapi/strfmt v0.25.0/go.mod h1:nNXct7OzbwrMY9+5tLX4I21pzcmE6ccMGXl3jFdPfn8=
github.com/go-openapi/swag v0.25.3 h1:FAa5wJXyDtI7yUztKDfZxDrSx+8WTg31MfCQ9s3PV+s=
github.com/go-openapi/swag v0.25.3/go.mod h1:tX9vI8Mj8Ny+uCEk39I1QADvIPI7lkndX4qCsEqhkS8=
github.com/go-openapi/swag/cmdutils v0.25.3 h1:EIwGxN143JCThNHnqfqs85R8lJcJG06qjJRZp3VvjLI=
github.com/go-openapi/swag/cmdutils v0.25.3/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
github.com/go-openapi/swag/conv v0.25.3 h1:PcB18wwfba7MN5BVlBIV+VxvUUeC2kEuCEyJ2/t2X7E=
github.com/go-openapi/swag/conv v0.25.3/go.mod h1:n4Ibfwhn8NJnPXNRhBO5Cqb9ez7alBR40JS4rbASUPU=
github.com/go-openapi/swag/fileutils v0.25.3 h1:P52Uhd7GShkeU/a1cBOuqIcHMHBrA54Z2t5fLlE85SQ=
github.com/go-openapi/swag/fileutils v0.25.3/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk=
github.com/go-openapi/swag/jsonname v0.25.3 h1:U20VKDS74HiPaLV7UZkztpyVOw3JNVsit+w+gTXRj0A=
github.com/go-openapi/swag/jsonname v0.25.3/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
github.com/go-openapi/swag/jsonutils v0.25.3 h1:kV7wer79KXUM4Ea4tBdAVTU842Rg6tWstX3QbM4fGdw=
github.com/go-openapi/swag/jsonutils v0.25.3/go.mod h1:ILcKqe4HC1VEZmJx51cVuZQ6MF8QvdfXsQfiaCs0z9o=
github.com/go-openapi/swag/loading v0.25.3 h1:Nn65Zlzf4854MY6Ft0JdNrtnHh2bdcS/tXckpSnOb2Y=
github.com/go-openapi/swag/loading v0.25.3/go.mod h1:xajJ5P4Ang+cwM5gKFrHBgkEDWfLcsAKepIuzTmOb/c=
github.com/go-openapi/swag/mangling v0.25.3 h1:rGIrEzXaYWuUW1MkFmG3pcH+EIA0/CoUkQnIyB6TUyo=
github.com/go-openapi/swag/mangling v0.25.3/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg=
github.com/go-openapi/swag/netutils v0.25.3 h1:XWXHZfL/65ABiv8rvGp9dtE0C6QHTYkCrNV77jTl358=
github.com/go-openapi/swag/netutils v0.25.3/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg=
github.com/go-openapi/swag/stringutils v0.25.3 h1:nAmWq1fUTWl/XiaEPwALjp/8BPZJun70iDHRNq/sH6w=
github.com/go-openapi/swag/stringutils v0.25.3/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=
github.com/go-openapi/swag/typeutils v0.25.3 h1:2w4mEEo7DQt3V4veWMZw0yTPQibiL3ri2fdDV4t2TQc=
github.com/go-openapi/swag/typeutils v0.25.3/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=
github.com/go-openapi/swag/yamlutils v0.25.3 h1:LKTJjCn/W1ZfMec0XDL4Vxh8kyAnv1orH5F2OREDUrg=
github.com/go-openapi/swag/yamlutils v0.25.3/go.mod h1:Y7QN6Wc5DOBXK14/xeo1cQlq0EA0wvLoSv13gDQoCao=
github.com/go-openapi/validate v0.25.1 h1:sSACUI6Jcnbo5IWqbYHgjibrhhmt3vR6lCzKZnmAgBw=
github.com/go-openapi/validate v0.25.1/go.mod h1:RMVyVFYte0gbSTaZ0N4KmTn6u/kClvAFp+mAVfS/DQc=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
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.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
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/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/certificate-transparency-go v1.3.2 h1:9ahSNZF2o7SYMaKaXhAumVEzXB2QaayzII9C8rv7v+A=
github.com/google/certificate-transparency-go v1.3.2/go.mod h1:H5FpMUaGa5Ab2+KCYsxg6sELw3Flkl7pGZzWdBoYLXs=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU=
github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -188,47 +194,26 @@ github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bP
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/in-toto/attestation v1.1.2 h1:MBFn6lsMq6dptQZJBhalXTcWMb/aJy3V+GX3VYj/V1E=
github.com/in-toto/attestation v1.1.2/go.mod h1:gYFddHMZj3DiQ0b62ltNi1Vj5rC879bTmBbrv9CRHpM=
github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU=
github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf h1:FtEj8sfIcaaBfAKrE1Cwb61YDtYq9JxChK1c7AKce7s=
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf/go.mod h1:yrqSXGoD/4EKfF26AOGzscPOgTTJcyAwM2rpixWT+t4=
github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8 h1:CZkYfurY6KGhVtlalI4QwQ6T0Cu6iuY3e0x5RLu96WE=
github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d h1:jRQLvyVGL+iVtDElaEIDdKwpPqUIZJfzkNLV34htpEc=
github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
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/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
@@ -239,28 +224,24 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/buildkit v0.26.3 h1:D+ruZVAk/3ipRq5XRxBH9/DIFpRjSlTtMbghT5gQP9g=
github.com/moby/buildkit v0.26.3/go.mod h1:4T4wJzQS4kYWIfFRjsbJry4QoxDBjK+UGOEOs1izL7w=
github.com/moby/buildkit v0.27.1 h1:qlIWpnZzqCkrYiGkctM1gBD/YZPOJTjtUdRBlI0oBOU=
github.com/moby/buildkit v0.27.1/go.mod h1:99qLrCrIAFgEOiFnCi9Y0Wwp6/qA7QvZ3uq/6wF0IsI=
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/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=
github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/moby/api v1.53.0 h1:PihqG1ncw4W+8mZs69jlwGXdaYBeb5brF6BL7mPIS/w=
github.com/moby/moby/api v1.53.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
github.com/moby/moby/client v0.2.2 h1:Pt4hRMCAIlyjL3cr8M5TrXCwKzguebPAc2do2ur7dEM=
github.com/moby/moby/client v0.2.2/go.mod h1:2EkIPVNCqR05CMIzL1mfA07t0HvVUUOl85pasRz/GmQ=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
@@ -281,70 +262,43 @@ github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ=
github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg=
github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE=
github.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg=
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=
github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
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-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/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.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@@ -355,52 +309,53 @@ github.com/secure-systems-lab/go-securesystemslib v0.9.1 h1:nZZaNz4DiERIQguNy0cL
github.com/secure-systems-lab/go-securesystemslib v0.9.1/go.mod h1:np53YzT0zXGMv6x4iEWc9Z59uR+x+ndLwCLqPYpLXVU=
github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI=
github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE=
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.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sigstore/protobuf-specs v0.5.0 h1:F8YTI65xOHw70NrvPwJ5PhAzsvTnuJMGLkA4FIkofAY=
github.com/sigstore/protobuf-specs v0.5.0/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc=
github.com/sigstore/rekor v1.4.3 h1:2+aw4Gbgumv8vYM/QVg6b+hvr4x4Cukur8stJrVPKU0=
github.com/sigstore/rekor v1.4.3/go.mod h1:o0zgY087Q21YwohVvGwV9vK1/tliat5mfnPiVI3i75o=
github.com/sigstore/rekor-tiles/v2 v2.0.1 h1:1Wfz15oSRNGF5Dzb0lWn5W8+lfO50ork4PGIfEKjZeo=
github.com/sigstore/rekor-tiles/v2 v2.0.1/go.mod h1:Pjsbhzj5hc3MKY8FfVTYHBUHQEnP0ozC4huatu4x7OU=
github.com/sigstore/sigstore v1.10.0 h1:lQrmdzqlR8p9SCfWIpFoGUqdXEzJSZT2X+lTXOMPaQI=
github.com/sigstore/sigstore v1.10.0/go.mod h1:Ygq+L/y9Bm3YnjpJTlQrOk/gXyrjkpn3/AEJpmk1n9Y=
github.com/sigstore/sigstore-go v1.1.4-0.20251124094504-b5fe07a5a7d7 h1:94NLPmq4bxvdmslzcG670IOkrlS98CGpmob8cjpFHuI=
github.com/sigstore/sigstore-go v1.1.4-0.20251124094504-b5fe07a5a7d7/go.mod h1:4r/PNX0G7uzkLpc3PSdYs5E2k4bWEJNXTK6kwAyw9TM=
github.com/sigstore/timestamp-authority/v2 v2.0.2 h1:WavlEeLh6HKt+osbmsHDg6/FaM/8Pz9iVUMh9pAsl/o=
github.com/sigstore/timestamp-authority/v2 v2.0.2/go.mod h1:D+wbQg8ASQzKnwBhLo7rIJD+9Zev4Ppqd4myPe8k57E=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk=
github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE=
github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94 h1:JmfC365KywYwHB946TTiQWEb8kqPY+pybPLoGE9GgVk=
github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spdx/tools-golang v0.5.7 h1:+sWcKGnhwp3vLdMqPcLdA6QK679vd86cK9hQWH3AwCg=
github.com/spdx/tools-golang v0.5.7/go.mod h1:jg7w0LOpoNAw6OxKEzCoqPC2GCTj45LyTlVmXubDsYw=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431 h1:XTHrT015sxHyJ5FnQ0AeemSspZWaDq7DoTRW0EVsDCE=
github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c h1:2EejZtjFjKJGk71ANb+wtFK5EjUzUkEM3R0xnp559xg=
github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c=
github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw=
github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI=
github.com/theupdateframework/go-tuf/v2 v2.3.0 h1:gt3X8xT8qu/HT4w+n1jgv+p7koi5ad8XEkLXXZqG9AA=
github.com/theupdateframework/go-tuf/v2 v2.3.0/go.mod h1:xW8yNvgXRncmovMLvBxKwrKpsOwJZu/8x+aB0KtFcdw=
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 h1:QB54BJwA6x8QU9nHY3xJSZR2kX9bgpZekRKGkLTmEXA=
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375/go.mod h1:xRroudyp5iVtxKqZCrA6n2TLFRBf8bmnjr1UD4x+z7g=
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 h1:r0p7fK56l8WPequOaR3i9LBqfPtEdXIQbUTzT55iqT4=
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY=
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f h1:MoxeMfHAe5Qj/ySSBfL8A7l1V+hxuluj8owsIEEZipI=
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98=
github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f h1:Z4NEQ86qFl1mHuCu9gwcE+EYCwDKfXAYXZbdIXyxmEA=
github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98=
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 h1:2f304B10LaZdB8kkVEaoXvAMVan2tl9AiK4G0odjQtE=
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE=
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk=
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab h1:H6aJ0yKQ0gF49Qb2z5hI1UHxSQt4JMyxebFR15KnApw=
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc=
github.com/transparency-dev/formats v0.0.0-20251017110053-404c0d5b696c h1:5a2XDQ2LiAUV+/RjckMyq9sXudfrPSuCY4FuPC1NyAw=
github.com/transparency-dev/formats v0.0.0-20251017110053-404c0d5b696c/go.mod h1:g85IafeFJZLxlzZCDRu4JLpfS7HKzR+Hw9qRh3bVzDI=
github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4=
github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A=
github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4=
github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
@@ -408,6 +363,8 @@ github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtX
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.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
@@ -444,56 +401,40 @@ 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/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U=
go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
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/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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-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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
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-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-20190222072716-a9d3bda3a223/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-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-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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -504,19 +445,18 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -530,39 +470,25 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda h1:+2XxjfsAu6vqFxwGBRcHiMaDCuZiqXGDUDVWVtrFAnE=
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101 h1:vk5TfqZHNn0obhPIYeS+cxIFKFQgser/M2jnI+9c6MM=
google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101/go.mod h1:E17fc4PDhkr22dE3RgnH2hEubUaky6ZwW4VhANxyspg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
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/cenkalti/backoff.v2 v2.2.1 h1:eJ9UAg01/HIHG987TwxvnzK2MgxXq97YY6rYDpY9aII=
gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1 h1:d4KQkxAaAiRY2h5Zqis161Pv91A37uZyJOx73duwUwM=
gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1/go.mod h1:WbjuEoo1oadwzQ4apSDU+JTvmllEHtsNHS6y7vFc7iw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
tags.cncf.io/container-device-interface v1.1.0 h1:RnxNhxF1JOu6CJUVpetTYvrXHdxw9j9jFYgZpI+anSY=
tags.cncf.io/container-device-interface v1.1.0/go.mod h1:76Oj0Yqp9FwTx/pySDc8Bxjpg+VqXfDb50cKAXVJ34Q=

View File

@@ -31,8 +31,8 @@ import (
"strings"
"sync"
"github.com/docker/docker/api/types/container"
"github.com/moby/go-archive"
"github.com/moby/moby/api/types/container"
"golang.org/x/sync/errgroup"
)

View File

@@ -25,7 +25,7 @@ import (
"time"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/api/types/container"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)

View File

@@ -36,15 +36,13 @@ func (m MuxExporter) ExportSpans(ctx context.Context, spans []sdktrace.ReadOnlyS
)
for _, exporter := range m.exporters {
wg.Add(1)
go func() {
defer wg.Done()
wg.Go(func() {
if err := exporter.ExportSpans(ctx, spans); err != nil {
errMu.Lock()
errs = append(errs, err)
errMu.Unlock()
}
}()
})
}
wg.Wait()
return errors.Join(errs...)
@@ -58,15 +56,13 @@ func (m MuxExporter) Shutdown(ctx context.Context) error {
)
for _, exporter := range m.exporters {
wg.Add(1)
go func() {
defer wg.Done()
wg.Go(func() {
if err := exporter.Shutdown(ctx); err != nil {
errMu.Lock()
errs = append(errs, err)
errMu.Unlock()
}
}()
})
}
wg.Wait()
return errors.Join(errs...)

View File

@@ -28,7 +28,8 @@ import (
"github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/platforms"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/volume"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/volume"
)
// LoadListener receives events during project loading.
@@ -154,7 +155,7 @@ type VolumesOptions struct {
Services []string
}
type VolumesSummary = *volume.Volume
type VolumesSummary = volume.Volume
type ScaleOptions struct {
Services []string
@@ -544,9 +545,9 @@ type ContainerSummary struct {
Project string
Service string
Created int64
State string
State container.ContainerState
Status string
Health string
Health container.HealthStatus
ExitCode int
Publishers PortPublishers
Labels map[string]string

View File

@@ -30,11 +30,11 @@ import (
"github.com/containerd/errdefs"
"github.com/docker/cli/cli/command"
cli "github.com/docker/cli/cli/command/container"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/go-connections/nat"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/client"
"github.com/moby/moby/client/pkg/jsonmessage"
"github.com/sirupsen/logrus"
"go.yaml.in/yaml/v4"
@@ -138,10 +138,14 @@ func convert(ctx context.Context, dockerCli command.Cli, model map[string]any, o
}
containerConfig.User = usr.Uid
}
created, err := dockerCli.Client().ContainerCreate(ctx, containerConfig, &container.HostConfig{
AutoRemove: true,
Binds: binds,
}, &network.NetworkingConfig{}, nil, "")
created, err := dockerCli.Client().ContainerCreate(ctx, client.ContainerCreateOptions{
Config: containerConfig,
HostConfig: &container.HostConfig{
Binds: binds,
AutoRemove: true,
},
NetworkingConfig: &network.NetworkingConfig{},
})
if err != nil {
return err
}
@@ -170,7 +174,11 @@ func LoadAdditionalResources(ctx context.Context, dockerCLI command.Cli, project
exposed := utils.Set[string]{}
exposed.AddAll(service.Expose...)
for port := range inspect.Config.ExposedPorts {
exposed.Add(nat.Port(port).Port())
p, err := network.ParsePort(port)
if err != nil {
return nil, err
}
exposed.Add(strconv.Itoa(int(p.Num())))
}
for _, port := range service.Ports {
exposed.Add(strconv.Itoa(int(port.Target)))
@@ -218,13 +226,14 @@ func inspectWithPull(ctx context.Context, dockerCli command.Cli, imageName strin
inspect, err := dockerCli.Client().ImageInspect(ctx, imageName)
if errdefs.IsNotFound(err) {
var stream io.ReadCloser
stream, err = dockerCli.Client().ImagePull(ctx, imageName, image.PullOptions{})
stream, err = dockerCli.Client().ImagePull(ctx, imageName, client.ImagePullOptions{})
if err != nil {
return image.InspectResponse{}, err
}
defer func() { _ = stream.Close() }()
err = jsonmessage.DisplayJSONMessagesToStream(stream, dockerCli.Out(), nil)
out := dockerCli.Out()
err = jsonmessage.DisplayJSONMessagesStream(stream, out, out.FD(), out.IsTerminal(), nil)
if err != nil {
return image.InspectResponse{}, err
}
@@ -232,5 +241,5 @@ func inspectWithPull(ctx context.Context, dockerCli command.Cli, imageName strin
return image.InspectResponse{}, err
}
}
return inspect, err
return inspect.InspectResponse, err
}

View File

@@ -23,11 +23,9 @@ import (
"path/filepath"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/moby/go-archive"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/client"
)
const (
@@ -65,34 +63,39 @@ func CreateTransformer(ctx context.Context, dockerCli command.Cli, options Creat
return err
}
created, err := dockerCli.Client().ContainerCreate(ctx, &container.Config{
created, err := dockerCli.Client().ContainerCreate(ctx, client.ContainerCreateOptions{
Image: options.From,
}, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
})
defer func() {
_ = dockerCli.Client().ContainerRemove(context.Background(), created.ID, container.RemoveOptions{Force: true})
_, _ = dockerCli.Client().ContainerRemove(context.Background(), created.ID, client.ContainerRemoveOptions{
Force: true,
})
}()
if err != nil {
return err
}
content, stat, err := dockerCli.Client().CopyFromContainer(ctx, created.ID, templatesPath)
resp, err := dockerCli.Client().CopyFromContainer(ctx, created.ID, client.CopyFromContainerOptions{
SourcePath: templatesPath,
})
if err != nil {
return err
}
defer func() {
_ = content.Close()
_ = resp.Content.Close()
}()
srcInfo := archive.CopyInfo{
Path: templatesPath,
Exists: true,
IsDir: stat.Mode.IsDir(),
IsDir: resp.Stat.Mode.IsDir(),
}
preArchive := content
preArchive := resp.Content
if srcInfo.RebaseName != "" {
_, srcBase := archive.SplitPathDirEntry(srcInfo.Path)
preArchive = archive.RebaseArchiveEntries(content, srcBase, srcInfo.RebaseName)
preArchive = archive.RebaseArchiveEntries(resp.Content, srcBase, srcInfo.RebaseName)
}
if err := archive.CopyTo(preArchive, srcInfo, out); err != nil {
@@ -111,10 +114,11 @@ COPY templates /templates
}
func ListTransformers(ctx context.Context, dockerCli command.Cli) ([]image.Summary, error) {
api := dockerCli.Client()
return api.ImageList(ctx, image.ListOptions{
Filters: filters.NewArgs(
filters.Arg("label", fmt.Sprintf("%s=%s", TransformerLabel, "transformation")),
),
res, err := dockerCli.Client().ImageList(ctx, client.ImageListOptions{
Filters: make(client.Filters).Add("label", fmt.Sprintf("%s=%s", TransformerLabel, "transformation")),
})
if err != nil {
return nil, err
}
return res.Items, nil
}

View File

@@ -19,29 +19,16 @@ package compose
// Docker Engine API version constants.
// These versions correspond to specific Docker Engine releases and their features.
const (
// APIVersion144 represents Docker Engine API version 1.44 (Engine v25.0).
//
// New features in this version:
// - Endpoint-specific MAC address configuration
// - Multiple networks can be connected during container creation
// - healthcheck.start_interval parameter support
//
// Before this version:
// - MAC address was container-wide only
// - Extra networks required post-creation NetworkConnect calls
// - healthcheck.start_interval was not available
APIVersion144 = "1.44"
// APIVersion148 represents Docker Engine API version 1.48 (Engine v28.0).
// apiVersion148 represents Docker Engine API version 1.48 (Engine v28.0).
//
// New features in this version:
// - Volume mounts with type=image support
//
// Before this version:
// - Only bind, volume, and tmpfs mount types were supported
APIVersion148 = "1.48"
apiVersion148 = "1.48"
// APIVersion149 represents Docker Engine API version 1.49 (Engine v28.1).
// apiVersion149 represents Docker Engine API version 1.49 (Engine v28.1).
//
// New features in this version:
// - Network interface_name configuration
@@ -50,17 +37,14 @@ const (
// Before this version:
// - interface_name was not configurable
// - ImageList didn't support platform filtering
APIVersion149 = "1.49"
apiVersion149 = "1.49"
)
// Docker Engine version strings for user-facing error messages.
// These should be used in error messages to provide clear version requirements.
const (
// DockerEngineV25 is the major version string for Docker Engine 25.x
DockerEngineV25 = "v25"
// DockerEngineV28 is the major version string for Docker Engine 28.x
DockerEngineV28 = "v28"
// dockerEngineV28 is the major version string for Docker Engine 28.x
dockerEngineV28 = "v28"
// DockerEngineV28_1 is the specific version string for Docker Engine 28.1
DockerEngineV28_1 = "v28.1"
@@ -68,6 +52,6 @@ const (
// Build tool version constants
const (
// BuildxMinVersion is the minimum required version of buildx for compose build
BuildxMinVersion = "0.17.0"
// buildxMinVersion is the minimum required version of buildx for compose build
buildxMinVersion = "0.17.0"
)

View File

@@ -24,8 +24,9 @@ import (
"strings"
"github.com/compose-spec/compose-go/v2/types"
containerType "github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/stdcopy"
"github.com/moby/moby/api/pkg/stdcopy"
containerType "github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/sirupsen/logrus"
"github.com/docker/compose/v5/pkg/api"
@@ -69,7 +70,7 @@ func (s *composeService) attachContainer(ctx context.Context, container containe
}
func (s *composeService) doAttachContainer(ctx context.Context, service, id, name string, listener api.ContainerEventListener) error {
inspect, err := s.apiClient().ContainerInspect(ctx, id)
inspect, err := s.apiClient().ContainerInspect(ctx, id, client.ContainerInspectOptions{})
if err != nil {
return err
}
@@ -93,7 +94,7 @@ func (s *composeService) doAttachContainer(ctx context.Context, service, id, nam
})
})
err = s.attachContainerStreams(ctx, id, inspect.Config.Tty, wOut, wErr)
err = s.attachContainerStreams(ctx, id, inspect.Container.Config.Tty, wOut, wErr)
if err != nil {
return err
}
@@ -136,7 +137,7 @@ func (s *composeService) attachContainerStreams(ctx context.Context, container s
}
func (s *composeService) getContainerStreams(ctx context.Context, container string) (io.ReadCloser, error) {
cnx, err := s.apiClient().ContainerAttach(ctx, container, containerType.AttachOptions{
cnx, err := s.apiClient().ContainerAttach(ctx, container, client.ContainerAttachOptions{
Stream: true,
Stdin: false,
Stdout: true,
@@ -144,12 +145,12 @@ func (s *composeService) getContainerStreams(ctx context.Context, container stri
Logs: false,
})
if err == nil {
stdout := ContainerStdout{HijackedResponse: cnx}
stdout := ContainerStdout{HijackedResponse: cnx.HijackedResponse}
return stdout, nil
}
// Fallback to logs API
logs, err := s.apiClient().ContainerLogs(ctx, container, containerType.LogsOptions{
logs, err := s.apiClient().ContainerLogs(ctx, container, client.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Follow: true,

View File

@@ -158,11 +158,37 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
if ok {
service.CustomLabels.Add(api.ImageDigestLabel, img.ID)
}
resolveImageVolumes(&service, images, project.Name)
project.Services[name] = service
}
return nil
}
func resolveImageVolumes(service *types.ServiceConfig, images map[string]api.ImageSummary, projectName string) {
for i, vol := range service.Volumes {
if vol.Type == types.VolumeTypeImage {
imgName := vol.Source
if _, ok := images[vol.Source]; !ok {
// check if source is another service in the project
imgName = api.GetImageNameOrDefault(types.ServiceConfig{Name: vol.Source}, projectName)
// If we still can't find it, it might be an external image that wasn't pulled yet or doesn't exist
if _, ok := images[imgName]; !ok {
continue
}
}
if img, ok := images[imgName]; ok {
// Use Image ID directly as source.
// Using name@digest format (via reference.WithDigest) fails for local-only images
// that don't have RepoDigests (e.g. built locally in CI).
// Image ID (sha256:...) is always valid and ensures ServiceHash changes on rebuild.
service.Volumes[i].Source = img.ID
}
}
}
}
func (s *composeService) getLocalImagesDigests(ctx context.Context, project *types.Project) (map[string]api.ImageSummary, error) {
imageNames := utils.Set[string]{}
for _, s := range project.Services {

View File

@@ -39,11 +39,11 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/image/build"
"github.com/docker/cli/cli/streams"
"github.com/docker/docker/api/types/versions"
"github.com/google/uuid"
"github.com/moby/buildkit/client"
gitutil "github.com/moby/buildkit/frontend/dockerfile/dfgitutil"
"github.com/moby/buildkit/util/progress/progressui"
"github.com/moby/moby/client/pkg/versions"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
@@ -424,8 +424,8 @@ func (s *composeService) getBuildxPlugin() (*manager.Plugin, error) {
return nil, fmt.Errorf("failed to get version of buildx")
}
if versions.LessThan(buildx.Version[1:], BuildxMinVersion) {
return nil, fmt.Errorf("compose build requires buildx %s or later", BuildxMinVersion)
if versions.LessThan(buildx.Version[1:], buildxMinVersion) {
return nil, fmt.Errorf("compose build requires buildx %s or later", buildxMinVersion)
}
return buildx, nil
@@ -447,7 +447,7 @@ type _console struct {
*streams.Out
}
func (c _console) Read(p []byte) (n int, err error) {
func (c _console) Read([]byte) (n int, err error) {
return 0, errors.New("not implemented")
}

View File

@@ -29,13 +29,15 @@ import (
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command/image/build"
buildtypes "github.com/docker/docker/api/types/build"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
"github.com/moby/go-archive"
buildtypes "github.com/moby/moby/api/types/build"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/jsonstream"
"github.com/moby/moby/api/types/registry"
"github.com/moby/moby/client"
"github.com/moby/moby/client/pkg/jsonmessage"
"github.com/moby/moby/client/pkg/progress"
"github.com/moby/moby/client/pkg/streamformatter"
"github.com/sirupsen/logrus"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
@@ -266,7 +268,7 @@ func (s *composeService) doBuildImage(ctx context.Context, project *types.Projec
defer response.Body.Close() //nolint:errcheck
imageID := ""
aux := func(msg jsonmessage.JSONMessage) {
aux := func(msg jsonstream.Message) {
var result buildtypes.Result
if err := json.Unmarshal(*msg.Aux, &result); err != nil {
logrus.Errorf("Failed to parse aux message: %s", err)
@@ -277,7 +279,7 @@ func (s *composeService) doBuildImage(ctx context.Context, project *types.Projec
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, progBuff.FD(), true, aux)
if err != nil {
var jerr *jsonmessage.JSONError
var jerr *jsonstream.Error
if errors.As(err, &jerr) {
// If no error code is set, default to 1
if jerr.Code == 0 {
@@ -291,9 +293,9 @@ func (s *composeService) doBuildImage(ctx context.Context, project *types.Projec
return imageID, nil
}
func imageBuildOptions(proxyConfigs map[string]string, project *types.Project, service types.ServiceConfig, options api.BuildOptions) buildtypes.ImageBuildOptions {
func imageBuildOptions(proxyConfigs map[string]string, project *types.Project, service types.ServiceConfig, options api.BuildOptions) client.ImageBuildOptions {
config := service.Build
return buildtypes.ImageBuildOptions{
return client.ImageBuildOptions{
Version: buildtypes.BuilderV1,
Tags: config.Tags,
NoCache: config.NoCache,

View File

@@ -21,7 +21,7 @@ import (
"fmt"
"strings"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/client"
"github.com/docker/compose/v5/pkg/api"
)
@@ -58,12 +58,12 @@ func (s *composeService) commit(ctx context.Context, projectName string, options
return nil
}
response, err := s.apiClient().ContainerCommit(ctx, ctr.ID, container.CommitOptions{
response, err := s.apiClient().ContainerCommit(ctx, ctr.ID, client.ContainerCommitOptions{
Reference: options.Reference,
Comment: options.Comment,
Author: options.Author,
Changes: options.Changes.GetSlice(),
Pause: options.Pause,
NoPause: !options.Pause,
})
if err != nil {
return err

View File

@@ -31,13 +31,10 @@ import (
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/flags"
"github.com/docker/cli/cli/streams"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"github.com/jonboulle/clockwork"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
"github.com/sirupsen/logrus"
"github.com/docker/compose/v5/pkg/api"
@@ -434,8 +431,8 @@ func increment(scale *int) *int {
}
func (s *composeService) actualVolumes(ctx context.Context, projectName string) (types.Volumes, error) {
opts := volume.ListOptions{
Filters: filters.NewArgs(projectFilter(projectName)),
opts := client.VolumeListOptions{
Filters: projectFilter(projectName),
}
volumes, err := s.apiClient().VolumeList(ctx, opts)
if err != nil {
@@ -443,7 +440,7 @@ func (s *composeService) actualVolumes(ctx context.Context, projectName string)
}
actual := types.Volumes{}
for _, vol := range volumes.Volumes {
for _, vol := range volumes.Items {
actual[vol.Labels[api.VolumeLabel]] = types.VolumeConfig{
Name: vol.Name,
Driver: vol.Driver,
@@ -454,15 +451,15 @@ func (s *composeService) actualVolumes(ctx context.Context, projectName string)
}
func (s *composeService) actualNetworks(ctx context.Context, projectName string) (types.Networks, error) {
networks, err := s.apiClient().NetworkList(ctx, network.ListOptions{
Filters: filters.NewArgs(projectFilter(projectName)),
networks, err := s.apiClient().NetworkList(ctx, client.NetworkListOptions{
Filters: projectFilter(projectName),
})
if err != nil {
return nil, err
}
actual := types.Networks{}
for _, net := range networks {
for _, net := range networks.Items {
actual[net.Labels[api.NetworkLabel]] = types.NetworkConfig{
Name: net.Name,
Driver: net.Driver,
@@ -480,11 +477,11 @@ var swarmEnabled = struct {
func (s *composeService) isSwarmEnabled(ctx context.Context) (bool, error) {
swarmEnabled.once.Do(func() {
info, err := s.apiClient().Info(ctx)
res, err := s.apiClient().Info(ctx, client.InfoOptions{})
if err != nil {
swarmEnabled.err = err
}
switch info.Swarm.LocalNodeState {
switch res.Info.Swarm.LocalNodeState {
case swarm.LocalNodeStateInactive, swarm.LocalNodeStateLocked:
swarmEnabled.val = false
default:
@@ -503,8 +500,9 @@ type runtimeVersionCache struct {
var runtimeVersion runtimeVersionCache
func (s *composeService) RuntimeVersion(ctx context.Context) (string, error) {
// TODO(thaJeztah): this should use Client.ClientVersion), which has the negotiated version.
runtimeVersion.once.Do(func() {
version, err := s.apiClient().ServerVersion(ctx)
version, err := s.apiClient().ServerVersion(ctx, client.ServerVersionOptions{})
if err != nil {
runtimeVersion.err = err
}

View File

@@ -19,14 +19,14 @@ package compose
import (
"io"
moby "github.com/docker/docker/api/types"
"github.com/moby/moby/client"
)
var _ io.ReadCloser = ContainerStdout{}
// ContainerStdout implement ReadCloser for moby.HijackedResponse
type ContainerStdout struct {
moby.HijackedResponse
client.HijackedResponse
}
// Read implement io.ReadCloser
@@ -44,7 +44,7 @@ var _ io.WriteCloser = ContainerStdin{}
// ContainerStdin implement WriteCloser for moby.HijackedResponse
type ContainerStdin struct {
moby.HijackedResponse
client.HijackedResponse
}
// Write implement io.WriteCloser

View File

@@ -24,8 +24,8 @@ import (
"strconv"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/docker/compose/v5/pkg/api"
)
@@ -42,32 +42,31 @@ const (
)
func (s *composeService) getContainers(ctx context.Context, project string, oneOff oneOff, all bool, selectedServices ...string) (Containers, error) {
var containers Containers
f := getDefaultFilters(project, oneOff, selectedServices...)
containers, err := s.apiClient().ContainerList(ctx, container.ListOptions{
Filters: filters.NewArgs(f...),
res, err := s.apiClient().ContainerList(ctx, client.ContainerListOptions{
Filters: getDefaultFilters(project, oneOff, selectedServices...),
All: all,
})
if err != nil {
return nil, err
}
containers := Containers(res.Items)
if len(selectedServices) > 1 {
containers = containers.filter(isService(selectedServices...))
}
return containers, nil
}
func getDefaultFilters(projectName string, oneOff oneOff, selectedServices ...string) []filters.KeyValuePair {
f := []filters.KeyValuePair{projectFilter(projectName)}
func getDefaultFilters(projectName string, oneOff oneOff, selectedServices ...string) client.Filters {
f := projectFilter(projectName)
if len(selectedServices) == 1 {
f = append(f, serviceFilter(selectedServices[0]))
f.Add("label", serviceFilter(selectedServices[0]))
}
f = append(f, hasConfigHashLabel())
f.Add("label", hasConfigHashLabel())
switch oneOff {
case oneOffOnly:
f = append(f, oneOffFilter(true))
f.Add("label", oneOffFilter(true))
case oneOffExclude:
f = append(f, oneOffFilter(false))
f.Add("label", oneOffFilter(false))
case oneOffInclude:
}
return f
@@ -76,17 +75,16 @@ func getDefaultFilters(projectName string, oneOff oneOff, selectedServices ...st
func (s *composeService) getSpecifiedContainer(ctx context.Context, projectName string, oneOff oneOff, all bool, serviceName string, containerIndex int) (container.Summary, error) {
defaultFilters := getDefaultFilters(projectName, oneOff, serviceName)
if containerIndex > 0 {
defaultFilters = append(defaultFilters, containerNumberFilter(containerIndex))
defaultFilters.Add("label", containerNumberFilter(containerIndex))
}
containers, err := s.apiClient().ContainerList(ctx, container.ListOptions{
Filters: filters.NewArgs(
defaultFilters...,
),
All: all,
res, err := s.apiClient().ContainerList(ctx, client.ContainerListOptions{
Filters: defaultFilters,
All: all,
})
if err != nil {
return container.Summary{}, err
}
containers := res.Items
if len(containers) < 1 {
if containerIndex > 0 {
return container.Summary{}, fmt.Errorf("service %q is not running container #%d", serviceName, containerIndex)

View File

@@ -30,9 +30,9 @@ import (
"github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/platforms"
"github.com/docker/docker/api/types/container"
mmount "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/versions"
"github.com/moby/moby/api/types/container"
mmount "github.com/moby/moby/api/types/mount"
"github.com/moby/moby/client"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
"go.opentelemetry.io/otel/attribute"
@@ -657,17 +657,19 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
}
timeoutInSecond := utils.DurationSecondToInt(timeout)
err = s.apiClient().ContainerStop(ctx, replaced.ID, container.StopOptions{Timeout: timeoutInSecond})
_, err = s.apiClient().ContainerStop(ctx, replaced.ID, client.ContainerStopOptions{Timeout: timeoutInSecond})
if err != nil {
return created, err
}
err = s.apiClient().ContainerRemove(ctx, replaced.ID, container.RemoveOptions{})
_, err = s.apiClient().ContainerRemove(ctx, replaced.ID, client.ContainerRemoveOptions{})
if err != nil {
return created, err
}
err = s.apiClient().ContainerRename(ctx, tmpName, name)
_, err = s.apiClient().ContainerRename(ctx, tmpName, client.ContainerRenameOptions{
NewName: name,
})
if err != nil {
return created, err
}
@@ -683,7 +685,7 @@ func (s *composeService) startContainer(ctx context.Context, ctr container.Summa
s.events.On(newEvent(getContainerProgressName(ctr), api.Working, "Restart"))
startMx.Lock()
defer startMx.Unlock()
err := s.apiClient().ContainerStart(ctx, ctr.ID, container.StartOptions{})
_, err := s.apiClient().ContainerStart(ctx, ctr.ID, client.ContainerStartOptions{})
if err != nil {
return err
}
@@ -713,7 +715,13 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
plat = &p
}
response, err := s.apiClient().ContainerCreate(ctx, cfgs.Container, cfgs.Host, cfgs.Network, plat, name)
response, err := s.apiClient().ContainerCreate(ctx, client.ContainerCreateOptions{
Name: name,
Platform: plat,
Config: cfgs.Container,
HostConfig: cfgs.Host,
NetworkingConfig: cfgs.Network,
})
if err != nil {
return created, err
}
@@ -724,42 +732,19 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
Text: warning,
})
}
inspectedContainer, err := s.apiClient().ContainerInspect(ctx, response.ID)
res, err := s.apiClient().ContainerInspect(ctx, response.ID, client.ContainerInspectOptions{})
if err != nil {
return created, err
}
created = container.Summary{
ID: inspectedContainer.ID,
Labels: inspectedContainer.Config.Labels,
Names: []string{inspectedContainer.Name},
ID: res.Container.ID,
Labels: res.Container.Config.Labels,
Names: []string{res.Container.Name},
NetworkSettings: &container.NetworkSettingsSummary{
Networks: inspectedContainer.NetworkSettings.Networks,
Networks: res.Container.NetworkSettings.Networks,
},
}
apiVersion, err := s.RuntimeVersion(ctx)
if err != nil {
return created, err
}
// Starting API version 1.44, the ContainerCreate API call takes multiple networks
// so we include all the configurations there and can skip the one-by-one calls here
if versions.LessThan(apiVersion, APIVersion144) {
// the highest-priority network is the primary and is included in the ContainerCreate API
// call via container.NetworkMode & network.NetworkingConfig
// any remaining networks are connected one-by-one here after creation (but before start)
serviceNetworks := service.NetworksByPriority()
for _, networkKey := range serviceNetworks {
mobyNetworkName := project.Networks[networkKey].Name
if string(cfgs.Host.NetworkMode) == mobyNetworkName {
// primary network already configured as part of ContainerCreate
continue
}
epSettings := createEndpointSettings(project, service, number, networkKey, cfgs.Links, opts.UseNetworkAliases)
if err := s.apiClient().NetworkConnect(ctx, mobyNetworkName, created.ID, epSettings); err != nil {
return created, err
}
}
}
return created, nil
}
@@ -774,11 +759,10 @@ func (s *composeService) getLinks(ctx context.Context, projectName string, servi
}
for _, rawLink := range service.Links {
linkSplit := strings.Split(rawLink, ":")
linkServiceName := linkSplit[0]
linkName := linkServiceName
if len(linkSplit) == 2 {
linkName = linkSplit[1] // linkName if informed like in: "serviceName:linkName"
// linkName if informed like in: "serviceName[:linkName]"
linkServiceName, linkName, ok := strings.Cut(rawLink, ":")
if !ok {
linkName = linkServiceName
}
cnts, err := getServiceContainers(linkServiceName)
if err != nil {
@@ -810,11 +794,9 @@ func (s *composeService) getLinks(ctx context.Context, projectName string, servi
}
for _, rawExtLink := range service.ExternalLinks {
extLinkSplit := strings.Split(rawExtLink, ":")
externalLink := extLinkSplit[0]
linkName := externalLink
if len(extLinkSplit) == 2 {
linkName = extLinkSplit[1]
externalLink, linkName, ok := strings.Cut(rawExtLink, ":")
if !ok {
linkName = externalLink
}
links = append(links, format(externalLink, linkName))
}
@@ -823,10 +805,11 @@ func (s *composeService) getLinks(ctx context.Context, projectName string, servi
func (s *composeService) isServiceHealthy(ctx context.Context, containers Containers, fallbackRunning bool) (bool, error) {
for _, c := range containers {
ctr, err := s.apiClient().ContainerInspect(ctx, c.ID)
res, err := s.apiClient().ContainerInspect(ctx, c.ID, client.ContainerInspectOptions{})
if err != nil {
return false, err
}
ctr := res.Container
name := ctr.Name[1:]
if ctr.State.Status == container.StateExited {
@@ -858,12 +841,12 @@ func (s *composeService) isServiceHealthy(ctx context.Context, containers Contai
func (s *composeService) isServiceCompleted(ctx context.Context, containers Containers) (bool, int, error) {
for _, c := range containers {
ctr, err := s.apiClient().ContainerInspect(ctx, c.ID)
res, err := s.apiClient().ContainerInspect(ctx, c.ID, client.ContainerInspectOptions{})
if err != nil {
return false, 0, err
}
if ctr.State != nil && ctr.State.Status == container.StateExited {
return true, ctr.State.ExitCode, nil
if res.Container.State != nil && res.Container.State.Status == container.StateExited {
return true, res.Container.State.ExitCode, nil
}
}
return false, 0, nil
@@ -907,7 +890,7 @@ func (s *composeService) startService(ctx context.Context,
eventName := getContainerProgressName(ctr)
s.events.On(startingEvent(eventName))
err = s.apiClient().ContainerStart(ctx, ctr.ID, container.StartOptions{})
_, err = s.apiClient().ContainerStart(ctx, ctr.ID, client.ContainerStartOptions{})
if err != nil {
return err
}

View File

@@ -17,18 +17,18 @@
package compose
import (
"context"
"fmt"
"net/netip"
"strings"
"testing"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli/config/configfile"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/docker/go-connections/nat"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/client"
"go.uber.org/mock/gomock"
"gotest.tools/v3/assert"
@@ -69,9 +69,8 @@ func TestServiceLinks(t *testing.T) {
Scale: intPtr(1),
}
containerListOptions := container.ListOptions{
Filters: filters.NewArgs(
projectFilter(testProject),
containerListOptions := client.ContainerListOptions{
Filters: projectFilter(testProject).Add("label",
serviceFilter("db"),
oneOffFilter(false),
hasConfigHashLabel(),
@@ -92,7 +91,9 @@ func TestServiceLinks(t *testing.T) {
s.Links = []string{"db"}
c := testContainer("db", dbContainerName, false)
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return([]container.Summary{c}, nil)
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return(client.ContainerListResult{
Items: []container.Summary{c},
}, nil)
links, err := tested.(*composeService).getLinks(t.Context(), testProject, s, 1)
assert.NilError(t, err)
@@ -116,7 +117,9 @@ func TestServiceLinks(t *testing.T) {
c := testContainer("db", dbContainerName, false)
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return([]container.Summary{c}, nil)
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return(client.ContainerListResult{
Items: []container.Summary{c},
}, nil)
links, err := tested.(*composeService).getLinks(t.Context(), testProject, s, 1)
assert.NilError(t, err)
@@ -138,7 +141,9 @@ func TestServiceLinks(t *testing.T) {
s.Links = []string{"db:dbname"}
c := testContainer("db", dbContainerName, false)
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return([]container.Summary{c}, nil)
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return(client.ContainerListResult{
Items: []container.Summary{c},
}, nil)
links, err := tested.(*composeService).getLinks(t.Context(), testProject, s, 1)
assert.NilError(t, err)
@@ -162,7 +167,9 @@ func TestServiceLinks(t *testing.T) {
s.ExternalLinks = []string{"db1:db2"}
c := testContainer("db", dbContainerName, false)
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return([]container.Summary{c}, nil)
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return(client.ContainerListResult{
Items: []container.Summary{c},
}, nil)
links, err := tested.(*composeService).getLinks(t.Context(), testProject, s, 1)
assert.NilError(t, err)
@@ -190,16 +197,17 @@ func TestServiceLinks(t *testing.T) {
s.Labels = s.Labels.Add(api.OneoffLabel, "True")
c := testContainer("web", webContainerName, true)
containerListOptionsOneOff := container.ListOptions{
Filters: filters.NewArgs(
projectFilter(testProject),
containerListOptionsOneOff := client.ContainerListOptions{
Filters: projectFilter(testProject).Add("label",
serviceFilter("web"),
oneOffFilter(false),
hasConfigHashLabel(),
),
All: true,
}
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptionsOneOff).Return([]container.Summary{c}, nil)
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptionsOneOff).Return(client.ContainerListResult{
Items: []container.Summary{c},
}, nil)
links, err := tested.(*composeService).getLinks(t.Context(), testProject, s, 1)
assert.NilError(t, err)
@@ -268,15 +276,15 @@ func TestIsServiceHealthy(t *testing.T) {
}
// Container with disabled healthcheck (Test: ["NONE"])
apiClient.EXPECT().ContainerInspect(ctx, containerID).Return(container.InspectResponse{
ContainerJSONBase: &container.ContainerJSONBase{
apiClient.EXPECT().ContainerInspect(ctx, containerID, gomock.Any()).Return(client.ContainerInspectResult{
Container: container.InspectResponse{
ID: containerID,
Name: "test-container",
State: &container.State{Status: "running"},
},
Config: &container.Config{
Healthcheck: &container.HealthConfig{
Test: []string{"NONE"},
Config: &container.Config{
Healthcheck: &container.HealthConfig{
Test: []string{"NONE"},
},
},
},
}, nil)
@@ -293,15 +301,15 @@ func TestIsServiceHealthy(t *testing.T) {
}
// Container with disabled healthcheck (Test: ["NONE"]) but fallbackRunning=false
apiClient.EXPECT().ContainerInspect(ctx, containerID).Return(container.InspectResponse{
ContainerJSONBase: &container.ContainerJSONBase{
apiClient.EXPECT().ContainerInspect(ctx, containerID, gomock.Any()).Return(client.ContainerInspectResult{
Container: container.InspectResponse{
ID: containerID,
Name: "test-container",
State: &container.State{Status: "running"},
},
Config: &container.Config{
Healthcheck: &container.HealthConfig{
Test: []string{"NONE"},
Config: &container.Config{
Healthcheck: &container.HealthConfig{
Test: []string{"NONE"},
},
},
},
}, nil)
@@ -317,14 +325,14 @@ func TestIsServiceHealthy(t *testing.T) {
}
// Container with no healthcheck at all
apiClient.EXPECT().ContainerInspect(ctx, containerID).Return(container.InspectResponse{
ContainerJSONBase: &container.ContainerJSONBase{
apiClient.EXPECT().ContainerInspect(ctx, containerID, gomock.Any()).Return(client.ContainerInspectResult{
Container: container.InspectResponse{
ID: containerID,
Name: "test-container",
State: &container.State{Status: "running"},
},
Config: &container.Config{
Healthcheck: nil,
Config: &container.Config{
Healthcheck: nil,
},
},
}, nil)
@@ -340,18 +348,18 @@ func TestIsServiceHealthy(t *testing.T) {
}
// Container with disabled healthcheck but exited
apiClient.EXPECT().ContainerInspect(ctx, containerID).Return(container.InspectResponse{
ContainerJSONBase: &container.ContainerJSONBase{
apiClient.EXPECT().ContainerInspect(ctx, containerID, gomock.Any()).Return(client.ContainerInspectResult{
Container: container.InspectResponse{
ID: containerID,
Name: "test-container",
State: &container.State{
Status: "exited",
ExitCode: 1,
},
},
Config: &container.Config{
Healthcheck: &container.HealthConfig{
Test: []string{"NONE"},
Config: &container.Config{
Healthcheck: &container.HealthConfig{
Test: []string{"NONE"},
},
},
},
}, nil)
@@ -367,8 +375,8 @@ func TestIsServiceHealthy(t *testing.T) {
}
// Container with actual healthcheck that is healthy
apiClient.EXPECT().ContainerInspect(ctx, containerID).Return(container.InspectResponse{
ContainerJSONBase: &container.ContainerJSONBase{
apiClient.EXPECT().ContainerInspect(ctx, containerID, gomock.Any()).Return(client.ContainerInspectResult{
Container: container.InspectResponse{
ID: containerID,
Name: "test-container",
State: &container.State{
@@ -377,10 +385,10 @@ func TestIsServiceHealthy(t *testing.T) {
Status: container.Healthy,
},
},
},
Config: &container.Config{
Healthcheck: &container.HealthConfig{
Test: []string{"CMD", "curl", "-f", "http://localhost"},
Config: &container.Config{
Healthcheck: &container.HealthConfig{
Test: []string{"CMD", "curl", "-f", "http://localhost"},
},
},
},
}, nil)
@@ -392,177 +400,101 @@ func TestIsServiceHealthy(t *testing.T) {
}
func TestCreateMobyContainer(t *testing.T) {
t.Run("connects container networks one by one if API <1.44", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
apiClient := mocks.NewMockAPIClient(mockCtrl)
cli := mocks.NewMockCli(mockCtrl)
tested, err := NewComposeService(cli)
assert.NilError(t, err)
cli.EXPECT().Client().Return(apiClient).AnyTimes()
cli.EXPECT().ConfigFile().Return(&configfile.ConfigFile{}).AnyTimes()
apiClient.EXPECT().DaemonHost().Return("").AnyTimes()
apiClient.EXPECT().ImageInspect(gomock.Any(), gomock.Any()).Return(image.InspectResponse{}, nil).AnyTimes()
// force `RuntimeVersion` to fetch again
runtimeVersion = runtimeVersionCache{}
apiClient.EXPECT().ServerVersion(gomock.Any()).Return(moby.Version{
APIVersion: "1.43",
}, nil).AnyTimes()
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
apiClient := mocks.NewMockAPIClient(mockCtrl)
cli := mocks.NewMockCli(mockCtrl)
tested, err := NewComposeService(cli)
assert.NilError(t, err)
cli.EXPECT().Client().Return(apiClient).AnyTimes()
cli.EXPECT().ConfigFile().Return(&configfile.ConfigFile{}).AnyTimes()
apiClient.EXPECT().DaemonHost().Return("").AnyTimes()
apiClient.EXPECT().ImageInspect(anyCancellableContext(), gomock.Any()).Return(client.ImageInspectResult{}, nil).AnyTimes()
service := types.ServiceConfig{
Name: "test",
Networks: map[string]*types.ServiceNetworkConfig{
"a": {
Priority: 10,
},
"b": {
Priority: 100,
},
// force `RuntimeVersion` to fetch fresh version
runtimeVersion = runtimeVersionCache{}
apiClient.EXPECT().ServerVersion(gomock.Any(), gomock.Any()).Return(client.ServerVersionResult{
APIVersion: "1.44",
}, nil).AnyTimes()
service := types.ServiceConfig{
Name: "test",
Networks: map[string]*types.ServiceNetworkConfig{
"a": {
Priority: 10,
},
}
project := types.Project{
Name: "bork",
Services: types.Services{
"test": service,
"b": {
Priority: 100,
},
Networks: types.Networks{
"a": types.NetworkConfig{
Name: "a-moby-name",
},
"b": types.NetworkConfig{
Name: "b-moby-name",
},
},
}
project := types.Project{
Name: "bork",
Services: types.Services{
"test": service,
},
Networks: types.Networks{
"a": types.NetworkConfig{
Name: "a-moby-name",
},
}
"b": types.NetworkConfig{
Name: "b-moby-name",
},
},
}
var falseBool bool
apiClient.EXPECT().ContainerCreate(gomock.Any(), gomock.Any(), gomock.Eq(
&container.HostConfig{
PortBindings: nat.PortMap{},
ExtraHosts: []string{},
Tmpfs: map[string]string{},
Resources: container.Resources{
OomKillDisable: &falseBool,
},
NetworkMode: "b-moby-name",
}), gomock.Eq(
&network.NetworkingConfig{
EndpointsConfig: map[string]*network.EndpointSettings{
"b-moby-name": {
IPAMConfig: &network.EndpointIPAMConfig{},
Aliases: []string{"bork-test-0"},
},
},
}), gomock.Any(), gomock.Any()).Times(1).Return(
container.CreateResponse{
ID: "an-id",
}, nil)
apiClient.EXPECT().ContainerInspect(gomock.Any(), gomock.Eq("an-id")).Times(1).Return(
container.InspectResponse{
ContainerJSONBase: &container.ContainerJSONBase{
ID: "an-id",
Name: "a-name",
},
Config: &container.Config{},
NetworkSettings: &container.NetworkSettings{},
}, nil)
apiClient.EXPECT().NetworkConnect(gomock.Any(), "a-moby-name", "an-id", gomock.Eq(
&network.EndpointSettings{
IPAMConfig: &network.EndpointIPAMConfig{},
Aliases: []string{"bork-test-0"},
}))
_, err = tested.(*composeService).createMobyContainer(t.Context(), &project, service, "test", 0, nil, createOptions{
Labels: make(types.Labels),
})
assert.NilError(t, err)
var got client.ContainerCreateOptions
apiClient.EXPECT().ContainerCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, opts client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
got = opts
return client.ContainerCreateResult{ID: "an-id"}, nil
})
t.Run("includes all container networks in ContainerCreate call if API >=1.44", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
apiClient := mocks.NewMockAPIClient(mockCtrl)
cli := mocks.NewMockCli(mockCtrl)
tested, err := NewComposeService(cli)
assert.NilError(t, err)
cli.EXPECT().Client().Return(apiClient).AnyTimes()
cli.EXPECT().ConfigFile().Return(&configfile.ConfigFile{}).AnyTimes()
apiClient.EXPECT().DaemonHost().Return("").AnyTimes()
apiClient.EXPECT().ImageInspect(gomock.Any(), gomock.Any()).Return(image.InspectResponse{}, nil).AnyTimes()
// force `RuntimeVersion` to fetch fresh version
runtimeVersion = runtimeVersionCache{}
apiClient.EXPECT().ServerVersion(gomock.Any()).Return(moby.Version{
APIVersion: APIVersion144,
}, nil).AnyTimes()
apiClient.EXPECT().ContainerInspect(gomock.Any(), gomock.Eq("an-id"), gomock.Any()).Times(1).Return(client.ContainerInspectResult{
Container: container.InspectResponse{
ID: "an-id",
Name: "a-name",
Config: &container.Config{},
NetworkSettings: &container.NetworkSettings{},
},
}, nil)
service := types.ServiceConfig{
Name: "test",
Networks: map[string]*types.ServiceNetworkConfig{
"a": {
Priority: 10,
},
"b": {
Priority: 100,
},
},
}
project := types.Project{
Name: "bork",
Services: types.Services{
"test": service,
},
Networks: types.Networks{
"a": types.NetworkConfig{
Name: "a-moby-name",
},
"b": types.NetworkConfig{
Name: "b-moby-name",
},
},
}
var falseBool bool
apiClient.EXPECT().ContainerCreate(gomock.Any(), gomock.Any(), gomock.Eq(
&container.HostConfig{
PortBindings: nat.PortMap{},
ExtraHosts: []string{},
Tmpfs: map[string]string{},
Resources: container.Resources{
OomKillDisable: &falseBool,
},
NetworkMode: "b-moby-name",
}), gomock.Eq(
&network.NetworkingConfig{
EndpointsConfig: map[string]*network.EndpointSettings{
"a-moby-name": {
IPAMConfig: &network.EndpointIPAMConfig{},
Aliases: []string{"bork-test-0"},
},
"b-moby-name": {
IPAMConfig: &network.EndpointIPAMConfig{},
Aliases: []string{"bork-test-0"},
},
},
}), gomock.Any(), gomock.Any()).Times(1).Return(
container.CreateResponse{
ID: "an-id",
}, nil)
apiClient.EXPECT().ContainerInspect(gomock.Any(), gomock.Eq("an-id")).Times(1).Return(
container.InspectResponse{
ContainerJSONBase: &container.ContainerJSONBase{
ID: "an-id",
Name: "a-name",
},
Config: &container.Config{},
NetworkSettings: &container.NetworkSettings{},
}, nil)
_, err = tested.(*composeService).createMobyContainer(t.Context(), &project, service, "test", 0, nil, createOptions{
Labels: make(types.Labels),
})
assert.NilError(t, err)
_, err = tested.(*composeService).createMobyContainer(t.Context(), &project, service, "test", 0, nil, createOptions{
Labels: make(types.Labels),
})
var falseBool bool
want := client.ContainerCreateOptions{
Config: &container.Config{
AttachStdout: true,
AttachStderr: true,
Image: "bork-test",
Labels: map[string]string{
"com.docker.compose.config-hash": "8dbce408396f8986266bc5deba0c09cfebac63c95c2238e405c7bee5f1bd84b8",
"com.docker.compose.depends_on": "",
},
},
HostConfig: &container.HostConfig{
PortBindings: network.PortMap{},
ExtraHosts: []string{},
Tmpfs: map[string]string{},
Resources: container.Resources{
OomKillDisable: &falseBool,
},
NetworkMode: "b-moby-name",
},
NetworkingConfig: &network.NetworkingConfig{
EndpointsConfig: map[string]*network.EndpointSettings{
"a-moby-name": {
IPAMConfig: &network.EndpointIPAMConfig{},
Aliases: []string{"bork-test-0"},
},
"b-moby-name": {
IPAMConfig: &network.EndpointIPAMConfig{},
Aliases: []string{"bork-test-0"},
},
},
},
Name: "test",
}
assert.DeepEqual(t, want, got, cmpopts.EquateComparable(netip.Addr{}), cmpopts.EquateEmpty())
assert.NilError(t, err)
}

View File

@@ -23,8 +23,7 @@ import (
"time"
compose "github.com/compose-spec/compose-go/v2/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/versions"
"github.com/moby/moby/api/types/container"
)
// ToMobyEnv convert into []string
@@ -69,15 +68,7 @@ func (s *composeService) ToMobyHealthCheck(ctx context.Context, check *compose.H
}
var startInterval time.Duration
if check.StartInterval != nil {
version, err := s.RuntimeVersion(ctx)
if err != nil {
return nil, err
}
if versions.LessThan(version, APIVersion144) {
return nil, fmt.Errorf("can't set healthcheck.start_interval as feature require Docker Engine %s or later", DockerEngineV25)
} else {
startInterval = time.Duration(*check.StartInterval)
}
startInterval = time.Duration(*check.StartInterval)
if check.StartPeriod == nil {
// see https://github.com/moby/moby/issues/48874
return nil, errors.New("healthcheck.start_interval requires healthcheck.start_period to be set")

View File

@@ -26,8 +26,9 @@ import (
"strings"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types/container"
"github.com/moby/go-archive"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"golang.org/x/sync/errgroup"
"github.com/docker/compose/v5/pkg/api"
@@ -153,7 +154,13 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
// Prepare destination copy info by stat-ing the container path.
dstInfo := archive.CopyInfo{Path: dstPath}
dstStat, err := s.apiClient().ContainerStatPath(ctx, containerID, dstPath)
var dstStat container.PathStat
res, err := s.apiClient().ContainerStatPath(ctx, containerID, client.ContainerStatPathOptions{
Path: dstPath,
})
if err == nil {
dstStat = res.Stat
}
// If the destination is a symbolic link, we should evaluate it.
if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
@@ -165,7 +172,12 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
}
dstInfo.Path = linkTarget
dstStat, err = s.apiClient().ContainerStatPath(ctx, containerID, linkTarget)
res, err = s.apiClient().ContainerStatPath(ctx, containerID, client.ContainerStatPathOptions{
Path: linkTarget,
})
if err == nil {
dstStat = res.Stat
}
}
// Validate the destination path
@@ -232,11 +244,13 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
}
}
options := container.CopyToContainerOptions{
_, err = s.apiClient().CopyToContainer(ctx, containerID, client.CopyToContainerOptions{
DestinationPath: resolvedDstPath,
Content: content,
AllowOverwriteDirWithFile: false,
CopyUIDGID: opts.CopyUIDGID,
}
return s.apiClient().CopyToContainer(ctx, containerID, resolvedDstPath, content, options)
})
return err
}
func (s *composeService) copyFromContainer(ctx context.Context, containerID, srcPath, dstPath string, opts api.CopyOptions) error {
@@ -256,7 +270,13 @@ func (s *composeService) copyFromContainer(ctx context.Context, containerID, src
// if client requests to follow symbol link, then must decide target file to be copied
var rebaseName string
if opts.FollowLink {
srcStat, err := s.apiClient().ContainerStatPath(ctx, containerID, srcPath)
var srcStat container.PathStat
res, err := s.apiClient().ContainerStatPath(ctx, containerID, client.ContainerStatPathOptions{
Path: srcPath,
})
if err == nil {
srcStat = res.Stat
}
// If the destination is a symbolic link, we should follow it.
if err == nil && srcStat.Mode&os.ModeSymlink != 0 {
@@ -272,28 +292,30 @@ func (s *composeService) copyFromContainer(ctx context.Context, containerID, src
}
}
content, stat, err := s.apiClient().CopyFromContainer(ctx, containerID, srcPath)
res, err := s.apiClient().CopyFromContainer(ctx, containerID, client.CopyFromContainerOptions{
SourcePath: srcPath,
})
if err != nil {
return err
}
defer content.Close() //nolint:errcheck
defer res.Content.Close() //nolint:errcheck
if dstPath == "-" {
_, err = io.Copy(s.stdout(), content)
_, err = io.Copy(s.stdout(), res.Content)
return err
}
srcInfo := archive.CopyInfo{
Path: srcPath,
Exists: true,
IsDir: stat.Mode.IsDir(),
IsDir: res.Stat.Mode.IsDir(),
RebaseName: rebaseName,
}
preArchive := content
preArchive := res.Content
if srcInfo.RebaseName != "" {
_, srcBase := archive.SplitPathDirEntry(srcInfo.Path)
preArchive = archive.RebaseArchiveEntries(content, srcBase, srcInfo.RebaseName)
preArchive = archive.RebaseArchiveEntries(res.Content, srcBase, srcInfo.RebaseName)
}
return archive.CopyTo(preArchive, srcInfo, dstPath)
@@ -317,15 +339,15 @@ func splitCpArg(arg string) (ctr, path string) {
return "", arg
}
parts := strings.SplitN(arg, ":", 2)
ctr, path, ok := strings.Cut(arg, ":")
if len(parts) == 1 || strings.HasPrefix(parts[0], ".") {
if !ok || strings.HasPrefix(ctr, ".") {
// Either there's no `:` in the arg
// OR it's an explicit local relative path like `./file:name.txt`.
return "", arg
}
return parts[0], parts[1]
return ctr, path
}
func resolveLocalPath(localPath string) (absPath string, err error) {

View File

@@ -22,6 +22,8 @@ import (
"encoding/json"
"errors"
"fmt"
"net"
"net/netip"
"os"
"path/filepath"
"slices"
@@ -31,14 +33,12 @@ import (
"github.com/compose-spec/compose-go/v2/paths"
"github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/errdefs"
"github.com/docker/docker/api/types/blkiodev"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/versions"
volumetypes "github.com/docker/docker/api/types/volume"
"github.com/docker/go-connections/nat"
"github.com/moby/moby/api/types/blkiodev"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/mount"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/client"
"github.com/moby/moby/client/pkg/versions"
"github.com/sirupsen/logrus"
cdi "tags.cncf.io/container-device-interface/pkg/parser"
@@ -201,8 +201,7 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
mainNw = service.Networks[mainNwName]
}
macAddress, err := s.prepareContainerMACAddress(ctx, service, mainNw, mainNwName)
if err != nil {
if err := s.prepareContainerMACAddress(service, mainNw, mainNwName); err != nil {
return createConfigs{}, err
}
@@ -211,7 +210,7 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
return createConfigs{}, err
}
exposed, err := buildContainerPorts(service)
exposedPorts, err := buildContainerPorts(service)
if err != nil {
return createConfigs{}, err
}
@@ -220,7 +219,7 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
Hostname: service.Hostname,
Domainname: service.DomainName,
User: service.User,
ExposedPorts: exposed,
ExposedPorts: exposedPorts,
Tty: tty,
OpenStdin: stdinOpen,
StdinOnce: opts.AttachStdin && stdinOpen,
@@ -232,7 +231,6 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
WorkingDir: service.WorkingDir,
Entrypoint: entrypoint,
NetworkDisabled: service.NetworkMode == "disabled",
MacAddress: macAddress, // Field is deprecated since API v1.44, but kept for compatibility with older API versions.
Labels: labels,
StopSignal: service.StopSignal,
Env: ToMobyEnv(env),
@@ -241,11 +239,8 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
} // VOLUMES/MOUNTS/FILESYSTEMS
tmpfs := map[string]string{}
for _, t := range service.Tmpfs {
if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
tmpfs[arr[0]] = arr[1]
} else {
tmpfs[arr[0]] = ""
}
k, v, _ := strings.Cut(t, ":")
tmpfs[k] = v
}
binds, mounts, err := s.buildContainerVolumes(ctx, *p, service, inherit)
if err != nil {
@@ -265,7 +260,10 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
if err != nil {
return createConfigs{}, err
}
portBindings := buildContainerPortBindingOptions(service)
portBindings, err := buildContainerPortBindingOptions(service)
if err != nil {
return createConfigs{}, err
}
// MISC
resources := getDeployResources(service)
@@ -281,6 +279,15 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
return createConfigs{}, err
}
var dnsIPs []netip.Addr
for _, d := range service.DNS {
dnsIP, err := netip.ParseAddr(d)
if err != nil {
return createConfigs{}, fmt.Errorf("invalid DNS address: %w", err)
}
dnsIPs = append(dnsIPs, dnsIP)
}
hostConfig := container.HostConfig{
AutoRemove: opts.AutoRemove,
Annotations: service.Annotations,
@@ -300,7 +307,7 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
Resources: resources,
VolumeDriver: service.VolumeDriver,
VolumesFrom: service.VolumesFrom,
DNS: service.DNS,
DNS: dnsIPs,
DNSSearch: service.DNSSearch,
DNSOptions: service.DNSOpts,
ExtraHosts: service.ExtraHosts.AsList(":"),
@@ -339,12 +346,7 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
// passed mainNw to provide backward-compatibility whenever possible.
//
// It returns the container-wide MAC address, but this value will be kept empty for newer API versions.
func (s *composeService) prepareContainerMACAddress(ctx context.Context, service types.ServiceConfig, mainNw *types.ServiceNetworkConfig, nwName string) (string, error) {
version, err := s.RuntimeVersion(ctx)
if err != nil {
return "", err
}
func (s *composeService) prepareContainerMACAddress(service types.ServiceConfig, mainNw *types.ServiceNetworkConfig, nwName string) error {
// Engine API 1.44 added support for endpoint-specific MAC address and now returns a warning when a MAC address is
// set in container.Config. Thus, we have to jump through a number of hoops:
//
@@ -358,31 +360,12 @@ func (s *composeService) prepareContainerMACAddress(ctx context.Context, service
// there's no need to check for API version in defaultNetworkSettings.
macAddress := service.MacAddress
if macAddress != "" && mainNw != nil && mainNw.MacAddress != "" && mainNw.MacAddress != macAddress {
return "", fmt.Errorf("the service-level mac_address should have the same value as network %s", nwName)
return fmt.Errorf("the service-level mac_address should have the same value as network %s", nwName)
}
if versions.GreaterThanOrEqualTo(version, APIVersion144) {
if mainNw != nil && mainNw.MacAddress == "" {
mainNw.MacAddress = macAddress
}
macAddress = ""
} else if len(service.Networks) > 0 {
var withMacAddress []string
for nwName, nw := range service.Networks {
if nw != nil && nw.MacAddress != "" {
withMacAddress = append(withMacAddress, nwName)
}
}
if len(withMacAddress) > 1 {
return "", fmt.Errorf("a MAC address is specified for multiple networks (%s), but this feature requires Docker Engine %s or later", strings.Join(withMacAddress, ", "), DockerEngineV25)
}
if mainNw != nil && mainNw.MacAddress != "" {
macAddress = mainNw.MacAddress
}
if mainNw != nil && mainNw.MacAddress == "" {
mainNw.MacAddress = macAddress
}
return macAddress, nil
return nil
}
func getAliases(project *types.Project, service types.ServiceConfig, serviceIndex int, cfg *types.ServiceNetworkConfig, useNetworkAliases bool) []string {
@@ -396,25 +379,48 @@ func getAliases(project *types.Project, service types.ServiceConfig, serviceInde
return aliases
}
func createEndpointSettings(p *types.Project, service types.ServiceConfig, serviceIndex int, networkKey string, links []string, useNetworkAliases bool) *network.EndpointSettings {
func createEndpointSettings(p *types.Project, service types.ServiceConfig, serviceIndex int, networkKey string, links []string, useNetworkAliases bool) (*network.EndpointSettings, error) {
const ifname = "com.docker.network.endpoint.ifname"
config := service.Networks[networkKey]
var ipam *network.EndpointIPAMConfig
var (
ipv4Address string
ipv6Address string
ipv4Address netip.Addr
ipv6Address netip.Addr
macAddress string
driverOpts types.Options
gwPriority int
)
if config != nil {
ipv4Address = config.Ipv4Address
ipv6Address = config.Ipv6Address
var err error
if config.Ipv4Address != "" {
ipv4Address, err = netip.ParseAddr(config.Ipv4Address)
if err != nil {
return nil, fmt.Errorf("invalid IPv4 address: %w", err)
}
}
if config.Ipv6Address != "" {
ipv6Address, err = netip.ParseAddr(config.Ipv6Address)
if err != nil {
return nil, fmt.Errorf("invalid IPv6 address: %w", err)
}
}
var linkLocalIPs []netip.Addr
for _, link := range config.LinkLocalIPs {
if link == "" {
continue
}
llIP, err := netip.ParseAddr(link)
if err != nil {
return nil, fmt.Errorf("invalid link-local IP: %w", err)
}
linkLocalIPs = append(linkLocalIPs, llIP)
}
ipam = &network.EndpointIPAMConfig{
IPv4Address: ipv4Address,
IPv4Address: ipv4Address.Unmap(),
IPv6Address: ipv6Address,
LinkLocalIPs: config.LinkLocalIPs,
LinkLocalIPs: linkLocalIPs,
}
macAddress = config.MacAddress
driverOpts = config.DriverOpts
@@ -429,16 +435,25 @@ func createEndpointSettings(p *types.Project, service types.ServiceConfig, servi
}
gwPriority = config.GatewayPriority
}
var ma network.HardwareAddr
if macAddress != "" {
var err error
ma, err = parseMACAddr(macAddress)
if err != nil {
return nil, err
}
}
return &network.EndpointSettings{
Aliases: getAliases(p, service, serviceIndex, config, useNetworkAliases),
Links: links,
IPAddress: ipv4Address,
IPv6Gateway: ipv6Address,
IPAMConfig: ipam,
MacAddress: macAddress,
MacAddress: ma,
DriverOpts: driverOpts,
GwPriority: gwPriority,
}
}, nil
}
// copy/pasted from https://github.com/docker/cli/blob/9de1b162f/cli/command/container/opts.go#L673-L697 + RelativePath
@@ -510,38 +525,10 @@ func defaultNetworkSettings(project *types.Project,
}
if len(project.Networks) == 0 {
return "none", nil, nil
return network.NetworkNone, nil, nil
}
var primaryNetworkKey string
if len(service.Networks) > 0 {
primaryNetworkKey = service.NetworksByPriority()[0]
} else {
primaryNetworkKey = "default"
}
primaryNetworkMobyNetworkName := project.Networks[primaryNetworkKey].Name
primaryNetworkEndpoint := createEndpointSettings(project, service, serviceIndex, primaryNetworkKey, links, useNetworkAliases)
endpointsConfig := map[string]*network.EndpointSettings{}
// Starting from API version 1.44, the Engine will take several EndpointsConfigs
// so we can pass all the extra networks we want the container to be connected to
// in the network configuration instead of connecting the container to each extra
// network individually after creation.
if versions.GreaterThanOrEqualTo(version, APIVersion144) {
if len(service.Networks) > 1 {
serviceNetworks := service.NetworksByPriority()
for _, networkKey := range serviceNetworks[1:] {
mobyNetworkName := project.Networks[networkKey].Name
epSettings := createEndpointSettings(project, service, serviceIndex, networkKey, links, useNetworkAliases)
endpointsConfig[mobyNetworkName] = epSettings
}
}
if primaryNetworkEndpoint.MacAddress == "" {
primaryNetworkEndpoint.MacAddress = service.MacAddress
}
}
if versions.LessThan(version, APIVersion149) {
if versions.LessThan(version, apiVersion149) {
for _, config := range service.Networks {
if config != nil && config.InterfaceName != "" {
return "", nil, fmt.Errorf("interface_name requires Docker Engine %s or later", DockerEngineV28_1)
@@ -549,7 +536,42 @@ func defaultNetworkSettings(project *types.Project,
}
}
endpointsConfig[primaryNetworkMobyNetworkName] = primaryNetworkEndpoint
serviceNetworks := service.NetworksByPriority()
primaryNetworkKey := "default"
if len(serviceNetworks) > 0 {
primaryNetworkKey = serviceNetworks[0]
serviceNetworks = serviceNetworks[1:]
}
primaryNetworkEndpoint, err := createEndpointSettings(project, service, serviceIndex, primaryNetworkKey, links, useNetworkAliases)
if err != nil {
return "", nil, err
}
if primaryNetworkEndpoint.MacAddress.String() == "" {
primaryNetworkEndpoint.MacAddress, err = parseMACAddr(service.MacAddress)
if err != nil {
return "", nil, err
}
}
primaryNetworkMobyNetworkName := project.Networks[primaryNetworkKey].Name
endpointsConfig := map[string]*network.EndpointSettings{
primaryNetworkMobyNetworkName: primaryNetworkEndpoint,
}
// Starting from API version 1.44, the Engine will take several EndpointsConfigs
// so we can pass all the extra networks we want the container to be connected to
// in the network configuration instead of connecting the container to each extra
// network individually after creation.
for _, networkKey := range serviceNetworks {
epSettings, err := createEndpointSettings(project, service, serviceIndex, networkKey, links, useNetworkAliases)
if err != nil {
return "", nil, err
}
mobyNetworkName := project.Networks[networkKey].Name
endpointsConfig[mobyNetworkName] = epSettings
}
networkConfig := &network.NetworkingConfig{
EndpointsConfig: endpointsConfig,
}
@@ -563,13 +585,13 @@ func defaultNetworkSettings(project *types.Project,
func getRestartPolicy(service types.ServiceConfig) container.RestartPolicy {
var restart container.RestartPolicy
if service.Restart != "" {
split := strings.Split(service.Restart, ":")
name, num, ok := strings.Cut(service.Restart, ":")
var attempts int
if len(split) > 1 {
attempts, _ = strconv.Atoi(split[1])
if ok {
attempts, _ = strconv.Atoi(num)
}
restart = container.RestartPolicy{
Name: mapRestartPolicyCondition(split[0]),
Name: mapRestartPolicyCondition(name),
MaximumRetryCount: attempts,
}
}
@@ -769,37 +791,55 @@ func setBlkio(blkio *types.BlkioConfig, resources *container.Resources) {
}
}
func buildContainerPorts(s types.ServiceConfig) (nat.PortSet, error) {
ports := nat.PortSet{}
for _, s := range s.Expose {
proto, port := nat.SplitProtoPort(s)
start, end, err := nat.ParsePortRange(port)
func buildContainerPorts(s types.ServiceConfig) (network.PortSet, error) {
// Add published ports as exposed ports.
exposedPorts := network.PortSet{}
for _, p := range s.Ports {
np, err := network.ParsePort(fmt.Sprintf("%d/%s", p.Target, p.Protocol))
if err != nil {
return nil, err
}
for i := start; i <= end; i++ {
p := nat.Port(fmt.Sprintf("%d/%s", i, proto))
ports[p] = struct{}{}
exposedPorts[np] = struct{}{}
}
// Merge in exposed ports to the map of published ports
for _, e := range s.Expose {
// support two formats for expose, original format <portnum>/[<proto>]
// or <startport-endport>/[<proto>]
pr, err := network.ParsePortRange(e)
if err != nil {
return nil, err
}
// parse the start and end port and create a sequence of ports to expose
// if expose a port, the start and end port are the same
for p := range pr.All() {
exposedPorts[p] = struct{}{}
}
}
for _, p := range s.Ports {
p := nat.Port(fmt.Sprintf("%d/%s", p.Target, p.Protocol))
ports[p] = struct{}{}
}
return ports, nil
return exposedPorts, nil
}
func buildContainerPortBindingOptions(s types.ServiceConfig) nat.PortMap {
bindings := nat.PortMap{}
func buildContainerPortBindingOptions(s types.ServiceConfig) (network.PortMap, error) {
bindings := network.PortMap{}
for _, port := range s.Ports {
p := nat.Port(fmt.Sprintf("%d/%s", port.Target, port.Protocol))
binding := nat.PortBinding{
HostIP: port.HostIP,
HostPort: port.Published,
var err error
p, err := network.ParsePort(fmt.Sprintf("%d/%s", port.Target, port.Protocol))
if err != nil {
return nil, err
}
bindings[p] = append(bindings[p], binding)
var hostIP netip.Addr
if port.HostIP != "" {
hostIP, err = netip.ParseAddr(port.HostIP)
if err != nil {
return nil, err
}
}
bindings[p] = append(bindings[p], network.PortBinding{
HostIP: hostIP,
HostPort: port.Published,
})
}
return bindings
return bindings, nil
}
func getDependentServiceFromMode(mode string) string {
@@ -861,8 +901,8 @@ func (s *composeService) buildContainerVolumes(
if err != nil {
return nil, nil, err
}
if versions.LessThan(version, APIVersion148) {
return nil, nil, fmt.Errorf("volume with type=image require Docker Engine %s or later", DockerEngineV28)
if versions.LessThan(version, apiVersion148) {
return nil, nil, fmt.Errorf("volume with type=image require Docker Engine %s or later", dockerEngineV28)
}
}
mounts = append(mounts, m)
@@ -1286,8 +1326,9 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, project *ty
var dangledContainers Containers
// First, try to find a unique network matching by name or ID
inspect, err := s.apiClient().NetworkInspect(ctx, n.Name, network.InspectOptions{})
res, err := s.apiClient().NetworkInspect(ctx, n.Name, client.NetworkInspectOptions{})
if err == nil {
inspect := res.Network
// NetworkInspect will match on ID prefix, so double check we get the expected one
// as looking for network named `db` we could erroneously match network ID `db9086999caf`
if inspect.Name == n.Name || inspect.ID == n.Name {
@@ -1327,22 +1368,22 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, project *ty
// ignore other errors. Typically, an ambiguous request by name results in some generic `invalidParameter` error
// Either not found, or name is ambiguous - use NetworkList to list by name
networks, err := s.apiClient().NetworkList(ctx, network.ListOptions{
Filters: filters.NewArgs(filters.Arg("name", n.Name)),
nwList, err := s.apiClient().NetworkList(ctx, client.NetworkListOptions{
Filters: make(client.Filters).Add("name", n.Name),
})
if err != nil {
return "", err
}
// NetworkList Matches all or part of a network name, so we have to filter for a strict match
networks = slices.DeleteFunc(networks, func(net network.Summary) bool {
networks := slices.DeleteFunc(nwList.Items, func(net network.Summary) bool {
return net.Name != n.Name
})
for _, net := range networks {
if net.Labels[api.ProjectLabel] == project.Name &&
net.Labels[api.NetworkLabel] == name {
return net.ID, nil
for _, nw := range networks {
if nw.Labels[api.ProjectLabel] == project.Name &&
nw.Labels[api.NetworkLabel] == name {
return nw.ID, nil
}
}
@@ -1359,12 +1400,11 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, project *ty
if n.Ipam.Config != nil {
var config []network.IPAMConfig
for _, pool := range n.Ipam.Config {
config = append(config, network.IPAMConfig{
Subnet: pool.Subnet,
IPRange: pool.IPRange,
Gateway: pool.Gateway,
AuxAddress: pool.AuxiliaryAddresses,
})
c, err := parseIPAMPool(pool)
if err != nil {
return "", err
}
config = append(config, c)
}
ipam = &network.IPAM{
Driver: n.Ipam.Driver,
@@ -1376,7 +1416,7 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, project *ty
return "", err
}
n.CustomLabels = n.CustomLabels.Add(api.ConfigHashLabel, hash)
createOpts := network.CreateOptions{
createOpts := client.NetworkCreateOptions{
Labels: mergeLabels(n.Labels, n.CustomLabels),
Driver: n.Driver,
Options: n.DriverOpts,
@@ -1396,13 +1436,11 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, project *ty
}
for _, ipamConfig := range n.Ipam.Config {
config := network.IPAMConfig{
Subnet: ipamConfig.Subnet,
IPRange: ipamConfig.IPRange,
Gateway: ipamConfig.Gateway,
AuxAddress: ipamConfig.AuxiliaryAddresses,
c, err := parseIPAMPool(ipamConfig)
if err != nil {
return "", err
}
createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
createOpts.IPAM.Config = append(createOpts.IPAM.Config, c)
}
networkEventName := fmt.Sprintf("Network %s", n.Name)
@@ -1453,7 +1491,7 @@ func (s *composeService) removeDivergedNetwork(ctx context.Context, project *typ
return nil, err
}
err = s.apiClient().NetworkRemove(ctx, n.Name)
_, err = s.apiClient().NetworkRemove(ctx, n.Name, client.NetworkRemoveOptions{})
eventName := fmt.Sprintf("Network %s", n.Name)
s.events.On(removedEvent(eventName))
return containers, err
@@ -1465,7 +1503,10 @@ func (s *composeService) disconnectNetwork(
containers Containers,
) error {
for _, c := range containers {
err := s.apiClient().NetworkDisconnect(ctx, nwName, c.ID, true)
_, err := s.apiClient().NetworkDisconnect(ctx, nwName, client.NetworkDisconnectOptions{
Container: c.ID,
Force: true,
})
if err != nil {
return err
}
@@ -1481,7 +1522,10 @@ func (s *composeService) connectNetwork(
config *network.EndpointSettings,
) error {
for _, c := range containers {
err := s.apiClient().NetworkConnect(ctx, nwName, c.ID, config)
_, err := s.apiClient().NetworkConnect(ctx, nwName, client.NetworkConnectOptions{
Container: c.ID,
EndpointConfig: config,
})
if err != nil {
return err
}
@@ -1495,26 +1539,26 @@ func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.Ne
// filter is used to look for an exact match to prevent e.g. a network
// named `db` from getting erroneously matched to a network with an ID
// like `db9086999caf`
networks, err := s.apiClient().NetworkList(ctx, network.ListOptions{
Filters: filters.NewArgs(filters.Arg("name", n.Name)),
res, err := s.apiClient().NetworkList(ctx, client.NetworkListOptions{
Filters: make(client.Filters).Add("name", n.Name),
})
if err != nil {
return "", err
}
networks := res.Items
if len(networks) == 0 {
// in this instance, n.Name is really an ID
sn, err := s.apiClient().NetworkInspect(ctx, n.Name, network.InspectOptions{})
sn, err := s.apiClient().NetworkInspect(ctx, n.Name, client.NetworkInspectOptions{})
if err == nil {
networks = append(networks, sn)
networks = append(networks, network.Summary{Network: sn.Network.Network})
} else if !errdefs.IsNotFound(err) {
return "", err
}
}
// NetworkList API doesn't return the exact name match, so we can retrieve more than one network with a request
networks = slices.DeleteFunc(networks, func(net network.Inspect) bool {
networks = slices.DeleteFunc(networks, func(net network.Summary) bool {
// this function is called during the rebuild stage of `compose watch`.
// we still require just one network back, but we need to run the search on the ID
return net.Name != n.Name && net.ID != n.Name
@@ -1542,7 +1586,7 @@ func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.Ne
}
func (s *composeService) ensureVolume(ctx context.Context, name string, volume types.VolumeConfig, project *types.Project) (string, error) {
inspected, err := s.apiClient().VolumeInspect(ctx, volume.Name)
inspected, err := s.apiClient().VolumeInspect(ctx, volume.Name, client.VolumeInspectOptions{})
if err != nil {
if !errdefs.IsNotFound(err) {
return "", err
@@ -1559,7 +1603,7 @@ func (s *composeService) ensureVolume(ctx context.Context, name string, volume t
}
// Volume exists with name, but let's double-check this is the expected one
p, ok := inspected.Labels[api.ProjectLabel]
p, ok := inspected.Volume.Labels[api.ProjectLabel]
if !ok {
logrus.Warnf("volume %q already exists but was not created by Docker Compose. Use `external: true` to use an existing volume", volume.Name)
}
@@ -1571,7 +1615,7 @@ func (s *composeService) ensureVolume(ctx context.Context, name string, volume t
if err != nil {
return "", err
}
actual, ok := inspected.Labels[api.ConfigHashLabel]
actual, ok := inspected.Volume.Labels[api.ConfigHashLabel]
if ok && actual != expected {
msg := fmt.Sprintf("Volume %q exists but doesn't match configuration in compose file. Recreate (data will be lost)?", volume.Name)
confirm, err := s.prompt(msg, false)
@@ -1586,7 +1630,7 @@ func (s *composeService) ensureVolume(ctx context.Context, name string, volume t
return volume.Name, s.createVolume(ctx, volume)
}
}
return inspected.Name, nil
return inspected.Volume.Name, nil
}
func (s *composeService) removeDivergedVolume(ctx context.Context, name string, volume types.VolumeConfig, project *types.Project) error {
@@ -1626,7 +1670,10 @@ func (s *composeService) removeDivergedVolume(ctx context.Context, name string,
return err
}
return s.apiClient().VolumeRemove(ctx, volume.Name, true)
_, err = s.apiClient().VolumeRemove(ctx, volume.Name, client.VolumeRemoveOptions{
Force: true,
})
return err
}
func (s *composeService) createVolume(ctx context.Context, volume types.VolumeConfig) error {
@@ -1637,7 +1684,7 @@ func (s *composeService) createVolume(ctx context.Context, volume types.VolumeCo
return err
}
volume.CustomLabels.Add(api.ConfigHashLabel, hash)
_, err = s.apiClient().VolumeCreate(ctx, volumetypes.CreateOptions{
_, err = s.apiClient().VolumeCreate(ctx, client.VolumeCreateOptions{
Labels: mergeLabels(volume.Labels, volume.CustomLabels),
Name: volume.Name,
Driver: volume.Driver,
@@ -1650,3 +1697,59 @@ func (s *composeService) createVolume(ctx context.Context, volume types.VolumeCo
s.events.On(createdEvent(eventName))
return nil
}
func parseIPAMPool(pool *types.IPAMPool) (network.IPAMConfig, error) {
var (
err error
subNet netip.Prefix
ipRange netip.Prefix
gateway netip.Addr
auxAddress map[string]netip.Addr
)
if pool.Subnet != "" {
subNet, err = netip.ParsePrefix(pool.Subnet)
if err != nil {
return network.IPAMConfig{}, fmt.Errorf("invalid subnet: %w", err)
}
}
if pool.IPRange != "" {
ipRange, err = netip.ParsePrefix(pool.IPRange)
if err != nil {
return network.IPAMConfig{}, fmt.Errorf("invalid ip-range: %w", err)
}
}
if pool.Gateway != "" {
gateway, err = netip.ParseAddr(pool.Gateway)
if err != nil {
return network.IPAMConfig{}, fmt.Errorf("invalid gateway address: %w", err)
}
}
if len(pool.AuxiliaryAddresses) > 0 {
auxAddress = make(map[string]netip.Addr, len(pool.AuxiliaryAddresses))
for auxName, addr := range pool.AuxiliaryAddresses {
auxAddr, err := netip.ParseAddr(addr)
if err != nil {
return network.IPAMConfig{}, fmt.Errorf("invalid auxiliary address: %w", err)
}
auxAddress[auxName] = auxAddr
}
}
return network.IPAMConfig{
Subnet: subNet,
IPRange: ipRange,
Gateway: gateway,
AuxAddress: auxAddress,
}, nil
}
func parseMACAddr(macAddress string) (network.HardwareAddr, error) {
if macAddress == "" {
return nil, nil
}
m, err := net.ParseMAC(macAddress)
if err != nil {
return nil, fmt.Errorf("invalid MAC address: %w", err)
}
return network.HardwareAddr(m), nil
}

View File

@@ -17,6 +17,8 @@
package compose
import (
"net"
"net/netip"
"os"
"path/filepath"
"sort"
@@ -24,10 +26,11 @@ import (
composeloader "github.com/compose-spec/compose-go/v2/loader"
composetypes "github.com/compose-spec/compose-go/v2/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
mountTypes "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/network"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/moby/moby/api/types/container"
mountTypes "github.com/moby/moby/api/types/mount"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/client"
"go.uber.org/mock/gomock"
"gotest.tools/v3/assert"
"gotest.tools/v3/assert/cmp"
@@ -161,7 +164,7 @@ func TestBuildContainerMountOptions(t *testing.T) {
s := composeService{
dockerCli: cli,
}
mock.EXPECT().ImageInspect(gomock.Any(), "myProject-myService").AnyTimes().Return(image.InspectResponse{}, nil)
mock.EXPECT().ImageInspect(gomock.Any(), "myProject-myService").AnyTimes().Return(client.ImageInspectResult{}, nil)
mounts, err := s.buildContainerMountOptions(t.Context(), project, project.Services["myService"], inherit)
sort.Slice(mounts, func(i, j int) bool {
@@ -189,7 +192,7 @@ func TestBuildContainerMountOptions(t *testing.T) {
}
func TestDefaultNetworkSettings(t *testing.T) {
t.Run("returns the network with the highest priority when service has multiple networks", func(t *testing.T) {
t.Run("returns the network with the highest priority as primary when service has multiple networks", func(t *testing.T) {
service := composetypes.ServiceConfig{
Name: "myService",
Networks: map[string]*composetypes.ServiceNetworkConfig{
@@ -216,10 +219,11 @@ func TestDefaultNetworkSettings(t *testing.T) {
}),
}
networkMode, networkConfig, err := defaultNetworkSettings(&project, service, 1, nil, true, "1.43")
networkMode, networkConfig, err := defaultNetworkSettings(&project, service, 1, nil, true, "1.44")
assert.NilError(t, err)
assert.Equal(t, string(networkMode), "myProject_myNetwork2")
assert.Check(t, cmp.Len(networkConfig.EndpointsConfig, 1))
assert.Check(t, cmp.Len(networkConfig.EndpointsConfig, 2))
assert.Check(t, cmp.Contains(networkConfig.EndpointsConfig, "myProject_myNetwork1"))
assert.Check(t, cmp.Contains(networkConfig.EndpointsConfig, "myProject_myNetwork2"))
})
@@ -245,7 +249,7 @@ func TestDefaultNetworkSettings(t *testing.T) {
}),
}
networkMode, networkConfig, err := defaultNetworkSettings(&project, service, 1, nil, true, "1.43")
networkMode, networkConfig, err := defaultNetworkSettings(&project, service, 1, nil, true, "1.44")
assert.NilError(t, err)
assert.Equal(t, string(networkMode), "myProject_default")
assert.Check(t, cmp.Len(networkConfig.EndpointsConfig, 1))
@@ -263,7 +267,7 @@ func TestDefaultNetworkSettings(t *testing.T) {
},
}
networkMode, networkConfig, err := defaultNetworkSettings(&project, service, 1, nil, true, "1.43")
networkMode, networkConfig, err := defaultNetworkSettings(&project, service, 1, nil, true, "1.44")
assert.NilError(t, err)
assert.Equal(t, string(networkMode), "none")
assert.Check(t, cmp.Nil(networkConfig))
@@ -284,7 +288,7 @@ func TestDefaultNetworkSettings(t *testing.T) {
}),
}
networkMode, networkConfig, err := defaultNetworkSettings(&project, service, 1, nil, true, "1.43")
networkMode, networkConfig, err := defaultNetworkSettings(&project, service, 1, nil, true, "1.44")
assert.NilError(t, err)
assert.Equal(t, string(networkMode), "host")
assert.Check(t, cmp.Nil(networkConfig))
@@ -292,7 +296,7 @@ func TestDefaultNetworkSettings(t *testing.T) {
}
func TestCreateEndpointSettings(t *testing.T) {
eps := createEndpointSettings(&composetypes.Project{
eps, err := createEndpointSettings(&composetypes.Project{
Name: "projName",
}, composetypes.ServiceConfig{
Name: "serviceName",
@@ -304,7 +308,7 @@ func TestCreateEndpointSettings(t *testing.T) {
Ipv4Address: "10.16.17.18",
Ipv6Address: "fdb4:7a7f:373a:3f0c::42",
LinkLocalIPs: []string{"169.254.10.20"},
MacAddress: "10:00:00:00:01",
MacAddress: "02:00:00:00:00:01",
DriverOpts: composetypes.Options{
"driverOpt1": "optval1",
"driverOpt2": "optval2",
@@ -312,15 +316,17 @@ func TestCreateEndpointSettings(t *testing.T) {
},
},
}, 0, "netName", []string{"link1", "link2"}, true)
assert.NilError(t, err)
macAddr, _ := net.ParseMAC("02:00:00:00:00:01")
assert.Check(t, cmp.DeepEqual(eps, &network.EndpointSettings{
IPAMConfig: &network.EndpointIPAMConfig{
IPv4Address: "10.16.17.18",
IPv6Address: "fdb4:7a7f:373a:3f0c::42",
LinkLocalIPs: []string{"169.254.10.20"},
IPv4Address: netip.MustParseAddr("10.16.17.18").Unmap(),
IPv6Address: netip.MustParseAddr("fdb4:7a7f:373a:3f0c::42"),
LinkLocalIPs: []netip.Addr{netip.MustParseAddr("169.254.10.20").Unmap()},
},
Links: []string{"link1", "link2"},
Aliases: []string{"containerName", "serviceName", "alias1", "alias2"},
MacAddress: "10:00:00:00:01",
MacAddress: network.HardwareAddr(macAddr),
DriverOpts: map[string]string{
"driverOpt1": "optval1",
"driverOpt2": "optval2",
@@ -330,9 +336,9 @@ func TestCreateEndpointSettings(t *testing.T) {
// - The IPv6 address here is the container's address, not the gateway.
// - Both fields will be cleared by the daemon, but they could be removed from
// the request.
IPAddress: "10.16.17.18",
IPv6Gateway: "fdb4:7a7f:373a:3f0c::42",
}))
IPAddress: netip.MustParseAddr("10.16.17.18").Unmap(),
IPv6Gateway: netip.MustParseAddr("fdb4:7a7f:373a:3f0c::42"),
}, cmpopts.EquateComparable(netip.Addr{})))
}
func Test_buildContainerVolumes(t *testing.T) {

View File

@@ -19,6 +19,8 @@ package compose
import (
"context"
"strings"
"github.com/moby/moby/client"
)
// engineLabelDesktopAddress is used to detect that Compose is running with a
@@ -27,11 +29,11 @@ import (
const engineLabelDesktopAddress = "com.docker.desktop.address"
func (s *composeService) isDesktopIntegrationActive(ctx context.Context) (bool, error) {
info, err := s.apiClient().Info(ctx)
res, err := s.apiClient().Info(ctx, client.InfoOptions{})
if err != nil {
return false, err
}
for _, l := range info.Labels {
for _, l := range res.Info.Labels {
k, _, ok := strings.Cut(l, "=")
if ok && k == engineLabelDesktopAddress {
return true, nil

View File

@@ -24,10 +24,8 @@ import (
"github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/errdefs"
containerType "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
imageapi "github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
containerType "github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
@@ -196,14 +194,13 @@ func (s *composeService) ensureNetworksDown(ctx context.Context, project *types.
}
func (s *composeService) removeNetwork(ctx context.Context, composeNetworkName string, projectName string, name string) error {
networks, err := s.apiClient().NetworkList(ctx, network.ListOptions{
Filters: filters.NewArgs(
projectFilter(projectName),
networkFilter(composeNetworkName)),
res, err := s.apiClient().NetworkList(ctx, client.NetworkListOptions{
Filters: projectFilter(projectName).Add("label", networkFilter(composeNetworkName)),
})
if err != nil {
return fmt.Errorf("failed to list networks: %w", err)
}
networks := res.Items
if len(networks) == 0 {
return nil
@@ -217,7 +214,7 @@ func (s *composeService) removeNetwork(ctx context.Context, composeNetworkName s
if net.Name != name {
continue
}
nw, err := s.apiClient().NetworkInspect(ctx, net.ID, network.InspectOptions{})
nwInspect, err := s.apiClient().NetworkInspect(ctx, net.ID, client.NetworkInspectOptions{})
if errdefs.IsNotFound(err) {
s.events.On(newEvent(eventName, api.Warning, "No resource found to remove"))
return nil
@@ -225,13 +222,14 @@ func (s *composeService) removeNetwork(ctx context.Context, composeNetworkName s
if err != nil {
return err
}
nw := nwInspect.Network
if len(nw.Containers) > 0 {
s.events.On(newEvent(eventName, api.Warning, "Resource is still in use"))
found++
continue
}
if err := s.apiClient().NetworkRemove(ctx, net.ID); err != nil {
if _, err := s.apiClient().NetworkRemove(ctx, net.ID, client.NetworkRemoveOptions{}); err != nil {
if errdefs.IsNotFound(err) {
continue
}
@@ -255,7 +253,7 @@ func (s *composeService) removeNetwork(ctx context.Context, composeNetworkName s
func (s *composeService) removeImage(ctx context.Context, image string) error {
id := fmt.Sprintf("Image %s", image)
s.events.On(newEvent(id, api.Working, "Removing"))
_, err := s.apiClient().ImageRemove(ctx, image, imageapi.RemoveOptions{})
_, err := s.apiClient().ImageRemove(ctx, image, client.ImageRemoveOptions{})
if err == nil {
s.events.On(newEvent(id, api.Done, "Removed"))
return nil
@@ -274,14 +272,16 @@ func (s *composeService) removeImage(ctx context.Context, image string) error {
func (s *composeService) removeVolume(ctx context.Context, id string) error {
resource := fmt.Sprintf("Volume %s", id)
_, err := s.apiClient().VolumeInspect(ctx, id)
_, err := s.apiClient().VolumeInspect(ctx, id, client.VolumeInspectOptions{})
if errdefs.IsNotFound(err) {
// Already gone
return nil
}
s.events.On(newEvent(resource, api.Working, "Removing"))
err = s.apiClient().VolumeRemove(ctx, id, true)
_, err = s.apiClient().VolumeRemove(ctx, id, client.VolumeRemoveOptions{
Force: true,
})
if err == nil {
s.events.On(newEvent(resource, api.Done, "Removed"))
return nil
@@ -314,8 +314,9 @@ func (s *composeService) stopContainer(ctx context.Context, service *types.Servi
}
}
timeoutInSecond := utils.DurationSecondToInt(timeout)
err := s.apiClient().ContainerStop(ctx, ctr.ID, containerType.StopOptions{Timeout: timeoutInSecond})
_, err := s.apiClient().ContainerStop(ctx, ctr.ID, client.ContainerStopOptions{
Timeout: utils.DurationSecondToInt(timeout),
})
if err != nil {
s.events.On(errorEvent(eventName, "Error while Stopping"))
return err
@@ -355,7 +356,7 @@ func (s *composeService) stopAndRemoveContainer(ctx context.Context, ctr contain
return err
}
s.events.On(removingEvent(eventName))
err = s.apiClient().ContainerRemove(ctx, ctr.ID, containerType.RemoveOptions{
_, err = s.apiClient().ContainerRemove(ctx, ctr.ID, client.ContainerRemoveOptions{
Force: true,
RemoveVolumes: volumes,
})

View File

@@ -25,11 +25,11 @@ import (
"github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/errdefs"
"github.com/docker/cli/cli/streams"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/volume"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/api/types/volume"
"github.com/moby/moby/client"
"go.uber.org/mock/gomock"
"gotest.tools/v3/assert"
@@ -46,48 +46,50 @@ func TestDown(t *testing.T) {
assert.NilError(t, err)
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(false)).Return(
[]container.Summary{
client.ContainerListResult{Items: []container.Summary{
testContainer("service1", "123", false),
testContainer("service2", "456", false),
testContainer("service2", "789", false),
testContainer("service_orphan", "321", true),
}, nil)
}}, nil)
api.EXPECT().VolumeList(
gomock.Any(),
volume.ListOptions{
Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject))),
client.VolumeListOptions{
Filters: projectFilter(strings.ToLower(testProject)),
}).
Return(volume.ListResponse{}, nil)
Return(client.VolumeListResult{}, nil)
// network names are not guaranteed to be unique, ensure Compose handles
// cleanup properly if duplicates are inadvertently created
api.EXPECT().NetworkList(gomock.Any(), network.ListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
Return([]network.Summary{
{ID: "abc123", Name: "myProject_default", Labels: map[string]string{compose.NetworkLabel: "default"}},
{ID: "def456", Name: "myProject_default", Labels: map[string]string{compose.NetworkLabel: "default"}},
}, nil)
api.EXPECT().NetworkList(gomock.Any(), client.NetworkListOptions{Filters: projectFilter(strings.ToLower(testProject))}).
Return(client.NetworkListResult{Items: []network.Summary{
{Network: network.Network{ID: "abc123", Name: "myProject_default", Labels: map[string]string{compose.NetworkLabel: "default"}}},
{Network: network.Network{ID: "def456", Name: "myProject_default", Labels: map[string]string{compose.NetworkLabel: "default"}}},
}}, nil)
stopOptions := container.StopOptions{}
api.EXPECT().ContainerStop(gomock.Any(), "123", stopOptions).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "456", stopOptions).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "789", stopOptions).Return(nil)
stopOptions := client.ContainerStopOptions{}
api.EXPECT().ContainerStop(gomock.Any(), "123", stopOptions).Return(client.ContainerStopResult{}, nil)
api.EXPECT().ContainerStop(gomock.Any(), "456", stopOptions).Return(client.ContainerStopResult{}, nil)
api.EXPECT().ContainerStop(gomock.Any(), "789", stopOptions).Return(client.ContainerStopResult{}, nil)
api.EXPECT().ContainerRemove(gomock.Any(), "123", container.RemoveOptions{Force: true}).Return(nil)
api.EXPECT().ContainerRemove(gomock.Any(), "456", container.RemoveOptions{Force: true}).Return(nil)
api.EXPECT().ContainerRemove(gomock.Any(), "789", container.RemoveOptions{Force: true}).Return(nil)
api.EXPECT().ContainerRemove(gomock.Any(), "123", client.ContainerRemoveOptions{Force: true}).Return(client.ContainerRemoveResult{}, nil)
api.EXPECT().ContainerRemove(gomock.Any(), "456", client.ContainerRemoveOptions{Force: true}).Return(client.ContainerRemoveResult{}, nil)
api.EXPECT().ContainerRemove(gomock.Any(), "789", client.ContainerRemoveOptions{Force: true}).Return(client.ContainerRemoveResult{}, nil)
api.EXPECT().NetworkList(gomock.Any(), network.ListOptions{
Filters: filters.NewArgs(
projectFilter(strings.ToLower(testProject)),
networkFilter("default")),
}).Return([]network.Summary{
{ID: "abc123", Name: "myProject_default"},
{ID: "def456", Name: "myProject_default"},
api.EXPECT().NetworkList(gomock.Any(), client.NetworkListOptions{
Filters: projectFilter(strings.ToLower(testProject)).Add("label", networkFilter("default")),
}).Return(client.NetworkListResult{Items: []network.Summary{
{Network: network.Network{ID: "abc123", Name: "myProject_default"}},
{Network: network.Network{ID: "def456", Name: "myProject_default"}},
}}, nil)
api.EXPECT().NetworkInspect(gomock.Any(), "abc123", gomock.Any()).Return(client.NetworkInspectResult{
Network: network.Inspect{Network: network.Network{ID: "abc123"}},
}, nil)
api.EXPECT().NetworkInspect(gomock.Any(), "abc123", gomock.Any()).Return(network.Inspect{ID: "abc123"}, nil)
api.EXPECT().NetworkInspect(gomock.Any(), "def456", gomock.Any()).Return(network.Inspect{ID: "def456"}, nil)
api.EXPECT().NetworkRemove(gomock.Any(), "abc123").Return(nil)
api.EXPECT().NetworkRemove(gomock.Any(), "def456").Return(nil)
api.EXPECT().NetworkInspect(gomock.Any(), "def456", gomock.Any()).Return(client.NetworkInspectResult{
Network: network.Inspect{Network: network.Network{ID: "def456"}},
}, nil)
api.EXPECT().NetworkRemove(gomock.Any(), "abc123", gomock.Any()).Return(client.NetworkRemoveResult{}, nil)
api.EXPECT().NetworkRemove(gomock.Any(), "def456", gomock.Any()).Return(client.NetworkRemoveResult{}, nil)
err = tested.Down(t.Context(), strings.ToLower(testProject), compose.DownOptions{})
assert.NilError(t, err)
@@ -101,42 +103,40 @@ func TestDownWithGivenServices(t *testing.T) {
tested, err := NewComposeService(cli)
assert.NilError(t, err)
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(false)).Return(
[]container.Summary{
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(false)).Return(client.ContainerListResult{
Items: []container.Summary{
testContainer("service1", "123", false),
testContainer("service2", "456", false),
testContainer("service2", "789", false),
testContainer("service_orphan", "321", true),
}, nil)
},
}, nil)
api.EXPECT().VolumeList(
gomock.Any(),
volume.ListOptions{
Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject))),
client.VolumeListOptions{
Filters: projectFilter(strings.ToLower(testProject)),
}).
Return(volume.ListResponse{}, nil)
Return(client.VolumeListResult{}, nil)
// network names are not guaranteed to be unique, ensure Compose handles
// cleanup properly if duplicates are inadvertently created
api.EXPECT().NetworkList(gomock.Any(), network.ListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
Return([]network.Summary{
{ID: "abc123", Name: "myProject_default", Labels: map[string]string{compose.NetworkLabel: "default"}},
{ID: "def456", Name: "myProject_default", Labels: map[string]string{compose.NetworkLabel: "default"}},
}, nil)
api.EXPECT().NetworkList(gomock.Any(), client.NetworkListOptions{Filters: projectFilter(strings.ToLower(testProject))}).
Return(client.NetworkListResult{Items: []network.Summary{
{Network: network.Network{ID: "abc123", Name: "myProject_default", Labels: map[string]string{compose.NetworkLabel: "default"}}},
{Network: network.Network{ID: "def456", Name: "myProject_default", Labels: map[string]string{compose.NetworkLabel: "default"}}},
}}, nil)
stopOptions := container.StopOptions{}
api.EXPECT().ContainerStop(gomock.Any(), "123", stopOptions).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "123", client.ContainerStopOptions{}).Return(client.ContainerStopResult{}, nil)
api.EXPECT().ContainerRemove(gomock.Any(), "123", container.RemoveOptions{Force: true}).Return(nil)
api.EXPECT().ContainerRemove(gomock.Any(), "123", client.ContainerRemoveOptions{Force: true}).Return(client.ContainerRemoveResult{}, nil)
api.EXPECT().NetworkList(gomock.Any(), network.ListOptions{
Filters: filters.NewArgs(
projectFilter(strings.ToLower(testProject)),
networkFilter("default")),
}).Return([]network.Summary{
{ID: "abc123", Name: "myProject_default"},
}, nil)
api.EXPECT().NetworkInspect(gomock.Any(), "abc123", gomock.Any()).Return(network.Inspect{ID: "abc123"}, nil)
api.EXPECT().NetworkRemove(gomock.Any(), "abc123").Return(nil)
api.EXPECT().NetworkList(gomock.Any(), client.NetworkListOptions{
Filters: projectFilter(strings.ToLower(testProject)).Add("label", networkFilter("default")),
}).Return(client.NetworkListResult{Items: []network.Summary{
{Network: network.Network{ID: "abc123", Name: "myProject_default"}},
}}, nil)
api.EXPECT().NetworkInspect(gomock.Any(), "abc123", gomock.Any()).Return(client.NetworkInspectResult{Network: network.Inspect{Network: network.Network{ID: "abc123"}}}, nil)
api.EXPECT().NetworkRemove(gomock.Any(), "abc123", gomock.Any()).Return(client.NetworkRemoveResult{}, nil)
err = tested.Down(t.Context(), strings.ToLower(testProject), compose.DownOptions{
Services: []string{"service1", "not-running-service"},
@@ -152,27 +152,28 @@ func TestDownWithSpecifiedServiceButTheServicesAreNotRunning(t *testing.T) {
tested, err := NewComposeService(cli)
assert.NilError(t, err)
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(false)).Return(
[]container.Summary{
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(false)).Return(client.ContainerListResult{
Items: []container.Summary{
testContainer("service1", "123", false),
testContainer("service2", "456", false),
testContainer("service2", "789", false),
testContainer("service_orphan", "321", true),
}, nil)
},
}, nil)
api.EXPECT().VolumeList(
gomock.Any(),
volume.ListOptions{
Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject))),
client.VolumeListOptions{
Filters: projectFilter(strings.ToLower(testProject)),
}).
Return(volume.ListResponse{}, nil)
Return(client.VolumeListResult{}, nil)
// network names are not guaranteed to be unique, ensure Compose handles
// cleanup properly if duplicates are inadvertently created
api.EXPECT().NetworkList(gomock.Any(), network.ListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
Return([]network.Summary{
{ID: "abc123", Name: "myProject_default", Labels: map[string]string{compose.NetworkLabel: "default"}},
{ID: "def456", Name: "myProject_default", Labels: map[string]string{compose.NetworkLabel: "default"}},
}, nil)
api.EXPECT().NetworkList(gomock.Any(), client.NetworkListOptions{Filters: projectFilter(strings.ToLower(testProject))}).
Return(client.NetworkListResult{Items: []network.Summary{
{Network: network.Network{ID: "abc123", Name: "myProject_default", Labels: map[string]string{compose.NetworkLabel: "default"}}},
{Network: network.Network{ID: "def456", Name: "myProject_default", Labels: map[string]string{compose.NetworkLabel: "default"}}},
}}, nil)
err = tested.Down(t.Context(), strings.ToLower(testProject), compose.DownOptions{
Services: []string{"not-running-service1", "not-running-service2"},
@@ -189,42 +190,47 @@ func TestDownRemoveOrphans(t *testing.T) {
assert.NilError(t, err)
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(true)).Return(
[]container.Summary{
testContainer("service1", "123", false),
testContainer("service2", "789", false),
testContainer("service_orphan", "321", true),
client.ContainerListResult{
Items: []container.Summary{
testContainer("service1", "123", false),
testContainer("service2", "789", false),
testContainer("service_orphan", "321", true),
},
}, nil)
api.EXPECT().VolumeList(
gomock.Any(),
volume.ListOptions{
Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject))),
client.VolumeListOptions{
Filters: projectFilter(strings.ToLower(testProject)),
}).
Return(volume.ListResponse{}, nil)
api.EXPECT().NetworkList(gomock.Any(), network.ListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
Return([]network.Summary{
{
Name: "myProject_default",
Labels: map[string]string{compose.NetworkLabel: "default"},
},
Return(client.VolumeListResult{}, nil)
api.EXPECT().NetworkList(gomock.Any(), client.NetworkListOptions{Filters: projectFilter(strings.ToLower(testProject))}).
Return(client.NetworkListResult{
Items: []network.Summary{{
Network: network.Network{
Name: "myProject_default",
Labels: map[string]string{compose.NetworkLabel: "default"},
},
}},
}, nil)
stopOptions := container.StopOptions{}
api.EXPECT().ContainerStop(gomock.Any(), "123", stopOptions).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "789", stopOptions).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "321", stopOptions).Return(nil)
stopOptions := client.ContainerStopOptions{}
api.EXPECT().ContainerStop(gomock.Any(), "123", stopOptions).Return(client.ContainerStopResult{}, nil)
api.EXPECT().ContainerStop(gomock.Any(), "789", stopOptions).Return(client.ContainerStopResult{}, nil)
api.EXPECT().ContainerStop(gomock.Any(), "321", stopOptions).Return(client.ContainerStopResult{}, nil)
api.EXPECT().ContainerRemove(gomock.Any(), "123", container.RemoveOptions{Force: true}).Return(nil)
api.EXPECT().ContainerRemove(gomock.Any(), "789", container.RemoveOptions{Force: true}).Return(nil)
api.EXPECT().ContainerRemove(gomock.Any(), "321", container.RemoveOptions{Force: true}).Return(nil)
api.EXPECT().ContainerRemove(gomock.Any(), "123", client.ContainerRemoveOptions{Force: true}).Return(client.ContainerRemoveResult{}, nil)
api.EXPECT().ContainerRemove(gomock.Any(), "789", client.ContainerRemoveOptions{Force: true}).Return(client.ContainerRemoveResult{}, nil)
api.EXPECT().ContainerRemove(gomock.Any(), "321", client.ContainerRemoveOptions{Force: true}).Return(client.ContainerRemoveResult{}, nil)
api.EXPECT().NetworkList(gomock.Any(), network.ListOptions{
Filters: filters.NewArgs(
networkFilter("default"),
projectFilter(strings.ToLower(testProject)),
),
}).Return([]network.Summary{{ID: "abc123", Name: "myProject_default"}}, nil)
api.EXPECT().NetworkInspect(gomock.Any(), "abc123", gomock.Any()).Return(network.Inspect{ID: "abc123"}, nil)
api.EXPECT().NetworkRemove(gomock.Any(), "abc123").Return(nil)
api.EXPECT().NetworkList(gomock.Any(), client.NetworkListOptions{
Filters: projectFilter(strings.ToLower(testProject)).Add("label", networkFilter("default")),
}).Return(client.NetworkListResult{
Items: []network.Summary{{Network: network.Network{ID: "abc123", Name: "myProject_default"}}},
}, nil)
api.EXPECT().NetworkInspect(gomock.Any(), "abc123", gomock.Any()).Return(client.NetworkInspectResult{
Network: network.Inspect{Network: network.Network{ID: "abc123"}},
}, nil)
api.EXPECT().NetworkRemove(gomock.Any(), "abc123", gomock.Any()).Return(client.NetworkRemoveResult{}, nil)
err = tested.Down(t.Context(), strings.ToLower(testProject), compose.DownOptions{RemoveOrphans: true})
assert.NilError(t, err)
@@ -239,24 +245,26 @@ func TestDownRemoveVolumes(t *testing.T) {
assert.NilError(t, err)
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(false)).Return(
[]container.Summary{testContainer("service1", "123", false)}, nil)
client.ContainerListResult{
Items: []container.Summary{testContainer("service1", "123", false)},
}, nil)
api.EXPECT().VolumeList(
gomock.Any(),
volume.ListOptions{
Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject))),
client.VolumeListOptions{
Filters: projectFilter(strings.ToLower(testProject)),
}).
Return(volume.ListResponse{
Volumes: []*volume.Volume{{Name: "myProject_volume"}},
Return(client.VolumeListResult{
Items: []volume.Volume{{Name: "myProject_volume"}},
}, nil)
api.EXPECT().VolumeInspect(gomock.Any(), "myProject_volume").
Return(volume.Volume{}, nil)
api.EXPECT().NetworkList(gomock.Any(), network.ListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
Return(nil, nil)
api.EXPECT().VolumeInspect(gomock.Any(), "myProject_volume", gomock.Any()).
Return(client.VolumeInspectResult{}, nil)
api.EXPECT().NetworkList(gomock.Any(), client.NetworkListOptions{Filters: projectFilter(strings.ToLower(testProject))}).
Return(client.NetworkListResult{}, nil)
api.EXPECT().ContainerStop(gomock.Any(), "123", container.StopOptions{}).Return(nil)
api.EXPECT().ContainerRemove(gomock.Any(), "123", container.RemoveOptions{Force: true, RemoveVolumes: true}).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "123", client.ContainerStopOptions{}).Return(client.ContainerStopResult{}, nil)
api.EXPECT().ContainerRemove(gomock.Any(), "123", client.ContainerRemoveOptions{Force: true, RemoveVolumes: true}).Return(client.ContainerRemoveResult{}, nil)
api.EXPECT().VolumeRemove(gomock.Any(), "myProject_volume", true).Return(nil)
api.EXPECT().VolumeRemove(gomock.Any(), "myProject_volume", client.VolumeRemoveOptions{Force: true}).Return(client.VolumeRemoveResult{}, nil)
err = tested.Down(t.Context(), strings.ToLower(testProject), compose.DownOptions{Volumes: true})
assert.NilError(t, err)
@@ -285,17 +293,16 @@ func TestDownRemoveImages(t *testing.T) {
assert.NilError(t, err)
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(false)).
Return([]container.Summary{
testContainer("service1", "123", false),
Return(client.ContainerListResult{
Items: []container.Summary{
testContainer("service1", "123", false),
},
}, nil).
AnyTimes()
api.EXPECT().ImageList(gomock.Any(), image.ListOptions{
Filters: filters.NewArgs(
projectFilter(strings.ToLower(testProject)),
filters.Arg("dangling", "false"),
),
}).Return([]image.Summary{
api.EXPECT().ImageList(gomock.Any(), client.ImageListOptions{
Filters: projectFilter(strings.ToLower(testProject)).Add("dangling", "false"),
}).Return(client.ImageListResult{Items: []image.Summary{
{
Labels: types.Labels{compose.ServiceLabel: "local-anonymous"},
RepoTags: []string{"testproject-local-anonymous:latest"},
@@ -304,7 +311,7 @@ func TestDownRemoveImages(t *testing.T) {
Labels: types.Labels{compose.ServiceLabel: "local-named"},
RepoTags: []string{"local-named-image:latest"},
},
}, nil).AnyTimes()
}}, nil).AnyTimes()
imagesToBeInspected := map[string]bool{
"testproject-local-anonymous": true,
@@ -323,12 +330,12 @@ func TestDownRemoveImages(t *testing.T) {
}
api.EXPECT().ImageInspect(gomock.Any(), img).
Return(resp, err).
Return(client.ImageInspectResult{InspectResponse: resp}, err).
AnyTimes()
}
api.EXPECT().ImageInspect(gomock.Any(), "registry.example.com/remote-image-tagged:v1.0").
Return(image.InspectResponse{RepoTags: []string{"registry.example.com/remote-image-tagged:v1.0"}}, nil).
Return(client.ImageInspectResult{InspectResponse: image.InspectResponse{RepoTags: []string{"registry.example.com/remote-image-tagged:v1.0"}}}, nil).
AnyTimes()
localImagesToBeRemoved := []string{
@@ -338,8 +345,8 @@ func TestDownRemoveImages(t *testing.T) {
for _, img := range localImagesToBeRemoved {
// test calls down --rmi=local then down --rmi=all, so local images
// get "removed" 2x, while other images are only 1x
api.EXPECT().ImageRemove(gomock.Any(), img, image.RemoveOptions{}).
Return(nil, nil).
api.EXPECT().ImageRemove(gomock.Any(), img, client.ImageRemoveOptions{}).
Return(client.ImageRemoveResult{}, nil).
Times(2)
}
@@ -353,8 +360,8 @@ func TestDownRemoveImages(t *testing.T) {
"registry.example.com/remote-image-tagged:v1.0",
}
for _, img := range otherImagesToBeRemoved {
api.EXPECT().ImageRemove(gomock.Any(), img, image.RemoveOptions{}).
Return(nil, nil).
api.EXPECT().ImageRemove(gomock.Any(), img, client.ImageRemoveOptions{}).
Return(client.ImageRemoveResult{}, nil).
Times(1)
}
@@ -375,35 +382,32 @@ func TestDownRemoveImages_NoLabel(t *testing.T) {
ctr := testContainer("service1", "123", false)
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(false)).Return(
[]container.Summary{ctr}, nil)
client.ContainerListResult{
Items: []container.Summary{ctr},
}, nil)
api.EXPECT().VolumeList(
gomock.Any(),
volume.ListOptions{
Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject))),
client.VolumeListOptions{
Filters: projectFilter(strings.ToLower(testProject)),
}).
Return(volume.ListResponse{
Volumes: []*volume.Volume{{Name: "myProject_volume"}},
Return(client.VolumeListResult{
Items: []volume.Volume{{Name: "myProject_volume"}},
}, nil)
api.EXPECT().NetworkList(gomock.Any(), network.ListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
Return(nil, nil)
api.EXPECT().NetworkList(gomock.Any(), client.NetworkListOptions{Filters: projectFilter(strings.ToLower(testProject))}).
Return(client.NetworkListResult{}, nil)
// ImageList returns no images for the project since they were unlabeled
// (created by an older version of Compose)
api.EXPECT().ImageList(gomock.Any(), image.ListOptions{
Filters: filters.NewArgs(
projectFilter(strings.ToLower(testProject)),
filters.Arg("dangling", "false"),
),
}).Return(nil, nil)
api.EXPECT().ImageList(gomock.Any(), client.ImageListOptions{
Filters: projectFilter(strings.ToLower(testProject)).Add("dangling", "false"),
}).Return(client.ImageListResult{}, nil)
api.EXPECT().ImageInspect(gomock.Any(), "testproject-service1").
Return(image.InspectResponse{}, nil)
api.EXPECT().ImageInspect(gomock.Any(), "testproject-service1", gomock.Any()).Return(client.ImageInspectResult{}, nil)
api.EXPECT().ContainerStop(gomock.Any(), "123", client.ContainerStopOptions{}).Return(client.ContainerStopResult{}, nil)
api.EXPECT().ContainerRemove(gomock.Any(), "123", client.ContainerRemoveOptions{Force: true}).Return(client.ContainerRemoveResult{}, nil)
api.EXPECT().ContainerStop(gomock.Any(), "123", container.StopOptions{}).Return(nil)
api.EXPECT().ContainerRemove(gomock.Any(), "123", container.RemoveOptions{Force: true}).Return(nil)
api.EXPECT().ImageRemove(gomock.Any(), "testproject-service1:latest", image.RemoveOptions{}).Return(nil, nil)
api.EXPECT().ImageRemove(gomock.Any(), "testproject-service1:latest", client.ImageRemoveOptions{}).Return(client.ImageRemoveResult{}, nil)
err = tested.Down(t.Context(), strings.ToLower(testProject), compose.DownOptions{Images: "local"})
assert.NilError(t, err)

View File

@@ -22,29 +22,27 @@ import (
"strings"
"time"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
"github.com/moby/moby/client"
"github.com/docker/compose/v5/pkg/api"
)
func (s *composeService) Events(ctx context.Context, projectName string, options api.EventsOptions) error {
projectName = strings.ToLower(projectName)
evts, errors := s.apiClient().Events(ctx, events.ListOptions{
Filters: filters.NewArgs(projectFilter(projectName)),
res := s.apiClient().Events(ctx, client.EventsListOptions{
Filters: projectFilter(projectName),
Since: options.Since,
Until: options.Until,
})
for {
select {
case event := <-evts:
case event := <-res.Messages:
// TODO: support other event types
if event.Type != "container" {
continue
}
oneOff := event.Actor.Attributes[api.OneoffLabel]
if oneOff == "True" {
if event.Actor.Attributes[api.OneoffLabel] == "True" {
// ignore
continue
}
@@ -76,7 +74,7 @@ func (s *composeService) Events(ctx context.Context, projectName string, options
return err
}
case err := <-errors:
case err := <-res.Err:
return err
}
}

View File

@@ -23,7 +23,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command/container"
containerType "github.com/docker/docker/api/types/container"
containerType "github.com/moby/moby/api/types/container"
"github.com/docker/compose/v5/pkg/api"
)

View File

@@ -23,6 +23,7 @@ import (
"strings"
"github.com/docker/cli/cli/command"
"github.com/moby/moby/client"
"github.com/moby/sys/atomicwriter"
"github.com/docker/compose/v5/pkg/api"
@@ -57,7 +58,7 @@ func (s *composeService) export(ctx context.Context, projectName string, options
Status: api.Working,
})
responseBody, err := s.apiClient().ContainerExport(ctx, container.ID)
responseBody, err := s.apiClient().ContainerExport(ctx, container.ID, client.ContainerExportOptions{})
if err != nil {
return err
}

View File

@@ -19,39 +19,35 @@ package compose
import (
"fmt"
"github.com/docker/docker/api/types/filters"
"github.com/moby/moby/client"
"github.com/docker/compose/v5/pkg/api"
)
func projectFilter(projectName string) filters.KeyValuePair {
return filters.Arg("label", fmt.Sprintf("%s=%s", api.ProjectLabel, projectName))
func projectFilter(projectName string) client.Filters {
return make(client.Filters).Add("label", fmt.Sprintf("%s=%s", api.ProjectLabel, projectName))
}
func serviceFilter(serviceName string) filters.KeyValuePair {
return filters.Arg("label", fmt.Sprintf("%s=%s", api.ServiceLabel, serviceName))
func serviceFilter(serviceName string) string {
return fmt.Sprintf("%s=%s", api.ServiceLabel, serviceName)
}
func networkFilter(name string) filters.KeyValuePair {
return filters.Arg("label", fmt.Sprintf("%s=%s", api.NetworkLabel, name))
func networkFilter(name string) string {
return fmt.Sprintf("%s=%s", api.NetworkLabel, name)
}
func oneOffFilter(b bool) filters.KeyValuePair {
func oneOffFilter(b bool) string {
v := "False"
if b {
v = "True"
}
return filters.Arg("label", fmt.Sprintf("%s=%s", api.OneoffLabel, v))
return fmt.Sprintf("%s=%s", api.OneoffLabel, v)
}
func containerNumberFilter(index int) filters.KeyValuePair {
return filters.Arg("label", fmt.Sprintf("%s=%d", api.ContainerNumberLabel, index))
func containerNumberFilter(index int) string {
return fmt.Sprintf("%s=%d", api.ContainerNumberLabel, index)
}
func hasProjectLabelFilter() filters.KeyValuePair {
return filters.Arg("label", api.ProjectLabel)
}
func hasConfigHashLabel() filters.KeyValuePair {
return filters.Arg("label", api.ConfigHashLabel)
func hasConfigHashLabel() string {
return api.ConfigHashLabel
}

View File

@@ -24,38 +24,33 @@ import (
"strings"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/network"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/mount"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/client"
"github.com/docker/compose/v5/pkg/api"
)
func (s *composeService) Generate(ctx context.Context, options api.GenerateOptions) (*types.Project, error) {
filtersListNames := filters.NewArgs()
filtersListIDs := filters.NewArgs()
for _, containerName := range options.Containers {
filtersListNames.Add("name", containerName)
filtersListIDs.Add("id", containerName)
res, err := s.apiClient().ContainerList(ctx, client.ContainerListOptions{
Filters: make(client.Filters).Add("name", options.Containers...),
All: true,
})
if err != nil {
return nil, err
}
containers, err := s.apiClient().ContainerList(ctx, container.ListOptions{
Filters: filtersListNames,
containers := res.Items
containersByIds, err := s.apiClient().ContainerList(ctx, client.ContainerListOptions{
Filters: make(client.Filters).Add("id", options.Containers...),
All: true,
})
if err != nil {
return nil, err
}
containersByIds, err := s.apiClient().ContainerList(ctx, container.ListOptions{
Filters: filtersListIDs,
All: true,
})
if err != nil {
return nil, err
}
for _, ctr := range containersByIds {
for _, ctr := range containersByIds.Items {
if !slices.ContainsFunc(containers, func(summary container.Summary) bool {
return summary.ID == ctr.ID
}) {
@@ -97,12 +92,12 @@ func (s *composeService) createProjectFromContainers(containers []container.Summ
}
service.Scale = increment(service.Scale)
inspect, err := s.apiClient().ContainerInspect(context.Background(), c.ID)
inspect, err := s.apiClient().ContainerInspect(context.Background(), c.ID, client.ContainerInspectOptions{})
if err != nil {
services[serviceLabel] = service
continue
}
s.extractComposeConfiguration(&service, inspect, volumes, secrets, networks)
s.extractComposeConfiguration(&service, inspect.Container, volumes, secrets, networks)
service.Labels = cleanDockerPreviousLabels(service.Labels)
services[serviceLabel] = service
}
@@ -136,10 +131,10 @@ func (s *composeService) extractComposeConfiguration(service *types.ServiceConfi
for key, portBindings := range inspect.HostConfig.PortBindings {
for _, portBinding := range portBindings {
service.Ports = append(service.Ports, types.ServicePortConfig{
Target: uint32(key.Int()),
Target: uint32(key.Num()),
Published: portBinding.HostPort,
Protocol: key.Proto(),
HostIP: portBinding.HostIP,
Protocol: string(key.Proto()),
HostIP: portBinding.HostIP.String(),
})
}
}
@@ -222,12 +217,12 @@ func (s *composeService) toComposeNetwork(networks map[string]*network.EndpointS
serviceNetworkConfigs := make(map[string]*types.ServiceNetworkConfig)
for name, net := range networks {
inspect, err := s.apiClient().NetworkInspect(context.Background(), name, network.InspectOptions{})
inspect, err := s.apiClient().NetworkInspect(context.Background(), name, client.NetworkInspectOptions{})
if err != nil {
networkConfigs[name] = types.NetworkConfig{}
} else {
networkConfigs[name] = types.NetworkConfig{
Internal: inspect.Internal,
Internal: inspect.Network.Internal,
}
}
serviceNetworkConfigs[name] = &types.ServiceNetworkConfig{

View File

@@ -23,8 +23,9 @@ import (
"time"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/stdcopy"
"github.com/moby/moby/api/pkg/stdcopy"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/utils"
@@ -43,7 +44,7 @@ func (s composeService) runHook(ctx context.Context, ctr container.Summary, serv
defer wOut.Close() //nolint:errcheck
detached := listener == nil
exec, err := s.apiClient().ContainerExecCreate(ctx, ctr.ID, container.ExecOptions{
exec, err := s.apiClient().ExecCreate(ctx, ctr.ID, client.ExecCreateOptions{
User: hook.User,
Privileged: hook.Privileged,
Env: ToMobyEnv(hook.Environment),
@@ -61,9 +62,12 @@ func (s composeService) runHook(ctx context.Context, ctr container.Summary, serv
}
height, width := s.stdout().GetTtySize()
consoleSize := &[2]uint{height, width}
attach, err := s.apiClient().ContainerExecAttach(ctx, exec.ID, container.ExecAttachOptions{
Tty: service.Tty,
consoleSize := client.ConsoleSize{
Width: width,
Height: height,
}
attach, err := s.apiClient().ExecAttach(ctx, exec.ID, client.ExecAttachOptions{
TTY: service.Tty,
ConsoleSize: consoleSize,
})
if err != nil {
@@ -80,7 +84,7 @@ func (s composeService) runHook(ctx context.Context, ctr container.Summary, serv
return err
}
inspected, err := s.apiClient().ContainerExecInspect(ctx, exec.ID)
inspected, err := s.apiClient().ExecInspect(ctx, exec.ID, client.ExecInspectOptions{})
if err != nil {
return err
}
@@ -91,9 +95,9 @@ func (s composeService) runHook(ctx context.Context, ctr container.Summary, serv
}
func (s composeService) runWaitExec(ctx context.Context, execID string, service types.ServiceConfig, listener api.ContainerEventListener) error {
err := s.apiClient().ContainerExecStart(ctx, execID, container.ExecStartOptions{
_, err := s.apiClient().ExecStart(ctx, execID, client.ExecStartOptions{
Detach: listener == nil,
Tty: service.Tty,
TTY: service.Tty,
})
if err != nil {
return nil
@@ -106,7 +110,7 @@ func (s composeService) runWaitExec(ctx context.Context, execID string, service
case <-ctx.Done():
return nil
case <-tick.C:
inspect, err := s.apiClient().ContainerExecInspect(ctx, execID)
inspect, err := s.apiClient().ExecInspect(ctx, execID, client.ExecInspectOptions{})
if err != nil {
return nil
}

View File

@@ -25,9 +25,8 @@ import (
"github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/errdefs"
"github.com/distribution/reference"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/client"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/client"
"golang.org/x/sync/errgroup"
"github.com/docker/compose/v5/pkg/api"
@@ -150,23 +149,19 @@ func (p *ImagePruner) namedImages(ctx context.Context) ([]string, error) {
// The image name could either have been defined by the user or implicitly
// created from the project + service name.
func (p *ImagePruner) labeledLocalImages(ctx context.Context) ([]image.Summary, error) {
imageListOpts := image.ListOptions{
Filters: filters.NewArgs(
projectFilter(p.project.Name),
// TODO(milas): we should really clean up the dangling images as
// well (historically we have NOT); need to refactor this to handle
// it gracefully without producing confusing CLI output, i.e. we
// do not want to print out a bunch of untagged/dangling image IDs,
// they should be grouped into a logical operation for the relevant
// service
filters.Arg("dangling", "false"),
),
}
projectImages, err := p.client.ImageList(ctx, imageListOpts)
res, err := p.client.ImageList(ctx, client.ImageListOptions{
// TODO(milas): we should really clean up the dangling images as
// well (historically we have NOT); need to refactor this to handle
// it gracefully without producing confusing CLI output, i.e. we
// do not want to print out a bunch of untagged/dangling image IDs,
// they should be grouped into a logical operation for the relevant
// service
Filters: projectFilter(p.project.Name).Add("dangling", "false"),
})
if err != nil {
return nil, err
}
return projectImages, nil
return res.Items, nil
}
// unlabeledLocalImages are images that match the implicit naming convention

View File

@@ -27,10 +27,9 @@ import (
"github.com/containerd/errdefs"
"github.com/containerd/platforms"
"github.com/distribution/reference"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/moby/moby/client/pkg/versions"
"golang.org/x/sync/errgroup"
"github.com/docker/compose/v5/pkg/api"
@@ -38,9 +37,9 @@ import (
func (s *composeService) Images(ctx context.Context, projectName string, options api.ImagesOptions) (map[string]api.ImageSummary, error) {
projectName = strings.ToLower(projectName)
allContainers, err := s.apiClient().ContainerList(ctx, container.ListOptions{
allContainers, err := s.apiClient().ContainerList(ctx, client.ContainerListOptions{
All: true,
Filters: filters.NewArgs(projectFilter(projectName)),
Filters: projectFilter(projectName),
})
if err != nil {
return nil, err
@@ -48,20 +47,20 @@ func (s *composeService) Images(ctx context.Context, projectName string, options
var containers []container.Summary
if len(options.Services) > 0 {
// filter service containers
for _, c := range allContainers {
for _, c := range allContainers.Items {
if slices.Contains(options.Services, c.Labels[api.ServiceLabel]) {
containers = append(containers, c)
}
}
} else {
containers = allContainers
containers = allContainers.Items
}
version, err := s.RuntimeVersion(ctx)
if err != nil {
return nil, err
}
withPlatform := versions.GreaterThanOrEqualTo(version, APIVersion149)
withPlatform := versions.GreaterThanOrEqualTo(version, apiVersion149)
summary := map[string]api.ImageSummary{}
var mux sync.Mutex

View File

@@ -17,14 +17,14 @@
package compose
import (
"net/netip"
"strings"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/client"
"go.uber.org/mock/gomock"
"gotest.tools/v3/assert"
@@ -39,22 +39,24 @@ func TestImages(t *testing.T) {
tested, err := NewComposeService(cli)
assert.NilError(t, err)
args := filters.NewArgs(projectFilter(strings.ToLower(testProject)))
listOpts := container.ListOptions{All: true, Filters: args}
api.EXPECT().ServerVersion(gomock.Any()).Return(types.Version{APIVersion: "1.96"}, nil).AnyTimes()
args := projectFilter(strings.ToLower(testProject))
listOpts := client.ContainerListOptions{All: true, Filters: args}
api.EXPECT().ServerVersion(gomock.Any(), gomock.Any()).Return(client.ServerVersionResult{APIVersion: "1.96"}, nil).AnyTimes()
timeStr1 := "2025-06-06T06:06:06.000000000Z"
created1, _ := time.Parse(time.RFC3339Nano, timeStr1)
timeStr2 := "2025-03-03T03:03:03.000000000Z"
created2, _ := time.Parse(time.RFC3339Nano, timeStr2)
image1 := imageInspect("image1", "foo:1", 12345, timeStr1)
image2 := imageInspect("image2", "bar:2", 67890, timeStr2)
api.EXPECT().ImageInspect(anyCancellableContext(), "foo:1").Return(image1, nil).MaxTimes(2)
api.EXPECT().ImageInspect(anyCancellableContext(), "bar:2").Return(image2, nil)
c1 := containerDetail("service1", "123", "running", "foo:1")
c2 := containerDetail("service1", "456", "running", "bar:2")
c2.Ports = []container.Port{{PublicPort: 80, PrivatePort: 90, IP: "localhost"}}
c3 := containerDetail("service2", "789", "exited", "foo:1")
api.EXPECT().ContainerList(t.Context(), listOpts).Return([]container.Summary{c1, c2, c3}, nil)
api.EXPECT().ImageInspect(anyCancellableContext(), "foo:1").Return(client.ImageInspectResult{InspectResponse: image1}, nil).MaxTimes(2)
api.EXPECT().ImageInspect(anyCancellableContext(), "bar:2").Return(client.ImageInspectResult{InspectResponse: image2}, nil)
c1 := containerDetail("service1", "123", container.StateRunning, "foo:1")
c2 := containerDetail("service1", "456", container.StateRunning, "bar:2")
c2.Ports = []container.PortSummary{{PublicPort: 80, PrivatePort: 90, IP: netip.MustParseAddr("127.0.0.1")}}
c3 := containerDetail("service2", "789", container.StateExited, "foo:1")
api.EXPECT().ContainerList(t.Context(), listOpts).Return(client.ContainerListResult{
Items: []container.Summary{c1, c2, c3},
}, nil)
images, err := tested.Images(t.Context(), strings.ToLower(testProject), compose.ImagesOptions{})
@@ -97,7 +99,7 @@ func imageInspect(id string, imageReference string, size int64, created string)
}
}
func containerDetail(service string, id string, status string, imageName string) container.Summary {
func containerDetail(service string, id string, status container.ContainerState, imageName string) container.Summary {
return container.Summary{
ID: id,
Names: []string{"/" + id},

View File

@@ -20,7 +20,8 @@ import (
"context"
"strings"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"golang.org/x/sync/errgroup"
"github.com/docker/compose/v5/pkg/api"
@@ -61,7 +62,9 @@ func (s *composeService) kill(ctx context.Context, projectName string, options a
eg.Go(func() error {
eventName := getContainerProgressName(ctr)
s.events.On(killingEvent(eventName))
err := s.apiClient().ContainerKill(ctx, ctr.ID, options.Signal)
_, err := s.apiClient().ContainerKill(ctx, ctr.ID, client.ContainerKillOptions{
Signal: options.Signal,
})
if err != nil {
s.events.On(errorEvent(eventName, "Error while Killing"))
return err

View File

@@ -18,15 +18,13 @@ package compose
import (
"context"
"fmt"
"path/filepath"
"strings"
"testing"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/volume"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/client"
"go.uber.org/mock/gomock"
"gotest.tools/v3/assert"
@@ -45,23 +43,30 @@ func TestKillAll(t *testing.T) {
name := strings.ToLower(testProject)
api.EXPECT().ContainerList(t.Context(), container.ListOptions{
Filters: filters.NewArgs(projectFilter(name), hasConfigHashLabel()),
}).Return(
[]container.Summary{testContainer("service1", "123", false), testContainer("service1", "456", false), testContainer("service2", "789", false)}, nil)
api.EXPECT().ContainerList(t.Context(), client.ContainerListOptions{
Filters: projectFilter(name).Add("label", hasConfigHashLabel()),
}).Return(client.ContainerListResult{
Items: []container.Summary{
testContainer("service1", "123", false),
testContainer("service1", "456", false),
testContainer("service2", "789", false),
},
}, nil)
api.EXPECT().VolumeList(
gomock.Any(),
volume.ListOptions{
Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject))),
client.VolumeListOptions{
Filters: projectFilter(strings.ToLower(testProject)),
}).
Return(volume.ListResponse{}, nil)
api.EXPECT().NetworkList(gomock.Any(), network.ListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
Return([]network.Summary{
{ID: "abc123", Name: "testProject_default"},
Return(client.VolumeListResult{}, nil)
api.EXPECT().NetworkList(gomock.Any(), client.NetworkListOptions{Filters: projectFilter(strings.ToLower(testProject))}).
Return(client.NetworkListResult{
Items: []network.Summary{{
Network: network.Network{ID: "abc123", Name: "testProject_default"},
}},
}, nil)
api.EXPECT().ContainerKill(anyCancellableContext(), "123", "").Return(nil)
api.EXPECT().ContainerKill(anyCancellableContext(), "456", "").Return(nil)
api.EXPECT().ContainerKill(anyCancellableContext(), "789", "").Return(nil)
api.EXPECT().ContainerKill(anyCancellableContext(), "123", client.ContainerKillOptions{}).Return(client.ContainerKillResult{}, nil)
api.EXPECT().ContainerKill(anyCancellableContext(), "456", client.ContainerKillOptions{}).Return(client.ContainerKillResult{}, nil)
api.EXPECT().ContainerKill(anyCancellableContext(), "789", client.ContainerKillOptions{}).Return(client.ContainerKillResult{}, nil)
err = tested.Kill(t.Context(), name, compose.KillOptions{})
assert.NilError(t, err)
@@ -77,22 +82,28 @@ func TestKillSignal(t *testing.T) {
assert.NilError(t, err)
name := strings.ToLower(testProject)
listOptions := container.ListOptions{
Filters: filters.NewArgs(projectFilter(name), serviceFilter(serviceName), hasConfigHashLabel()),
listOptions := client.ContainerListOptions{
Filters: projectFilter(name).Add("label", serviceFilter(serviceName), hasConfigHashLabel()),
}
api.EXPECT().ContainerList(t.Context(), listOptions).Return([]container.Summary{testContainer(serviceName, "123", false)}, nil)
api.EXPECT().ContainerList(t.Context(), listOptions).Return(client.ContainerListResult{
Items: []container.Summary{testContainer(serviceName, "123", false)},
}, nil)
api.EXPECT().VolumeList(
gomock.Any(),
volume.ListOptions{
Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject))),
client.VolumeListOptions{
Filters: projectFilter(strings.ToLower(testProject)),
}).
Return(volume.ListResponse{}, nil)
api.EXPECT().NetworkList(gomock.Any(), network.ListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
Return([]network.Summary{
{ID: "abc123", Name: "testProject_default"},
Return(client.VolumeListResult{}, nil)
api.EXPECT().NetworkList(gomock.Any(), client.NetworkListOptions{Filters: projectFilter(strings.ToLower(testProject))}).
Return(client.NetworkListResult{
Items: []network.Summary{{
Network: network.Network{ID: "abc123", Name: "testProject_default"},
}},
}, nil)
api.EXPECT().ContainerKill(anyCancellableContext(), "123", "SIGTERM").Return(nil)
api.EXPECT().ContainerKill(anyCancellableContext(), "123", client.ContainerKillOptions{
Signal: "SIGTERM",
}).Return(client.ContainerKillResult{}, nil)
err = tested.Kill(t.Context(), name, compose.KillOptions{Services: []string{serviceName}, Signal: "SIGTERM"})
assert.NilError(t, err)
@@ -133,15 +144,12 @@ func anyCancellableContext() gomock.Matcher {
return gomock.AssignableToTypeOf(ctxWithCancel)
}
func projectFilterListOpt(withOneOff bool) container.ListOptions {
filter := filters.NewArgs(
projectFilter(strings.ToLower(testProject)),
hasConfigHashLabel(),
)
func projectFilterListOpt(withOneOff bool) client.ContainerListOptions {
filter := projectFilter(strings.ToLower(testProject)).Add("label", hasConfigHashLabel())
if !withOneOff {
filter.Add("label", fmt.Sprintf("%s=False", compose.OneoffLabel))
filter.Add("label", oneOffFilter(false))
}
return container.ListOptions{
return client.ContainerListOptions{
Filters: filter,
All: true,
}

View File

@@ -21,8 +21,9 @@ import (
"io"
"github.com/containerd/errdefs"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/stdcopy"
"github.com/moby/moby/api/pkg/stdcopy"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
@@ -83,14 +84,14 @@ func (s *composeService) Logs(
monitor.withListener(func(event api.ContainerEvent) {
if event.Type == api.ContainerEventStarted {
eg.Go(func() error {
ctr, err := s.apiClient().ContainerInspect(ctx, event.ID)
res, err := s.apiClient().ContainerInspect(ctx, event.ID, client.ContainerInspectOptions{})
if err != nil {
return err
}
err = s.doLogContainer(ctx, consumer, event.Source, ctr, api.LogOptions{
err = s.doLogContainer(ctx, consumer, event.Source, res.Container, api.LogOptions{
Follow: options.Follow,
Since: ctr.State.StartedAt,
Since: res.Container.State.StartedAt,
Until: options.Until,
Tail: options.Tail,
Timestamps: options.Timestamps,
@@ -113,16 +114,16 @@ func (s *composeService) Logs(
}
func (s *composeService) logContainer(ctx context.Context, consumer api.LogConsumer, c container.Summary, options api.LogOptions) error {
ctr, err := s.apiClient().ContainerInspect(ctx, c.ID)
res, err := s.apiClient().ContainerInspect(ctx, c.ID, client.ContainerInspectOptions{})
if err != nil {
return err
}
name := getContainerNameWithoutProject(c)
return s.doLogContainer(ctx, consumer, name, ctr, options)
return s.doLogContainer(ctx, consumer, name, res.Container, options)
}
func (s *composeService) doLogContainer(ctx context.Context, consumer api.LogConsumer, name string, ctr container.InspectResponse, options api.LogOptions) error {
r, err := s.apiClient().ContainerLogs(ctx, ctr.ID, container.LogsOptions{
r, err := s.apiClient().ContainerLogs(ctx, ctr.ID, client.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Follow: options.Follow,

View File

@@ -23,9 +23,9 @@ import (
"testing"
"github.com/compose-spec/compose-go/v2/types"
containerType "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/pkg/stdcopy"
containerType "github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
@@ -43,21 +43,25 @@ func TestComposeService_Logs_Demux(t *testing.T) {
name := strings.ToLower(testProject)
api.EXPECT().ContainerList(t.Context(), containerType.ListOptions{
api.EXPECT().ContainerList(t.Context(), client.ContainerListOptions{
All: true,
Filters: filters.NewArgs(oneOffFilter(false), projectFilter(name), hasConfigHashLabel()),
Filters: projectFilter(name).Add("label", oneOffFilter(false), hasConfigHashLabel()),
}).Return(
[]containerType.Summary{
testContainer("service", "c", false),
client.ContainerListResult{
Items: []containerType.Summary{
testContainer("service", "c", false),
},
},
nil,
)
api.EXPECT().
ContainerInspect(anyCancellableContext(), "c").
Return(containerType.InspectResponse{
ContainerJSONBase: &containerType.ContainerJSONBase{ID: "c"},
Config: &containerType.Config{Tty: false},
ContainerInspect(anyCancellableContext(), "c", gomock.Any()).
Return(client.ContainerInspectResult{
Container: containerType.InspectResponse{
ID: "c",
Config: &containerType.Config{Tty: false},
},
}, nil)
c1Reader, c1Writer := io.Pipe()
t.Cleanup(func() {
@@ -112,28 +116,32 @@ func TestComposeService_Logs_ServiceFiltering(t *testing.T) {
name := strings.ToLower(testProject)
api.EXPECT().ContainerList(t.Context(), containerType.ListOptions{
api.EXPECT().ContainerList(t.Context(), client.ContainerListOptions{
All: true,
Filters: filters.NewArgs(oneOffFilter(false), projectFilter(name), hasConfigHashLabel()),
Filters: projectFilter(name).Add("label", oneOffFilter(false), hasConfigHashLabel()),
}).Return(
[]containerType.Summary{
testContainer("serviceA", "c1", false),
testContainer("serviceA", "c2", false),
// serviceB will be filtered out by the project definition to
// ensure we ignore "orphan" containers
testContainer("serviceB", "c3", false),
testContainer("serviceC", "c4", false),
client.ContainerListResult{
Items: []containerType.Summary{
testContainer("serviceA", "c1", false),
testContainer("serviceA", "c2", false),
// serviceB will be filtered out by the project definition to
// ensure we ignore "orphan" containers
testContainer("serviceB", "c3", false),
testContainer("serviceC", "c4", false),
},
},
nil,
)
for _, id := range []string{"c1", "c2", "c4"} {
api.EXPECT().
ContainerInspect(anyCancellableContext(), id).
ContainerInspect(anyCancellableContext(), id, gomock.Any()).
Return(
containerType.InspectResponse{
ContainerJSONBase: &containerType.ContainerJSONBase{ID: id},
Config: &containerType.Config{Tty: true},
client.ContainerInspectResult{
Container: containerType.InspectResponse{
ID: id,
Config: &containerType.Config{Tty: true},
},
},
nil,
)

View File

@@ -23,23 +23,23 @@ import (
"sort"
"strings"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/sirupsen/logrus"
"github.com/docker/compose/v5/pkg/api"
)
func (s *composeService) List(ctx context.Context, opts api.ListOptions) ([]api.Stack, error) {
list, err := s.apiClient().ContainerList(ctx, container.ListOptions{
Filters: filters.NewArgs(hasProjectLabelFilter(), hasConfigHashLabel()),
list, err := s.apiClient().ContainerList(ctx, client.ContainerListOptions{
Filters: make(client.Filters).Add("label", api.ProjectLabel).Add("label", api.ConfigHashLabel),
All: opts.All,
})
if err != nil {
return nil, err
}
return containersToStacks(list)
return containersToStacks(list.Items)
}
func containersToStacks(containers []container.Summary) ([]api.Stack, error) {
@@ -87,7 +87,7 @@ func combinedConfigFiles(containers []container.Summary) (string, error) {
func containerToState(containers []container.Summary) []string {
statuses := []string{}
for _, c := range containers {
statuses = append(statuses, c.State)
statuses = append(statuses, string(c.State))
}
return statuses
}

View File

@@ -20,7 +20,7 @@ import (
"fmt"
"testing"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/api/types/container"
"gotest.tools/v3/assert"
"github.com/docker/compose/v5/pkg/api"

View File

@@ -21,10 +21,8 @@ import (
"strconv"
"github.com/containerd/errdefs"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/moby/moby/api/types/events"
"github.com/moby/moby/client"
"github.com/sirupsen/logrus"
"github.com/docker/compose/v5/pkg/api"
@@ -58,10 +56,9 @@ func (c *monitor) withServices(services []string) {
//nolint:gocyclo
func (c *monitor) Start(ctx context.Context) error {
// collect initial application container
initialState, err := c.apiClient.ContainerList(ctx, container.ListOptions{
initialState, err := c.apiClient.ContainerList(ctx, client.ContainerListOptions{
All: true,
Filters: filters.NewArgs(
projectFilter(c.project),
Filters: projectFilter(c.project).Add("label",
oneOffFilter(false),
hasConfigHashLabel(),
),
@@ -72,17 +69,15 @@ func (c *monitor) Start(ctx context.Context) error {
// containers is the set if container IDs the application is based on
containers := utils.Set[string]{}
for _, ctr := range initialState {
for _, ctr := range initialState.Items {
if len(c.services) == 0 || c.services[ctr.Labels[api.ServiceLabel]] {
containers.Add(ctr.ID)
}
}
restarting := utils.Set[string]{}
evtCh, errCh := c.apiClient.Events(ctx, events.ListOptions{
Filters: filters.NewArgs(
filters.Arg("type", "container"),
projectFilter(c.project)),
res := c.apiClient.Events(ctx, client.EventsListOptions{
Filters: projectFilter(c.project).Add("type", "container"),
})
for {
if len(containers) == 0 {
@@ -91,9 +86,9 @@ func (c *monitor) Start(ctx context.Context) error {
select {
case <-ctx.Done():
return nil
case err := <-errCh:
case err := <-res.Err:
return err
case event := <-evtCh:
case event := <-res.Messages:
if len(c.services) > 0 && !c.services[event.Actor.Attributes[api.ServiceLabel]] {
continue
}
@@ -140,14 +135,14 @@ func (c *monitor) Start(ctx context.Context) error {
logrus.Debugf("container %s restarted", ctr.Name)
case events.ActionDie:
logrus.Debugf("container %s exited with code %d", ctr.Name, ctr.ExitCode)
inspect, err := c.apiClient.ContainerInspect(ctx, event.Actor.ID)
inspect, err := c.apiClient.ContainerInspect(ctx, event.Actor.ID, client.ContainerInspectOptions{})
if errdefs.IsNotFound(err) {
// Source is already removed
} else if err != nil {
return err
}
if inspect.State != nil && inspect.State.Restarting || inspect.State.Running {
if inspect.Container.State != nil && (inspect.Container.State.Restarting || inspect.Container.State.Running) {
// State.Restarting is set by engine when container is configured to restart on exit
// on ContainerRestart it doesn't (see https://github.com/moby/moby/issues/45538)
// container state still is reported as "running"

View File

@@ -20,7 +20,8 @@ import (
"context"
"strings"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"golang.org/x/sync/errgroup"
"github.com/docker/compose/v5/pkg/api"
@@ -45,7 +46,7 @@ func (s *composeService) pause(ctx context.Context, projectName string, options
eg, ctx := errgroup.WithContext(ctx)
containers.forEach(func(container container.Summary) {
eg.Go(func() error {
err := s.apiClient().ContainerPause(ctx, container.ID)
_, err := s.apiClient().ContainerPause(ctx, container.ID, client.ContainerPauseOptions{})
if err == nil {
eventName := getContainerProgressName(container)
s.events.On(newEvent(eventName, api.Done, "Paused"))
@@ -75,7 +76,7 @@ func (s *composeService) unPause(ctx context.Context, projectName string, option
eg, ctx := errgroup.WithContext(ctx)
containers.forEach(func(ctr container.Summary) {
eg.Go(func() error {
err = s.apiClient().ContainerUnpause(ctx, ctr.ID)
_, err = s.apiClient().ContainerUnpause(ctx, ctr.ID, client.ContainerUnpauseOptions{})
if err == nil {
eventName := getContainerProgressName(ctr)
s.events.On(newEvent(eventName, api.Done, "Unpaused"))

View File

@@ -21,7 +21,7 @@ import (
"fmt"
"strings"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/api/types/container"
"github.com/docker/compose/v5/pkg/api"
)
@@ -34,7 +34,7 @@ func (s *composeService) Port(ctx context.Context, projectName string, service s
}
for _, p := range ctr.Ports {
if p.PrivatePort == port && p.Type == options.Protocol {
return p.IP, int(p.PublicPort), nil
return p.IP.String(), int(p.PublicPort), nil
}
}
return "", 0, portNotFoundError(options.Protocol, port, ctr)

View File

@@ -21,12 +21,14 @@ import (
"sort"
"strings"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"golang.org/x/sync/errgroup"
"github.com/docker/compose/v5/pkg/api"
)
//nolint:gocyclo
func (s *composeService) Ps(ctx context.Context, projectName string, options api.PsOptions) ([]api.ContainerSummary, error) {
projectName = strings.ToLower(projectName)
oneOff := oneOffExclude
@@ -50,15 +52,19 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options api
return ctr.Ports[i].PrivatePort < ctr.Ports[j].PrivatePort
})
for i, p := range ctr.Ports {
var url string
if p.IP.IsValid() {
url = p.IP.String()
}
publishers[i] = api.PortPublisher{
URL: p.IP,
URL: url, // TODO(thaJeztah); change this to a netip.Addr ??
TargetPort: int(p.PrivatePort),
PublishedPort: int(p.PublicPort),
Protocol: p.Type,
}
}
inspect, err := s.apiClient().ContainerInspect(ctx, ctr.ID)
inspect, err := s.apiClient().ContainerInspect(ctx, ctr.ID, client.ContainerInspectOptions{})
if err != nil {
return err
}
@@ -67,14 +73,14 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options api
health container.HealthStatus
exitCode int
)
if inspect.State != nil {
switch inspect.State.Status {
if inspect.Container.State != nil {
switch inspect.Container.State.Status {
case container.StateRunning:
if inspect.State.Health != nil {
health = inspect.State.Health.Status
if inspect.Container.State.Health != nil {
health = inspect.Container.State.Health.Status
}
case container.StateExited, container.StateDead:
exitCode = inspect.State.ExitCode
exitCode = inspect.Container.State.ExitCode
}
}

View File

@@ -17,11 +17,12 @@
package compose
import (
"net/netip"
"strings"
"testing"
containerType "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
containerType "github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"go.uber.org/mock/gomock"
"gotest.tools/v3/assert"
@@ -36,17 +37,20 @@ func TestPs(t *testing.T) {
tested, err := NewComposeService(cli)
assert.NilError(t, err)
args := filters.NewArgs(projectFilter(strings.ToLower(testProject)), hasConfigHashLabel())
args.Add("label", "com.docker.compose.oneoff=False")
listOpts := containerType.ListOptions{Filters: args, All: false}
listOpts := client.ContainerListOptions{
Filters: projectFilter(strings.ToLower(testProject)).Add("label", hasConfigHashLabel(), oneOffFilter(false)),
All: false,
}
c1, inspect1 := containerDetails("service1", "123", containerType.StateRunning, containerType.Healthy, 0)
c2, inspect2 := containerDetails("service1", "456", containerType.StateRunning, "", 0)
c2.Ports = []containerType.Port{{PublicPort: 80, PrivatePort: 90, IP: "localhost"}}
c2.Ports = []containerType.PortSummary{{PublicPort: 80, PrivatePort: 90, IP: netip.MustParseAddr("127.0.0.1")}}
c3, inspect3 := containerDetails("service2", "789", containerType.StateExited, "", 130)
api.EXPECT().ContainerList(t.Context(), listOpts).Return([]containerType.Summary{c1, c2, c3}, nil)
api.EXPECT().ContainerInspect(anyCancellableContext(), "123").Return(inspect1, nil)
api.EXPECT().ContainerInspect(anyCancellableContext(), "456").Return(inspect2, nil)
api.EXPECT().ContainerInspect(anyCancellableContext(), "789").Return(inspect3, nil)
api.EXPECT().ContainerList(t.Context(), listOpts).Return(client.ContainerListResult{
Items: []containerType.Summary{c1, c2, c3},
}, nil)
api.EXPECT().ContainerInspect(anyCancellableContext(), "123", gomock.Any()).Return(client.ContainerInspectResult{Container: inspect1}, nil)
api.EXPECT().ContainerInspect(anyCancellableContext(), "456", gomock.Any()).Return(client.ContainerInspectResult{Container: inspect2}, nil)
api.EXPECT().ContainerInspect(anyCancellableContext(), "789", gomock.Any()).Return(client.ContainerInspectResult{Container: inspect3}, nil)
containers, err := tested.Ps(t.Context(), strings.ToLower(testProject), compose.PsOptions{})
@@ -66,8 +70,7 @@ func TestPs(t *testing.T) {
{
ID: "456", Name: "456", Names: []string{"/456"}, Image: "foo", Project: strings.ToLower(testProject), Service: "service1",
State: containerType.StateRunning,
Health: "",
Publishers: []compose.PortPublisher{{URL: "localhost", TargetPort: 90, PublishedPort: 80}},
Publishers: []compose.PortPublisher{{URL: "127.0.0.1", TargetPort: 90, PublishedPort: 80}},
Labels: map[string]string{
compose.ProjectLabel: strings.ToLower(testProject),
compose.ConfigFilesLabel: "/src/pkg/compose/testdata/compose.yaml",
@@ -78,7 +81,6 @@ func TestPs(t *testing.T) {
{
ID: "789", Name: "789", Names: []string{"/789"}, Image: "foo", Project: strings.ToLower(testProject), Service: "service2",
State: containerType.StateExited,
Health: "",
ExitCode: 130,
Publishers: []compose.PortPublisher{},
Labels: map[string]string{
@@ -102,12 +104,10 @@ func containerDetails(service string, id string, status containerType.ContainerS
State: status,
}
inspect := containerType.InspectResponse{
ContainerJSONBase: &containerType.ContainerJSONBase{
State: &containerType.State{
Status: status,
Health: &containerType.Health{Status: health},
ExitCode: exitCode,
},
State: &containerType.State{
Status: status,
Health: &containerType.Health{Status: health},
ExitCode: exitCode,
},
}
return ctr, inspect

View File

@@ -28,13 +28,15 @@ import (
"time"
"github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/platforms"
"github.com/distribution/reference"
"github.com/docker/buildx/driver"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/jsonmessage"
clitypes "github.com/docker/cli/cli/config/types"
"github.com/docker/go-units"
"github.com/moby/moby/api/types/jsonstream"
"github.com/moby/moby/client"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
@@ -189,9 +191,18 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
platform = defaultPlatform
}
stream, err := s.apiClient().ImagePull(ctx, service.Image, image.PullOptions{
var ociPlatforms []ocispec.Platform
if platform != "" {
p, err := platforms.Parse(platform)
if err != nil {
return "", err
}
ociPlatforms = append(ociPlatforms, p)
}
stream, err := s.apiClient().ImagePull(ctx, service.Image, client.ImagePullOptions{
RegistryAuth: encodedAuth,
Platform: platform,
Platforms: ociPlatforms,
})
if ctx.Err() != nil {
@@ -221,7 +232,7 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
dec := json.NewDecoder(stream)
for {
var jm jsonmessage.JSONMessage
var jm jsonstream.Message
if err := dec.Decode(&jm); err != nil {
if errors.Is(err, io.EOF) {
break
@@ -251,7 +262,9 @@ func ImageDigestResolver(ctx context.Context, file *configfile.ConfigFile, apiCl
if err != nil {
return "", err
}
inspect, err := apiClient.DistributionInspect(ctx, named.String(), auth)
inspect, err := apiClient.DistributionInspect(ctx, named.String(), client.DistributionInspectOptions{
EncodedRegistryAuth: auth,
})
if err != nil {
return "",
fmt.Errorf("failed to resolve digest for %s: %w", named.String(), err)
@@ -260,7 +273,11 @@ func ImageDigestResolver(ctx context.Context, file *configfile.ConfigFile, apiCl
}
}
func encodedAuth(ref reference.Named, configFile driver.Auth) (string, error) {
type authProvider interface {
GetAuthConfig(registryHostname string) (clitypes.AuthConfig, error)
}
func encodedAuth(ref reference.Named, configFile authProvider) (string, error) {
authConfig, err := configFile.GetAuthConfig(registry.GetAuthConfigKey(reference.Domain(ref)))
if err != nil {
return "", err
@@ -392,21 +409,19 @@ const (
PullCompletePhase = "Pull complete"
)
func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, events api.EventProcessor) {
func toPullProgressEvent(parent string, jm jsonstream.Message, events api.EventProcessor) {
if jm.ID == "" || jm.Progress == nil {
return
}
var (
progress string
total int64
percent int
current int64
status = api.Working
details string
total int64
percent int
current int64
status = api.Working
)
progress = jm.Progress.String()
switch jm.Status {
case PreparingPhase, WaitingPhase, PullingFsPhase:
percent = 0
@@ -415,10 +430,7 @@ func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, events api.E
current = jm.Progress.Current
total = jm.Progress.Total
if jm.Progress.Total > 0 {
percent = int(jm.Progress.Current * 100 / jm.Progress.Total)
if percent > 100 {
percent = 100
}
percent = min(int(jm.Progress.Current*100/jm.Progress.Total), 100)
}
}
case DownloadCompletePhase, AlreadyExistsPhase, PullCompletePhase:
@@ -434,7 +446,9 @@ func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, events api.E
if jm.Error != nil {
status = api.Error
progress = jm.Error.Message
details = jm.Error.Message
} else {
details = units.HumanSize(float64(jm.Progress.Current))
}
events.On(api.Resource{
@@ -445,6 +459,6 @@ func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, events api.E
Percent: percent,
Status: status,
Text: jm.Status,
Details: progress,
Details: details,
})
}

View File

@@ -27,8 +27,9 @@ import (
"github.com/compose-spec/compose-go/v2/types"
"github.com/distribution/reference"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/go-units"
"github.com/moby/moby/api/types/jsonstream"
"github.com/moby/moby/client"
"golang.org/x/sync/errgroup"
"github.com/docker/compose/v5/internal/registry"
@@ -101,7 +102,7 @@ func (s *composeService) pushServiceImage(ctx context.Context, tag string, quiet
return err
}
stream, err := s.apiClient().ImagePush(ctx, tag, image.PushOptions{
stream, err := s.apiClient().ImagePush(ctx, tag, client.ImagePushOptions{
RegistryAuth: base64.URLEncoding.EncodeToString(buf),
})
if err != nil {
@@ -109,7 +110,7 @@ func (s *composeService) pushServiceImage(ctx context.Context, tag string, quiet
}
dec := json.NewDecoder(stream)
for {
var jm jsonmessage.JSONMessage
var jm jsonstream.Message
if err := dec.Decode(&jm); err != nil {
if errors.Is(err, io.EOF) {
break
@@ -128,7 +129,7 @@ func (s *composeService) pushServiceImage(ctx context.Context, tag string, quiet
return nil
}
func toPushProgressEvent(prefix string, jm jsonmessage.JSONMessage, events api.EventProcessor) {
func toPushProgressEvent(prefix string, jm jsonstream.Message, events api.EventProcessor) {
if jm.ID == "" {
// skipped
return
@@ -149,15 +150,12 @@ func toPushProgressEvent(prefix string, jm jsonmessage.JSONMessage, events api.E
text = jm.Error.Message
}
if jm.Progress != nil {
text = jm.Progress.String()
text = progressText(jm.Progress)
if jm.Progress.Total != 0 {
current = jm.Progress.Current
total = jm.Progress.Total
if jm.Progress.Total > 0 {
percent = int(jm.Progress.Current * 100 / jm.Progress.Total)
if percent > 100 {
percent = 100
}
percent = min(int(jm.Progress.Current*100/jm.Progress.Total), 100)
}
}
}
@@ -173,7 +171,7 @@ func toPushProgressEvent(prefix string, jm jsonmessage.JSONMessage, events api.E
})
}
func isDone(msg jsonmessage.JSONMessage) bool {
func isDone(msg jsonstream.Message) bool {
// TODO there should be a better way to detect push is done than such a status message check
switch strings.ToLower(msg.Status) {
case "pushed", "layer already exists":
@@ -182,3 +180,27 @@ func isDone(msg jsonmessage.JSONMessage) bool {
return false
}
}
// progressText is a minimal variant of [jsonmessage.JSONProgress.String()]
//
// [jsonmessage.JSONProgress.String()]: https://github.com/moby/moby/blob/v28.5.2/pkg/jsonmessage/jsonmessage.go#L54-L117
func progressText(p *jsonstream.Progress) string {
switch {
case p.Current <= 0 && p.Total <= 0:
return ""
case p.Units == "": // no units, use bytes
current := units.HumanSize(float64(p.Current))
if p.Total <= 0 || p.Total > p.Current {
// remove total display if the reported current is wonky.
return fmt.Sprintf("%8v", current)
}
total := units.HumanSize(float64(p.Total))
return fmt.Sprintf("%8v/%v", current, total)
default:
if p.Total <= 0 || p.Total > p.Current {
// remove total display if the reported current is wonky.
return fmt.Sprintf("%d %s", p.Current, p.Units)
}
return fmt.Sprintf("%d/%d %s", p.Current, p.Total, p.Units)
}
}

View File

@@ -21,7 +21,8 @@ import (
"fmt"
"strings"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"golang.org/x/sync/errgroup"
"github.com/docker/compose/v5/pkg/api"
@@ -56,7 +57,7 @@ func (s *composeService) Remove(ctx context.Context, projectName string, options
var stoppedContainers Containers
for _, ctr := range containers {
// We have to inspect containers, as State reported by getContainers suffers a race condition
inspected, err := s.apiClient().ContainerInspect(ctx, ctr.ID)
inspected, err := s.apiClient().ContainerInspect(ctx, ctr.ID, client.ContainerInspectOptions{})
if api.IsNotFoundError(err) {
// Already removed. Maybe configured with auto-remove
continue
@@ -64,7 +65,7 @@ func (s *composeService) Remove(ctx context.Context, projectName string, options
if err != nil {
return err
}
if !inspected.State.Running || (options.Stop && s.dryRun) {
if !inspected.Container.State.Running || (options.Stop && s.dryRun) {
stoppedContainers = append(stoppedContainers, ctr)
}
}
@@ -101,7 +102,7 @@ func (s *composeService) remove(ctx context.Context, containers Containers, opti
eg.Go(func() error {
eventName := getContainerProgressName(ctr)
s.events.On(removingEvent(eventName))
err := s.apiClient().ContainerRemove(ctx, ctr.ID, container.RemoveOptions{
_, err := s.apiClient().ContainerRemove(ctx, ctr.ID, client.ContainerRemoveOptions{
RemoveVolumes: options.Volumes,
Force: options.Force,
})

View File

@@ -21,7 +21,7 @@ import (
"strings"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/client"
"golang.org/x/sync/errgroup"
"github.com/docker/compose/v5/pkg/api"
@@ -94,8 +94,9 @@ func (s *composeService) restart(ctx context.Context, projectName string, option
}
eventName := getContainerProgressName(ctr)
s.events.On(restartingEvent(eventName))
timeout := utils.DurationSecondToInt(options.Timeout)
err = s.apiClient().ContainerRestart(ctx, ctr.ID, container.StopOptions{Timeout: timeout})
_, err = s.apiClient().ContainerRestart(ctx, ctr.ID, client.ContainerRestartOptions{
Timeout: utils.DurationSecondToInt(options.Timeout),
})
if err != nil {
return err
}

View File

@@ -27,7 +27,8 @@ import (
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli"
cmd "github.com/docker/cli/cli/command/container"
"github.com/docker/docker/pkg/stringid"
"github.com/moby/moby/client"
"github.com/moby/moby/client/pkg/stringid"
"github.com/docker/compose/v5/pkg/api"
)
@@ -136,17 +137,17 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
return "", err
}
ctr, err := s.apiClient().ContainerInspect(ctx, created.ID)
inspect, err := s.apiClient().ContainerInspect(ctx, created.ID, client.ContainerInspectOptions{})
if err != nil {
return "", err
}
err = s.injectSecrets(ctx, project, service, ctr.ID)
err = s.injectSecrets(ctx, project, service, inspect.Container.ID)
if err != nil {
return created.ID, err
}
err = s.injectConfigs(ctx, project, service, ctr.ID)
err = s.injectConfigs(ctx, project, service, inspect.Container.ID)
return created.ID, err
}

View File

@@ -25,7 +25,7 @@ import (
"time"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/client"
)
type mountType string
@@ -128,9 +128,12 @@ func (s *composeService) copyFileToContainer(ctx context.Context, id, content st
return err
}
return s.apiClient().CopyToContainer(ctx, id, "/", &b, container.CopyToContainerOptions{
CopyUIDGID: file.UID != "" || file.GID != "",
_, err = s.apiClient().CopyToContainer(ctx, id, client.CopyToContainerOptions{
DestinationPath: "/",
Content: &b,
CopyUIDGID: file.UID != "" || file.GID != "",
})
return err
}
func createTar(env string, config types.FileReferenceConfig) (bytes.Buffer, error) {

View File

@@ -26,7 +26,7 @@ import (
"github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/flags"
"github.com/docker/docker/client"
"github.com/moby/moby/client"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"

View File

@@ -23,8 +23,7 @@ import (
"strings"
"github.com/compose-spec/compose-go/v2/types"
containerType "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/moby/moby/client"
"github.com/docker/compose/v5/pkg/api"
)
@@ -50,17 +49,14 @@ func (s *composeService) start(ctx context.Context, projectName string, options
}
}
var containers Containers
containers, err := s.apiClient().ContainerList(ctx, containerType.ListOptions{
Filters: filters.NewArgs(
projectFilter(project.Name),
oneOffFilter(false),
),
All: true,
res, err := s.apiClient().ContainerList(ctx, client.ContainerListOptions{
Filters: projectFilter(project.Name).Add("label", oneOffFilter(false)),
All: true,
})
if err != nil {
return err
}
containers := Containers(res.Items)
err = InDependencyOrder(ctx, project, func(c context.Context, name string) error {
service, err := project.GetService(name)

View File

@@ -21,10 +21,8 @@ import (
"testing"
"time"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/volume"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"go.uber.org/mock/gomock"
"gotest.tools/v3/assert"
@@ -41,25 +39,27 @@ func TestStopTimeout(t *testing.T) {
assert.NilError(t, err)
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(false)).Return(
[]container.Summary{
testContainer("service1", "123", false),
testContainer("service1", "456", false),
testContainer("service2", "789", false),
client.ContainerListResult{
Items: []container.Summary{
testContainer("service1", "123", false),
testContainer("service1", "456", false),
testContainer("service2", "789", false),
},
}, nil)
api.EXPECT().VolumeList(
gomock.Any(),
volume.ListOptions{
Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject))),
client.VolumeListOptions{
Filters: projectFilter(strings.ToLower(testProject)),
}).
Return(volume.ListResponse{}, nil)
api.EXPECT().NetworkList(gomock.Any(), network.ListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
Return([]network.Summary{}, nil)
Return(client.VolumeListResult{}, nil)
api.EXPECT().NetworkList(gomock.Any(), client.NetworkListOptions{Filters: projectFilter(strings.ToLower(testProject))}).
Return(client.NetworkListResult{}, nil)
timeout := 2 * time.Second
stopConfig := container.StopOptions{Timeout: utils.DurationSecondToInt(&timeout)}
api.EXPECT().ContainerStop(gomock.Any(), "123", stopConfig).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "456", stopConfig).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "789", stopConfig).Return(nil)
stopConfig := client.ContainerStopOptions{Timeout: utils.DurationSecondToInt(&timeout)}
api.EXPECT().ContainerStop(gomock.Any(), "123", stopConfig).Return(client.ContainerStopResult{}, nil)
api.EXPECT().ContainerStop(gomock.Any(), "456", stopConfig).Return(client.ContainerStopResult{}, nil)
api.EXPECT().ContainerStop(gomock.Any(), "789", stopConfig).Return(client.ContainerStopResult{}, nil)
err = tested.Stop(t.Context(), strings.ToLower(testProject), compose.StopOptions{
Timeout: &timeout,

View File

@@ -20,6 +20,7 @@ import (
"context"
"strings"
"github.com/moby/moby/client"
"golang.org/x/sync/errgroup"
"github.com/docker/compose/v5/pkg/api"
@@ -39,7 +40,9 @@ func (s *composeService) Top(ctx context.Context, projectName string, services [
eg, ctx := errgroup.WithContext(ctx)
for i, ctr := range containers {
eg.Go(func() error {
topContent, err := s.apiClient().ContainerTop(ctx, ctr.ID, []string{})
topContent, err := s.apiClient().ContainerTop(ctx, ctr.ID, client.ContainerTopOptions{
Arguments: []string{},
})
if err != nil {
return err
}

View File

@@ -31,6 +31,7 @@ import (
"github.com/containerd/errdefs"
"github.com/docker/cli/cli"
"github.com/eiannone/keyboard"
"github.com/moby/moby/client"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
@@ -252,15 +253,15 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
return
}
eg.Go(func() error {
ctr, err := s.apiClient().ContainerInspect(globalCtx, event.ID)
res, err := s.apiClient().ContainerInspect(globalCtx, event.ID, client.ContainerInspectOptions{})
if err != nil {
appendErr(err)
return nil
}
err = s.doLogContainer(globalCtx, options.Start.Attach, event.Source, ctr, api.LogOptions{
err = s.doLogContainer(globalCtx, options.Start.Attach, event.Source, res.Container, api.LogOptions{
Follow: true,
Since: ctr.State.StartedAt,
Since: res.Container.State.StartedAt,
})
if errdefs.IsNotImplemented(err) {
// container may be configured with logging_driver: none

View File

@@ -20,16 +20,15 @@ import (
"context"
"slices"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/volume"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/docker/compose/v5/pkg/api"
)
func (s *composeService) Volumes(ctx context.Context, project string, options api.VolumesOptions) ([]api.VolumesSummary, error) {
allContainers, err := s.apiClient().ContainerList(ctx, container.ListOptions{
Filters: filters.NewArgs(projectFilter(project)),
allContainers, err := s.apiClient().ContainerList(ctx, client.ContainerListOptions{
Filters: projectFilter(project),
})
if err != nil {
return nil, err
@@ -39,23 +38,23 @@ func (s *composeService) Volumes(ctx context.Context, project string, options ap
if len(options.Services) > 0 {
// filter service containers
for _, c := range allContainers {
for _, c := range allContainers.Items {
if slices.Contains(options.Services, c.Labels[api.ServiceLabel]) {
containers = append(containers, c)
}
}
} else {
containers = allContainers
containers = allContainers.Items
}
volumesResponse, err := s.apiClient().VolumeList(ctx, volume.ListOptions{
Filters: filters.NewArgs(projectFilter(project)),
volumesResponse, err := s.apiClient().VolumeList(ctx, client.VolumeListOptions{
Filters: projectFilter(project),
})
if err != nil {
return nil, err
}
projectVolumes := volumesResponse.Volumes
projectVolumes := volumesResponse.Items
if len(options.Services) == 0 {
return projectVolumes, nil
@@ -66,8 +65,8 @@ func (s *composeService) Volumes(ctx context.Context, project string, options ap
// create a name lookup of volumes used by containers
serviceVolumes := make(map[string]bool)
for _, container := range containers {
for _, mount := range container.Mounts {
for _, ctr := range containers {
for _, mount := range ctr.Mounts {
serviceVolumes[mount.Name] = true
}
}

View File

@@ -19,9 +19,9 @@ package compose
import (
"testing"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/volume"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/volume"
"github.com/moby/moby/client"
"go.uber.org/mock/gomock"
"gotest.tools/v3/assert"
@@ -38,9 +38,9 @@ func TestVolumes(t *testing.T) {
}
// Create test volumes
vol1 := &volume.Volume{Name: testProject + "_vol1"}
vol2 := &volume.Volume{Name: testProject + "_vol2"}
vol3 := &volume.Volume{Name: testProject + "_vol3"}
vol1 := volume.Volume{Name: testProject + "_vol1"}
vol2 := volume.Volume{Name: testProject + "_vol2"}
vol3 := volume.Volume{Name: testProject + "_vol3"}
// Create test containers with volume mounts
c1 := container.Summary{
@@ -57,28 +57,26 @@ func TestVolumes(t *testing.T) {
},
}
args := filters.NewArgs(projectFilter(testProject))
listOpts := container.ListOptions{Filters: args}
volumeListArgs := filters.NewArgs(projectFilter(testProject))
volumeListOpts := volume.ListOptions{Filters: volumeListArgs}
volumeReturn := volume.ListResponse{
Volumes: []*volume.Volume{vol1, vol2, vol3},
listOpts := client.ContainerListOptions{Filters: projectFilter(testProject)}
volumeListOpts := client.VolumeListOptions{Filters: projectFilter(testProject)}
volumeReturn := client.VolumeListResult{
Items: []volume.Volume{vol1, vol2, vol3},
}
containerReturn := client.ContainerListResult{
Items: []container.Summary{c1, c2},
}
containerReturn := []container.Summary{c1, c2}
mockApi.EXPECT().ContainerList(t.Context(), listOpts).Times(2).Return(containerReturn, nil)
mockApi.EXPECT().VolumeList(t.Context(), volumeListOpts).Times(2).Return(volumeReturn, nil)
// Test without service filter - should return all project volumes
volumeOptions := api.VolumesOptions{}
volumes, err := tested.Volumes(t.Context(), testProject, volumeOptions)
volumes, err := tested.Volumes(t.Context(), testProject, api.VolumesOptions{})
expected := []api.VolumesSummary{vol1, vol2, vol3}
assert.NilError(t, err)
assert.DeepEqual(t, volumes, expected)
// Test with service filter - should only return volumes used by service1
volumeOptions = api.VolumesOptions{Services: []string{"service1"}}
volumes, err = tested.Volumes(t.Context(), testProject, volumeOptions)
volumes, err = tested.Volumes(t.Context(), testProject, api.VolumesOptions{Services: []string{"service1"}})
expected = []api.VolumesSummary{vol1, vol2}
assert.NilError(t, err)
assert.DeepEqual(t, volumes, expected)

View File

@@ -20,6 +20,7 @@ import (
"context"
"fmt"
"github.com/moby/moby/client"
"golang.org/x/sync/errgroup"
"github.com/docker/compose/v5/pkg/api"
@@ -39,15 +40,13 @@ func (s *composeService) Wait(ctx context.Context, projectName string, options a
for _, ctr := range containers {
eg.Go(func() error {
var err error
resultC, errC := s.apiClient().ContainerWait(waitCtx, ctr.ID, "")
res := s.apiClient().ContainerWait(waitCtx, ctr.ID, client.ContainerWaitOptions{})
select {
case result := <-resultC:
case result := <-res.Result:
_, _ = fmt.Fprintf(s.stdout(), "container %q exited with status code %d\n", ctr.ID, result.StatusCode)
statusCode = result.StatusCode
case err = <-errC:
case err = <-res.Error:
}
return err
})
}

View File

@@ -32,11 +32,10 @@ import (
"github.com/compose-spec/compose-go/v2/types"
"github.com/compose-spec/compose-go/v2/utils"
ccli "github.com/docker/cli/cli/command/container"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
"github.com/go-viper/mapstructure/v2"
"github.com/moby/buildkit/util/progress/progressui"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
@@ -459,20 +458,20 @@ func (t tarDockerClient) ContainersForService(ctx context.Context, projectName s
}
func (t tarDockerClient) Exec(ctx context.Context, containerID string, cmd []string, in io.Reader) error {
execCfg := container.ExecOptions{
execCreateResp, err := t.s.apiClient().ExecCreate(ctx, containerID, client.ExecCreateOptions{
Cmd: cmd,
AttachStdout: false,
AttachStderr: true,
AttachStdin: in != nil,
Tty: false,
}
execCreateResp, err := t.s.apiClient().ContainerExecCreate(ctx, containerID, execCfg)
TTY: false,
})
if err != nil {
return err
}
startCheck := container.ExecStartOptions{Tty: false, Detach: false}
conn, err := t.s.apiClient().ContainerExecAttach(ctx, execCreateResp.ID, startCheck)
conn, err := t.s.apiClient().ExecAttach(ctx, execCreateResp.ID, client.ExecAttachOptions{
TTY: false,
})
if err != nil {
return err
}
@@ -493,7 +492,10 @@ func (t tarDockerClient) Exec(ctx context.Context, containerID string, cmd []str
return err
})
err = t.s.apiClient().ContainerExecStart(ctx, execCreateResp.ID, startCheck)
_, err = t.s.apiClient().ExecStart(ctx, execCreateResp.ID, client.ExecStartOptions{
TTY: false,
Detach: false,
})
if err != nil {
return err
}
@@ -505,7 +507,7 @@ func (t tarDockerClient) Exec(ctx context.Context, containerID string, cmd []str
return err
}
execResult, err := t.s.apiClient().ContainerExecInspect(ctx, execCreateResp.ID)
execResult, err := t.s.apiClient().ExecInspect(ctx, execCreateResp.ID, client.ExecInspectOptions{})
if err != nil {
return err
}
@@ -519,9 +521,12 @@ func (t tarDockerClient) Exec(ctx context.Context, containerID string, cmd []str
}
func (t tarDockerClient) Untar(ctx context.Context, id string, archive io.ReadCloser) error {
return t.s.apiClient().CopyToContainer(ctx, id, "/", archive, container.CopyToContainerOptions{
CopyUIDGID: true,
_, err := t.s.apiClient().CopyToContainer(ctx, id, client.CopyToContainerOptions{
DestinationPath: "/",
Content: archive,
CopyUIDGID: true,
})
return err
}
//nolint:gocyclo
@@ -705,20 +710,17 @@ func writeWatchSyncMessage(log api.LogConsumer, serviceName string, pathMappings
}
func (s *composeService) pruneDanglingImagesOnRebuild(ctx context.Context, projectName string, imageNameToIdMap map[string]string) {
images, err := s.apiClient().ImageList(ctx, image.ListOptions{
Filters: filters.NewArgs(
filters.Arg("dangling", "true"),
filters.Arg("label", api.ProjectLabel+"="+projectName),
),
images, err := s.apiClient().ImageList(ctx, client.ImageListOptions{
Filters: projectFilter(projectName).Add("dangling", "true"),
})
if err != nil {
logrus.Debugf("Failed to list images: %v", err)
return
}
for _, img := range images {
for _, img := range images.Items {
if _, ok := imageNameToIdMap[img.ID]; !ok {
_, err := s.apiClient().ImageRemove(ctx, img.ID, image.RemoveOptions{})
_, err := s.apiClient().ImageRemove(ctx, img.ID, client.ImageRemoveOptions{})
if err != nil {
logrus.Debugf("Failed to remove image %s: %v", img.ID, err)
}
@@ -832,20 +834,18 @@ func shouldIgnore(name string, ignore watch.PathMatcher) bool {
// gets the image creation time for a service
func (s *composeService) imageCreatedTime(ctx context.Context, project *types.Project, serviceName string) (time.Time, error) {
containers, err := s.apiClient().ContainerList(ctx, container.ListOptions{
All: true,
Filters: filters.NewArgs(
filters.Arg("label", fmt.Sprintf("%s=%s", api.ProjectLabel, project.Name)),
filters.Arg("label", fmt.Sprintf("%s=%s", api.ServiceLabel, serviceName))),
res, err := s.apiClient().ContainerList(ctx, client.ContainerListOptions{
All: true,
Filters: projectFilter(project.Name).Add("label", serviceFilter(serviceName)),
})
if err != nil {
return time.Now(), err
}
if len(containers) == 0 {
if len(res.Items) == 0 {
return time.Now(), fmt.Errorf("could not get created time for service's image")
}
img, err := s.apiClient().ImageInspect(ctx, containers[0].ImageID)
img, err := s.apiClient().ImageInspect(ctx, res.Items[0].ImageID)
if err != nil {
return time.Now(), err
}

View File

@@ -23,10 +23,10 @@ import (
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli/streams"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
"github.com/jonboulle/clockwork"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/client"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"gotest.tools/v3/assert"
@@ -77,21 +77,24 @@ func TestWatch_Sync(t *testing.T) {
cli := mocks.NewMockCli(mockCtrl)
cli.EXPECT().Err().Return(streams.NewOut(os.Stderr)).AnyTimes()
apiClient := mocks.NewMockAPIClient(mockCtrl)
apiClient.EXPECT().ContainerList(gomock.Any(), gomock.Any()).Return([]container.Summary{
testContainer("test", "123", false),
apiClient.EXPECT().ContainerList(gomock.Any(), gomock.Any()).Return(client.ContainerListResult{
Items: []container.Summary{
testContainer("test", "123", false),
},
}, nil).AnyTimes()
// we expect the image to be pruned
apiClient.EXPECT().ImageList(gomock.Any(), image.ListOptions{
Filters: filters.NewArgs(
filters.Arg("dangling", "true"),
filters.Arg("label", api.ProjectLabel+"=myProjectName"),
),
}).Return([]image.Summary{
{ID: "123"},
{ID: "456"},
apiClient.EXPECT().ImageList(gomock.Any(), client.ImageListOptions{
Filters: make(client.Filters).
Add("dangling", "true").
Add("label", api.ProjectLabel+"=myProjectName"),
}).Return(client.ImageListResult{
Items: []image.Summary{
{ID: "123"},
{ID: "456"},
},
}, nil).Times(1)
apiClient.EXPECT().ImageRemove(gomock.Any(), "123", image.RemoveOptions{}).Times(1)
apiClient.EXPECT().ImageRemove(gomock.Any(), "456", image.RemoveOptions{}).Times(1)
apiClient.EXPECT().ImageRemove(gomock.Any(), "123", client.ImageRemoveOptions{}).Times(1)
apiClient.EXPECT().ImageRemove(gomock.Any(), "456", client.ImageRemoveOptions{}).Times(1)
//
cli.EXPECT().Client().Return(apiClient).AnyTimes()

View File

@@ -33,21 +33,11 @@ import (
"github.com/docker/buildx/builder"
"github.com/docker/buildx/util/imagetools"
"github.com/docker/cli/cli/command"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/build"
"github.com/docker/docker/api/types/checkpoint"
containerType "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/system"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/jsonmessage"
specs "github.com/opencontainers/image-spec/specs-go/v1"
containerType "github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/api/types/jsonstream"
"github.com/moby/moby/api/types/volume"
"github.com/moby/moby/client"
)
var _ client.APIClient = &DryRunClient{}
@@ -65,6 +55,14 @@ type execDetails struct {
command []string
}
type fakeStreamResult struct {
io.ReadCloser
client.ImagePushResponse // same interface as [client.ImagePullResponse]
}
func (e fakeStreamResult) Read(p []byte) (int, error) { return e.ReadCloser.Read(p) }
func (e fakeStreamResult) Close() error { return e.ReadCloser.Close() }
// NewDryRunClient produces a DryRunClient
func NewDryRunClient(apiClient client.APIClient, cli command.Cli) (*DryRunClient, error) {
b, err := builder.New(cli, builder.WithSkippedValidation())
@@ -91,27 +89,25 @@ func getCallingFunction() string {
// All methods and functions which need to be overridden for dry run.
func (d *DryRunClient) ContainerAttach(ctx context.Context, container string, options containerType.AttachOptions) (moby.HijackedResponse, error) {
return moby.HijackedResponse{}, errors.New("interactive run is not supported in dry-run mode")
func (d *DryRunClient) ContainerAttach(ctx context.Context, container string, options client.ContainerAttachOptions) (client.ContainerAttachResult, error) {
return client.ContainerAttachResult{}, errors.New("interactive run is not supported in dry-run mode")
}
func (d *DryRunClient) ContainerCreate(ctx context.Context, config *containerType.Config, hostConfig *containerType.HostConfig,
networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string,
) (containerType.CreateResponse, error) {
func (d *DryRunClient) ContainerCreate(ctx context.Context, options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
d.containers = append(d.containers, containerType.Summary{
ID: containerName,
Names: []string{containerName},
Labels: config.Labels,
ID: options.Name,
Names: []string{options.Name},
Labels: options.Config.Labels,
HostConfig: struct {
NetworkMode string `json:",omitempty"`
Annotations map[string]string `json:",omitempty"`
}{},
})
return containerType.CreateResponse{ID: containerName}, nil
return client.ContainerCreateResult{ID: options.Name}, nil
}
func (d *DryRunClient) ContainerInspect(ctx context.Context, container string) (containerType.InspectResponse, error) {
containerJSON, err := d.apiClient.ContainerInspect(ctx, container)
func (d *DryRunClient) ContainerInspect(ctx context.Context, container string, options client.ContainerInspectOptions) (client.ContainerInspectResult, error) {
containerJSON, err := d.apiClient.ContainerInspect(ctx, container, options)
if err != nil {
id := "dryRunId"
for _, c := range d.containers {
@@ -119,8 +115,8 @@ func (d *DryRunClient) ContainerInspect(ctx context.Context, container string) (
id = container
}
}
return containerType.InspectResponse{
ContainerJSONBase: &containerType.ContainerJSONBase{
return client.ContainerInspectResult{
Container: containerType.InspectResponse{
ID: id,
Name: container,
State: &containerType.State{
@@ -129,117 +125,111 @@ func (d *DryRunClient) ContainerInspect(ctx context.Context, container string) (
Status: containerType.Healthy, // needed for healthcheck control
},
},
Mounts: nil,
Config: &containerType.Config{},
NetworkSettings: &containerType.NetworkSettings{},
},
Mounts: nil,
Config: &containerType.Config{},
NetworkSettings: &containerType.NetworkSettings{},
}, nil
}
return containerJSON, err
}
func (d *DryRunClient) ContainerKill(ctx context.Context, container, signal string) error {
return nil
func (d *DryRunClient) ContainerKill(ctx context.Context, container string, options client.ContainerKillOptions) (client.ContainerKillResult, error) {
return client.ContainerKillResult{}, nil
}
func (d *DryRunClient) ContainerList(ctx context.Context, options containerType.ListOptions) ([]containerType.Summary, error) {
func (d *DryRunClient) ContainerList(ctx context.Context, options client.ContainerListOptions) (client.ContainerListResult, error) {
caller := getCallingFunction()
switch caller {
case "start":
return d.containers, nil
return client.ContainerListResult{
Items: d.containers,
}, nil
case "getContainers":
if len(d.containers) == 0 {
var err error
d.containers, err = d.apiClient.ContainerList(ctx, options)
return d.containers, err
res, err := d.apiClient.ContainerList(ctx, options)
if err == nil {
d.containers = res.Items
}
return client.ContainerListResult{
Items: d.containers,
}, err
}
}
return d.apiClient.ContainerList(ctx, options)
}
func (d *DryRunClient) ContainerPause(ctx context.Context, container string) error {
return nil
func (d *DryRunClient) ContainerPause(ctx context.Context, container string, options client.ContainerPauseOptions) (client.ContainerPauseResult, error) {
return client.ContainerPauseResult{}, nil
}
func (d *DryRunClient) ContainerRemove(ctx context.Context, container string, options containerType.RemoveOptions) error {
return nil
func (d *DryRunClient) ContainerRemove(ctx context.Context, container string, options client.ContainerRemoveOptions) (client.ContainerRemoveResult, error) {
return client.ContainerRemoveResult{}, nil
}
func (d *DryRunClient) ContainerRename(ctx context.Context, container, newContainerName string) error {
return nil
func (d *DryRunClient) ContainerRename(ctx context.Context, container string, options client.ContainerRenameOptions) (client.ContainerRenameResult, error) {
return client.ContainerRenameResult{}, nil
}
func (d *DryRunClient) ContainerRestart(ctx context.Context, container string, options containerType.StopOptions) error {
return nil
func (d *DryRunClient) ContainerRestart(ctx context.Context, container string, options client.ContainerRestartOptions) (client.ContainerRestartResult, error) {
return client.ContainerRestartResult{}, nil
}
func (d *DryRunClient) ContainerStart(ctx context.Context, container string, options containerType.StartOptions) error {
return nil
func (d *DryRunClient) ContainerStart(ctx context.Context, container string, options client.ContainerStartOptions) (client.ContainerStartResult, error) {
return client.ContainerStartResult{}, nil
}
func (d *DryRunClient) ContainerStop(ctx context.Context, container string, options containerType.StopOptions) error {
return nil
func (d *DryRunClient) ContainerStop(ctx context.Context, container string, options client.ContainerStopOptions) (client.ContainerStopResult, error) {
return client.ContainerStopResult{}, nil
}
func (d *DryRunClient) ContainerUnpause(ctx context.Context, container string) error {
return nil
func (d *DryRunClient) ContainerUnpause(ctx context.Context, container string, options client.ContainerUnpauseOptions) (client.ContainerUnpauseResult, error) {
return client.ContainerUnpauseResult{}, nil
}
func (d *DryRunClient) CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, containerType.PathStat, error) {
rc := io.NopCloser(strings.NewReader(""))
if _, err := d.ContainerStatPath(ctx, container, srcPath); err != nil {
return rc, containerType.PathStat{}, fmt.Errorf("could not find the file %s in container %s", srcPath, container)
func (d *DryRunClient) CopyFromContainer(ctx context.Context, container string, options client.CopyFromContainerOptions) (client.CopyFromContainerResult, error) {
if _, err := d.ContainerStatPath(ctx, container, client.ContainerStatPathOptions{Path: options.SourcePath}); err != nil {
return client.CopyFromContainerResult{}, fmt.Errorf("could not find the file %s in container %s", options.SourcePath, container)
}
return rc, containerType.PathStat{}, nil
return client.CopyFromContainerResult{}, nil
}
func (d *DryRunClient) CopyToContainer(ctx context.Context, container, path string, content io.Reader, options containerType.CopyToContainerOptions) error {
return nil
func (d *DryRunClient) CopyToContainer(ctx context.Context, container string, options client.CopyToContainerOptions) (client.CopyToContainerResult, error) {
return client.CopyToContainerResult{}, nil
}
func (d *DryRunClient) ImageBuild(ctx context.Context, reader io.Reader, options build.ImageBuildOptions) (build.ImageBuildResponse, error) {
rc := io.NopCloser(bytes.NewReader(nil))
return build.ImageBuildResponse{
Body: rc,
func (d *DryRunClient) ImageBuild(ctx context.Context, reader io.Reader, options client.ImageBuildOptions) (client.ImageBuildResult, error) {
return client.ImageBuildResult{
Body: io.NopCloser(bytes.NewReader(nil)),
}, nil
}
func (d *DryRunClient) ImageInspect(ctx context.Context, imageName string, options ...client.ImageInspectOption) (image.InspectResponse, error) {
func (d *DryRunClient) ImageInspect(ctx context.Context, imageName string, options ...client.ImageInspectOption) (client.ImageInspectResult, error) {
caller := getCallingFunction()
switch caller {
case "pullServiceImage", "buildContainerVolumes":
return image.InspectResponse{ID: "dryRunId"}, nil
return client.ImageInspectResult{
InspectResponse: image.InspectResponse{ID: "dryRunId"},
}, nil
default:
return d.apiClient.ImageInspect(ctx, imageName, options...)
}
}
// Deprecated: Use [DryRunClient.ImageInspect] instead; raw response can be obtained by [client.ImageInspectWithRawResponse] option.
func (d *DryRunClient) ImageInspectWithRaw(ctx context.Context, imageName string) (image.InspectResponse, []byte, error) {
var buf bytes.Buffer
resp, err := d.ImageInspect(ctx, imageName, client.ImageInspectWithRawResponse(&buf))
if err != nil {
return image.InspectResponse{}, nil, err
}
return resp, buf.Bytes(), err
}
func (d *DryRunClient) ImagePull(ctx context.Context, ref string, options image.PullOptions) (io.ReadCloser, error) {
func (d *DryRunClient) ImagePull(ctx context.Context, ref string, options client.ImagePullOptions) (client.ImagePullResponse, error) {
if _, _, err := d.resolver.Resolve(ctx, ref); err != nil {
return nil, err
}
rc := io.NopCloser(strings.NewReader(""))
return rc, nil
return fakeStreamResult{ReadCloser: http.NoBody}, nil
}
func (d *DryRunClient) ImagePush(ctx context.Context, ref string, options image.PushOptions) (io.ReadCloser, error) {
func (d *DryRunClient) ImagePush(ctx context.Context, ref string, options client.ImagePushOptions) (client.ImagePushResponse, error) {
if _, _, err := d.resolver.Resolve(ctx, ref); err != nil {
return nil, err
}
jsonMessage, err := json.Marshal(&jsonmessage.JSONMessage{
jsonMessage, err := json.Marshal(&jsonstream.Message{
Status: "Pushed",
Progress: &jsonmessage.JSONProgress{
Progress: &jsonstream.Progress{
Current: 100,
Total: 100,
Start: 0,
@@ -251,48 +241,48 @@ func (d *DryRunClient) ImagePush(ctx context.Context, ref string, options image.
if err != nil {
return nil, err
}
rc := io.NopCloser(bytes.NewReader(jsonMessage))
return rc, nil
return fakeStreamResult{ReadCloser: io.NopCloser(bytes.NewReader(jsonMessage))}, nil
}
func (d *DryRunClient) ImageRemove(ctx context.Context, imageName string, options image.RemoveOptions) ([]image.DeleteResponse, error) {
return nil, nil
func (d *DryRunClient) ImageRemove(ctx context.Context, imageName string, options client.ImageRemoveOptions) (client.ImageRemoveResult, error) {
return client.ImageRemoveResult{}, nil
}
func (d *DryRunClient) NetworkConnect(ctx context.Context, networkName, container string, config *network.EndpointSettings) error {
return nil
func (d *DryRunClient) NetworkConnect(ctx context.Context, networkName string, options client.NetworkConnectOptions) (client.NetworkConnectResult, error) {
return client.NetworkConnectResult{}, nil
}
func (d *DryRunClient) NetworkCreate(ctx context.Context, name string, options network.CreateOptions) (network.CreateResponse, error) {
return network.CreateResponse{
ID: name,
Warning: "",
func (d *DryRunClient) NetworkCreate(ctx context.Context, name string, options client.NetworkCreateOptions) (client.NetworkCreateResult, error) {
return client.NetworkCreateResult{
ID: name,
}, nil
}
func (d *DryRunClient) NetworkDisconnect(ctx context.Context, networkName, container string, force bool) error {
return nil
func (d *DryRunClient) NetworkDisconnect(ctx context.Context, networkName string, options client.NetworkDisconnectOptions) (client.NetworkDisconnectResult, error) {
return client.NetworkDisconnectResult{}, nil
}
func (d *DryRunClient) NetworkRemove(ctx context.Context, networkName string) error {
return nil
func (d *DryRunClient) NetworkRemove(ctx context.Context, networkName string, options client.NetworkRemoveOptions) (client.NetworkRemoveResult, error) {
return client.NetworkRemoveResult{}, nil
}
func (d *DryRunClient) VolumeCreate(ctx context.Context, options volume.CreateOptions) (volume.Volume, error) {
return volume.Volume{
ClusterVolume: nil,
Driver: options.Driver,
Labels: options.Labels,
Name: options.Name,
Options: options.DriverOpts,
func (d *DryRunClient) VolumeCreate(ctx context.Context, options client.VolumeCreateOptions) (client.VolumeCreateResult, error) {
return client.VolumeCreateResult{
Volume: volume.Volume{
ClusterVolume: nil,
Driver: options.Driver,
Labels: options.Labels,
Name: options.Name,
Options: options.DriverOpts,
},
}, nil
}
func (d *DryRunClient) VolumeRemove(ctx context.Context, volumeID string, force bool) error {
return nil
func (d *DryRunClient) VolumeRemove(ctx context.Context, volumeID string, options client.VolumeRemoveOptions) (client.VolumeRemoveResult, error) {
return client.VolumeRemoveResult{}, nil
}
func (d *DryRunClient) ContainerExecCreate(ctx context.Context, container string, config containerType.ExecOptions) (containerType.ExecCreateResponse, error) {
func (d *DryRunClient) ExecCreate(ctx context.Context, container string, config client.ExecCreateOptions) (client.ExecCreateResult, error) {
b := make([]byte, 32)
_, _ = rand.Read(b)
id := fmt.Sprintf("%x", b)
@@ -300,347 +290,327 @@ func (d *DryRunClient) ContainerExecCreate(ctx context.Context, container string
container: container,
command: config.Cmd,
})
return containerType.ExecCreateResponse{
return client.ExecCreateResult{
ID: id,
}, nil
}
func (d *DryRunClient) ContainerExecStart(ctx context.Context, execID string, config containerType.ExecStartOptions) error {
func (d *DryRunClient) ExecStart(ctx context.Context, execID string, config client.ExecStartOptions) (client.ExecStartResult, error) {
_, ok := d.execs.LoadAndDelete(execID)
if !ok {
return fmt.Errorf("invalid exec ID %q", execID)
return client.ExecStartResult{}, fmt.Errorf("invalid exec ID %q", execID)
}
return nil
return client.ExecStartResult{}, nil
}
// Functions delegated to original APIClient (not used by Compose or not modifying the Compose stack)
func (d *DryRunClient) ConfigList(ctx context.Context, options swarm.ConfigListOptions) ([]swarm.Config, error) {
func (d *DryRunClient) ConfigList(ctx context.Context, options client.ConfigListOptions) (client.ConfigListResult, error) {
return d.apiClient.ConfigList(ctx, options)
}
func (d *DryRunClient) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (swarm.ConfigCreateResponse, error) {
return d.apiClient.ConfigCreate(ctx, config)
func (d *DryRunClient) ConfigInspect(ctx context.Context, name string, options client.ConfigInspectOptions) (client.ConfigInspectResult, error) {
return d.apiClient.ConfigInspect(ctx, name, options)
}
func (d *DryRunClient) ConfigRemove(ctx context.Context, id string) error {
return d.apiClient.ConfigRemove(ctx, id)
func (d *DryRunClient) ConfigCreate(ctx context.Context, options client.ConfigCreateOptions) (client.ConfigCreateResult, error) {
return d.apiClient.ConfigCreate(ctx, options)
}
func (d *DryRunClient) ConfigInspectWithRaw(ctx context.Context, name string) (swarm.Config, []byte, error) {
return d.apiClient.ConfigInspectWithRaw(ctx, name)
func (d *DryRunClient) ConfigRemove(ctx context.Context, id string, options client.ConfigRemoveOptions) (client.ConfigRemoveResult, error) {
return d.apiClient.ConfigRemove(ctx, id, options)
}
func (d *DryRunClient) ConfigUpdate(ctx context.Context, id string, version swarm.Version, config swarm.ConfigSpec) error {
return d.apiClient.ConfigUpdate(ctx, id, version, config)
func (d *DryRunClient) ConfigUpdate(ctx context.Context, id string, options client.ConfigUpdateOptions) (client.ConfigUpdateResult, error) {
return d.apiClient.ConfigUpdate(ctx, id, options)
}
func (d *DryRunClient) ContainerCommit(ctx context.Context, container string, options containerType.CommitOptions) (containerType.CommitResponse, error) {
func (d *DryRunClient) ContainerCommit(ctx context.Context, container string, options client.ContainerCommitOptions) (client.ContainerCommitResult, error) {
return d.apiClient.ContainerCommit(ctx, container, options)
}
func (d *DryRunClient) ContainerDiff(ctx context.Context, container string) ([]containerType.FilesystemChange, error) {
return d.apiClient.ContainerDiff(ctx, container)
func (d *DryRunClient) ContainerDiff(ctx context.Context, container string, options client.ContainerDiffOptions) (client.ContainerDiffResult, error) {
return d.apiClient.ContainerDiff(ctx, container, options)
}
func (d *DryRunClient) ContainerExecAttach(ctx context.Context, execID string, config containerType.ExecStartOptions) (moby.HijackedResponse, error) {
return moby.HijackedResponse{}, errors.New("interactive exec is not supported in dry-run mode")
func (d *DryRunClient) ExecAttach(ctx context.Context, execID string, config client.ExecAttachOptions) (client.ExecAttachResult, error) {
return client.ExecAttachResult{}, errors.New("interactive exec is not supported in dry-run mode")
}
func (d *DryRunClient) ContainerExecInspect(ctx context.Context, execID string) (containerType.ExecInspect, error) {
return d.apiClient.ContainerExecInspect(ctx, execID)
func (d *DryRunClient) ExecInspect(ctx context.Context, execID string, options client.ExecInspectOptions) (client.ExecInspectResult, error) {
return d.apiClient.ExecInspect(ctx, execID, options)
}
func (d *DryRunClient) ContainerExecResize(ctx context.Context, execID string, options containerType.ResizeOptions) error {
return d.apiClient.ContainerExecResize(ctx, execID, options)
func (d *DryRunClient) ExecResize(ctx context.Context, execID string, options client.ExecResizeOptions) (client.ExecResizeResult, error) {
return d.apiClient.ExecResize(ctx, execID, options)
}
func (d *DryRunClient) ContainerExport(ctx context.Context, container string) (io.ReadCloser, error) {
return d.apiClient.ContainerExport(ctx, container)
func (d *DryRunClient) ContainerExport(ctx context.Context, container string, options client.ContainerExportOptions) (client.ContainerExportResult, error) {
return d.apiClient.ContainerExport(ctx, container, options)
}
func (d *DryRunClient) ContainerInspectWithRaw(ctx context.Context, container string, getSize bool) (containerType.InspectResponse, []byte, error) {
return d.apiClient.ContainerInspectWithRaw(ctx, container, getSize)
}
func (d *DryRunClient) ContainerLogs(ctx context.Context, container string, options containerType.LogsOptions) (io.ReadCloser, error) {
func (d *DryRunClient) ContainerLogs(ctx context.Context, container string, options client.ContainerLogsOptions) (client.ContainerLogsResult, error) {
return d.apiClient.ContainerLogs(ctx, container, options)
}
func (d *DryRunClient) ContainerResize(ctx context.Context, container string, options containerType.ResizeOptions) error {
func (d *DryRunClient) ContainerResize(ctx context.Context, container string, options client.ContainerResizeOptions) (client.ContainerResizeResult, error) {
return d.apiClient.ContainerResize(ctx, container, options)
}
func (d *DryRunClient) ContainerStatPath(ctx context.Context, container, path string) (containerType.PathStat, error) {
return d.apiClient.ContainerStatPath(ctx, container, path)
func (d *DryRunClient) ContainerStatPath(ctx context.Context, container string, options client.ContainerStatPathOptions) (client.ContainerStatPathResult, error) {
return d.apiClient.ContainerStatPath(ctx, container, options)
}
func (d *DryRunClient) ContainerStats(ctx context.Context, container string, stream bool) (containerType.StatsResponseReader, error) {
return d.apiClient.ContainerStats(ctx, container, stream)
func (d *DryRunClient) ContainerStats(ctx context.Context, container string, options client.ContainerStatsOptions) (client.ContainerStatsResult, error) {
return d.apiClient.ContainerStats(ctx, container, options)
}
func (d *DryRunClient) ContainerStatsOneShot(ctx context.Context, container string) (containerType.StatsResponseReader, error) {
return d.apiClient.ContainerStatsOneShot(ctx, container)
func (d *DryRunClient) ContainerTop(ctx context.Context, container string, options client.ContainerTopOptions) (client.ContainerTopResult, error) {
return d.apiClient.ContainerTop(ctx, container, options)
}
func (d *DryRunClient) ContainerTop(ctx context.Context, container string, arguments []string) (containerType.TopResponse, error) {
return d.apiClient.ContainerTop(ctx, container, arguments)
func (d *DryRunClient) ContainerUpdate(ctx context.Context, container string, options client.ContainerUpdateOptions) (client.ContainerUpdateResult, error) {
return d.apiClient.ContainerUpdate(ctx, container, options)
}
func (d *DryRunClient) ContainerUpdate(ctx context.Context, container string, updateConfig containerType.UpdateConfig) (containerType.UpdateResponse, error) {
return d.apiClient.ContainerUpdate(ctx, container, updateConfig)
func (d *DryRunClient) ContainerWait(ctx context.Context, container string, options client.ContainerWaitOptions) client.ContainerWaitResult {
return d.apiClient.ContainerWait(ctx, container, options)
}
func (d *DryRunClient) ContainerWait(ctx context.Context, container string, condition containerType.WaitCondition) (<-chan containerType.WaitResponse, <-chan error) {
return d.apiClient.ContainerWait(ctx, container, condition)
func (d *DryRunClient) ContainerPrune(ctx context.Context, options client.ContainerPruneOptions) (client.ContainerPruneResult, error) {
return d.apiClient.ContainerPrune(ctx, options)
}
func (d *DryRunClient) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (containerType.PruneReport, error) {
return d.apiClient.ContainersPrune(ctx, pruneFilters)
func (d *DryRunClient) DistributionInspect(ctx context.Context, imageName string, options client.DistributionInspectOptions) (client.DistributionInspectResult, error) {
return d.apiClient.DistributionInspect(ctx, imageName, options)
}
func (d *DryRunClient) DistributionInspect(ctx context.Context, imageName, encodedRegistryAuth string) (registry.DistributionInspect, error) {
return d.apiClient.DistributionInspect(ctx, imageName, encodedRegistryAuth)
}
func (d *DryRunClient) BuildCachePrune(ctx context.Context, opts build.CachePruneOptions) (*build.CachePruneReport, error) {
func (d *DryRunClient) BuildCachePrune(ctx context.Context, opts client.BuildCachePruneOptions) (client.BuildCachePruneResult, error) {
return d.apiClient.BuildCachePrune(ctx, opts)
}
func (d *DryRunClient) BuildCancel(ctx context.Context, id string) error {
return d.apiClient.BuildCancel(ctx, id)
func (d *DryRunClient) BuildCancel(ctx context.Context, id string, opts client.BuildCancelOptions) (client.BuildCancelResult, error) {
return d.apiClient.BuildCancel(ctx, id, opts)
}
func (d *DryRunClient) ImageCreate(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error) {
return d.apiClient.ImageCreate(ctx, parentReference, options)
}
func (d *DryRunClient) ImageHistory(ctx context.Context, imageName string, options ...client.ImageHistoryOption) ([]image.HistoryResponseItem, error) {
func (d *DryRunClient) ImageHistory(ctx context.Context, imageName string, options ...client.ImageHistoryOption) (client.ImageHistoryResult, error) {
return d.apiClient.ImageHistory(ctx, imageName, options...)
}
func (d *DryRunClient) ImageImport(ctx context.Context, source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error) {
func (d *DryRunClient) ImageImport(ctx context.Context, source client.ImageImportSource, ref string, options client.ImageImportOptions) (client.ImageImportResult, error) {
return d.apiClient.ImageImport(ctx, source, ref, options)
}
func (d *DryRunClient) ImageList(ctx context.Context, options image.ListOptions) ([]image.Summary, error) {
func (d *DryRunClient) ImageList(ctx context.Context, options client.ImageListOptions) (client.ImageListResult, error) {
return d.apiClient.ImageList(ctx, options)
}
func (d *DryRunClient) ImageLoad(ctx context.Context, input io.Reader, options ...client.ImageLoadOption) (image.LoadResponse, error) {
func (d *DryRunClient) ImageLoad(ctx context.Context, input io.Reader, options ...client.ImageLoadOption) (client.ImageLoadResult, error) {
return d.apiClient.ImageLoad(ctx, input, options...)
}
func (d *DryRunClient) ImageSearch(ctx context.Context, term string, options registry.SearchOptions) ([]registry.SearchResult, error) {
func (d *DryRunClient) ImageSearch(ctx context.Context, term string, options client.ImageSearchOptions) (client.ImageSearchResult, error) {
return d.apiClient.ImageSearch(ctx, term, options)
}
func (d *DryRunClient) ImageSave(ctx context.Context, images []string, options ...client.ImageSaveOption) (io.ReadCloser, error) {
func (d *DryRunClient) ImageSave(ctx context.Context, images []string, options ...client.ImageSaveOption) (client.ImageSaveResult, error) {
return d.apiClient.ImageSave(ctx, images, options...)
}
func (d *DryRunClient) ImageTag(ctx context.Context, imageName, ref string) error {
return d.apiClient.ImageTag(ctx, imageName, ref)
func (d *DryRunClient) ImageTag(ctx context.Context, options client.ImageTagOptions) (client.ImageTagResult, error) {
return d.apiClient.ImageTag(ctx, options)
}
func (d *DryRunClient) ImagesPrune(ctx context.Context, pruneFilter filters.Args) (image.PruneReport, error) {
return d.apiClient.ImagesPrune(ctx, pruneFilter)
func (d *DryRunClient) ImagePrune(ctx context.Context, options client.ImagePruneOptions) (client.ImagePruneResult, error) {
return d.apiClient.ImagePrune(ctx, options)
}
func (d *DryRunClient) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) {
return d.apiClient.NodeInspectWithRaw(ctx, nodeID)
func (d *DryRunClient) NodeInspect(ctx context.Context, nodeID string, options client.NodeInspectOptions) (client.NodeInspectResult, error) {
return d.apiClient.NodeInspect(ctx, nodeID, options)
}
func (d *DryRunClient) NodeList(ctx context.Context, options swarm.NodeListOptions) ([]swarm.Node, error) {
func (d *DryRunClient) NodeList(ctx context.Context, options client.NodeListOptions) (client.NodeListResult, error) {
return d.apiClient.NodeList(ctx, options)
}
func (d *DryRunClient) NodeRemove(ctx context.Context, nodeID string, options swarm.NodeRemoveOptions) error {
func (d *DryRunClient) NodeRemove(ctx context.Context, nodeID string, options client.NodeRemoveOptions) (client.NodeRemoveResult, error) {
return d.apiClient.NodeRemove(ctx, nodeID, options)
}
func (d *DryRunClient) NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error {
return d.apiClient.NodeUpdate(ctx, nodeID, version, node)
func (d *DryRunClient) NodeUpdate(ctx context.Context, nodeID string, options client.NodeUpdateOptions) (client.NodeUpdateResult, error) {
return d.apiClient.NodeUpdate(ctx, nodeID, options)
}
func (d *DryRunClient) NetworkInspect(ctx context.Context, networkName string, options network.InspectOptions) (network.Inspect, error) {
func (d *DryRunClient) NetworkInspect(ctx context.Context, networkName string, options client.NetworkInspectOptions) (client.NetworkInspectResult, error) {
return d.apiClient.NetworkInspect(ctx, networkName, options)
}
func (d *DryRunClient) NetworkInspectWithRaw(ctx context.Context, networkName string, options network.InspectOptions) (network.Inspect, []byte, error) {
return d.apiClient.NetworkInspectWithRaw(ctx, networkName, options)
}
func (d *DryRunClient) NetworkList(ctx context.Context, options network.ListOptions) ([]network.Inspect, error) {
func (d *DryRunClient) NetworkList(ctx context.Context, options client.NetworkListOptions) (client.NetworkListResult, error) {
return d.apiClient.NetworkList(ctx, options)
}
func (d *DryRunClient) NetworksPrune(ctx context.Context, pruneFilter filters.Args) (network.PruneReport, error) {
return d.apiClient.NetworksPrune(ctx, pruneFilter)
func (d *DryRunClient) NetworkPrune(ctx context.Context, options client.NetworkPruneOptions) (client.NetworkPruneResult, error) {
return d.apiClient.NetworkPrune(ctx, options)
}
func (d *DryRunClient) PluginList(ctx context.Context, filter filters.Args) (moby.PluginsListResponse, error) {
return d.apiClient.PluginList(ctx, filter)
func (d *DryRunClient) PluginList(ctx context.Context, options client.PluginListOptions) (client.PluginListResult, error) {
return d.apiClient.PluginList(ctx, options)
}
func (d *DryRunClient) PluginRemove(ctx context.Context, name string, options moby.PluginRemoveOptions) error {
func (d *DryRunClient) PluginRemove(ctx context.Context, name string, options client.PluginRemoveOptions) (client.PluginRemoveResult, error) {
return d.apiClient.PluginRemove(ctx, name, options)
}
func (d *DryRunClient) PluginEnable(ctx context.Context, name string, options moby.PluginEnableOptions) error {
func (d *DryRunClient) PluginEnable(ctx context.Context, name string, options client.PluginEnableOptions) (client.PluginEnableResult, error) {
return d.apiClient.PluginEnable(ctx, name, options)
}
func (d *DryRunClient) PluginDisable(ctx context.Context, name string, options moby.PluginDisableOptions) error {
func (d *DryRunClient) PluginDisable(ctx context.Context, name string, options client.PluginDisableOptions) (client.PluginDisableResult, error) {
return d.apiClient.PluginDisable(ctx, name, options)
}
func (d *DryRunClient) PluginInstall(ctx context.Context, name string, options moby.PluginInstallOptions) (io.ReadCloser, error) {
func (d *DryRunClient) PluginInstall(ctx context.Context, name string, options client.PluginInstallOptions) (client.PluginInstallResult, error) {
return d.apiClient.PluginInstall(ctx, name, options)
}
func (d *DryRunClient) PluginUpgrade(ctx context.Context, name string, options moby.PluginInstallOptions) (io.ReadCloser, error) {
func (d *DryRunClient) PluginUpgrade(ctx context.Context, name string, options client.PluginUpgradeOptions) (client.PluginUpgradeResult, error) {
return d.apiClient.PluginUpgrade(ctx, name, options)
}
func (d *DryRunClient) PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error) {
return d.apiClient.PluginPush(ctx, name, registryAuth)
func (d *DryRunClient) PluginPush(ctx context.Context, name string, options client.PluginPushOptions) (client.PluginPushResult, error) {
return d.apiClient.PluginPush(ctx, name, options)
}
func (d *DryRunClient) PluginSet(ctx context.Context, name string, args []string) error {
return d.apiClient.PluginSet(ctx, name, args)
func (d *DryRunClient) PluginSet(ctx context.Context, name string, options client.PluginSetOptions) (client.PluginSetResult, error) {
return d.apiClient.PluginSet(ctx, name, options)
}
func (d *DryRunClient) PluginInspectWithRaw(ctx context.Context, name string) (*moby.Plugin, []byte, error) {
return d.apiClient.PluginInspectWithRaw(ctx, name)
func (d *DryRunClient) PluginInspect(ctx context.Context, name string, options client.PluginInspectOptions) (client.PluginInspectResult, error) {
return d.apiClient.PluginInspect(ctx, name, options)
}
func (d *DryRunClient) PluginCreate(ctx context.Context, createContext io.Reader, options moby.PluginCreateOptions) error {
func (d *DryRunClient) PluginCreate(ctx context.Context, createContext io.Reader, options client.PluginCreateOptions) (client.PluginCreateResult, error) {
return d.apiClient.PluginCreate(ctx, createContext, options)
}
func (d *DryRunClient) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options swarm.ServiceCreateOptions) (swarm.ServiceCreateResponse, error) {
return d.apiClient.ServiceCreate(ctx, service, options)
func (d *DryRunClient) ServiceCreate(ctx context.Context, options client.ServiceCreateOptions) (client.ServiceCreateResult, error) {
return d.apiClient.ServiceCreate(ctx, options)
}
func (d *DryRunClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
return d.apiClient.ServiceInspectWithRaw(ctx, serviceID, options)
func (d *DryRunClient) ServiceInspect(ctx context.Context, serviceID string, options client.ServiceInspectOptions) (client.ServiceInspectResult, error) {
return d.apiClient.ServiceInspect(ctx, serviceID, options)
}
func (d *DryRunClient) ServiceList(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
func (d *DryRunClient) ServiceList(ctx context.Context, options client.ServiceListOptions) (client.ServiceListResult, error) {
return d.apiClient.ServiceList(ctx, options)
}
func (d *DryRunClient) ServiceRemove(ctx context.Context, serviceID string) error {
return d.apiClient.ServiceRemove(ctx, serviceID)
func (d *DryRunClient) ServiceRemove(ctx context.Context, serviceID string, options client.ServiceRemoveOptions) (client.ServiceRemoveResult, error) {
return d.apiClient.ServiceRemove(ctx, serviceID, options)
}
func (d *DryRunClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
return d.apiClient.ServiceUpdate(ctx, serviceID, version, service, options)
func (d *DryRunClient) ServiceUpdate(ctx context.Context, serviceID string, options client.ServiceUpdateOptions) (client.ServiceUpdateResult, error) {
return d.apiClient.ServiceUpdate(ctx, serviceID, options)
}
func (d *DryRunClient) ServiceLogs(ctx context.Context, serviceID string, options containerType.LogsOptions) (io.ReadCloser, error) {
func (d *DryRunClient) ServiceLogs(ctx context.Context, serviceID string, options client.ServiceLogsOptions) (client.ServiceLogsResult, error) {
return d.apiClient.ServiceLogs(ctx, serviceID, options)
}
func (d *DryRunClient) TaskLogs(ctx context.Context, taskID string, options containerType.LogsOptions) (io.ReadCloser, error) {
func (d *DryRunClient) TaskLogs(ctx context.Context, taskID string, options client.TaskLogsOptions) (client.TaskLogsResult, error) {
return d.apiClient.TaskLogs(ctx, taskID, options)
}
func (d *DryRunClient) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) {
return d.apiClient.TaskInspectWithRaw(ctx, taskID)
func (d *DryRunClient) TaskInspect(ctx context.Context, taskID string, options client.TaskInspectOptions) (client.TaskInspectResult, error) {
return d.apiClient.TaskInspect(ctx, taskID, options)
}
func (d *DryRunClient) TaskList(ctx context.Context, options swarm.TaskListOptions) ([]swarm.Task, error) {
func (d *DryRunClient) TaskList(ctx context.Context, options client.TaskListOptions) (client.TaskListResult, error) {
return d.apiClient.TaskList(ctx, options)
}
func (d *DryRunClient) SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error) {
return d.apiClient.SwarmInit(ctx, req)
func (d *DryRunClient) SwarmInit(ctx context.Context, options client.SwarmInitOptions) (client.SwarmInitResult, error) {
return d.apiClient.SwarmInit(ctx, options)
}
func (d *DryRunClient) SwarmJoin(ctx context.Context, req swarm.JoinRequest) error {
return d.apiClient.SwarmJoin(ctx, req)
func (d *DryRunClient) SwarmJoin(ctx context.Context, options client.SwarmJoinOptions) (client.SwarmJoinResult, error) {
return d.apiClient.SwarmJoin(ctx, options)
}
func (d *DryRunClient) SwarmGetUnlockKey(ctx context.Context) (swarm.UnlockKeyResponse, error) {
func (d *DryRunClient) SwarmGetUnlockKey(ctx context.Context) (client.SwarmGetUnlockKeyResult, error) {
return d.apiClient.SwarmGetUnlockKey(ctx)
}
func (d *DryRunClient) SwarmUnlock(ctx context.Context, req swarm.UnlockRequest) error {
return d.apiClient.SwarmUnlock(ctx, req)
func (d *DryRunClient) SwarmUnlock(ctx context.Context, options client.SwarmUnlockOptions) (client.SwarmUnlockResult, error) {
return d.apiClient.SwarmUnlock(ctx, options)
}
func (d *DryRunClient) SwarmLeave(ctx context.Context, force bool) error {
return d.apiClient.SwarmLeave(ctx, force)
func (d *DryRunClient) SwarmLeave(ctx context.Context, options client.SwarmLeaveOptions) (client.SwarmLeaveResult, error) {
return d.apiClient.SwarmLeave(ctx, options)
}
func (d *DryRunClient) SwarmInspect(ctx context.Context) (swarm.Swarm, error) {
return d.apiClient.SwarmInspect(ctx)
func (d *DryRunClient) SwarmInspect(ctx context.Context, options client.SwarmInspectOptions) (client.SwarmInspectResult, error) {
return d.apiClient.SwarmInspect(ctx, options)
}
func (d *DryRunClient) SwarmUpdate(ctx context.Context, version swarm.Version, swarmSpec swarm.Spec, flags swarm.UpdateFlags) error {
return d.apiClient.SwarmUpdate(ctx, version, swarmSpec, flags)
func (d *DryRunClient) SwarmUpdate(ctx context.Context, options client.SwarmUpdateOptions) (client.SwarmUpdateResult, error) {
return d.apiClient.SwarmUpdate(ctx, options)
}
func (d *DryRunClient) SecretList(ctx context.Context, options swarm.SecretListOptions) ([]swarm.Secret, error) {
func (d *DryRunClient) SecretList(ctx context.Context, options client.SecretListOptions) (client.SecretListResult, error) {
return d.apiClient.SecretList(ctx, options)
}
func (d *DryRunClient) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (swarm.SecretCreateResponse, error) {
return d.apiClient.SecretCreate(ctx, secret)
func (d *DryRunClient) SecretCreate(ctx context.Context, options client.SecretCreateOptions) (client.SecretCreateResult, error) {
return d.apiClient.SecretCreate(ctx, options)
}
func (d *DryRunClient) SecretRemove(ctx context.Context, id string) error {
return d.apiClient.SecretRemove(ctx, id)
func (d *DryRunClient) SecretRemove(ctx context.Context, id string, options client.SecretRemoveOptions) (client.SecretRemoveResult, error) {
return d.apiClient.SecretRemove(ctx, id, options)
}
func (d *DryRunClient) SecretInspectWithRaw(ctx context.Context, name string) (swarm.Secret, []byte, error) {
return d.apiClient.SecretInspectWithRaw(ctx, name)
func (d *DryRunClient) SecretInspect(ctx context.Context, name string, options client.SecretInspectOptions) (client.SecretInspectResult, error) {
return d.apiClient.SecretInspect(ctx, name, options)
}
func (d *DryRunClient) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error {
return d.apiClient.SecretUpdate(ctx, id, version, secret)
func (d *DryRunClient) SecretUpdate(ctx context.Context, id string, options client.SecretUpdateOptions) (client.SecretUpdateResult, error) {
return d.apiClient.SecretUpdate(ctx, id, options)
}
func (d *DryRunClient) Events(ctx context.Context, options events.ListOptions) (<-chan events.Message, <-chan error) {
func (d *DryRunClient) Events(ctx context.Context, options client.EventsListOptions) client.EventsResult {
return d.apiClient.Events(ctx, options)
}
func (d *DryRunClient) Info(ctx context.Context) (system.Info, error) {
return d.apiClient.Info(ctx)
func (d *DryRunClient) Info(ctx context.Context, options client.InfoOptions) (client.SystemInfoResult, error) {
return d.apiClient.Info(ctx, options)
}
func (d *DryRunClient) RegistryLogin(ctx context.Context, auth registry.AuthConfig) (registry.AuthenticateOKBody, error) {
return d.apiClient.RegistryLogin(ctx, auth)
func (d *DryRunClient) RegistryLogin(ctx context.Context, options client.RegistryLoginOptions) (client.RegistryLoginResult, error) {
return d.apiClient.RegistryLogin(ctx, options)
}
func (d *DryRunClient) DiskUsage(ctx context.Context, options moby.DiskUsageOptions) (moby.DiskUsage, error) {
func (d *DryRunClient) DiskUsage(ctx context.Context, options client.DiskUsageOptions) (client.DiskUsageResult, error) {
return d.apiClient.DiskUsage(ctx, options)
}
func (d *DryRunClient) Ping(ctx context.Context) (moby.Ping, error) {
return d.apiClient.Ping(ctx)
func (d *DryRunClient) Ping(ctx context.Context, options client.PingOptions) (client.PingResult, error) {
return d.apiClient.Ping(ctx, options)
}
func (d *DryRunClient) VolumeInspect(ctx context.Context, volumeID string) (volume.Volume, error) {
return d.apiClient.VolumeInspect(ctx, volumeID)
func (d *DryRunClient) VolumeInspect(ctx context.Context, volumeID string, options client.VolumeInspectOptions) (client.VolumeInspectResult, error) {
return d.apiClient.VolumeInspect(ctx, volumeID, options)
}
func (d *DryRunClient) VolumeInspectWithRaw(ctx context.Context, volumeID string) (volume.Volume, []byte, error) {
return d.apiClient.VolumeInspectWithRaw(ctx, volumeID)
}
func (d *DryRunClient) VolumeList(ctx context.Context, opts volume.ListOptions) (volume.ListResponse, error) {
func (d *DryRunClient) VolumeList(ctx context.Context, opts client.VolumeListOptions) (client.VolumeListResult, error) {
return d.apiClient.VolumeList(ctx, opts)
}
func (d *DryRunClient) VolumesPrune(ctx context.Context, pruneFilter filters.Args) (volume.PruneReport, error) {
return d.apiClient.VolumesPrune(ctx, pruneFilter)
func (d *DryRunClient) VolumePrune(ctx context.Context, options client.VolumePruneOptions) (client.VolumePruneResult, error) {
return d.apiClient.VolumePrune(ctx, options)
}
func (d *DryRunClient) VolumeUpdate(ctx context.Context, volumeID string, version swarm.Version, options volume.UpdateOptions) error {
return d.apiClient.VolumeUpdate(ctx, volumeID, version, options)
func (d *DryRunClient) VolumeUpdate(ctx context.Context, volumeID string, options client.VolumeUpdateOptions) (client.VolumeUpdateResult, error) {
return d.apiClient.VolumeUpdate(ctx, volumeID, options)
}
func (d *DryRunClient) ClientVersion() string {
@@ -651,20 +621,8 @@ func (d *DryRunClient) DaemonHost() string {
return d.apiClient.DaemonHost()
}
func (d *DryRunClient) HTTPClient() *http.Client {
return d.apiClient.HTTPClient()
}
func (d *DryRunClient) ServerVersion(ctx context.Context) (moby.Version, error) {
return d.apiClient.ServerVersion(ctx)
}
func (d *DryRunClient) NegotiateAPIVersion(ctx context.Context) {
d.apiClient.NegotiateAPIVersion(ctx)
}
func (d *DryRunClient) NegotiateAPIVersionPing(ping moby.Ping) {
d.apiClient.NegotiateAPIVersionPing(ping)
func (d *DryRunClient) ServerVersion(ctx context.Context, options client.ServerVersionOptions) (client.ServerVersionResult, error) {
return d.apiClient.ServerVersion(ctx, options)
}
func (d *DryRunClient) DialHijack(ctx context.Context, url, proto string, meta map[string][]string) (net.Conn, error) {
@@ -679,14 +637,14 @@ func (d *DryRunClient) Close() error {
return d.apiClient.Close()
}
func (d *DryRunClient) CheckpointCreate(ctx context.Context, container string, options checkpoint.CreateOptions) error {
func (d *DryRunClient) CheckpointCreate(ctx context.Context, container string, options client.CheckpointCreateOptions) (client.CheckpointCreateResult, error) {
return d.apiClient.CheckpointCreate(ctx, container, options)
}
func (d *DryRunClient) CheckpointDelete(ctx context.Context, container string, options checkpoint.DeleteOptions) error {
return d.apiClient.CheckpointDelete(ctx, container, options)
func (d *DryRunClient) CheckpointRemove(ctx context.Context, container string, options client.CheckpointRemoveOptions) (client.CheckpointRemoveResult, error) {
return d.apiClient.CheckpointRemove(ctx, container, options)
}
func (d *DryRunClient) CheckpointList(ctx context.Context, container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error) {
func (d *DryRunClient) CheckpointList(ctx context.Context, container string, options client.CheckpointListOptions) (client.CheckpointListResult, error) {
return d.apiClient.CheckpointList(ctx, container, options)
}

View File

@@ -0,0 +1,21 @@
# syntax=docker/dockerfile:1
#
# Copyright 2020 Docker Compose CLI 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.
#
FROM alpine
WORKDIR /app
ARG CONTENT=initial
RUN echo "$CONTENT" > /app/content.txt

View File

@@ -0,0 +1,18 @@
services:
source:
build:
context: .
dockerfile: Dockerfile
image: image-volume-source
consumer:
image: alpine
depends_on:
- source
command: ["cat", "/data/content.txt"]
volumes:
- type: image
source: image-volume-source
target: /data
image:
subpath: app

View File

@@ -109,7 +109,7 @@ func TestLocalComposeLargeLogs(t *testing.T) {
f, err := os.Create(file)
assert.NilError(t, err)
for i := 0; i < 300_000; i++ {
for i := range 300_000 {
_, err := io.WriteString(f, fmt.Sprintf("This is line %d in a laaaarge text file\n", i))
assert.NilError(t, err)
}

View File

@@ -212,11 +212,9 @@ func TestNetworkRecreate(t *testing.T) {
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/network-recreate/compose.yaml", "--project-name", projectName, "--progress=plain", "up", "-d")
err := res.Stderr()
fmt.Println(err)
res.Assert(t, icmd.Expected{Err: `
Container network_recreate-web-1 Stopped
Network network_recreate_test Removed
Network network_recreate_test Creating
Network network_recreate_test Created
Container network_recreate-web-1 Starting
Container network_recreate-web-1 Started`})
hasStopped := strings.Contains(err, "Stopped")
hasResumed := strings.Contains(err, "Started") || strings.Contains(err, "Recreated")
if !hasStopped || !hasResumed {
t.Fatalf("unexpected output, missing expected events, stderr: %s", err)
}
}

View File

@@ -190,3 +190,47 @@ func TestImageVolume(t *testing.T) {
out := res.Combined()
assert.Check(t, strings.Contains(out, "index.html"))
}
func TestImageVolumeRecreateOnRebuild(t *testing.T) {
c := NewCLI(t)
const projectName = "compose-e2e-image-volume-recreate"
t.Cleanup(func() {
c.cleanupWithDown(t, projectName)
c.RunDockerOrExitError(t, "rmi", "-f", "image-volume-source")
})
version := c.RunDockerCmd(t, "version", "-f", "{{.Server.Version}}")
major, _, found := strings.Cut(version.Combined(), ".")
assert.Assert(t, found)
if major == "26" || major == "27" {
t.Skip("Skipping test due to docker version < 28")
}
// First build and run with initial content
c.RunDockerComposeCmd(t, "-f", "./fixtures/image-volume-recreate/compose.yaml",
"--project-name", projectName, "build", "--build-arg", "CONTENT=foo")
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/image-volume-recreate/compose.yaml",
"--project-name", projectName, "up", "-d")
assert.Check(t, !strings.Contains(res.Combined(), "error"))
// Check initial content
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/image-volume-recreate/compose.yaml",
"--project-name", projectName, "logs", "consumer")
assert.Check(t, strings.Contains(res.Combined(), "foo"), "Expected 'foo' in output, got: %s", res.Combined())
// Rebuild source image with different content
c.RunDockerComposeCmd(t, "-f", "./fixtures/image-volume-recreate/compose.yaml",
"--project-name", projectName, "build", "--build-arg", "CONTENT=bar")
// Run up again - consumer should be recreated because source image changed
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/image-volume-recreate/compose.yaml",
"--project-name", projectName, "up", "-d")
// The consumer container should be recreated
assert.Check(t, strings.Contains(res.Combined(), "Recreate") || strings.Contains(res.Combined(), "Created"),
"Expected container to be recreated, got: %s", res.Combined())
// Check updated content
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/image-volume-recreate/compose.yaml",
"--project-name", projectName, "logs", "consumer")
assert.Check(t, strings.Contains(res.Combined(), "bar"), "Expected 'bar' in output after rebuild, got: %s", res.Combined())
}

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,7 @@ import (
docker "github.com/docker/cli/cli/context/docker"
store "github.com/docker/cli/cli/context/store"
streams "github.com/docker/cli/cli/streams"
client "github.com/docker/docker/client"
client "github.com/moby/moby/client"
metric "go.opentelemetry.io/otel/metric"
resource "go.opentelemetry.io/otel/sdk/resource"
trace "go.opentelemetry.io/otel/trace"
@@ -47,24 +47,6 @@ func (m *MockCli) EXPECT() *MockCliMockRecorder {
return m.recorder
}
// Apply mocks base method.
func (m *MockCli) Apply(arg0 ...command.CLIOption) error {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "Apply", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// Apply indicates an expected call of Apply.
func (mr *MockCliMockRecorder) Apply(arg0 ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Apply", reflect.TypeOf((*MockCli)(nil).Apply), arg0...)
}
// BuildKitEnabled mocks base method.
func (m *MockCli) BuildKitEnabled() (bool, error) {
m.ctrl.T.Helper()

View File

@@ -31,7 +31,7 @@ func Test_BatchDebounceEvents(t *testing.T) {
t.Cleanup(stop)
eventBatchCh := BatchDebounceEvents(ctx, clock, ch)
for i := 0; i < 100; i++ {
for i := range 100 {
path := "/a"
if i%2 == 0 {
path = "/b"

View File

@@ -118,7 +118,7 @@ func TestGitBranchSwitch(t *testing.T) {
done := f.consumeEventsInBackground(ctx)
for i, dir := range dirs {
for j := 0; j < count; j++ {
for j := range count {
base := fmt.Sprintf("x/y/dir-%d/x.txt", j)
p := filepath.Join(dir, base)
f.WriteFile(p, "contents")

View File

@@ -22,6 +22,7 @@ import (
"fmt"
"os"
"path/filepath"
"sync"
"time"
"github.com/fsnotify/fsevents"
@@ -38,6 +39,7 @@ type fseventNotify struct {
stop chan struct{}
pathsWereWatching map[string]any
closeOnce sync.Once
}
func (d *fseventNotify) loop() {
@@ -81,6 +83,8 @@ func (d *fseventNotify) Start() error {
return nil
}
d.closeOnce = sync.Once{}
numberOfWatches.Add(int64(len(d.stream.Paths)))
err := d.stream.Start()
@@ -92,11 +96,13 @@ func (d *fseventNotify) Start() error {
}
func (d *fseventNotify) Close() error {
numberOfWatches.Add(int64(-len(d.stream.Paths)))
d.closeOnce.Do(func() {
numberOfWatches.Add(int64(-len(d.stream.Paths)))
d.stream.Stop()
close(d.errors)
close(d.stop)
d.stream.Stop()
close(d.errors)
close(d.stop)
})
return nil
}

View File

@@ -0,0 +1,48 @@
//go:build fsnotify
/*
Copyright 2020 Docker Compose CLI 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 watch
import (
"testing"
"gotest.tools/v3/assert"
)
func TestFseventNotifyCloseIdempotent(t *testing.T) {
// Create a watcher with a temporary directory
tmpDir := t.TempDir()
watcher, err := newWatcher([]string{tmpDir})
assert.NilError(t, err)
// Start the watcher
err = watcher.Start()
assert.NilError(t, err)
// Close should work the first time
err = watcher.Close()
assert.NilError(t, err)
// Close should be idempotent - calling it again should not panic
err = watcher.Close()
assert.NilError(t, err)
// Even a third time should be safe
err = watcher.Close()
assert.NilError(t, err)
}

View File

@@ -56,7 +56,7 @@ func TestDontWatchEachFile(t *testing.T) {
t.Fatal(err)
}
for i := 0; i < 100; i++ {
for i := range 100 {
f.WriteFile(f.JoinPath(initialDir, fmt.Sprintf("%d", i)), "initial data")
}
@@ -79,7 +79,7 @@ func TestDontWatchEachFile(t *testing.T) {
t.Fatal(err)
}
for i := 0; i < 100; i++ {
for i := range 100 {
f.WriteFile(f.JoinPath(inplaceDir, fmt.Sprintf("%d", i)), "inplace data")
}
@@ -98,7 +98,7 @@ func TestDontWatchEachFile(t *testing.T) {
t.Fatal(err)
}
for i := 0; i < 100; i++ {
for i := range 100 {
f.WriteFile(f.JoinPath(stagedDir, fmt.Sprintf("%d", i)), "staged data")
}
@@ -146,7 +146,7 @@ func TestDontRecurseWhenWatchingParentsOfNonExistentFiles(t *testing.T) {
f.watch(filepath.Join(watched, ".tiltignore"))
excludedDir := f.JoinPath(watched, "excluded")
for i := 0; i < 10; i++ {
for i := range 10 {
f.WriteFile(f.JoinPath(excludedDir, fmt.Sprintf("%d", i), "data.txt"), "initial data")
}
f.fsync()