Compare commits

..

15 Commits

Author SHA1 Message Date
Guillaume Lours
49b1c1e932 bump compose-go to version v2.9.1
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
(cherry picked from commit 3eb2934eb7)
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-10-30 10:14:58 +01:00
Suleiman Dibirov
19351bbd7d fix compose_run_build_once_test.go
Signed-off-by: Suleiman Dibirov <idsulik@gmail.com>
(cherry picked from commit c416ea7036)
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-10-30 10:12:21 +01:00
Suleiman Dibirov
3de9b0cc71 fix(git): Add validation for Git subdirectory paths to prevent traversal
Signed-off-by: Suleiman Dibirov <idsulik@gmail.com>
(cherry picked from commit 0d396bbacb)
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-10-30 10:12:04 +01:00
Suleiman Dibirov
1d4acfb5fa Update e2e tests in compose_run_build_once_test.go to use project names for Docker Compose commands.
Signed-off-by: Suleiman Dibirov <idsulik@gmail.com>
(cherry picked from commit fc74c78963)
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-10-30 10:11:17 +01:00
Suleiman Dibirov
670a181f77 Revert "no parallel in compose_run_build_once_test.go"
This reverts commit e4f4a5aa86.

Signed-off-by: Suleiman Dibirov <idsulik@gmail.com>
(cherry picked from commit 658bff335f)
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-10-30 10:10:56 +01:00
Suleiman Dibirov
739ae1b773 no parallel in compose_run_build_once_test.go
Signed-off-by: Suleiman Dibirov <idsulik@gmail.com>
(cherry picked from commit 80030e1390)
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-10-30 10:10:43 +01:00
Suleiman Dibirov
141921e72b lint fix
Signed-off-by: Suleiman Dibirov <idsulik@gmail.com>
(cherry picked from commit 6a35be5112)
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-10-30 10:10:19 +01:00
Suleiman Dibirov
c6bec2e712 add e2e tests
Signed-off-by: Suleiman Dibirov <idsulik@gmail.com>
(cherry picked from commit 0c854a6ab7)
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-10-30 10:09:52 +01:00
Suleiman Dibirov
0bd132b547 fix(run): Ensure images exist only for the target service in run command
Signed-off-by: Suleiman Dibirov <idsulik@gmail.com>
(cherry picked from commit 557e0b6ec7)
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-10-30 10:09:29 +01:00
Nicolas De Loof
ee86f5527c run hooks on restart
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
(cherry picked from commit 5924387e89)
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-10-30 09:58:34 +01:00
Nicolas De Loof
d0f2c3504a prompt default implementation to prevent a panic
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
(cherry picked from commit 3ce52883cb)
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-10-30 09:57:38 +01:00
Nicolas De Loof
db1c279447 Code Cleanup
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
(cherry picked from commit ac3b8fd8a5)
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-10-30 09:57:17 +01:00
Anton Ovchinnikov
5e209ea113 Fix help output for "exec --no-tty" option
Signed-off-by: Anton Ovchinnikov <anton@tonyo.info>
(cherry picked from commit 8619f5d72a)
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-10-30 09:55:53 +01:00
Nicolas De Loof
5dec597d4b fix OCI compose override support
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
(cherry picked from commit e59150baa8)
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-10-30 09:55:19 +01:00
Nicolas De Loof
47f6d02bef Test to check writeComposeFile detects invalid OCI artifact
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
(cherry picked from commit 6a90742ef2)
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-10-30 09:54:58 +01:00
221 changed files with 4342 additions and 5699 deletions

View File

@@ -22,6 +22,24 @@ 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:
@@ -45,88 +63,63 @@ 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:
- binary
- prepare
strategy:
fail-fast: false
matrix:
platform: ${{ fromJson(needs.prepare.outputs.matrix) }}
steps:
-
name: Download artifacts
uses: actions/download-artifact@v7
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
with:
path: /tmp/compose-output
name: ${{ needs.binary.outputs.artifact-name }}
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
-
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: |
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
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
-
name: Upload artifacts
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v4
with:
name: release
path: ./bin/release/*
name: compose-${{ env.PLATFORM_PAIR }}
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:
@@ -154,29 +147,17 @@ jobs:
with:
paths: bin/coverage/unit/report.xml
if: always()
e2e:
runs-on: ubuntu-latest
name: e2e (${{ matrix.mode }}, ${{ matrix.channel }})
strategy:
fail-fast: false
matrix:
include:
# current stable
- mode: plugin
engine: 29
channel: stable
- mode: standalone
engine: 29
channel: stable
# old stable (latest major - 1)
- mode: plugin
engine: 28
channel: oldstable
- mode: standalone
engine: 28
channel: oldstable
mode:
- plugin
- standalone
engine:
- 27 # old stable (latest major - 1)
- 28 # current stable
steps:
- name: Prepare
run: |
@@ -207,9 +188,9 @@ jobs:
docker model version
- name: Set up Go
uses: actions/setup-go@v6
uses: actions/setup-go@v5
with:
go-version-file: '.go-version'
go-version-file: 'go.mod'
check-latest: true
cache: true
@@ -262,7 +243,6 @@ jobs:
with:
paths: /tmp/report/report.xml
if: always()
coverage:
runs-on: ubuntu-latest
needs:
@@ -273,9 +253,9 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v6
uses: actions/setup-go@v5
with:
go-version-file: '.go-version'
go-version-file: 'go.mod'
check-latest: true
- name: Download unit test coverage
uses: actions/download-artifact@v4
@@ -299,26 +279,40 @@ jobs:
path: ./coverage.txt
if-no-files-found: error
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v3
with:
files: ./coverage.txt
release:
permissions:
contents: write # to create a release (ncipollo/release-action)
runs-on: ubuntu-latest
needs:
- binary-finalize
- binary
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Download artifacts
uses: actions/download-artifact@v7
uses: actions/download-artifact@v4
with:
pattern: compose-*
path: ./bin/release
name: 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: List artifacts
run: |

View File

@@ -33,9 +33,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v6
- uses: actions/setup-go@v5
with:
go-version-file: '.go-version'
go-version-file: go.mod
cache: true
check-latest: true
@@ -74,41 +74,63 @@ jobs:
run: |
make e2e-compose-standalone
bin-image-prepare:
runs-on: ubuntu-24.04
outputs:
repo-slug: ${{ env.REPO_SLUG }}
steps:
# 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
runs-on: ubuntu-22.04
outputs:
digest: ${{ fromJSON(steps.bake.outputs.metadata).image-cross['containerimage.digest'] }}
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:
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
@@ -136,6 +158,6 @@ jobs:
workflow_id: 'compose-edge-integration.yml',
ref: 'main',
inputs: {
"image-tag": "${{ env.REPO_SLUG }}:edge"
"image-tag": "${{ needs.bin-image.outputs.digest }}"
}
})

1
.gitignore vendored
View File

@@ -3,4 +3,3 @@ bin/
coverage.out
covdatafiles/
.DS_Store
pkg/e2e/*.tar

View File

@@ -1 +0,0 @@
1.25.7

View File

@@ -8,7 +8,6 @@ linters:
- depguard
- errcheck
- errorlint
- forbidigo
- gocritic
- gocyclo
- gomodguard
@@ -39,15 +38,6 @@ linters:
desc: use stdlib slices package
- pkg: gopkg.in/yaml.v2
desc: compose-go uses yaml.v3
forbidigo:
analyze-types: true
forbid:
- pattern: 'context\.Background'
pkg: '^context$'
msg: "in tests, use t.Context() instead of context.Background()"
- pattern: 'context\.TODO'
pkg: '^context$'
msg: "in tests, use t.Context() instead of context.TODO()"
gocritic:
disabled-checks:
- paramTypeCombine
@@ -84,27 +74,16 @@ linters:
- third_party$
- builtin$
- examples$
rules:
- path-except: '_test\.go'
linters:
- forbidigo
issues:
max-issues-per-linter: 0
max-same-issues: 0
formatters:
enable:
- gci
- gofumpt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
settings:
gci:
sections:
- standard
- default
- localmodule
custom-order: true # make the section order the same as the order of "sections".

View File

@@ -15,9 +15,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
ARG GO_VERSION=1.25.7
ARG XX_VERSION=1.9.0
ARG GOLANGCI_LINT_VERSION=v2.8.0
ARG GO_VERSION=1.24.9
ARG XX_VERSION=1.6.1
ARG GOLANGCI_LINT_VERSION=v2.0.2
ARG ADDLICENSE_VERSION=v1.0.0
ARG BUILD_TAGS="e2e"
@@ -28,12 +28,12 @@ ARG LICENSE_FILES=".*\(Dockerfile\|Makefile\|\.go\|\.hcl\|\.sh\)"
FROM --platform=${BUILDPLATFORM} tonistiigi/xx:${XX_VERSION} AS xx
# osxcross contains the MacOSX cross toolchain for xx
FROM crazymax/osxcross:15.5-alpine AS osxcross
FROM crazymax/osxcross:11.3-alpine AS osxcross
FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION}-alpine AS golangci-lint
FROM ghcr.io/google/addlicense:${ADDLICENSE_VERSION} AS addlicense
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine3.22 AS base
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine AS base
COPY --from=xx / /
RUN apk add --no-cache \
clang \
@@ -83,7 +83,7 @@ RUN --mount=type=bind,target=. \
--mount=type=cache,target=/go/pkg/mod \
--mount=type=bind,from=osxcross,src=/osxsdk,target=/xx-sdk \
xx-go --wrap && \
if [ "$(xx-info os)" == "darwin" ]; then export CGO_ENABLED=1; export BUILD_TAGS=fsnotify,$BUILD_TAGS; fi && \
if [ "$(xx-info os)" == "darwin" ]; then export CGO_ENABLED=1; fi && \
make build GO_BUILDTAGS="$BUILD_TAGS" DESTDIR=/out && \
xx-verify --static /out/docker-compose

View File

@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
PKG := github.com/docker/compose/v5
PKG := github.com/docker/compose/v2
VERSION ?= $(shell git describe --match 'v[0-9]*' --dirty='.m' --always --tags)
GO_LDFLAGS ?= -w -X ${PKG}/internal.Version=${VERSION}
@@ -62,11 +62,11 @@ build:
.PHONY: binary
binary:
BUILD_TAGS="$(GO_BUILDTAGS)" $(BUILDX_CMD) bake binary
$(BUILDX_CMD) bake binary
.PHONY: binary-with-coverage
binary-with-coverage:
BUILD_TAGS="$(GO_BUILDTAGS)" $(BUILDX_CMD) bake binary-with-coverage
$(BUILDX_CMD) bake binary-with-coverage
.PHONY: install
install: binary

View File

@@ -1,18 +1,17 @@
# Table of Contents
- [Docker Compose](#docker-compose)
- [Docker Compose v2](#docker-compose-v2)
- [Where to get Docker Compose](#where-to-get-docker-compose)
+ [Windows and macOS](#windows-and-macos)
+ [Linux](#linux)
- [Quick Start](#quick-start)
- [Contributing](#contributing)
- [Legacy](#legacy)
# Docker Compose
# Docker Compose v2
[![GitHub release](https://img.shields.io/github/v/release/docker/compose.svg?style=flat-square)](https://github.com/docker/compose/releases/latest)
[![PkgGoDev](https://img.shields.io/badge/go.dev-docs-007d9c?style=flat-square&logo=go&logoColor=white)](https://pkg.go.dev/github.com/docker/compose/v5)
[![PkgGoDev](https://img.shields.io/badge/go.dev-docs-007d9c?style=flat-square&logo=go&logoColor=white)](https://pkg.go.dev/github.com/docker/compose/v2)
[![Build Status](https://img.shields.io/github/actions/workflow/status/docker/compose/ci.yml?label=ci&logo=github&style=flat-square)](https://github.com/docker/compose/actions?query=workflow%3Aci)
[![Go Report Card](https://goreportcard.com/badge/github.com/docker/compose/v5?style=flat-square)](https://goreportcard.com/report/github.com/docker/compose/v5)
[![Go Report Card](https://goreportcard.com/badge/github.com/docker/compose/v2?style=flat-square)](https://goreportcard.com/report/github.com/docker/compose/v2)
[![Codecov](https://codecov.io/gh/docker/compose/branch/main/graph/badge.svg?token=HP3K4Y4ctu)](https://codecov.io/gh/docker/compose)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/docker/compose/badge)](https://api.securityscorecards.dev/projects/github.com/docker/compose)
![Docker Compose](logo.png?raw=true "Docker Compose Logo")
@@ -25,7 +24,7 @@ Once you have a Compose file, you can create and start your application with a
single command: `docker compose up`.
> **Note**: About Docker Swarm
> Docker Swarm used to rely on the legacy compose file format but did not adopt the compose specification
> Docker Swarm used to rely on the legacy compose file format but did not adopted the compose specification
> so is missing some of the recent enhancements in the compose syntax. After
> [acquisition by Mirantis](https://www.mirantis.com/software/swarm/) swarm isn't maintained by Docker Inc, and
> as such some Docker Compose features aren't accessible to swarm users.

View File

@@ -26,15 +26,14 @@ import (
dockercli "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
commands "github.com/docker/compose/v2/cmd/compose"
"github.com/docker/compose/v2/internal/tracing"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
commands "github.com/docker/compose/v5/cmd/compose"
"github.com/docker/compose/v5/internal/tracing"
)
// Setup should be called as part of the command's PersistentPreRunE

View File

@@ -20,10 +20,9 @@ import (
"reflect"
"testing"
commands "github.com/docker/compose/v2/cmd/compose"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
commands "github.com/docker/compose/v5/cmd/compose"
)
func TestGetFlags(t *testing.T) {

View File

@@ -21,7 +21,7 @@ import (
"os"
"strings"
"github.com/docker/compose/v5/cmd/compose"
"github.com/docker/compose/v2/cmd/compose"
)
func getCompletionCommands() []string {

View File

@@ -1,4 +1,5 @@
/*
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.
@@ -15,11 +16,12 @@ package compose
import (
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
)
// alphaCommand groups all experimental subcommands
func alphaCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func alphaCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
cmd := &cobra.Command{
Short: "Experimental commands",
Use: "alpha [COMMAND]",
@@ -29,9 +31,9 @@ func alphaCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Back
},
}
cmd.AddCommand(
vizCommand(p, dockerCli, backendOptions),
publishCommand(p, dockerCli, backendOptions),
generateCommand(p, dockerCli, backendOptions),
vizCommand(p, dockerCli, backend),
publishCommand(p, dockerCli, backend),
generateCommand(p, backend),
)
return cmd
}

View File

@@ -20,10 +20,8 @@ import (
"context"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
)
type attachOpts struct {
@@ -37,7 +35,7 @@ type attachOpts struct {
proxy bool
}
func attachCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func attachCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := attachOpts{
composeOptions: &composeOptions{
ProjectOptions: p,
@@ -52,7 +50,7 @@ func attachCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Bac
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
return runAttach(ctx, dockerCli, backendOptions, opts)
return runAttach(ctx, dockerCli, backend, opts)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
@@ -65,7 +63,7 @@ func attachCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Bac
return runCmd
}
func runAttach(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts attachOpts) error {
func runAttach(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts attachOpts) error {
projectName, err := opts.toProjectName(ctx, dockerCli)
if err != nil {
return err
@@ -78,9 +76,5 @@ func runAttach(ctx context.Context, dockerCli command.Cli, backendOptions *Backe
NoStdin: opts.noStdin,
Proxy: opts.proxy,
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
return backend.Attach(ctx, projectName, attachOpts)
}

View File

@@ -28,9 +28,8 @@ import (
"github.com/docker/go-units"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/cmd/formatter"
"github.com/docker/compose/v5/pkg/bridge"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v2/cmd/formatter"
"github.com/docker/compose/v2/pkg/bridge"
)
func bridgeCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
@@ -63,12 +62,7 @@ func convertCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
}
func runConvert(ctx context.Context, dockerCli command.Cli, p *ProjectOptions, opts bridge.ConvertOptions) error {
backend, err := compose.NewComposeService(dockerCli)
if err != nil {
return err
}
project, _, err := p.ToProject(ctx, dockerCli, backend, nil)
project, _, err := p.ToProject(ctx, dockerCli, nil)
if err != nil {
return err
}

View File

@@ -26,11 +26,10 @@ import (
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli/command"
cliopts "github.com/docker/cli/opts"
ui "github.com/docker/compose/v2/pkg/progress"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/cmd/display"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v2/pkg/api"
)
type buildOptions struct {
@@ -67,8 +66,8 @@ func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions,
builderName = os.Getenv("BUILDX_BUILDER")
}
uiMode := display.Mode
if uiMode == display.ModeJSON {
uiMode := ui.Mode
if uiMode == ui.ModeJSON {
uiMode = "rawjson"
}
@@ -91,7 +90,7 @@ func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions,
}, nil
}
func buildCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := buildOptions{
ProjectOptions: p,
}
@@ -100,7 +99,7 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Back
Short: "Build or rebuild services",
PreRunE: Adapt(func(ctx context.Context, args []string) error {
if opts.quiet {
display.Mode = display.ModeQuiet
ui.Mode = ui.ModeQuiet
devnull, err := os.Open(os.DevNull)
if err != nil {
return err
@@ -116,7 +115,7 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Back
if cmd.Flags().Changed("progress") && opts.ssh == "" {
fmt.Fprint(os.Stderr, "--progress is a global compose flag, better use `docker compose --progress xx build ...\n")
}
return runBuild(ctx, dockerCli, backendOptions, opts, args)
return runBuild(ctx, dockerCli, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
@@ -149,17 +148,9 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Back
return cmd
}
func runBuild(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts buildOptions, services []string) error {
if opts.print {
backendOptions.Add(compose.WithEventProcessor(display.Quiet()))
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts buildOptions, services []string) error {
opts.All = true // do not drop resources as build may involve some dependencies by additional_contexts
project, _, err := opts.ToProject(ctx, dockerCli, backend, nil, cli.WithoutEnvironmentResolution)
project, _, err := opts.ToProject(ctx, dockerCli, nil, cli.WithResolvedPaths(true), cli.WithoutEnvironmentResolution)
if err != nil {
return err
}

View File

@@ -21,10 +21,8 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
)
type commitOptions struct {
@@ -41,7 +39,7 @@ type commitOptions struct {
index int
}
func commitCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func commitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
options := commitOptions{
ProjectOptions: p,
}
@@ -58,7 +56,7 @@ func commitCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Bac
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
return runCommit(ctx, dockerCli, backendOptions, options)
return runCommit(ctx, dockerCli, backend, options)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
@@ -75,16 +73,12 @@ func commitCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Bac
return cmd
}
func runCommit(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, options commitOptions) error {
func runCommit(ctx context.Context, dockerCli command.Cli, backend api.Compose, options commitOptions) error {
projectName, err := options.toProjectName(ctx, dockerCli)
if err != nil {
return err
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
return backend.Commit(ctx, projectName, api.CommitOptions{
Service: options.service,
Reference: options.reference,

View File

@@ -21,10 +21,8 @@ import (
"strings"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
)
// validArgsFn defines a completion func to be returned to fetch completion options
@@ -39,12 +37,7 @@ func noCompletion() validArgsFn {
func completeServiceNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
p.Offline = true
backend, err := compose.NewComposeService(dockerCli)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
project, _, err := p.ToProject(cmd.Context(), dockerCli, backend, nil)
project, _, err := p.ToProject(cmd.Context(), dockerCli, nil)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
@@ -59,13 +52,8 @@ func completeServiceNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn
}
}
func completeProjectNames(dockerCli command.Cli, backendOptions *BackendOptions) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
func completeProjectNames(backend api.Compose) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
list, err := backend.List(cmd.Context(), api.ListOptions{
All: true,
})
@@ -85,12 +73,7 @@ func completeProjectNames(dockerCli command.Cli, backendOptions *BackendOptions)
func completeProfileNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
p.Offline = true
backend, err := compose.NewComposeService(dockerCli)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
project, _, err := p.ToProject(cmd.Context(), dockerCli, backend, nil)
project, _, err := p.ToProject(cmd.Context(), dockerCli, nil)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}

View File

@@ -32,7 +32,6 @@ import (
"github.com/compose-spec/compose-go/v2/cli"
"github.com/compose-spec/compose-go/v2/dotenv"
"github.com/compose-spec/compose-go/v2/loader"
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"
@@ -40,18 +39,16 @@ import (
"github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/pkg/kvfile"
"github.com/docker/compose/v2/cmd/formatter"
"github.com/docker/compose/v2/internal/tracing"
"github.com/docker/compose/v2/pkg/api"
ui "github.com/docker/compose/v2/pkg/progress"
"github.com/docker/compose/v2/pkg/remote"
"github.com/docker/compose/v2/pkg/utils"
"github.com/morikuni/aec"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/docker/compose/v5/cmd/display"
"github.com/docker/compose/v5/cmd/formatter"
"github.com/docker/compose/v5/internal/tracing"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v5/pkg/remote"
"github.com/docker/compose/v5/pkg/utils"
)
const (
@@ -60,7 +57,7 @@ const (
// ComposeProjectName define the project name to be used, instead of guessing from parent directory
ComposeProjectName = "COMPOSE_PROJECT_NAME"
// ComposeCompatibility try to mimic compose v1 as much as possible
ComposeCompatibility = api.ComposeCompatibility
ComposeCompatibility = "COMPOSE_COMPATIBILITY"
// ComposeRemoveOrphans remove "orphaned" containers, i.e. containers tagged for current project but not declared as service
ComposeRemoveOrphans = "COMPOSE_REMOVE_ORPHANS"
// ComposeIgnoreOrphans ignore "orphaned" containers
@@ -86,16 +83,10 @@ func rawEnv(r io.Reader, filename string, vars map[string]string, lookup func(ke
return nil
}
var stdioToStdout bool
func init() {
// compose evaluates env file values for interpolation
// `raw` format allows to load env_file with the same parser used by docker run --env-file
dotenv.RegisterFormat("raw", rawEnv)
if v, ok := os.LookupEnv("COMPOSE_STATUS_STDOUT"); ok {
stdioToStdout, _ = strconv.ParseBool(v)
}
}
// Command defines a compose CLI command as a func with args
@@ -124,7 +115,7 @@ func AdaptCmd(fn CobraCommand) func(cmd *cobra.Command, args []string) error {
StatusCode: 130,
}
}
if display.Mode == display.ModeJSON {
if ui.Mode == ui.ModeJSON {
err = makeJSONError(err)
}
return err
@@ -139,17 +130,16 @@ func Adapt(fn Command) func(cmd *cobra.Command, args []string) error {
}
type ProjectOptions struct {
ProjectName string
Profiles []string
ConfigPaths []string
WorkDir string
ProjectDir string
EnvFiles []string
Compatibility bool
Progress string
Offline bool
All bool
insecureRegistries []string
ProjectName string
Profiles []string
ConfigPaths []string
WorkDir string
ProjectDir string
EnvFiles []string
Compatibility bool
Progress string
Offline bool
All bool
}
// ProjectFunc does stuff within a types.Project
@@ -167,13 +157,13 @@ func (o *ProjectOptions) WithProject(fn ProjectFunc, dockerCli command.Cli) func
// WithServices creates a cobra run command from a ProjectFunc based on configured project options and selected services
func (o *ProjectOptions) WithServices(dockerCli command.Cli, fn ProjectServicesFunc) func(cmd *cobra.Command, args []string) error {
return Adapt(func(ctx context.Context, services []string) error {
backend, err := compose.NewComposeService(dockerCli)
if err != nil {
return err
return Adapt(func(ctx context.Context, args []string) error {
options := []cli.ProjectOptionsFn{
cli.WithResolvedPaths(true),
cli.WithoutEnvironmentResolution,
}
project, metrics, err := o.ToProject(ctx, dockerCli, backend, services, cli.WithoutEnvironmentResolution)
project, metrics, err := o.ToProject(ctx, dockerCli, args, options...)
if err != nil {
return err
}
@@ -185,7 +175,7 @@ func (o *ProjectOptions) WithServices(dockerCli command.Cli, fn ProjectServicesF
return err
}
return fn(ctx, project, services)
return fn(ctx, project, args)
})
}
@@ -225,8 +215,6 @@ func (o *ProjectOptions) addProjectFlags(f *pflag.FlagSet) {
f.StringArrayVar(&o.Profiles, "profile", []string{}, "Specify a profile to enable")
f.StringVarP(&o.ProjectName, "project-name", "p", "", "Project name")
f.StringArrayVarP(&o.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
f.StringArrayVar(&o.insecureRegistries, "insecure-registry", []string{}, "Use insecure registry to pull Compose OCI artifacts. Doesn't apply to images")
_ = f.MarkHidden("insecure-registry")
f.StringArrayVar(&o.EnvFiles, "env-file", defaultStringArrayVar(ComposeEnvFiles), "Specify an alternate environment file")
f.StringVar(&o.ProjectDir, "project-directory", "", "Specify an alternate working directory\n(default: the path of the, first specified, Compose file)")
f.StringVar(&o.WorkDir, "workdir", "", "DEPRECATED! USE --project-directory INSTEAD.\nSpecify an alternate working directory\n(default: the path of the, first specified, Compose file)")
@@ -247,12 +235,7 @@ func (o *ProjectOptions) projectOrName(ctx context.Context, dockerCli command.Cl
name := o.ProjectName
var project *types.Project
if len(o.ConfigPaths) > 0 || o.ProjectName == "" {
backend, err := compose.NewComposeService(dockerCli)
if err != nil {
return nil, "", err
}
p, _, err := o.ToProject(ctx, dockerCli, backend, services, cli.WithDiscardEnvFile, cli.WithoutEnvironmentResolution)
p, _, err := o.ToProject(ctx, dockerCli, services, cli.WithDiscardEnvFile, cli.WithoutEnvironmentResolution)
if err != nil {
envProjectName := os.Getenv(ComposeProjectName)
if envProjectName != "" {
@@ -276,12 +259,7 @@ func (o *ProjectOptions) toProjectName(ctx context.Context, dockerCli command.Cl
return envProjectName, nil
}
backend, err := compose.NewComposeService(dockerCli)
if err != nil {
return "", err
}
project, _, err := o.ToProject(ctx, dockerCli, backend, nil)
project, _, err := o.ToProject(ctx, dockerCli, nil)
if err != nil {
return "", err
}
@@ -306,14 +284,19 @@ func (o *ProjectOptions) ToModel(ctx context.Context, dockerCli command.Cli, ser
return options.LoadModel(ctx)
}
// ToProject loads a Compose project using the LoadProject API.
// Accepts optional cli.ProjectOptionsFn to control loader behavior.
func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, backend api.Compose, services []string, po ...cli.ProjectOptionsFn) (*types.Project, tracing.Metrics, error) {
func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, tracing.Metrics, error) { //nolint:gocyclo
var metrics tracing.Metrics
remotes := o.remoteLoaders(dockerCli)
for _, r := range remotes {
po = append(po, cli.WithResourceLoader(r))
}
// Setup metrics listener to collect project data
metricsListener := func(event string, metadata map[string]any) {
options, err := o.toProjectOptions(po...)
if err != nil {
return nil, metrics, err
}
options.WithListeners(func(event string, metadata map[string]any) {
switch event {
case "extends":
metrics.CountExtends++
@@ -334,31 +317,50 @@ func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, b
}
}
}
})
if o.Compatibility || utils.StringToBool(options.Environment[ComposeCompatibility]) {
api.Separator = "_"
}
loadOpts := api.ProjectLoadOptions{
ProjectName: o.ProjectName,
ConfigPaths: o.ConfigPaths,
WorkingDir: o.ProjectDir,
EnvFiles: o.EnvFiles,
Profiles: o.Profiles,
Services: services,
Offline: o.Offline,
All: o.All,
Compatibility: o.Compatibility,
ProjectOptionsFns: po,
LoadListeners: []api.LoadListener{metricsListener},
OCI: api.OCIOptions{
InsecureRegistries: o.insecureRegistries,
},
}
project, err := backend.LoadProject(ctx, loadOpts)
project, err := options.LoadProject(ctx)
if err != nil {
return nil, metrics, err
}
return project, metrics, nil
if project.Name == "" {
return nil, metrics, errors.New("project name can't be empty. Use `--project-name` to set a valid name")
}
project, err = project.WithServicesEnabled(services...)
if err != nil {
return nil, metrics, err
}
for name, s := range project.Services {
s.CustomLabels = map[string]string{
api.ProjectLabel: project.Name,
api.ServiceLabel: name,
api.VersionLabel: api.ComposeVersion,
api.WorkingDirLabel: project.WorkingDir,
api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","),
api.OneoffLabel: "False", // default, will be overridden by `run` command
}
if len(o.EnvFiles) != 0 {
s.CustomLabels[api.EnvironmentFileLabel] = strings.Join(o.EnvFiles, ",")
}
project.Services[name] = s
}
project, err = project.WithSelectedServices(services)
if err != nil {
return nil, tracing.Metrics{}, err
}
if !o.All {
project = project.WithoutUnnecessaryResources()
}
return project, metrics, err
}
func (o *ProjectOptions) remoteLoaders(dockerCli command.Cli) []loader.ResourceLoader {
@@ -366,7 +368,7 @@ func (o *ProjectOptions) remoteLoaders(dockerCli command.Cli) []loader.ResourceL
return nil
}
git := remote.NewGitRemoteLoader(dockerCli, o.Offline)
oci := remote.NewOCIRemoteLoader(dockerCli, o.Offline, api.OCIOptions{})
oci := remote.NewOCIRemoteLoader(dockerCli, o.Offline)
return []loader.ResourceLoader{git, oci}
}
@@ -395,8 +397,8 @@ func (o *ProjectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.Proj
// if none was selected, get default compose.yaml file from current dir or parent folder
cli.WithDefaultConfigPath,
// .. and then, a project directory != PWD maybe has been set so let's load .env file
cli.WithEnvFiles(o.EnvFiles...), //nolint:gocritic // intentionally applying cli.WithEnvFiles twice.
cli.WithDotEnv, //nolint:gocritic // intentionally applying cli.WithDotEnv twice.
cli.WithEnvFiles(o.EnvFiles...),
cli.WithDotEnv,
// eventually COMPOSE_PROFILES should have been set
cli.WithDefaultProfiles(o.Profiles...),
cli.WithName(o.ProjectName),
@@ -413,16 +415,8 @@ func RunningAsStandalone() bool {
return len(os.Args) < 2 || os.Args[1] != metadata.MetadataSubcommandName && os.Args[1] != PluginName
}
type BackendOptions struct {
Options []compose.Option
}
func (o *BackendOptions) Add(option compose.Option) {
o.Options = append(o.Options, option)
}
// RootCommand returns the compose command with its child commands
func RootCommand(dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command { //nolint:gocyclo
func RootCommand(dockerCli command.Cli, backend api.Compose) *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
@@ -462,7 +456,9 @@ func RootCommand(dockerCli command.Cli, backendOptions *BackendOptions) *cobra.C
}
},
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
parent := cmd.Root()
if parent != nil {
parentPrerun := parent.PersistentPreRunE
if parentPrerun != nil {
@@ -477,7 +473,7 @@ func RootCommand(dockerCli command.Cli, backendOptions *BackendOptions) *cobra.C
logrus.SetLevel(logrus.TraceLevel)
}
err := setEnvWithDotEnv(opts, dockerCli)
err := setEnvWithDotEnv(opts)
if err != nil {
return err
}
@@ -494,54 +490,40 @@ func RootCommand(dockerCli command.Cli, backendOptions *BackendOptions) *cobra.C
formatter.SetANSIMode(dockerCli, ansi)
if noColor, ok := os.LookupEnv("NO_COLOR"); ok && noColor != "" {
display.NoColor()
ui.NoColor()
formatter.SetANSIMode(dockerCli, formatter.Never)
}
switch ansi {
case "never":
display.Mode = display.ModePlain
ui.Mode = ui.ModePlain
case "always":
display.Mode = display.ModeTTY
ui.Mode = ui.ModeTTY
}
detached, _ := cmd.Flags().GetBool("detach")
var ep api.EventProcessor
switch opts.Progress {
case "", display.ModeAuto:
switch {
case ansi == "never":
display.Mode = display.ModePlain
ep = display.Plain(dockerCli.Err())
case dockerCli.Out().IsTerminal():
ep = display.Full(dockerCli.Err(), stdinfo(dockerCli), detached)
default:
ep = display.Plain(dockerCli.Err())
case "", ui.ModeAuto:
if ansi == "never" {
ui.Mode = ui.ModePlain
}
case display.ModeTTY:
case ui.ModeTTY:
if ansi == "never" {
return fmt.Errorf("can't use --progress tty while ANSI support is disabled")
}
display.Mode = display.ModeTTY
ep = display.Full(dockerCli.Err(), stdinfo(dockerCli), detached)
case display.ModePlain:
ui.Mode = ui.ModeTTY
case ui.ModePlain:
if ansi == "always" {
return fmt.Errorf("can't use --progress plain while ANSI support is forced")
}
display.Mode = display.ModePlain
ep = display.Plain(dockerCli.Err())
case display.ModeQuiet, "none":
display.Mode = display.ModeQuiet
ep = display.Quiet()
case display.ModeJSON:
display.Mode = display.ModeJSON
ui.Mode = ui.ModePlain
case ui.ModeQuiet, "none":
ui.Mode = ui.ModeQuiet
case ui.ModeJSON:
ui.Mode = ui.ModeJSON
logrus.SetFormatter(&logrus.JSONFormatter{})
ep = display.JSON(dockerCli.Err())
default:
return fmt.Errorf("unsupported --progress value %q", opts.Progress)
}
backendOptions.Add(compose.WithEventProcessor(ep))
// (4) options validation / normalization
if opts.WorkDir != "" {
@@ -552,15 +534,12 @@ func RootCommand(dockerCli command.Cli, backendOptions *BackendOptions) *cobra.C
fmt.Fprint(os.Stderr, aec.Apply("option '--workdir' is DEPRECATED at root level! Please use '--project-directory' instead.\n", aec.RedF))
}
for i, file := range opts.EnvFiles {
file = composepaths.ExpandUser(file)
if !filepath.IsAbs(file) {
file, err := filepath.Abs(file)
if err != nil {
return err
}
opts.EnvFiles[i] = file
} else {
opts.EnvFiles[i] = file
}
}
@@ -581,61 +560,64 @@ func RootCommand(dockerCli command.Cli, backendOptions *BackendOptions) *cobra.C
}
if parallel > 0 {
logrus.Debugf("Limiting max concurrency to %d jobs", parallel)
backendOptions.Add(compose.WithMaxConcurrency(parallel))
backend.MaxConcurrency(parallel)
}
// dry run detection
if dryRun {
backendOptions.Add(compose.WithDryRun)
ctx, err = backend.DryRunMode(ctx, dryRun)
if err != nil {
return err
}
cmd.SetContext(ctx)
return nil
},
}
c.AddCommand(
upCommand(&opts, dockerCli, backendOptions),
downCommand(&opts, dockerCli, backendOptions),
startCommand(&opts, dockerCli, backendOptions),
restartCommand(&opts, dockerCli, backendOptions),
stopCommand(&opts, dockerCli, backendOptions),
psCommand(&opts, dockerCli, backendOptions),
listCommand(dockerCli, backendOptions),
logsCommand(&opts, dockerCli, backendOptions),
upCommand(&opts, dockerCli, backend),
downCommand(&opts, dockerCli, backend),
startCommand(&opts, dockerCli, backend),
restartCommand(&opts, dockerCli, backend),
stopCommand(&opts, dockerCli, backend),
psCommand(&opts, dockerCli, backend),
listCommand(dockerCli, backend),
logsCommand(&opts, dockerCli, backend),
configCommand(&opts, dockerCli),
killCommand(&opts, dockerCli, backendOptions),
runCommand(&opts, dockerCli, backendOptions),
removeCommand(&opts, dockerCli, backendOptions),
execCommand(&opts, dockerCli, backendOptions),
attachCommand(&opts, dockerCli, backendOptions),
exportCommand(&opts, dockerCli, backendOptions),
commitCommand(&opts, dockerCli, backendOptions),
pauseCommand(&opts, dockerCli, backendOptions),
unpauseCommand(&opts, dockerCli, backendOptions),
topCommand(&opts, dockerCli, backendOptions),
eventsCommand(&opts, dockerCli, backendOptions),
portCommand(&opts, dockerCli, backendOptions),
imagesCommand(&opts, dockerCli, backendOptions),
killCommand(&opts, dockerCli, backend),
runCommand(&opts, dockerCli, backend),
removeCommand(&opts, dockerCli, backend),
execCommand(&opts, dockerCli, backend),
attachCommand(&opts, dockerCli, backend),
exportCommand(&opts, dockerCli, backend),
commitCommand(&opts, dockerCli, backend),
pauseCommand(&opts, dockerCli, backend),
unpauseCommand(&opts, dockerCli, backend),
topCommand(&opts, dockerCli, backend),
eventsCommand(&opts, dockerCli, backend),
portCommand(&opts, dockerCli, backend),
imagesCommand(&opts, dockerCli, backend),
versionCommand(dockerCli),
buildCommand(&opts, dockerCli, backendOptions),
pushCommand(&opts, dockerCli, backendOptions),
pullCommand(&opts, dockerCli, backendOptions),
createCommand(&opts, dockerCli, backendOptions),
copyCommand(&opts, dockerCli, backendOptions),
waitCommand(&opts, dockerCli, backendOptions),
scaleCommand(&opts, dockerCli, backendOptions),
buildCommand(&opts, dockerCli, backend),
pushCommand(&opts, dockerCli, backend),
pullCommand(&opts, dockerCli, backend),
createCommand(&opts, dockerCli, backend),
copyCommand(&opts, dockerCli, backend),
waitCommand(&opts, dockerCli, backend),
scaleCommand(&opts, dockerCli, backend),
statsCommand(&opts, dockerCli),
watchCommand(&opts, dockerCli, backendOptions),
publishCommand(&opts, dockerCli, backendOptions),
alphaCommand(&opts, dockerCli, backendOptions),
watchCommand(&opts, dockerCli, backend),
publishCommand(&opts, dockerCli, backend),
alphaCommand(&opts, dockerCli, backend),
bridgeCommand(&opts, dockerCli),
volumesCommand(&opts, dockerCli, backendOptions),
volumesCommand(&opts, dockerCli, backend),
)
c.Flags().SetInterspersed(false)
opts.addProjectFlags(c.Flags())
c.RegisterFlagCompletionFunc( //nolint:errcheck
"project-name",
completeProjectNames(dockerCli, backendOptions),
completeProjectNames(backend),
)
c.RegisterFlagCompletionFunc( //nolint:errcheck
"project-directory",
@@ -670,28 +652,7 @@ func RootCommand(dockerCli command.Cli, backendOptions *BackendOptions) *cobra.C
return c
}
func stdinfo(dockerCli command.Cli) io.Writer {
if stdioToStdout {
return dockerCli.Out()
}
return dockerCli.Err()
}
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
}
}
}
func setEnvWithDotEnv(opts ProjectOptions) error {
options, err := cli.NewProjectOptions(opts.ConfigPaths,
cli.WithWorkingDirectory(opts.ProjectDir),
cli.WithOsEnv,
@@ -699,26 +660,26 @@ func setEnvWithDotEnv(opts ProjectOptions, dockerCli command.Cli) error {
cli.WithDotEnv,
)
if err != nil {
return err
return nil
}
envFromFile, err := dotenv.GetEnvFromFile(composegoutils.GetAsEqualsMap(os.Environ()), options.EnvFiles)
if err != nil {
return err
return nil
}
for k, v := range envFromFile {
if _, ok := os.LookupEnv(k); !ok && strings.HasPrefix(k, "COMPOSE_") {
if err := os.Setenv(k, v); err != nil {
return err
if err = os.Setenv(k, v); err != nil {
return nil
}
}
}
return nil
return err
}
var printerModes = []string{
display.ModeAuto,
display.ModeTTY,
display.ModePlain,
display.ModeJSON,
display.ModeQuiet,
ui.ModeAuto,
ui.ModeTTY,
ui.ModePlain,
ui.ModeJSON,
ui.ModeQuiet,
}

View File

@@ -1,76 +0,0 @@
/*
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

@@ -30,12 +30,12 @@ import (
"github.com/compose-spec/compose-go/v2/template"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/cmd/formatter"
"github.com/spf13/cobra"
"go.yaml.in/yaml/v4"
"gopkg.in/yaml.v3"
"github.com/docker/compose/v5/cmd/formatter"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/compose"
)
type configOptions struct {
@@ -61,19 +61,19 @@ type configOptions struct {
lockImageDigests bool
}
func (o *configOptions) ToProject(ctx context.Context, dockerCli command.Cli, backend api.Compose, services []string) (*types.Project, error) {
project, _, err := o.ProjectOptions.ToProject(ctx, dockerCli, backend, services, o.toProjectOptionsFns()...)
func (o *configOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
po = append(po, o.ToProjectOptions()...)
project, _, err := o.ProjectOptions.ToProject(ctx, dockerCli, services, po...)
return project, err
}
func (o *configOptions) ToModel(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (map[string]any, error) {
po = append(po, o.toProjectOptionsFns()...)
po = append(po, o.ToProjectOptions()...)
return o.ProjectOptions.ToModel(ctx, dockerCli, services, po...)
}
// toProjectOptionsFns converts config options to cli.ProjectOptionsFn
func (o *configOptions) toProjectOptionsFns() []cli.ProjectOptionsFn {
fns := []cli.ProjectOptionsFn{
func (o *configOptions) ToProjectOptions() []cli.ProjectOptionsFn {
return []cli.ProjectOptionsFn{
cli.WithInterpolation(!o.noInterpolate),
cli.WithResolvedPaths(!o.noResolvePath),
cli.WithNormalization(!o.noNormalize),
@@ -81,10 +81,6 @@ func (o *configOptions) toProjectOptionsFns() []cli.ProjectOptionsFn {
cli.WithDefaultProfiles(o.Profiles...),
cli.WithDiscardEnvFile,
}
if o.noResolveEnv {
fns = append(fns, cli.WithoutEnvironmentResolution)
}
return fns
}
func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
@@ -201,12 +197,7 @@ func runConfig(ctx context.Context, dockerCli command.Cli, opts configOptions, s
}
func runConfigInterpolate(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) ([]byte, error) {
backend, err := compose.NewComposeService(dockerCli)
if err != nil {
return nil, err
}
project, err := opts.ToProject(ctx, dockerCli, backend, services)
project, err := opts.ToProject(ctx, dockerCli, services)
if err != nil {
return nil, err
}
@@ -333,16 +324,17 @@ func resolveImageDigests(ctx context.Context, dockerCli command.Cli, model map[s
func formatModel(model map[string]any, format string) (content []byte, err error) {
switch format {
case "json":
return json.MarshalIndent(model, "", " ")
content, err = json.MarshalIndent(model, "", " ")
case "yaml":
buf := bytes.NewBuffer([]byte{})
encoder := yaml.NewEncoder(buf)
encoder.SetIndent(2)
err = encoder.Encode(model)
return buf.Bytes(), err
content = buf.Bytes()
default:
return nil, fmt.Errorf("unsupported format %q", format)
}
return
}
func runServices(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
@@ -362,12 +354,7 @@ func runServices(ctx context.Context, dockerCli command.Cli, opts configOptions)
return nil
}
backend, err := compose.NewComposeService(dockerCli)
if err != nil {
return err
}
project, _, err := opts.ProjectOptions.ToProject(ctx, dockerCli, backend, nil, cli.WithoutEnvironmentResolution)
project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
if err != nil {
return err
}
@@ -380,12 +367,7 @@ func runServices(ctx context.Context, dockerCli command.Cli, opts configOptions)
}
func runVolumes(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
backend, err := compose.NewComposeService(dockerCli)
if err != nil {
return err
}
project, _, err := opts.ProjectOptions.ToProject(ctx, dockerCli, backend, nil, cli.WithoutEnvironmentResolution)
project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
if err != nil {
return err
}
@@ -396,12 +378,7 @@ func runVolumes(ctx context.Context, dockerCli command.Cli, opts configOptions)
}
func runNetworks(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
backend, err := compose.NewComposeService(dockerCli)
if err != nil {
return err
}
project, _, err := opts.ProjectOptions.ToProject(ctx, dockerCli, backend, nil, cli.WithoutEnvironmentResolution)
project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
if err != nil {
return err
}
@@ -412,12 +389,7 @@ func runNetworks(ctx context.Context, dockerCli command.Cli, opts configOptions)
}
func runModels(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
backend, err := compose.NewComposeService(dockerCli)
if err != nil {
return err
}
project, _, err := opts.ProjectOptions.ToProject(ctx, dockerCli, backend, nil, cli.WithoutEnvironmentResolution)
project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
if err != nil {
return err
}
@@ -434,13 +406,7 @@ func runHash(ctx context.Context, dockerCli command.Cli, opts configOptions) err
if opts.hash != "*" {
services = append(services, strings.Split(opts.hash, ",")...)
}
backend, err := compose.NewComposeService(dockerCli)
if err != nil {
return err
}
project, _, err := opts.ProjectOptions.ToProject(ctx, dockerCli, backend, nil, cli.WithoutEnvironmentResolution)
project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
if err != nil {
return err
}
@@ -475,13 +441,7 @@ func runHash(ctx context.Context, dockerCli command.Cli, opts configOptions) err
func runProfiles(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error {
set := map[string]struct{}{}
backend, err := compose.NewComposeService(dockerCli)
if err != nil {
return err
}
project, err := opts.ToProject(ctx, dockerCli, backend, services)
project, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution)
if err != nil {
return err
}
@@ -502,12 +462,7 @@ func runProfiles(ctx context.Context, dockerCli command.Cli, opts configOptions,
}
func runConfigImages(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error {
backend, err := compose.NewComposeService(dockerCli)
if err != nil {
return err
}
project, err := opts.ToProject(ctx, dockerCli, backend, services)
project, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution)
if err != nil {
return err
}
@@ -544,12 +499,7 @@ func runVariables(ctx context.Context, dockerCli command.Cli, opts configOptions
}
func runEnvironment(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error {
backend, err := compose.NewComposeService(dockerCli)
if err != nil {
return err
}
project, err := opts.ToProject(ctx, dockerCli, backend, services)
project, err := opts.ToProject(ctx, dockerCli, services)
if err != nil {
return err
}

View File

@@ -24,8 +24,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v2/pkg/api"
)
type copyOptions struct {
@@ -39,7 +38,7 @@ type copyOptions struct {
copyUIDGID bool
}
func copyCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func copyCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := copyOptions{
ProjectOptions: p,
}
@@ -60,7 +59,7 @@ func copyCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
RunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
opts.source = args[0]
opts.destination = args[1]
return runCopy(ctx, dockerCli, backendOptions, opts)
return runCopy(ctx, dockerCli, backend, opts)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
@@ -74,16 +73,12 @@ func copyCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
return copyCmd
}
func runCopy(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts copyOptions) error {
func runCopy(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts copyOptions) error {
name, err := opts.toProjectName(ctx, dockerCli)
if err != nil {
return err
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
return backend.Copy(ctx, name, api.CopyOptions{
Source: opts.source,
Destination: opts.destination,

View File

@@ -30,8 +30,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v2/pkg/api"
)
type createOptions struct {
@@ -52,7 +51,7 @@ type createOptions struct {
AssumeYes bool
}
func createCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := createOptions{}
buildOpts := buildOptions{
ProjectOptions: p,
@@ -71,7 +70,7 @@ func createCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Bac
return nil
}),
RunE: p.WithServices(dockerCli, func(ctx context.Context, project *types.Project, services []string) error {
return runCreate(ctx, dockerCli, backendOptions, opts, buildOpts, project, services)
return runCreate(ctx, dockerCli, backend, opts, buildOpts, project, services)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
@@ -96,7 +95,7 @@ func createCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Bac
return cmd
}
func runCreate(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, createOpts createOptions, buildOpts buildOptions, project *types.Project, services []string) error {
func runCreate(ctx context.Context, _ command.Cli, backend api.Compose, createOpts createOptions, buildOpts buildOptions, project *types.Project, services []string) error {
if err := createOpts.Apply(project); err != nil {
return err
}
@@ -110,14 +109,6 @@ func runCreate(ctx context.Context, dockerCli command.Cli, backendOptions *Backe
build = &bo
}
if createOpts.AssumeYes {
backendOptions.Options = append(backendOptions.Options, compose.WithPrompt(compose.AlwaysOkPrompt()))
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
return backend.Create(ctx, project, api.CreateOptions{
Build: build,
Services: services,
@@ -128,6 +119,7 @@ func runCreate(ctx context.Context, dockerCli command.Cli, backendOptions *Backe
Inherit: !createOpts.noInherit,
Timeout: createOpts.GetTimeout(),
QuietPull: createOpts.quietPull,
AssumeYes: createOpts.AssumeYes,
})
}
@@ -198,11 +190,12 @@ func (opts createOptions) Apply(project *types.Project) error {
func applyScaleOpts(project *types.Project, opts []string) error {
for _, scale := range opts {
name, val, ok := strings.Cut(scale, "=")
if !ok || val == "" {
split := strings.Split(scale, "=")
if len(split) != 2 {
return fmt.Errorf("invalid --scale option %q. Should be SERVICE=NUM", scale)
}
replicas, err := strconv.Atoi(val)
name := split[0]
replicas, err := strconv.Atoi(split[1])
if err != nil {
return err
}

174
cmd/compose/create_test.go Normal file
View File

@@ -0,0 +1,174 @@
/*
Copyright 2023 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 (
"context"
"fmt"
"testing"
"github.com/compose-spec/compose-go/v2/types"
"github.com/davecgh/go-spew/spew"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/mocks"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
)
func TestRunCreate(t *testing.T) {
ctrl, ctx := gomock.WithContext(context.Background(), t)
backend := mocks.NewMockService(ctrl)
backend.EXPECT().Create(
gomock.Eq(ctx),
pullPolicy(""),
deepEqual(defaultCreateOptions(true)),
)
createOpts := createOptions{}
buildOpts := buildOptions{
ProjectOptions: &ProjectOptions{},
}
project := sampleProject()
err := runCreate(ctx, nil, backend, createOpts, buildOpts, project, nil)
require.NoError(t, err)
}
func TestRunCreate_Build(t *testing.T) {
ctrl, ctx := gomock.WithContext(context.Background(), t)
backend := mocks.NewMockService(ctrl)
backend.EXPECT().Create(
gomock.Eq(ctx),
pullPolicy("build"),
deepEqual(defaultCreateOptions(true)),
)
createOpts := createOptions{
Build: true,
}
buildOpts := buildOptions{
ProjectOptions: &ProjectOptions{},
}
project := sampleProject()
err := runCreate(ctx, nil, backend, createOpts, buildOpts, project, nil)
require.NoError(t, err)
}
func TestRunCreate_NoBuild(t *testing.T) {
ctrl, ctx := gomock.WithContext(context.Background(), t)
backend := mocks.NewMockService(ctrl)
backend.EXPECT().Create(
gomock.Eq(ctx),
pullPolicy(""),
deepEqual(defaultCreateOptions(false)),
)
createOpts := createOptions{
noBuild: true,
}
buildOpts := buildOptions{}
project := sampleProject()
err := runCreate(ctx, nil, backend, createOpts, buildOpts, project, nil)
require.NoError(t, err)
}
func sampleProject() *types.Project {
return &types.Project{
Name: "test",
Services: types.Services{
"svc": {
Name: "svc",
Build: &types.BuildConfig{
Context: ".",
},
},
},
}
}
func defaultCreateOptions(includeBuild bool) api.CreateOptions {
var build *api.BuildOptions
if includeBuild {
bo := defaultBuildOptions()
build = &bo
}
return api.CreateOptions{
Build: build,
Services: nil,
RemoveOrphans: false,
IgnoreOrphans: false,
Recreate: "diverged",
RecreateDependencies: "diverged",
Inherit: true,
Timeout: nil,
QuietPull: false,
}
}
func defaultBuildOptions() api.BuildOptions {
return api.BuildOptions{
Args: make(types.MappingWithEquals),
Progress: "auto",
}
}
// deepEqual returns a nice diff on failure vs gomock.Eq when used
// on structs.
func deepEqual(x interface{}) gomock.Matcher {
return gomock.GotFormatterAdapter(
gomock.GotFormatterFunc(func(got interface{}) string {
return cmp.Diff(x, got)
}),
gomock.Eq(x),
)
}
func spewAdapter(m gomock.Matcher) gomock.Matcher {
return gomock.GotFormatterAdapter(
gomock.GotFormatterFunc(func(got interface{}) string {
return spew.Sdump(got)
}),
m,
)
}
type withPullPolicy struct {
policy string
}
func pullPolicy(policy string) gomock.Matcher {
return spewAdapter(withPullPolicy{policy: policy})
}
func (w withPullPolicy) Matches(x interface{}) bool {
proj, ok := x.(*types.Project)
if !ok || proj == nil || len(proj.Services) == 0 {
return false
}
for _, svc := range proj.Services {
if svc.PullPolicy != w.policy {
return false
}
}
return true
}
func (w withPullPolicy) String() string {
return fmt.Sprintf("has pull policy %q for all services", w.policy)
}

View File

@@ -23,13 +23,12 @@ import (
"time"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/utils"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v5/pkg/utils"
"github.com/docker/compose/v2/pkg/api"
)
type downOptions struct {
@@ -41,7 +40,7 @@ type downOptions struct {
images string
}
func downCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func downCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := downOptions{
ProjectOptions: p,
}
@@ -58,9 +57,9 @@ func downCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
return runDown(ctx, dockerCli, backendOptions, opts, args)
return runDown(ctx, dockerCli, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
ValidArgsFunction: noCompletion(),
}
flags := downCmd.Flags()
removeOrphans := utils.StringToBool(os.Getenv(ComposeRemoveOrphans))
@@ -78,7 +77,7 @@ func downCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
return downCmd
}
func runDown(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts downOptions, services []string) error {
func runDown(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts downOptions, services []string) error {
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
if err != nil {
return err
@@ -89,10 +88,6 @@ func runDown(ctx context.Context, dockerCli command.Cli, backendOptions *Backend
timeoutValue := time.Duration(opts.timeout) * time.Second
timeout = &timeoutValue
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
return backend.Down(ctx, name, api.DownOptions{
RemoveOrphans: opts.removeOrphans,
Project: project,

View File

@@ -22,10 +22,9 @@ import (
"fmt"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/spf13/cobra"
)
type eventsOpts struct {
@@ -35,7 +34,7 @@ type eventsOpts struct {
until string
}
func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := eventsOpts{
composeOptions: &composeOptions{
ProjectOptions: p,
@@ -45,7 +44,7 @@ func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Bac
Use: "events [OPTIONS] [SERVICE...]",
Short: "Receive real time events from containers",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runEvents(ctx, dockerCli, backendOptions, opts, args)
return runEvents(ctx, dockerCli, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
@@ -56,23 +55,19 @@ func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Bac
return cmd
}
func runEvents(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts eventsOpts, services []string) error {
func runEvents(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts eventsOpts, services []string) error {
name, err := opts.toProjectName(ctx, dockerCli)
if err != nil {
return err
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
return backend.Events(ctx, name, api.EventsOptions{
Services: services,
Since: opts.since,
Until: opts.until,
Consumer: func(event api.Event) error {
if opts.json {
marshal, err := json.Marshal(map[string]any{
marshal, err := json.Marshal(map[string]interface{}{
"time": event.Timestamp,
"type": "container",
"service": event.Service,

View File

@@ -25,12 +25,11 @@ import (
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/compose"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
)
type execOpts struct {
@@ -49,7 +48,7 @@ type execOpts struct {
interactive bool
}
func execCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := execOpts{
composeOptions: &composeOptions{
ProjectOptions: p,
@@ -65,7 +64,7 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
err := runExec(ctx, dockerCli, backendOptions, opts)
err := runExec(ctx, dockerCli, backend, opts)
if err != nil {
logrus.Debugf("%v", err)
var cliError cli.StatusError
@@ -101,7 +100,7 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
return runCmd
}
func runExec(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts execOpts) error {
func runExec(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts execOpts) error {
projectName, err := opts.toProjectName(ctx, dockerCli)
if err != nil {
return err
@@ -127,10 +126,6 @@ func runExec(ctx context.Context, dockerCli command.Cli, backendOptions *Backend
Interactive: opts.interactive,
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
exitCode, err := backend.Exec(ctx, projectName, execOpts)
if exitCode != 0 {
errMsg := fmt.Sprintf("exit status %d", exitCode)

View File

@@ -22,8 +22,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v2/pkg/api"
)
type exportOptions struct {
@@ -34,7 +33,7 @@ type exportOptions struct {
index int
}
func exportCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func exportCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
options := exportOptions{
ProjectOptions: p,
}
@@ -47,7 +46,7 @@ func exportCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Bac
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
return runExport(ctx, dockerCli, backendOptions, options)
return runExport(ctx, dockerCli, backend, options)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
@@ -59,7 +58,7 @@ func exportCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Bac
return cmd
}
func runExport(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, options exportOptions) error {
func runExport(ctx context.Context, dockerCli command.Cli, backend api.Compose, options exportOptions) error {
projectName, err := options.toProjectName(ctx, dockerCli)
if err != nil {
return err
@@ -71,9 +70,5 @@ func runExport(ctx context.Context, dockerCli command.Cli, backendOptions *Backe
Output: options.output,
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
return backend.Export(ctx, projectName, exportOptions)
}

View File

@@ -21,11 +21,8 @@ import (
"fmt"
"os"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
)
type generateOptions struct {
@@ -33,7 +30,7 @@ type generateOptions struct {
Format string
}
func generateCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func generateCommand(p *ProjectOptions, backend api.Compose) *cobra.Command {
opts := generateOptions{
ProjectOptions: p,
}
@@ -45,7 +42,7 @@ func generateCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *B
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
return runGenerate(ctx, dockerCli, backendOptions, opts, args)
return runGenerate(ctx, backend, opts, args)
}),
}
@@ -55,16 +52,11 @@ func generateCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *B
return cmd
}
func runGenerate(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts generateOptions, containers []string) error {
func runGenerate(ctx context.Context, backend api.Compose, opts generateOptions, containers []string) error {
_, _ = fmt.Fprintln(os.Stderr, "generate command is EXPERIMENTAL")
if len(containers) == 0 {
return fmt.Errorf("at least one container must be specified")
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
project, err := backend.Generate(ctx, api.GenerateOptions{
Containers: containers,
ProjectName: opts.ProjectName,
@@ -72,7 +64,6 @@ func runGenerate(ctx context.Context, dockerCli command.Cli, backendOptions *Bac
if err != nil {
return err
}
var content []byte
switch opts.Format {
case "json":

View File

@@ -31,9 +31,8 @@ import (
"github.com/docker/go-units"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/cmd/formatter"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v2/cmd/formatter"
"github.com/docker/compose/v2/pkg/api"
)
type imageOptions struct {
@@ -42,7 +41,7 @@ type imageOptions struct {
Format string
}
func imagesCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func imagesCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := imageOptions{
ProjectOptions: p,
}
@@ -50,7 +49,7 @@ func imagesCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Bac
Use: "images [OPTIONS] [SERVICE...]",
Short: "List images used by the created containers",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runImages(ctx, dockerCli, backendOptions, opts, args)
return runImages(ctx, dockerCli, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
@@ -59,16 +58,12 @@ func imagesCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Bac
return imgCmd
}
func runImages(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts imageOptions, services []string) error {
func runImages(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts imageOptions, services []string) error {
projectName, err := opts.toProjectName(ctx, dockerCli)
if err != nil {
return err
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
images, err := backend.Images(ctx, projectName, api.ImagesOptions{
Services: services,
})
@@ -95,19 +90,21 @@ func runImages(ctx context.Context, dockerCli command.Cli, backendOptions *Backe
if opts.Format == "json" {
type img struct {
ID string `json:"ID"`
ContainerName string `json:"ContainerName"`
Repository string `json:"Repository"`
Tag string `json:"Tag"`
Platform string `json:"Platform"`
Size int64 `json:"Size"`
Created *time.Time `json:"Created,omitempty"`
LastTagTime time.Time `json:"LastTagTime,omitzero"`
ID string `json:"ID"`
ContainerName string `json:"ContainerName"`
Repository string `json:"Repository"`
Tag string `json:"Tag"`
Platform string `json:"Platform"`
Size int64 `json:"Size"`
LastTagTime time.Time `json:"LastTagTime"`
}
// Convert map to slice
var imageList []img
for ctr, i := range images {
lastTagTime := i.LastTagTime
if lastTagTime.IsZero() {
lastTagTime = i.Created
}
imageList = append(imageList, img{
ContainerName: ctr,
ID: i.ID,
@@ -115,7 +112,6 @@ func runImages(ctx context.Context, dockerCli command.Cli, backendOptions *Backe
Tag: i.Tag,
Platform: platforms.Format(i.Platform),
Size: i.Size,
Created: i.Created,
LastTagTime: lastTagTime,
})
}
@@ -141,10 +137,7 @@ func runImages(ctx context.Context, dockerCli command.Cli, backendOptions *Backe
if tag == "" {
tag = "<none>"
}
created := "N/A"
if img.Created != nil {
created = units.HumanDuration(time.Now().UTC().Sub(*img.Created)) + " ago"
}
created := units.HumanDuration(time.Now().UTC().Sub(img.LastTagTime)) + " ago"
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
container, repo, tag, platforms.Format(img.Platform), id, size, created)
}

View File

@@ -18,16 +18,13 @@ package compose
import (
"context"
"errors"
"fmt"
"os"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v5/pkg/utils"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/utils"
)
type killOptions struct {
@@ -36,7 +33,7 @@ type killOptions struct {
signal string
}
func killCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func killCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := killOptions{
ProjectOptions: p,
}
@@ -44,7 +41,7 @@ func killCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
Use: "kill [OPTIONS] [SERVICE...]",
Short: "Force stop service containers",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runKill(ctx, dockerCli, backendOptions, opts, args)
return runKill(ctx, dockerCli, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
@@ -57,25 +54,16 @@ func killCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
return cmd
}
func runKill(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts killOptions, services []string) error {
func runKill(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts killOptions, services []string) error {
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
if err != nil {
return err
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
err = backend.Kill(ctx, name, api.KillOptions{
return backend.Kill(ctx, name, api.KillOptions{
RemoveOrphans: opts.removeOrphans,
Project: project,
Services: services,
Signal: opts.signal,
})
if errors.Is(err, api.ErrNoResources) {
_, _ = fmt.Fprintln(stdinfo(dockerCli), "No container to kill")
return nil
}
return err
}

View File

@@ -23,12 +23,12 @@ import (
"strings"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/cmd/formatter"
"github.com/docker/cli/opts"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/cmd/formatter"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v2/pkg/api"
)
type lsOptions struct {
@@ -38,13 +38,13 @@ type lsOptions struct {
Filter opts.FilterOpt
}
func listCommand(dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func listCommand(dockerCli command.Cli, backend api.Compose) *cobra.Command {
lsOpts := lsOptions{Filter: opts.NewFilterOpt()}
lsCmd := &cobra.Command{
Use: "ls [OPTIONS]",
Short: "List running compose projects",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runList(ctx, dockerCli, backendOptions, lsOpts)
return runList(ctx, dockerCli, backend, lsOpts)
}),
Args: cobra.NoArgs,
ValidArgsFunction: noCompletion(),
@@ -61,17 +61,13 @@ var acceptedListFilters = map[string]bool{
"name": true,
}
func runList(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, lsOpts lsOptions) error {
func runList(ctx context.Context, dockerCli command.Cli, backend api.Compose, lsOpts lsOptions) error {
filters := lsOpts.Filter.Value()
err := filters.Validate(acceptedListFilters)
if err != nil {
return err
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
stackList, err := backend.List(ctx, api.ListOptions{All: lsOpts.All})
if err != nil {
return err

View File

@@ -23,9 +23,8 @@ import (
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/cmd/formatter"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v2/cmd/formatter"
"github.com/docker/compose/v2/pkg/api"
)
type logsOptions struct {
@@ -41,7 +40,7 @@ type logsOptions struct {
timestamps bool
}
func logsCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func logsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := logsOptions{
ProjectOptions: p,
}
@@ -49,7 +48,7 @@ func logsCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
Use: "logs [OPTIONS] [SERVICE...]",
Short: "View output from containers",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runLogs(ctx, dockerCli, backendOptions, opts, args)
return runLogs(ctx, dockerCli, backend, opts, args)
}),
PreRunE: func(cmd *cobra.Command, args []string) error {
if opts.index > 0 && len(args) != 1 {
@@ -71,7 +70,7 @@ func logsCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
return logsCmd
}
func runLogs(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts logsOptions, services []string) error {
func runLogs(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts logsOptions, services []string) error {
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
if err != nil {
return err
@@ -86,10 +85,6 @@ func runLogs(ctx context.Context, dockerCli command.Cli, backendOptions *Backend
}
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
consumer := formatter.NewLogConsumer(ctx, dockerCli.Out(), dockerCli.Err(), !opts.noColor, !opts.noPrefix, false)
return backend.Logs(ctx, name, consumer, api.LogOptions{
Project: project,
@@ -102,32 +97,3 @@ func runLogs(ctx context.Context, dockerCli command.Cli, backendOptions *Backend
Timestamps: opts.timestamps,
})
}
var _ api.LogConsumer = &logConsumer{}
type logConsumer struct {
events api.EventProcessor
}
func (l logConsumer) Log(containerName, message string) {
l.events.On(api.Resource{
ID: containerName,
Text: message,
})
}
func (l logConsumer) Err(containerName, message string) {
l.events.On(api.Resource{
ID: containerName,
Status: api.Error,
Text: message,
})
}
func (l logConsumer) Status(containerName, message string) {
l.events.On(api.Resource{
ID: containerName,
Status: api.Error,
Text: message,
})
}

View File

@@ -30,10 +30,9 @@ import (
"github.com/compose-spec/compose-go/v2/template"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v5/cmd/display"
"github.com/docker/compose/v5/cmd/prompt"
"github.com/docker/compose/v5/internal/tracing"
"github.com/docker/compose/v2/cmd/prompt"
"github.com/docker/compose/v2/internal/tracing"
ui "github.com/docker/compose/v2/pkg/progress"
)
func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
@@ -213,9 +212,9 @@ func extractEnvCLIDefined(cmdEnvs []string) map[string]string {
// Parse command-line environment variables
cmdEnvMap := make(map[string]string)
for _, env := range cmdEnvs {
key, val, ok := strings.Cut(env, "=")
if ok {
cmdEnvMap[key] = val
parts := strings.SplitN(env, "=", 2)
if len(parts) == 2 {
cmdEnvMap[parts[0]] = parts[1]
}
}
return cmdEnvMap
@@ -248,7 +247,7 @@ func displayInterpolationVariables(writer io.Writer, varsInfo []varInfo) {
func displayLocationRemoteStack(dockerCli command.Cli, project *types.Project, options buildOptions) {
mainComposeFile := options.ProjectOptions.ConfigPaths[0] //nolint:staticcheck
if display.Mode != display.ModeQuiet && display.Mode != display.ModeJSON {
if ui.Mode != ui.ModeQuiet && ui.Mode != ui.ModeJSON {
_, _ = fmt.Fprintf(dockerCli.Out(), "Your compose stack %q is stored in %q\n", mainComposeFile, project.WorkingDir)
}
}

View File

@@ -18,6 +18,7 @@ package compose
import (
"bytes"
"context"
"fmt"
"io"
"os"
@@ -27,10 +28,9 @@ import (
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli/streams"
"github.com/docker/compose/v2/pkg/mocks"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"github.com/docker/compose/v5/pkg/mocks"
)
func TestApplyPlatforms_InferFromRuntime(t *testing.T) {
@@ -213,7 +213,10 @@ func TestDisplayInterpolationVariables(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
tmpDir := t.TempDir()
// Create a temporary directory for the test
tmpDir, err := os.MkdirTemp("", "compose-test")
require.NoError(t, err)
defer func() { _ = os.RemoveAll(tmpDir) }()
// Create a temporary compose file
composeContent := `
@@ -227,7 +230,8 @@ services:
- UNSET_VAR # optional without default
`
composePath := filepath.Join(tmpDir, "docker-compose.yml")
require.NoError(t, os.WriteFile(composePath, []byte(composeContent), 0o644))
err = os.WriteFile(composePath, []byte(composeContent), 0o644)
require.NoError(t, err)
buf := new(bytes.Buffer)
cli := mocks.NewMockCli(ctrl)
@@ -239,11 +243,16 @@ services:
}
// Set up the context with necessary environment variables
t.Setenv("TEST_VAR", "test-value")
t.Setenv("API_KEY", "123456")
ctx := context.Background()
_ = os.Setenv("TEST_VAR", "test-value")
_ = os.Setenv("API_KEY", "123456")
defer func() {
_ = os.Unsetenv("TEST_VAR")
_ = os.Unsetenv("API_KEY")
}()
// Extract variables from the model
info, noVariables, err := extractInterpolationVariablesFromModel(t.Context(), cli, projectOptions, []string{})
info, noVariables, err := extractInterpolationVariablesFromModel(ctx, cli, projectOptions, []string{})
require.NoError(t, err)
require.False(t, noVariables)

View File

@@ -22,15 +22,14 @@ import (
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v2/pkg/api"
)
type pauseOptions struct {
*ProjectOptions
}
func pauseCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func pauseCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := pauseOptions{
ProjectOptions: p,
}
@@ -38,23 +37,19 @@ func pauseCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Back
Use: "pause [SERVICE...]",
Short: "Pause services",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runPause(ctx, dockerCli, backendOptions, opts, args)
return runPause(ctx, dockerCli, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
return cmd
}
func runPause(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts pauseOptions, services []string) error {
func runPause(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts pauseOptions, services []string) error {
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
if err != nil {
return err
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
return backend.Pause(ctx, name, api.PauseOptions{
Services: services,
Project: project,
@@ -65,7 +60,7 @@ type unpauseOptions struct {
*ProjectOptions
}
func unpauseCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func unpauseCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := unpauseOptions{
ProjectOptions: p,
}
@@ -73,23 +68,19 @@ func unpauseCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Ba
Use: "unpause [SERVICE...]",
Short: "Unpause services",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runUnPause(ctx, dockerCli, backendOptions, opts, args)
return runUnPause(ctx, dockerCli, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
return cmd
}
func runUnPause(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts unpauseOptions, services []string) error {
func runUnPause(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts unpauseOptions, services []string) error {
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
if err != nil {
return err
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
return backend.UnPause(ctx, name, api.PauseOptions{
Services: services,
Project: project,

View File

@@ -25,8 +25,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v2/pkg/api"
)
type portOptions struct {
@@ -36,7 +35,7 @@ type portOptions struct {
index int
}
func portCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func portCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := portOptions{
ProjectOptions: p,
}
@@ -54,7 +53,7 @@ func portCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
return runPort(ctx, dockerCli, backendOptions, opts, args[0])
return runPort(ctx, dockerCli, backend, opts, args[0])
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
@@ -63,16 +62,11 @@ func portCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
return cmd
}
func runPort(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts portOptions, service string) error {
func runPort(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts portOptions, service string) error {
projectName, err := opts.toProjectName(ctx, dockerCli)
if err != nil {
return err
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
ip, port, err := backend.Port(ctx, projectName, service, opts.port, api.PortOptions{
Protocol: opts.protocol,
Index: opts.index,

View File

@@ -24,14 +24,13 @@ import (
"sort"
"strings"
"github.com/docker/compose/v2/cmd/formatter"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/cli/cli/command"
cliformatter "github.com/docker/cli/cli/command/formatter"
cliflags "github.com/docker/cli/cli/flags"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/cmd/formatter"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
)
type psOptions struct {
@@ -50,22 +49,22 @@ func (p *psOptions) parseFilter() error {
if p.Filter == "" {
return nil
}
key, val, ok := strings.Cut(p.Filter, "=")
if !ok {
parts := strings.SplitN(p.Filter, "=", 2)
if len(parts) != 2 {
return errors.New("arguments to --filter should be in form KEY=VAL")
}
switch key {
switch parts[0] {
case "status":
p.Status = append(p.Status, val)
return nil
p.Status = append(p.Status, parts[1])
case "source":
return api.ErrNotImplemented
default:
return fmt.Errorf("unknown filter %s", key)
return fmt.Errorf("unknown filter %s", parts[0])
}
return nil
}
func psCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func psCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := psOptions{
ProjectOptions: p,
}
@@ -76,7 +75,7 @@ func psCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backend
return opts.parseFilter()
},
RunE: Adapt(func(ctx context.Context, args []string) error {
return runPs(ctx, dockerCli, backendOptions, args, opts)
return runPs(ctx, dockerCli, backend, args, opts)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
@@ -92,7 +91,7 @@ func psCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backend
return psCmd
}
func runPs(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, services []string, opts psOptions) error { //nolint:gocyclo
func runPs(ctx context.Context, dockerCli command.Cli, backend api.Compose, services []string, opts psOptions) error {
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
if err != nil {
return err
@@ -112,10 +111,6 @@ func runPs(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOp
}
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
containers, err := backend.Ps(ctx, name, api.PsOptions{
Project: project,
All: opts.All || len(opts.Status) != 0,

87
cmd/compose/ps_test.go Normal file
View File

@@ -0,0 +1,87 @@
/*
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 (
"context"
"os"
"path/filepath"
"testing"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/streams"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
)
func TestPsTable(t *testing.T) {
ctx := context.Background()
dir := t.TempDir()
out := filepath.Join(dir, "output.txt")
f, err := os.Create(out)
if err != nil {
t.Fatal("could not create output file")
}
defer func() { _ = f.Close() }()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
backend := mocks.NewMockService(ctrl)
backend.EXPECT().
Ps(gomock.Eq(ctx), gomock.Any(), gomock.Any()).
DoAndReturn(func(ctx context.Context, projectName string, options api.PsOptions) ([]api.ContainerSummary, error) {
return []api.ContainerSummary{
{
ID: "abc123",
Name: "ABC",
Image: "foo/bar",
Publishers: api.PortPublishers{
{
TargetPort: 8080,
PublishedPort: 8080,
Protocol: "tcp",
},
{
TargetPort: 8443,
PublishedPort: 8443,
Protocol: "tcp",
},
},
},
}, nil
}).AnyTimes()
opts := psOptions{ProjectOptions: &ProjectOptions{ProjectName: "test"}}
stdout := streams.NewOut(f)
cli := mocks.NewMockCli(ctrl)
cli.EXPECT().Out().Return(stdout).AnyTimes()
cli.EXPECT().ConfigFile().Return(&configfile.ConfigFile{}).AnyTimes()
err = runPs(ctx, cli, backend, nil, opts)
require.NoError(t, err)
_, err = f.Seek(0, 0)
require.NoError(t, err)
output, err := os.ReadFile(out)
require.NoError(t, err)
assert.Contains(t, string(output), "8080/tcp, 8443/tcp")
}

View File

@@ -22,12 +22,10 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
)
type publishOptions struct {
@@ -37,10 +35,9 @@ type publishOptions struct {
withEnvironment bool
assumeYes bool
app bool
insecureRegistry bool
}
func publishCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := publishOptions{
ProjectOptions: p,
}
@@ -48,7 +45,7 @@ func publishCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Ba
Use: "publish [OPTIONS] REPOSITORY[:TAG]",
Short: "Publish compose application",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runPublish(ctx, dockerCli, backendOptions, opts, args[0])
return runPublish(ctx, dockerCli, backend, opts, args[0])
}),
Args: cli.ExactArgs(1),
}
@@ -58,7 +55,6 @@ func publishCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Ba
flags.BoolVar(&opts.withEnvironment, "with-env", false, "Include environment variables in the published OCI artifact")
flags.BoolVarP(&opts.assumeYes, "yes", "y", false, `Assume "yes" as answer to all prompts`)
flags.BoolVar(&opts.app, "app", false, "Published compose application (includes referenced images)")
flags.BoolVar(&opts.insecureRegistry, "insecure-registry", false, "Use insecure registry")
flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
// assumeYes was introduced by mistake as `--y`
if name == "y" {
@@ -67,23 +63,12 @@ func publishCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Ba
}
return pflag.NormalizedName(name)
})
// Should **only** be used for testing purpose, we don't want to promote use of insecure registries
_ = flags.MarkHidden("insecure-registry")
return cmd
}
func runPublish(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts publishOptions, repository string) error {
if opts.assumeYes {
backendOptions.Options = append(backendOptions.Options, compose.WithPrompt(compose.AlwaysOkPrompt()))
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
project, metrics, err := opts.ToProject(ctx, dockerCli, backend, nil)
func runPublish(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts publishOptions, repository string) error {
project, metrics, err := opts.ToProject(ctx, dockerCli, nil)
if err != nil {
return err
}
@@ -97,6 +82,6 @@ func runPublish(ctx context.Context, dockerCli command.Cli, backendOptions *Back
Application: opts.app,
OCIVersion: api.OCIVersion(opts.ociVersion),
WithEnvironment: opts.withEnvironment,
InsecureRegistry: opts.insecureRegistry,
AssumeYes: opts.assumeYes,
})
}

View File

@@ -27,8 +27,7 @@ import (
"github.com/morikuni/aec"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v2/pkg/api"
)
type pullOptions struct {
@@ -43,7 +42,7 @@ type pullOptions struct {
policy string
}
func pullCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func pullCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := pullOptions{
ProjectOptions: p,
}
@@ -60,7 +59,7 @@ func pullCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
return nil
},
RunE: Adapt(func(ctx context.Context, args []string) error {
return runPull(ctx, dockerCli, backendOptions, opts, args)
return runPull(ctx, dockerCli, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
@@ -98,13 +97,8 @@ func (opts pullOptions) apply(project *types.Project, services []string) (*types
return project, nil
}
func runPull(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts pullOptions, services []string) error {
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
project, _, err := opts.ToProject(ctx, dockerCli, backend, services, cli.WithoutEnvironmentResolution)
func runPull(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts pullOptions, services []string) error {
project, _, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution)
if err != nil {
return err
}

View File

@@ -23,8 +23,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v2/pkg/api"
)
type pushOptions struct {
@@ -35,7 +34,7 @@ type pushOptions struct {
Quiet bool
}
func pushCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func pushCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := pushOptions{
ProjectOptions: p,
}
@@ -43,7 +42,7 @@ func pushCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
Use: "push [OPTIONS] [SERVICE...]",
Short: "Push service images",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runPush(ctx, dockerCli, backendOptions, opts, args)
return runPush(ctx, dockerCli, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
@@ -54,13 +53,8 @@ func pushCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
return pushCmd
}
func runPush(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts pushOptions, services []string) error {
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
project, _, err := opts.ToProject(ctx, dockerCli, backend, services)
func runPush(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts pushOptions, services []string) error {
project, _, err := opts.ToProject(ctx, dockerCli, services)
if err != nil {
return err
}

View File

@@ -18,14 +18,10 @@ package compose
import (
"context"
"errors"
"fmt"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
)
type removeOptions struct {
@@ -35,7 +31,7 @@ type removeOptions struct {
volumes bool
}
func removeCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func removeCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := removeOptions{
ProjectOptions: p,
}
@@ -49,7 +45,7 @@ can override this with -v. To list all volumes, use "docker volume ls".
Any data which is not in a volume will be lost.`,
RunE: Adapt(func(ctx context.Context, args []string) error {
return runRemove(ctx, dockerCli, backendOptions, opts, args)
return runRemove(ctx, dockerCli, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
@@ -63,26 +59,17 @@ Any data which is not in a volume will be lost.`,
return cmd
}
func runRemove(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts removeOptions, services []string) error {
func runRemove(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts removeOptions, services []string) error {
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
if err != nil {
return err
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
err = backend.Remove(ctx, name, api.RemoveOptions{
return backend.Remove(ctx, name, api.RemoveOptions{
Services: services,
Force: opts.force,
Volumes: opts.volumes,
Project: project,
Stop: opts.stop,
})
if errors.Is(err, api.ErrNoResources) {
_, _ = fmt.Fprintln(stdinfo(dockerCli), "No stopped containers")
return nil
}
return err
}

View File

@@ -23,8 +23,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v2/pkg/api"
)
type restartOptions struct {
@@ -34,7 +33,7 @@ type restartOptions struct {
noDeps bool
}
func restartCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func restartCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := restartOptions{
ProjectOptions: p,
}
@@ -45,7 +44,7 @@ func restartCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Ba
opts.timeChanged = cmd.Flags().Changed("timeout")
},
RunE: Adapt(func(ctx context.Context, args []string) error {
return runRestart(ctx, dockerCli, backendOptions, opts, args)
return runRestart(ctx, dockerCli, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
@@ -56,7 +55,7 @@ func restartCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Ba
return restartCmd
}
func runRestart(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts restartOptions, services []string) error {
func runRestart(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts restartOptions, services []string) error {
project, name, err := opts.projectOrName(ctx, dockerCli)
if err != nil {
return err
@@ -75,10 +74,6 @@ func runRestart(ctx context.Context, dockerCli command.Cli, backendOptions *Back
timeout = &timeoutValue
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
return backend.Restart(ctx, name, api.RestartOptions{
Timeout: timeout,
Services: services,

View File

@@ -22,23 +22,23 @@ import (
"os"
"strings"
composecli "github.com/compose-spec/compose-go/v2/cli"
"github.com/compose-spec/compose-go/v2/dotenv"
"github.com/compose-spec/compose-go/v2/format"
xprogress "github.com/moby/buildkit/util/progress/progressui"
"github.com/sirupsen/logrus"
cgo "github.com/compose-spec/compose-go/v2/cli"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/mattn/go-shellwords"
xprogress "github.com/moby/buildkit/util/progress/progressui"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/docker/compose/v5/cmd/display"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v5/pkg/utils"
"github.com/docker/cli/cli"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/progress"
"github.com/docker/compose/v2/pkg/utils"
)
type runOptions struct {
@@ -142,7 +142,7 @@ func (options runOptions) getEnvironment(resolve func(string) (string, bool)) (t
return environment, nil
}
func runCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
options := runOptions{
composeOptions: &composeOptions{
ProjectOptions: p,
@@ -185,26 +185,25 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backen
}
} else if !cmd.Flags().Changed("no-TTY") && !cmd.Flags().Changed("interactive") && !dockerCli.In().IsTerminal() {
// while `docker run` requires explicit `-it` flags, Compose enables interactive mode and TTY by default
// but when compose is used from a script that has stdin piped from another command, we just can't
// but when compose is used from a scripr has stdin piped from another command, we just can't
// Here, we detect we run "by default" (user didn't passed explicit flags) and disable TTY allocation if
// we don't have an actual terminal to attach to for interactive mode
options.noTty = true
}
if options.quiet {
display.Mode = display.ModeQuiet
backendOptions.Add(compose.WithEventProcessor(display.Quiet()))
progress.Mode = progress.ModeQuiet
devnull, err := os.Open(os.DevNull)
if err != nil {
return err
}
os.Stdout = devnull
}
createOpts.pullChanged = cmd.Flags().Changed("pull")
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
project, _, err := p.ToProject(ctx, dockerCli, backend, []string{options.Service}, composecli.WithoutEnvironmentResolution)
project, _, err := p.ToProject(ctx, dockerCli, []string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithoutEnvironmentResolution)
if err != nil {
return err
}
@@ -284,11 +283,11 @@ func runRun(ctx context.Context, backend api.Compose, project *types.Project, op
labels := types.Labels{}
for _, s := range options.labels {
key, val, ok := strings.Cut(s, "=")
if !ok {
parts := strings.SplitN(s, "=", 2)
if len(parts) != 2 {
return fmt.Errorf("label must be set as KEY=VALUE")
}
labels[key] = val
labels[parts[0]] = parts[1]
}
var buildForRun *api.BuildOptions

View File

@@ -26,10 +26,8 @@ import (
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
)
type scaleOptions struct {
@@ -37,7 +35,7 @@ type scaleOptions struct {
noDeps bool
}
func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := scaleOptions{
ProjectOptions: p,
}
@@ -50,7 +48,7 @@ func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Back
if err != nil {
return err
}
return runScale(ctx, dockerCli, backendOptions, opts, serviceTuples)
return runScale(ctx, dockerCli, backend, opts, serviceTuples)
}),
ValidArgsFunction: completeScaleArgs(dockerCli, p),
}
@@ -60,14 +58,9 @@ func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Back
return scaleCmd
}
func runScale(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts scaleOptions, serviceReplicaTuples map[string]int) error {
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
func runScale(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts scaleOptions, serviceReplicaTuples map[string]int) error {
services := slices.Sorted(maps.Keys(serviceReplicaTuples))
project, _, err := opts.ToProject(ctx, dockerCli, backend, services)
project, _, err := opts.ToProject(ctx, dockerCli, services)
if err != nil {
return err
}

View File

@@ -18,22 +18,17 @@ package compose
import (
"context"
"time"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
)
type startOptions struct {
*ProjectOptions
wait bool
waitTimeout int
}
func startCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func startCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := startOptions{
ProjectOptions: p,
}
@@ -41,37 +36,22 @@ func startCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Back
Use: "start [SERVICE...]",
Short: "Start services",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runStart(ctx, dockerCli, backendOptions, opts, args)
return runStart(ctx, dockerCli, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
flags := startCmd.Flags()
flags.BoolVar(&opts.wait, "wait", false, "Wait for services to be running|healthy. Implies detached mode.")
flags.IntVar(&opts.waitTimeout, "wait-timeout", 0, "Maximum duration in seconds to wait for the project to be running|healthy")
return startCmd
}
func runStart(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts startOptions, services []string) error {
func runStart(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts startOptions, services []string) error {
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
if err != nil {
return err
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
var timeout time.Duration
if opts.waitTimeout > 0 {
timeout = time.Duration(opts.waitTimeout) * time.Second
}
return backend.Start(ctx, name, api.StartOptions{
AttachTo: services,
Project: project,
Services: services,
Wait: opts.wait,
WaitTimeout: timeout,
AttachTo: services,
Project: project,
Services: services,
})
}

View File

@@ -25,7 +25,7 @@ import (
"github.com/docker/docker/api/types/filters"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v2/pkg/api"
)
type statsOptions struct {

View File

@@ -23,8 +23,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v2/pkg/api"
)
type stopOptions struct {
@@ -33,7 +32,7 @@ type stopOptions struct {
timeout int
}
func stopCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func stopCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := stopOptions{
ProjectOptions: p,
}
@@ -44,7 +43,7 @@ func stopCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
opts.timeChanged = cmd.Flags().Changed("timeout")
},
RunE: Adapt(func(ctx context.Context, args []string) error {
return runStop(ctx, dockerCli, backendOptions, opts, args)
return runStop(ctx, dockerCli, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
@@ -54,7 +53,7 @@ func stopCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
return cmd
}
func runStop(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts stopOptions, services []string) error {
func runStop(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts stopOptions, services []string) error {
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
if err != nil {
return err
@@ -65,10 +64,6 @@ func runStop(ctx context.Context, dockerCli command.Cli, backendOptions *Backend
timeoutValue := time.Duration(opts.timeout) * time.Second
timeout = &timeoutValue
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
return backend.Stop(ctx, name, api.StopOptions{
Timeout: timeout,
Services: services,

View File

@@ -27,15 +27,14 @@ import (
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v2/pkg/api"
)
type topOptions struct {
*ProjectOptions
}
func topCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func topCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := topOptions{
ProjectOptions: p,
}
@@ -43,7 +42,7 @@ func topCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backen
Use: "top [SERVICES...]",
Short: "Display the running processes",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runTop(ctx, dockerCli, backendOptions, opts, args)
return runTop(ctx, dockerCli, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
@@ -55,16 +54,11 @@ type (
topEntries map[string]string
)
func runTop(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts topOptions, services []string) error {
func runTop(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts topOptions, services []string) error {
projectName, err := opts.toProjectName(ctx, dockerCli)
if err != nil {
return err
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
containers, err := backend.Top(ctx, projectName, services)
if err != nil {
return err

View File

@@ -21,10 +21,9 @@ import (
"strings"
"testing"
"github.com/docker/compose/v2/pkg/api"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/docker/compose/v5/pkg/api"
)
var topTestCases = []struct {
@@ -322,7 +321,7 @@ func TestRunTopCore(t *testing.T) {
func trim(s string) string {
var out bytes.Buffer
for line := range strings.SplitSeq(strings.TrimSpace(s), "\n") {
for _, line := range strings.Split(strings.TrimSpace(s), "\n") {
out.WriteString(strings.TrimSpace(line))
out.WriteRune('\n')
}

View File

@@ -31,11 +31,10 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/docker/compose/v5/cmd/display"
"github.com/docker/compose/v5/cmd/formatter"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v5/pkg/utils"
"github.com/docker/compose/v2/cmd/formatter"
"github.com/docker/compose/v2/pkg/api"
ui "github.com/docker/compose/v2/pkg/progress"
"github.com/docker/compose/v2/pkg/utils"
)
// composeOptions hold options common to `up` and `run` to run compose project
@@ -110,7 +109,7 @@ func (opts upOptions) OnExit() api.Cascade {
}
}
func upCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
up := upOptions{}
create := createOptions{}
build := buildOptions{ProjectOptions: p}
@@ -141,7 +140,7 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backend
return fmt.Errorf("no service selected")
}
return runUp(ctx, dockerCli, backendOptions, create, up, build, project, services)
return runUp(ctx, dockerCli, backend, create, up, build, project, services)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
@@ -188,9 +187,6 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backend
//nolint:gocyclo
func validateFlags(up *upOptions, create *createOptions) error {
if up.waitTimeout < 0 {
return fmt.Errorf("--wait-timeout must be a non-negative integer")
}
if up.exitCodeFrom != "" && !up.cascadeFail {
up.cascadeStop = true
}
@@ -232,7 +228,7 @@ func validateFlags(up *upOptions, create *createOptions) error {
func runUp(
ctx context.Context,
dockerCli command.Cli,
backendOptions *BackendOptions,
backend api.Compose,
createOptions createOptions,
upOptions upOptions,
buildOptions buildOptions,
@@ -266,7 +262,7 @@ func runUp(
if err != nil {
return err
}
bo.Services = project.ServiceNames()
bo.Services = services
bo.Deps = !upOptions.noDeps
build = &bo
}
@@ -281,15 +277,7 @@ func runUp(
Inherit: !createOptions.noInherit,
Timeout: createOptions.GetTimeout(),
QuietPull: createOptions.quietPull,
}
if createOptions.AssumeYes {
backendOptions.Options = append(backendOptions.Options, compose.WithPrompt(compose.AlwaysOkPrompt()))
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
AssumeYes: createOptions.AssumeYes,
}
if upOptions.noStart {
@@ -331,10 +319,7 @@ func runUp(
attach = attachSet.Elements()
}
var timeout time.Duration
if upOptions.waitTimeout > 0 {
timeout = time.Duration(upOptions.waitTimeout) * time.Second
}
timeout := time.Duration(upOptions.waitTimeout) * time.Second
return backend.Up(ctx, project, api.UpOptions{
Create: create,
Start: api.StartOptions{
@@ -347,7 +332,7 @@ func runUp(
WaitTimeout: timeout,
Watch: upOptions.watch,
Services: services,
NavigationMenu: upOptions.navigationMenu && display.Mode != "plain" && dockerCli.In().IsTerminal(),
NavigationMenu: upOptions.navigationMenu && ui.Mode != "plain" && dockerCli.In().IsTerminal(),
},
})
}

View File

@@ -21,8 +21,6 @@ import (
"github.com/compose-spec/compose-go/v2/types"
"gotest.tools/v3/assert"
"github.com/docker/compose/v5/pkg/api"
)
func TestApplyScaleOpt(t *testing.T) {
@@ -50,42 +48,3 @@ func TestApplyScaleOpt(t *testing.T) {
assert.Equal(t, *bar.Scale, 3)
assert.Equal(t, *bar.Deploy.Replicas, 3)
}
func TestUpOptions_OnExit(t *testing.T) {
tests := []struct {
name string
args upOptions
want api.Cascade
}{
{
name: "no cascade",
args: upOptions{},
want: api.CascadeIgnore,
},
{
name: "cascade stop",
args: upOptions{cascadeStop: true},
want: api.CascadeStop,
},
{
name: "cascade fail",
args: upOptions{cascadeFail: true},
want: api.CascadeFail,
},
{
name: "both set - stop takes precedence",
args: upOptions{
cascadeStop: true,
cascadeFail: true,
},
want: api.CascadeStop,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.args.OnExit()
assert.Equal(t, got, tt.want)
})
}
}

View File

@@ -21,10 +21,11 @@ import (
"strings"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/cmd/formatter"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/cmd/formatter"
"github.com/docker/compose/v5/internal"
"github.com/docker/compose/v2/internal"
)
type versionOptions struct {

View File

@@ -21,11 +21,10 @@ import (
"testing"
"github.com/docker/cli/cli/streams"
"github.com/docker/compose/v2/internal"
"github.com/docker/compose/v2/pkg/mocks"
"go.uber.org/mock/gomock"
"gotest.tools/v3/assert"
"github.com/docker/compose/v5/internal"
"github.com/docker/compose/v5/pkg/mocks"
)
func TestVersionCommand(t *testing.T) {

View File

@@ -23,10 +23,8 @@ import (
"strings"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
)
type vizOptions struct {
@@ -37,7 +35,7 @@ type vizOptions struct {
indentationStr string
}
func vizCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func vizCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := vizOptions{
ProjectOptions: p,
}
@@ -53,7 +51,7 @@ func vizCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backen
return err
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
return runViz(ctx, dockerCli, backendOptions, &opts)
return runViz(ctx, dockerCli, backend, &opts)
}),
}
@@ -65,15 +63,9 @@ func vizCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backen
return cmd
}
func runViz(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts *vizOptions) error {
func runViz(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts *vizOptions) error {
_, _ = fmt.Fprintln(os.Stderr, "viz command is EXPERIMENTAL")
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
project, _, err := opts.ToProject(ctx, dockerCli, backend, nil)
project, _, err := opts.ToProject(ctx, dockerCli, nil)
if err != nil {
return err
}

View File

@@ -24,10 +24,8 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/flags"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
)
type volumesOptions struct {
@@ -36,7 +34,7 @@ type volumesOptions struct {
Format string
}
func volumesCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func volumesCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
options := volumesOptions{
ProjectOptions: p,
}
@@ -45,7 +43,7 @@ func volumesCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Ba
Use: "volumes [OPTIONS] [SERVICE...]",
Short: "List volumes",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runVol(ctx, dockerCli, backendOptions, args, options)
return runVol(ctx, dockerCli, backend, args, options)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
@@ -56,7 +54,7 @@ func volumesCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Ba
return cmd
}
func runVol(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, services []string, options volumesOptions) error {
func runVol(ctx context.Context, dockerCli command.Cli, backend api.Compose, services []string, options volumesOptions) error {
project, name, err := options.projectOrName(ctx, dockerCli, services...)
if err != nil {
return err
@@ -71,10 +69,6 @@ func runVol(ctx context.Context, dockerCli command.Cli, backendOptions *BackendO
}
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
volumes, err := backend.Volumes(ctx, name, api.VolumesOptions{
Services: services,
})

View File

@@ -22,10 +22,8 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
)
type waitOptions struct {
@@ -36,7 +34,7 @@ type waitOptions struct {
downProject bool
}
func waitCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func waitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
opts := waitOptions{
ProjectOptions: p,
}
@@ -49,7 +47,7 @@ func waitCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
Args: cli.RequiresMinArgs(1),
RunE: Adapt(func(ctx context.Context, services []string) error {
opts.services = services
statusCode, err = runWait(ctx, dockerCli, backendOptions, &opts)
statusCode, err = runWait(ctx, dockerCli, backend, &opts)
return err
}),
PostRun: func(cmd *cobra.Command, args []string) {
@@ -62,16 +60,12 @@ func waitCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
return cmd
}
func runWait(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts *waitOptions) (int64, error) {
func runWait(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts *waitOptions) (int64, error) {
_, name, err := opts.projectOrName(ctx, dockerCli)
if err != nil {
return 0, err
}
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return 0, err
}
return backend.Wait(ctx, name, api.WaitOptions{
Services: opts.services,
DownProjectOnContainerExit: opts.downProject,

View File

@@ -21,14 +21,13 @@ import (
"fmt"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/compose/v2/cmd/formatter"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/internal/locker"
"github.com/docker/compose/v2/pkg/api"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/cmd/formatter"
"github.com/docker/compose/v5/internal/locker"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
)
type watchOptions struct {
@@ -37,7 +36,7 @@ type watchOptions struct {
noUp bool
}
func watchCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
func watchCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command {
watchOpts := watchOptions{
ProjectOptions: p,
}
@@ -54,7 +53,7 @@ func watchCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Back
if cmd.Parent().Name() == "alpha" {
logrus.Warn("watch command is now available as a top level command")
}
return runWatch(ctx, dockerCli, backendOptions, watchOpts, buildOpts, args)
return runWatch(ctx, dockerCli, backend, watchOpts, buildOpts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
@@ -65,13 +64,8 @@ func watchCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Back
return cmd
}
func runWatch(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, watchOpts watchOptions, buildOpts buildOptions, services []string) error {
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return err
}
project, _, err := watchOpts.ToProject(ctx, dockerCli, backend, services)
func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Compose, watchOpts watchOptions, buildOpts buildOptions, services []string) error {
project, _, err := watchOpts.ToProject(ctx, dockerCli, services)
if err != nil {
return err
}

View File

@@ -1,33 +0,0 @@
/*
Copyright 2024 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 display
// Mode define how progress should be rendered, either as ModePlain or ModeTTY
var Mode = ModeAuto
const (
// ModeAuto detect console capabilities
ModeAuto = "auto"
// ModeTTY use terminal capability for advanced rendering
ModeTTY = "tty"
// ModePlain dump raw events to output
ModePlain = "plain"
// ModeQuiet don't display events
ModeQuiet = "quiet"
// ModeJSON outputs a machine-readable JSON stream
ModeJSON = "json"
)

View File

@@ -1,664 +0,0 @@
/*
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 display
import (
"context"
"fmt"
"io"
"iter"
"slices"
"strings"
"sync"
"time"
"unicode/utf8"
"github.com/buger/goterm"
"github.com/docker/go-units"
"github.com/morikuni/aec"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/utils"
)
// 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, detached bool) api.EventProcessor {
return &ttyWriter{
out: out,
info: info,
tasks: map[string]*task{},
done: make(chan bool),
mtx: &sync.Mutex{},
detached: detached,
}
}
type ttyWriter struct {
out io.Writer
ids []string // tasks ids ordered as first event appeared
tasks map[string]*task
repeated bool
numLines int
done chan bool
mtx *sync.Mutex
dryRun bool // FIXME(ndeloof) (re)implement support for dry-run
operation string
ticker *time.Ticker
suspended bool
info io.Writer
detached bool
}
type task struct {
ID string
parent string // the resource this task receives updates from - other parents will be ignored
parents utils.Set[string] // all resources to depend on this task
startTime time.Time
endTime time.Time
text string
details string
status api.EventStatus
current int64
percent int
total int64
spinner *Spinner
}
func newTask(e api.Resource) task {
t := task{
ID: e.ID,
parents: utils.NewSet[string](),
startTime: time.Now(),
text: e.Text,
details: e.Details,
status: e.Status,
current: e.Current,
percent: e.Percent,
total: e.Total,
spinner: NewSpinner(),
}
if e.ParentID != "" {
t.parent = e.ParentID
t.parents.Add(e.ParentID)
}
if e.Status == api.Done || e.Status == api.Error {
t.stop()
}
return t
}
// update adjusts task state based on last received event
func (t *task) update(e api.Resource) {
if e.ParentID != "" {
t.parents.Add(e.ParentID)
// we may receive same event from distinct parents (typically: images sharing layers)
// to avoid status to flicker, only accept updates from our first declared parent
if t.parent != e.ParentID {
return
}
}
// update task based on received event
switch e.Status {
case api.Done, api.Error, api.Warning:
if t.status != e.Status {
t.stop()
}
case api.Working:
t.hasMore()
}
t.status = e.Status
t.text = e.Text
t.details = e.Details
// progress can only go up
if e.Total > t.total {
t.total = e.Total
}
if e.Current > t.current {
t.current = e.Current
}
if e.Percent > t.percent {
t.percent = e.Percent
}
}
func (t *task) stop() {
t.endTime = time.Now()
t.spinner.Stop()
}
func (t *task) hasMore() {
t.spinner.Restart()
}
func (t *task) Completed() bool {
switch t.status {
case api.Done, api.Error, api.Warning:
return true
default:
return false
}
}
func (w *ttyWriter) Start(ctx context.Context, operation string) {
w.ticker = time.NewTicker(100 * time.Millisecond)
w.operation = operation
go func() {
for {
select {
case <-ctx.Done():
// interrupted
w.ticker.Stop()
return
case <-w.done:
return
case <-w.ticker.C:
w.print()
}
}
}()
}
func (w *ttyWriter) Done(operation string, success bool) {
w.print()
w.mtx.Lock()
defer w.mtx.Unlock()
w.ticker.Stop()
w.operation = ""
w.done <- true
}
func (w *ttyWriter) On(events ...api.Resource) {
w.mtx.Lock()
defer w.mtx.Unlock()
for _, e := range events {
if e.ID == "Compose" {
_, _ = fmt.Fprintln(w.info, ErrorColor(e.Details))
continue
}
if w.operation != "start" && (e.Text == api.StatusStarted || e.Text == api.StatusStarting) && !w.detached {
// skip those events to avoid mix with container logs
continue
}
w.event(e)
}
}
func (w *ttyWriter) event(e api.Resource) {
// Suspend print while a build is in progress, to avoid collision with buildkit Display
if e.Text == api.StatusBuilding {
w.ticker.Stop()
w.suspended = true
} else if w.suspended {
w.ticker.Reset(100 * time.Millisecond)
w.suspended = false
}
if last, ok := w.tasks[e.ID]; ok {
last.update(e)
} else {
t := newTask(e)
w.tasks[e.ID] = &t
w.ids = append(w.ids, e.ID)
}
w.printEvent(e)
}
func (w *ttyWriter) printEvent(e api.Resource) {
if w.operation != "" {
// event will be displayed by progress UI on ticker's ticks
return
}
var color colorFunc
switch e.Status {
case api.Working:
color = SuccessColor
case api.Done:
color = SuccessColor
case api.Warning:
color = WarningColor
case api.Error:
color = ErrorColor
}
_, _ = fmt.Fprintf(w.out, "%s %s %s\n", e.ID, color(e.Text), e.Details)
}
func (w *ttyWriter) parentTasks() iter.Seq[*task] {
return func(yield func(*task) bool) {
for _, id := range w.ids { // iterate on ids to enforce a consistent order
t := w.tasks[id]
if len(t.parents) == 0 {
yield(t)
}
}
}
}
func (w *ttyWriter) childrenTasks(parent string) iter.Seq[*task] {
return func(yield func(*task) bool) {
for _, id := range w.ids { // iterate on ids to enforce a consistent order
t := w.tasks[id]
if t.parents.Has(parent) {
yield(t)
}
}
}
}
// lineData holds pre-computed formatting for a task line
type lineData struct {
spinner string // rendered spinner with color
prefix string // dry-run prefix if any
taskID string // possibly abbreviated
progress string // progress bar and size info
status string // rendered status with color
details string // possibly abbreviated
timer string // rendered timer with color
statusPad int // padding before status to align
timerPad int // padding before timer to align
statusColor colorFunc
}
func (w *ttyWriter) print() {
terminalWidth := goterm.Width()
terminalHeight := goterm.Height()
if terminalWidth <= 0 {
terminalWidth = 80
}
if terminalHeight <= 0 {
terminalHeight = 24
}
w.printWithDimensions(terminalWidth, terminalHeight)
}
func (w *ttyWriter) printWithDimensions(terminalWidth, terminalHeight int) {
w.mtx.Lock()
defer w.mtx.Unlock()
if len(w.tasks) == 0 {
return
}
up := w.numLines + 1
if !w.repeated {
up--
w.repeated = true
}
b := aec.NewBuilder(
aec.Hide, // Hide the cursor while we are printing
aec.Up(uint(up)),
aec.Column(0),
)
_, _ = fmt.Fprint(w.out, b.ANSI)
defer func() {
_, _ = fmt.Fprint(w.out, aec.Show)
}()
firstLine := fmt.Sprintf("[+] %s %d/%d", w.operation, numDone(w.tasks), len(w.tasks))
_, _ = fmt.Fprintln(w.out, firstLine)
// Collect parent tasks in original order
allTasks := slices.Collect(w.parentTasks())
// Available lines: terminal height - 2 (header line + potential "more" line)
maxLines := terminalHeight - 2
if maxLines < 1 {
maxLines = 1
}
showMore := len(allTasks) > maxLines
tasksToShow := allTasks
if showMore {
tasksToShow = allTasks[:maxLines-1] // Reserve one line for "more" message
}
// collect line data and compute timerLen
lines := make([]lineData, len(tasksToShow))
var timerLen int
for i, t := range tasksToShow {
lines[i] = w.prepareLineData(t)
if len(lines[i].timer) > timerLen {
timerLen = len(lines[i].timer)
}
}
// shorten details/taskID to fit terminal width
w.adjustLineWidth(lines, timerLen, terminalWidth)
// compute padding
w.applyPadding(lines, terminalWidth, timerLen)
// Render lines
numLines := 0
for _, l := range lines {
_, _ = fmt.Fprint(w.out, lineText(l))
numLines++
}
if showMore {
moreCount := len(allTasks) - len(tasksToShow)
moreText := fmt.Sprintf(" ... %d more", moreCount)
pad := terminalWidth - len(moreText)
if pad < 0 {
pad = 0
}
_, _ = fmt.Fprintf(w.out, "%s%s\n", moreText, strings.Repeat(" ", pad))
numLines++
}
// Clear any remaining lines from previous render
for i := numLines; i < w.numLines; i++ {
_, _ = fmt.Fprintln(w.out, strings.Repeat(" ", terminalWidth))
numLines++
}
w.numLines = numLines
}
func (w *ttyWriter) applyPadding(lines []lineData, terminalWidth int, timerLen int) {
var maxBeforeStatus int
for i := range lines {
l := &lines[i]
// Width before statusPad: space(1) + spinner(1) + prefix + space(1) + taskID + progress
beforeStatus := 3 + lenAnsi(l.prefix) + utf8.RuneCountInString(l.taskID) + lenAnsi(l.progress)
if beforeStatus > maxBeforeStatus {
maxBeforeStatus = beforeStatus
}
}
for i, l := range lines {
// Position before statusPad: space(1) + spinner(1) + prefix + space(1) + taskID + progress
beforeStatus := 3 + lenAnsi(l.prefix) + utf8.RuneCountInString(l.taskID) + lenAnsi(l.progress)
// statusPad aligns status; lineText adds 1 more space after statusPad
l.statusPad = maxBeforeStatus - beforeStatus
// Format: beforeStatus + statusPad + space(1) + status
lineLen := beforeStatus + l.statusPad + 1 + utf8.RuneCountInString(l.status)
if l.details != "" {
lineLen += 1 + utf8.RuneCountInString(l.details)
}
l.timerPad = terminalWidth - lineLen - timerLen
if l.timerPad < 1 {
l.timerPad = 1
}
lines[i] = l
}
}
func (w *ttyWriter) adjustLineWidth(lines []lineData, timerLen int, terminalWidth int) {
const minIDLen = 10
maxStatusLen := maxStatusLength(lines)
// Iteratively truncate until all lines fit
for range 100 { // safety limit
maxBeforeStatus := maxBeforeStatusWidth(lines)
overflow := computeOverflow(lines, maxBeforeStatus, maxStatusLen, timerLen, terminalWidth)
if overflow <= 0 {
break
}
// First try to truncate details, then taskID
if !truncateDetails(lines, overflow) && !truncateLongestTaskID(lines, overflow, minIDLen) {
break // Can't truncate further
}
}
}
// maxStatusLength returns the maximum status text length across all lines.
func maxStatusLength(lines []lineData) int {
var maxLen int
for i := range lines {
if len(lines[i].status) > maxLen {
maxLen = len(lines[i].status)
}
}
return maxLen
}
// maxBeforeStatusWidth computes the maximum width before statusPad across all lines.
// This is: space(1) + spinner(1) + prefix + space(1) + taskID + progress
func maxBeforeStatusWidth(lines []lineData) int {
var maxWidth int
for i := range lines {
l := &lines[i]
width := 3 + lenAnsi(l.prefix) + len(l.taskID) + lenAnsi(l.progress)
if width > maxWidth {
maxWidth = width
}
}
return maxWidth
}
// computeOverflow calculates how many characters the widest line exceeds the terminal width.
// Returns 0 or negative if all lines fit.
func computeOverflow(lines []lineData, maxBeforeStatus, maxStatusLen, timerLen, terminalWidth int) int {
var maxOverflow int
for i := range lines {
l := &lines[i]
detailsLen := len(l.details)
if detailsLen > 0 {
detailsLen++ // space before details
}
// Line width: maxBeforeStatus + space(1) + status + details + minTimerPad(1) + timer
lineWidth := maxBeforeStatus + 1 + maxStatusLen + detailsLen + 1 + timerLen
overflow := lineWidth - terminalWidth
if overflow > maxOverflow {
maxOverflow = overflow
}
}
return maxOverflow
}
// truncateDetails tries to truncate the first line's details to reduce overflow.
// Returns true if any truncation was performed.
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
}
l.details = l.details[:len(l.details)-reduction-3] + "..."
return true
} else if l.details != "" {
l.details = ""
return true
}
}
return false
}
// truncateLongestTaskID truncates the longest taskID to reduce overflow.
// Returns true if truncation was performed.
func truncateLongestTaskID(lines []lineData, overflow, minIDLen int) bool {
longestIdx := -1
longestLen := minIDLen
for i := range lines {
if len(lines[i].taskID) > longestLen {
longestLen = len(lines[i].taskID)
longestIdx = i
}
}
if longestIdx < 0 {
return false
}
l := &lines[longestIdx]
reduction := overflow + 3 // account for "..."
newLen := len(l.taskID) - reduction
if newLen < minIDLen-3 {
newLen = minIDLen - 3
}
if newLen > 0 {
l.taskID = l.taskID[:newLen] + "..."
}
return true
}
func (w *ttyWriter) prepareLineData(t *task) lineData {
endTime := time.Now()
if t.status != api.Working {
endTime = t.startTime
if (t.endTime != time.Time{}) {
endTime = t.endTime
}
}
prefix := ""
if w.dryRun {
prefix = PrefixColor(DRYRUN_PREFIX)
}
elapsed := endTime.Sub(t.startTime).Seconds()
var (
hideDetails bool
total int64
current int64
completion []string
)
// only show the aggregated progress while the root operation is in-progress
if t.status == api.Working {
for child := range w.childrenTasks(t.ID) {
if child.status == api.Working && child.total == 0 {
hideDetails = true
}
total += child.total
current += child.current
r := len(percentChars) - 1
p := child.percent
if p > 100 {
p = 100
}
completion = append(completion, percentChars[r*p/100])
}
}
if total == 0 {
hideDetails = true
}
var progress string
if len(completion) > 0 {
progress = " [" + SuccessColor(strings.Join(completion, "")) + "]"
if !hideDetails {
progress += fmt.Sprintf(" %7s / %-7s", units.HumanSize(float64(current)), units.HumanSize(float64(total)))
}
}
return lineData{
spinner: spinner(t),
prefix: prefix,
taskID: t.ID,
progress: progress,
status: t.text,
statusColor: colorFn(t.status),
details: t.details,
timer: fmt.Sprintf("%.1fs", elapsed),
}
}
func lineText(l lineData) string {
var sb strings.Builder
sb.WriteString(" ")
sb.WriteString(l.spinner)
sb.WriteString(l.prefix)
sb.WriteString(" ")
sb.WriteString(l.taskID)
sb.WriteString(l.progress)
sb.WriteString(strings.Repeat(" ", l.statusPad))
sb.WriteString(" ")
sb.WriteString(l.statusColor(l.status))
if l.details != "" {
sb.WriteString(" ")
sb.WriteString(l.details)
}
sb.WriteString(strings.Repeat(" ", l.timerPad))
sb.WriteString(TimerColor(l.timer))
sb.WriteString("\n")
return sb.String()
}
var (
spinnerDone = "✔"
spinnerWarning = "!"
spinnerError = "✘"
)
func spinner(t *task) string {
switch t.status {
case api.Done:
return SuccessColor(spinnerDone)
case api.Warning:
return WarningColor(spinnerWarning)
case api.Error:
return ErrorColor(spinnerError)
default:
return CountColor(t.spinner.String())
}
}
func colorFn(s api.EventStatus) colorFunc {
switch s {
case api.Done:
return SuccessColor
case api.Warning:
return WarningColor
case api.Error:
return ErrorColor
default:
return nocolor
}
}
func numDone(tasks map[string]*task) int {
i := 0
for _, t := range tasks {
if t.status != api.Working {
i++
}
}
return i
}
// lenAnsi count of user-perceived characters in ANSI string.
func lenAnsi(s string) int {
length := 0
ansiCode := false
for _, r := range s {
if r == '\x1b' {
ansiCode = true
continue
}
if ansiCode && r == 'm' {
ansiCode = false
continue
}
if !ansiCode {
length++
}
}
return length
}
var percentChars = strings.Split("⠀⡀⣀⣄⣤⣦⣶⣷⣿", "")

View File

@@ -1,424 +0,0 @@
/*
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 display
import (
"bytes"
"strings"
"sync"
"testing"
"time"
"unicode/utf8"
"gotest.tools/v3/assert"
"github.com/docker/compose/v5/pkg/api"
)
func newTestWriter() (*ttyWriter, *bytes.Buffer) {
var buf bytes.Buffer
w := &ttyWriter{
out: &buf,
info: &buf,
tasks: map[string]*task{},
done: make(chan bool),
mtx: &sync.Mutex{},
operation: "pull",
}
return w, &buf
}
func addTask(w *ttyWriter, id, text, details string, status api.EventStatus) {
t := &task{
ID: id,
parents: make(map[string]struct{}),
startTime: time.Now(),
text: text,
details: details,
status: status,
spinner: NewSpinner(),
}
w.tasks[id] = t
w.ids = append(w.ids, id)
}
// extractLines parses the output buffer and returns lines without ANSI control sequences
func extractLines(buf *bytes.Buffer) []string {
content := buf.String()
// Split by newline
rawLines := strings.Split(content, "\n")
var lines []string
for _, line := range rawLines {
// Skip empty lines and lines that are just ANSI codes
if lenAnsi(line) > 0 {
lines = append(lines, line)
}
}
return lines
}
func TestPrintWithDimensions_LinesFitTerminalWidth(t *testing.T) {
testCases := []struct {
name string
taskID string
status string
details string
terminalWidth int
}{
{
name: "short task fits wide terminal",
taskID: "Image foo",
status: "Pulling",
details: "layer abc123",
terminalWidth: 100,
},
{
name: "long details truncated to fit",
taskID: "Image foo",
status: "Pulling",
details: "downloading layer sha256:abc123def456789xyz0123456789abcdef",
terminalWidth: 50,
},
{
name: "long taskID truncated to fit",
taskID: "very-long-image-name-that-exceeds-terminal-width",
status: "Pulling",
details: "",
terminalWidth: 40,
},
{
name: "both long taskID and details",
taskID: "my-very-long-service-name-here",
status: "Downloading",
details: "layer sha256:abc123def456789xyz0123456789",
terminalWidth: 50,
},
{
name: "narrow terminal",
taskID: "service-name",
status: "Pulling",
details: "some details",
terminalWidth: 35,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
w, buf := newTestWriter()
addTask(w, tc.taskID, tc.status, tc.details, api.Working)
w.printWithDimensions(tc.terminalWidth, 24)
lines := extractLines(buf)
for i, line := range lines {
lineLen := lenAnsi(line)
assert.Assert(t, lineLen <= tc.terminalWidth,
"line %d has length %d which exceeds terminal width %d: %q",
i, lineLen, tc.terminalWidth, line)
}
})
}
}
func TestPrintWithDimensions_MultipleTasksFitTerminalWidth(t *testing.T) {
w, buf := newTestWriter()
// Add multiple tasks with varying lengths
addTask(w, "Image nginx", "Pulling", "layer sha256:abc123", api.Working)
addTask(w, "Image postgres-database", "Pulling", "downloading", api.Working)
addTask(w, "Image redis", "Pulled", "", api.Done)
terminalWidth := 60
w.printWithDimensions(terminalWidth, 24)
lines := extractLines(buf)
for i, line := range lines {
lineLen := lenAnsi(line)
assert.Assert(t, lineLen <= terminalWidth,
"line %d has length %d which exceeds terminal width %d: %q",
i, lineLen, terminalWidth, line)
}
}
func TestPrintWithDimensions_VeryNarrowTerminal(t *testing.T) {
w, buf := newTestWriter()
addTask(w, "Image nginx", "Pulling", "details", api.Working)
terminalWidth := 30
w.printWithDimensions(terminalWidth, 24)
lines := extractLines(buf)
for i, line := range lines {
lineLen := lenAnsi(line)
assert.Assert(t, lineLen <= terminalWidth,
"line %d has length %d which exceeds terminal width %d: %q",
i, lineLen, terminalWidth, line)
}
}
func TestPrintWithDimensions_TaskWithProgress(t *testing.T) {
w, buf := newTestWriter()
// Create parent task
parent := &task{
ID: "Image nginx",
parents: make(map[string]struct{}),
startTime: time.Now(),
text: "Pulling",
status: api.Working,
spinner: NewSpinner(),
}
w.tasks["Image nginx"] = parent
w.ids = append(w.ids, "Image nginx")
// Create child tasks to trigger progress display
for i := 0; i < 3; i++ {
child := &task{
ID: "layer" + string(rune('a'+i)),
parents: map[string]struct{}{"Image nginx": {}},
startTime: time.Now(),
text: "Downloading",
status: api.Working,
total: 1000,
current: 500,
percent: 50,
spinner: NewSpinner(),
}
w.tasks[child.ID] = child
w.ids = append(w.ids, child.ID)
}
terminalWidth := 80
w.printWithDimensions(terminalWidth, 24)
lines := extractLines(buf)
for i, line := range lines {
lineLen := lenAnsi(line)
assert.Assert(t, lineLen <= terminalWidth,
"line %d has length %d which exceeds terminal width %d: %q",
i, lineLen, terminalWidth, line)
}
}
func TestAdjustLineWidth_DetailsCorrectlyTruncated(t *testing.T) {
w := &ttyWriter{}
lines := []lineData{
{
taskID: "Image foo",
status: "Pulling",
details: "downloading layer sha256:abc123def456789xyz",
},
}
terminalWidth := 50
timerLen := 5
w.adjustLineWidth(lines, timerLen, terminalWidth)
// Verify the line fits
detailsLen := len(lines[0].details)
if detailsLen > 0 {
detailsLen++ // space before details
}
// widthWithoutDetails = 5 + prefix(0) + taskID(9) + progress(0) + status(7) + timer(5) = 26
lineWidth := 5 + len(lines[0].taskID) + len(lines[0].status) + detailsLen + timerLen
assert.Assert(t, lineWidth <= terminalWidth,
"line width %d should not exceed terminal width %d (taskID=%q, details=%q)",
lineWidth, terminalWidth, lines[0].taskID, lines[0].details)
// Verify details were truncated (not removed entirely)
assert.Assert(t, lines[0].details != "", "details should be truncated, not removed")
assert.Assert(t, strings.HasSuffix(lines[0].details, "..."), "truncated details should end with ...")
}
func TestAdjustLineWidth_TaskIDCorrectlyTruncated(t *testing.T) {
w := &ttyWriter{}
lines := []lineData{
{
taskID: "very-long-image-name-that-exceeds-minimum-length",
status: "Pulling",
details: "",
},
}
terminalWidth := 40
timerLen := 5
w.adjustLineWidth(lines, timerLen, terminalWidth)
lineWidth := 5 + len(lines[0].taskID) + 7 + timerLen
assert.Assert(t, lineWidth <= terminalWidth,
"line width %d should not exceed terminal width %d (taskID=%q)",
lineWidth, terminalWidth, lines[0].taskID)
assert.Assert(t, strings.HasSuffix(lines[0].taskID, "..."), "truncated taskID should end with ...")
}
func TestAdjustLineWidth_NoTruncationNeeded(t *testing.T) {
w := &ttyWriter{}
originalDetails := "short"
originalTaskID := "Image foo"
lines := []lineData{
{
taskID: originalTaskID,
status: "Pulling",
details: originalDetails,
},
}
// Wide terminal, nothing should be truncated
w.adjustLineWidth(lines, 5, 100)
assert.Equal(t, originalTaskID, lines[0].taskID, "taskID should not be modified")
assert.Equal(t, originalDetails, lines[0].details, "details should not be modified")
}
func TestAdjustLineWidth_DetailsRemovedWhenTooShort(t *testing.T) {
w := &ttyWriter{}
lines := []lineData{
{
taskID: "Image foo",
status: "Pulling",
details: "abc", // Very short, can't be meaningfully truncated
},
}
// Terminal so narrow that even minimal details + "..." wouldn't help
w.adjustLineWidth(lines, 5, 28)
assert.Equal(t, "", lines[0].details, "details should be removed entirely when too short to truncate")
}
// stripAnsi removes ANSI escape codes from a string
func stripAnsi(s string) string {
var result strings.Builder
inAnsi := false
for _, r := range s {
if r == '\x1b' {
inAnsi = true
continue
}
if inAnsi {
// ANSI sequences end with a letter (m, h, l, G, etc.)
if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') {
inAnsi = false
}
continue
}
result.WriteRune(r)
}
return result.String()
}
func TestPrintWithDimensions_PulledAndPullingWithLongIDs(t *testing.T) {
w, buf := newTestWriter()
// Add a completed task with long ID
completedTask := &task{
ID: "Image docker.io/library/nginx-long-name",
parents: make(map[string]struct{}),
startTime: time.Now().Add(-2 * time.Second),
endTime: time.Now(),
text: "Pulled",
status: api.Done,
spinner: NewSpinner(),
}
completedTask.spinner.Stop()
w.tasks[completedTask.ID] = completedTask
w.ids = append(w.ids, completedTask.ID)
// Add a pending task with long ID
pendingTask := &task{
ID: "Image docker.io/library/postgres-database",
parents: make(map[string]struct{}),
startTime: time.Now(),
text: "Pulling",
status: api.Working,
spinner: NewSpinner(),
}
w.tasks[pendingTask.ID] = pendingTask
w.ids = append(w.ids, pendingTask.ID)
terminalWidth := 50
w.printWithDimensions(terminalWidth, 24)
// Strip all ANSI codes from output and split by newline
stripped := stripAnsi(buf.String())
lines := strings.Split(stripped, "\n")
// Filter non-empty lines
var nonEmptyLines []string
for _, line := range lines {
if strings.TrimSpace(line) != "" {
nonEmptyLines = append(nonEmptyLines, line)
}
}
// Expected output format (50 runes per task line)
expected := `[+] pull 1/2
✔ Image docker.io/library/nginx-l... Pulled 2.0s
⠋ Image docker.io/library/postgre... Pulling 0.0s`
expectedLines := strings.Split(expected, "\n")
// Debug output
t.Logf("Actual output:\n")
for i, line := range nonEmptyLines {
t.Logf(" line %d (%2d runes): %q", i, utf8.RuneCountInString(line), line)
}
// Verify number of lines
assert.Equal(t, len(expectedLines), len(nonEmptyLines), "number of lines should match")
// Verify each line matches expected
for i, line := range nonEmptyLines {
if i < len(expectedLines) {
assert.Equal(t, expectedLines[i], line,
"line %d should match expected", i)
}
}
// Verify task lines fit within terminal width (strict - no tolerance)
for i, line := range nonEmptyLines {
if i > 0 { // Skip header line
runeCount := utf8.RuneCountInString(line)
assert.Assert(t, runeCount <= terminalWidth,
"line %d has %d runes which exceeds terminal width %d: %q",
i, runeCount, terminalWidth, line)
}
}
}
func TestLenAnsi(t *testing.T) {
testCases := []struct {
input string
expected int
}{
{"hello", 5},
{"\x1b[32mhello\x1b[0m", 5},
{"\x1b[1;32mgreen\x1b[0m text", 10},
{"", 0},
{"\x1b[0m", 0},
}
for _, tc := range testCases {
t.Run(tc.input, func(t *testing.T) {
result := lenAnsi(tc.input)
assert.Equal(t, tc.expected, result)
})
}
}

View File

@@ -20,46 +20,47 @@ import (
"fmt"
"github.com/acarl005/stripansi"
"github.com/morikuni/aec"
)
var disableAnsi bool
func ansi(code string) string {
return fmt.Sprintf("\033%s", code)
}
func saveCursor() {
if disableAnsi {
return
}
// see https://github.com/morikuni/aec/pull/5
fmt.Print(aec.Save)
fmt.Print(ansi("7"))
}
func restoreCursor() {
if disableAnsi {
return
}
// see https://github.com/morikuni/aec/pull/5
fmt.Print(aec.Restore)
fmt.Print(ansi("8"))
}
func showCursor() {
if disableAnsi {
return
}
fmt.Print(aec.Show)
fmt.Print(ansi("[?25h"))
}
func moveCursor(y, x int) {
if disableAnsi {
return
}
fmt.Print(aec.Position(uint(y), uint(x)))
fmt.Print(ansi(fmt.Sprintf("[%d;%dH", y, x)))
}
func carriageReturn() {
if disableAnsi {
return
}
fmt.Print(aec.Column(0))
fmt.Print(ansi(fmt.Sprintf("[%dG", 0)))
}
func clearLine() {
@@ -67,7 +68,7 @@ func clearLine() {
return
}
// Does not move cursor from its current position
fmt.Print(aec.EraseLine(aec.EraseModes.Tail))
fmt.Print(ansi("[2K"))
}
func moveCursorUp(lines int) {
@@ -75,7 +76,7 @@ func moveCursorUp(lines int) {
return
}
// Does not add new lines
fmt.Print(aec.Up(uint(lines)))
fmt.Print(ansi(fmt.Sprintf("[%dA", lines)))
}
func moveCursorDown(lines int) {
@@ -83,7 +84,7 @@ func moveCursorDown(lines int) {
return
}
// Does not add new lines
fmt.Print(aec.Down(uint(lines)))
fmt.Print(ansi(fmt.Sprintf("[%dB", lines)))
}
func newLine() {

View File

@@ -22,7 +22,6 @@ const (
// TemplateLegacyJSON the legacy json formatting value using go template
TemplateLegacyJSON = "{{json.}}"
// PRETTY is the constant for default formats on list commands
//
// Deprecated: use TABLE
PRETTY = "pretty"
// TABLE Print output in table format with column headers (default)

View File

@@ -23,11 +23,10 @@ import (
"time"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/go-units"
"github.com/docker/compose/v5/pkg/api"
)
const (
@@ -105,7 +104,7 @@ type ContainerContext struct {
// used in the template. It's currently only used to detect use of the .Size
// field which (if used) automatically sets the '--size' option when making
// the API call.
FieldsUsed map[string]any
FieldsUsed map[string]interface{}
}
// NewContainerContext creates a new context for rendering containers
@@ -274,7 +273,7 @@ func (c *ContainerContext) Networks() string {
// Size returns the container's size and virtual size (e.g. "2B (virtual 21.5MB)")
func (c *ContainerContext) Size() string {
if c.FieldsUsed == nil {
c.FieldsUsed = map[string]any{}
c.FieldsUsed = map[string]interface{}{}
}
c.FieldsUsed["Size"] = struct{}{}
srw := units.HumanSizeWithPrecision(float64(c.c.SizeRw), 3)

View File

@@ -22,11 +22,11 @@ import (
"reflect"
"strings"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v2/pkg/api"
)
// Print prints formatted lists in different formats
func Print(toJSON any, format string, outWriter io.Writer, writerFn func(w io.Writer), headers ...string) error {
func Print(toJSON interface{}, format string, outWriter io.Writer, writerFn func(w io.Writer), headers ...string) error {
switch strings.ToLower(format) {
case TABLE, PRETTY, "":
return PrintPrettySection(outWriter, writerFn, headers...)

View File

@@ -45,7 +45,7 @@ func TestPrint(t *testing.T) {
}
b := &bytes.Buffer{}
assert.NilError(t, Print(testList, TABLE, b, func(w io.Writer) {
assert.NilError(t, Print(testList, PRETTY, b, func(w io.Writer) {
for _, t := range testList {
_, _ = fmt.Fprintf(w, "%s\t%s\n", t.Name, t.Status)
}

View File

@@ -24,12 +24,12 @@ import (
const standardIndentation = " "
// ToStandardJSON return a string with the JSON representation of the interface{}
func ToStandardJSON(i any) (string, error) {
func ToStandardJSON(i interface{}) (string, error) {
return ToJSON(i, "", standardIndentation)
}
// ToJSON return a string with the JSON representation of the interface{}
func ToJSON(i any, prefix string, indentation string) (string, error) {
func ToJSON(i interface{}, prefix string, indentation string) (string, error) {
buffer := &bytes.Buffer{}
encoder := json.NewEncoder(buffer)
encoder.SetEscapeHTML(false)

View File

@@ -26,9 +26,8 @@ import (
"time"
"github.com/buger/goterm"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/compose/v5/pkg/api"
)
// LogConsumer consume logs from services and format them
@@ -87,7 +86,7 @@ func (l *logConsumer) register(name string) *presenter {
l.presenters.Store(name, p)
l.computeWidth()
if l.prefix {
l.presenters.Range(func(key, value any) bool {
l.presenters.Range(func(key, value interface{}) bool {
p := value.(*presenter)
p.setPrefix(l.width)
return true
@@ -120,7 +119,7 @@ func (l *logConsumer) write(w io.Writer, container, message string) {
}
p := l.getPresenter(container)
timestamp := time.Now().Format(jsonmessage.RFC3339NanoFixed)
for line := range strings.SplitSeq(message, "\n") {
for _, line := range strings.Split(message, "\n") {
if l.timestamp {
_, _ = fmt.Fprintf(w, "%s%s %s\n", p.prefix, timestamp, line)
} else {
@@ -137,7 +136,7 @@ func (l *logConsumer) Status(container, msg string) {
func (l *logConsumer) computeWidth() {
width := 0
l.presenters.Range(func(key, value any) bool {
l.presenters.Range(func(key, value interface{}) bool {
p := value.(*presenter)
if len(p.name) > width {
width = len(p.name)

View File

@@ -22,17 +22,15 @@ import (
"fmt"
"math"
"os"
"strings"
"syscall"
"time"
"github.com/buger/goterm"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/compose/v2/internal/tracing"
"github.com/docker/compose/v2/pkg/api"
"github.com/eiannone/keyboard"
"github.com/skratchdot/open-golang/open"
"github.com/docker/compose/v5/internal/tracing"
"github.com/docker/compose/v5/pkg/api"
)
const DISPLAY_ERROR_TIME = 10
@@ -92,7 +90,6 @@ const (
type LogKeyboard struct {
kError KeyboardError
Watch *KeyboardWatch
Detach func()
IsDockerDesktopActive bool
logLevel KEYBOARD_LOG_LEVEL
signalChannel chan<- os.Signal
@@ -164,23 +161,29 @@ func (lk *LogKeyboard) printNavigationMenu() {
}
func (lk *LogKeyboard) navigationMenu() string {
var items []string
var openDDInfo string
if lk.IsDockerDesktopActive {
items = append(items, shortcutKeyColor("v")+navColor(" View in Docker Desktop"))
openDDInfo = shortcutKeyColor("v") + navColor(" View in Docker Desktop")
}
var openDDUI string
if openDDInfo != "" {
openDDUI = navColor(" ")
}
if lk.IsDockerDesktopActive {
items = append(items, shortcutKeyColor("o")+navColor(" View Config"))
openDDUI = openDDUI + shortcutKeyColor("o") + navColor(" View Config")
}
var watchInfo string
if openDDInfo != "" || openDDUI != "" {
watchInfo = navColor(" ")
}
isEnabled := " Enable"
if lk.Watch != nil && lk.Watch.Watching {
isEnabled = " Disable"
}
items = append(items, shortcutKeyColor("w")+navColor(isEnabled+" Watch"))
items = append(items, shortcutKeyColor("d")+navColor(" Detach"))
return strings.Join(items, " ")
watchInfo = watchInfo + shortcutKeyColor("w") + navColor(isEnabled+" Watch")
return openDDInfo + openDDUI + watchInfo
}
func (lk *LogKeyboard) clearNavigationMenu() {
@@ -287,9 +290,6 @@ func (lk *LogKeyboard) ToggleWatch(ctx context.Context, options api.UpOptions) {
func (lk *LogKeyboard) HandleKeyEvents(ctx context.Context, event keyboard.KeyEvent, project *types.Project, options api.UpOptions) {
switch kRune := event.Rune; kRune {
case 'd':
lk.clearNavigationMenu()
lk.Detach()
case 'v':
lk.openDockerDesktop(ctx, project)
case 'w':
@@ -336,10 +336,6 @@ func (lk *LogKeyboard) EnableWatch(enabled bool, watcher Feature) {
}
}
func (lk *LogKeyboard) EnableDetach(detach func()) {
lk.Detach = detach
}
func allocateSpace(lines int) {
for i := 0; i < lines; i++ {
clearLine()

View File

@@ -23,27 +23,24 @@ import (
"github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli-plugins/plugin"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/cmd/cmdtrace"
"github.com/docker/compose/v2/cmd/prompt"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/cmd/cmdtrace"
"github.com/docker/compose/v5/cmd/compatibility"
commands "github.com/docker/compose/v5/cmd/compose"
"github.com/docker/compose/v5/cmd/prompt"
"github.com/docker/compose/v5/internal"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/compose/v2/cmd/compatibility"
commands "github.com/docker/compose/v2/cmd/compose"
"github.com/docker/compose/v2/internal"
"github.com/docker/compose/v2/pkg/compose"
)
func pluginMain() {
plugin.Run(
func(cli command.Cli) *cobra.Command {
backendOptions := &commands.BackendOptions{
Options: []compose.Option{
compose.WithPrompt(prompt.NewPrompt(cli.In(), cli.Out()).Confirm),
},
}
cmd := commands.RootCommand(cli, backendOptions)
backend := compose.NewComposeService(cli,
compose.WithPrompt(prompt.NewPrompt(cli.In(), cli.Out()).Confirm),
)
cmd := commands.RootCommand(cli, backend)
originalPreRunE := cmd.PersistentPreRunE
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
// initialize the cli instance

View File

@@ -22,11 +22,10 @@ import (
"github.com/AlecAivazis/survey/v2"
"github.com/docker/cli/cli/streams"
"github.com/docker/compose/v5/pkg/utils"
"github.com/docker/compose/v2/pkg/utils"
)
//go:generate mockgen -destination=./prompt_mock.go -self_package "github.com/docker/compose/v5/pkg/prompt" -package=prompt . UI
//go:generate mockgen -destination=./prompt_mock.go -self_package "github.com/docker/compose/v2/pkg/prompt" -package=prompt . UI
// UI - prompt user input
type UI interface {

View File

@@ -1,7 +1,7 @@
# About
The Compose application model defines `service` as an abstraction for a computing unit managing (a subset of)
application needs, which can interact with other services by relying on network(s). Docker Compose is designed
application needs, which can interact with other service by relying on network(s). Docker Compose is designed
to use the Docker Engine ("Moby") API to manage services as containers, but the abstraction _could_ also cover
many other runtimes, typically cloud services or services natively provided by host.
@@ -55,8 +55,8 @@ JSON messages MUST include a `type` and a `message` attribute.
`type` can be either:
- `info`: Reports status updates to the user. Compose will render message as the service state in the progress UI
- `error`: Lets the user know something went wrong with details about the error. Compose will render the message as the reason for the service failure.
- `setenv`: Lets the plugin tell Compose how dependent services can access the created resource. See next section for further details.
- `error`: Let's the user know something went wrong with details about the error. Compose will render the message as the reason for the service failure.
- `setenv`: Let's the plugin tell Compose how dependent services can access the created resource. See next section for further details.
- `debug`: Those messages could help debugging the provider, but are not rendered to the user by default. They are rendered when Compose is started with `--verbose` flag.
```mermaid

View File

@@ -1,4 +1,3 @@
# docker compose
```text
@@ -127,57 +126,6 @@ get the postgres image for the db service from anywhere by using the `-f` flag a
$ docker compose -f ~/sandbox/rails/compose.yaml pull db
```
#### Using an OCI published artifact
You can use the `-f` flag with the `oci://` prefix to reference a Compose file that has been published to an OCI registry.
This allows you to distribute and version your Compose configurations as OCI artifacts.
To use a Compose file from an OCI registry:
```console
$ docker compose -f oci://registry.example.com/my-compose-project:latest up
```
You can also combine OCI artifacts with local files:
```console
$ docker compose -f oci://registry.example.com/my-compose-project:v1.0 -f compose.override.yaml up
```
The OCI artifact must contain a valid Compose file. You can publish Compose files to an OCI registry using the
`docker compose publish` command.
#### Using a git repository
You can use the `-f` flag to reference a Compose file from a git repository. Compose supports various git URL formats:
Using HTTPS:
```console
$ docker compose -f https://github.com/user/repo.git up
```
Using SSH:
```console
$ docker compose -f git@github.com:user/repo.git up
```
You can specify a specific branch, tag, or commit:
```console
$ docker compose -f https://github.com/user/repo.git@main up
$ docker compose -f https://github.com/user/repo.git@v1.0.0 up
$ docker compose -f https://github.com/user/repo.git@abc123 up
```
You can also specify a subdirectory within the repository:
```console
$ docker compose -f https://github.com/user/repo.git#main:path/to/compose.yaml up
```
When using git resources, Compose will clone the repository and use the specified Compose file. You can combine
git resources with local files:
```console
$ docker compose -f https://github.com/user/repo.git -f compose.override.yaml up
```
### Use `-p` to specify a project name
Each configuration has a project name. Compose sets the project name using

View File

@@ -5,11 +5,9 @@ Starts existing containers for a service
### Options
| Name | Type | Default | Description |
|:-----------------|:-------|:--------|:---------------------------------------------------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--wait` | `bool` | | Wait for services to be running\|healthy. Implies detached mode. |
| `--wait-timeout` | `int` | `0` | Maximum duration in seconds to wait for the project to be running\|healthy |
| Name | Type | Default | Description |
|:------------|:-------|:--------|:--------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
<!---MARKER_GEN_END-->

View File

@@ -139,17 +139,6 @@ options:
experimentalcli: false
kubernetes: false
swarm: false
- option: insecure-registry
value_type: stringArray
default_value: '[]'
description: |
Use insecure registry to pull Compose OCI artifacts. Doesn't apply to images
deprecated: false
hidden: true
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: no-ansi
value_type: bool
default_value: "false"
@@ -301,57 +290,6 @@ examples: |-
$ docker compose -f ~/sandbox/rails/compose.yaml pull db
```
#### Using an OCI published artifact
You can use the `-f` flag with the `oci://` prefix to reference a Compose file that has been published to an OCI registry.
This allows you to distribute and version your Compose configurations as OCI artifacts.
To use a Compose file from an OCI registry:
```console
$ docker compose -f oci://registry.example.com/my-compose-project:latest up
```
You can also combine OCI artifacts with local files:
```console
$ docker compose -f oci://registry.example.com/my-compose-project:v1.0 -f compose.override.yaml up
```
The OCI artifact must contain a valid Compose file. You can publish Compose files to an OCI registry using the
`docker compose publish` command.
#### Using a git repository
You can use the `-f` flag to reference a Compose file from a git repository. Compose supports various git URL formats:
Using HTTPS:
```console
$ docker compose -f https://github.com/user/repo.git up
```
Using SSH:
```console
$ docker compose -f git@github.com:user/repo.git up
```
You can specify a specific branch, tag, or commit:
```console
$ docker compose -f https://github.com/user/repo.git@main up
$ docker compose -f https://github.com/user/repo.git@v1.0.0 up
$ docker compose -f https://github.com/user/repo.git@abc123 up
```
You can also specify a subdirectory within the repository:
```console
$ docker compose -f https://github.com/user/repo.git#main:path/to/compose.yaml up
```
When using git resources, Compose will clone the repository and use the specified Compose file. You can combine
git resources with local files:
```console
$ docker compose -f https://github.com/user/repo.git -f compose.override.yaml up
```
### Use `-p` to specify a project name
Each configuration has a project name. Compose sets the project name using

View File

@@ -15,16 +15,6 @@ options:
experimentalcli: false
kubernetes: false
swarm: false
- option: insecure-registry
value_type: bool
default_value: "false"
description: Use insecure registry
deprecated: false
hidden: true
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: oci-version
value_type: string
description: |

View File

@@ -15,16 +15,6 @@ options:
experimentalcli: false
kubernetes: false
swarm: false
- option: insecure-registry
value_type: bool
default_value: "false"
description: Use insecure registry
deprecated: false
hidden: true
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: oci-version
value_type: string
description: |

View File

@@ -4,28 +4,6 @@ long: Starts existing containers for a service
usage: docker compose start [SERVICE...]
pname: docker compose
plink: docker_compose.yaml
options:
- option: wait
value_type: bool
default_value: "false"
description: Wait for services to be running|healthy. Implies detached mode.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: wait-timeout
value_type: int
default_value: "0"
description: |
Maximum duration in seconds to wait for the project to be running|healthy
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
inherited_options:
- option: dry-run
value_type: bool

View File

@@ -1,157 +0,0 @@
# Using the `docker/compose` SDK
The `docker/compose` package can be used as a Go library by third-party applications to programmatically manage
containerized applications defined in Compose files. This SDK provides a comprehensive API that lets you
integrate Compose functionality directly into your applications, allowing you to load, validate, and manage
multi-container environments without relying on the Compose CLI.
Whether you need to orchestrate containers as part of
a deployment pipeline, build custom management tools, or embed container orchestration into your application, the
Compose SDK offers the same powerful capabilities that drive the Docker Compose command-line tool.
## Set up the SDK
To get started, create an SDK instance using the `NewComposeService()` function, which initializes a service with the
necessary configuration to interact with the Docker daemon and manage Compose projects. This service instance provides
methods for all core Compose operations including creating, starting, stopping, and removing containers, as well as
loading and validating Compose files. The service handles the underlying Docker API interactions and resource
management, allowing you to focus on your application logic.
## Example usage
Here's a basic example demonstrating how to load a Compose project and start the services:
```go
package main
import (
"context"
"log"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/flags"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
)
func main() {
ctx := context.Background()
dockerCLI, err := command.NewDockerCli()
if err != nil {
log.Fatalf("Failed to create docker CLI: %v", err)
}
err = dockerCLI.Initialize(&flags.ClientOptions{})
if err != nil {
log.Fatalf("Failed to initialize docker CLI: %v", err)
}
// Create a new Compose service instance
service, err := compose.NewComposeService(dockerCLI)
if err != nil {
log.Fatalf("Failed to create compose service: %v", err)
}
// Load the Compose project from a compose file
project, err := service.LoadProject(ctx, api.ProjectLoadOptions{
ConfigPaths: []string{"compose.yaml"},
ProjectName: "my-app",
})
if err != nil {
log.Fatalf("Failed to load project: %v", err)
}
// Start the services defined in the Compose file
err = service.Up(ctx, project, api.UpOptions{
Create: api.CreateOptions{},
Start: api.StartOptions{},
})
if err != nil {
log.Fatalf("Failed to start services: %v", err)
}
log.Printf("Successfully started project: %s", project.Name)
}
```
This example demonstrates the core workflow - creating a service instance, loading a project from a Compose file, and
starting the services. The SDK provides many additional operations for managing the lifecycle of your containerized
application.
## Customizing the SDK
The `NewComposeService()` function accepts optional `compose.Option` parameters to customize the SDK behavior. These
options allow you to configure I/O streams, concurrency limits, dry-run mode, and other advanced features.
```go
// Create a custom output buffer to capture logs
var outputBuffer bytes.Buffer
// Create a compose service with custom options
service, err := compose.NewComposeService(dockerCLI,
compose.WithOutputStream(&outputBuffer), // Redirect output to custom writer
compose.WithErrorStream(os.Stderr), // Use stderr for errors
compose.WithMaxConcurrency(4), // Limit concurrent operations
compose.WithPrompt(compose.AlwaysOkPrompt()), // Auto-confirm all prompts
)
```
### Available options
- `WithOutputStream(io.Writer)` - Redirect standard output to a custom writer
- `WithErrorStream(io.Writer)` - Redirect error output to a custom writer
- `WithInputStream(io.Reader)` - Provide a custom input stream for interactive prompts
- `WithStreams(out, err, in)` - Set all I/O streams at once
- `WithMaxConcurrency(int)` - Limit the number of concurrent operations against the Docker API
- `WithPrompt(Prompt)` - Customize user confirmation behavior (use `AlwaysOkPrompt()` for non-interactive mode)
- `WithDryRun` - Run operations in dry-run mode without actually applying changes
- `WithContextInfo(api.ContextInfo)` - Set custom Docker context information
- `WithProxyConfig(map[string]string)` - Configure HTTP proxy settings for builds
- `WithEventProcessor(progress.EventProcessor)` - Receive progress events and operation notifications
These options provide fine-grained control over the SDK's behavior, making it suitable for various integration
scenarios including CLI tools, web services, automation scripts, and testing environments.
## Tracking operations with `EventProcessor`
The `EventProcessor` interface allows you to monitor Compose operations in real-time by receiving events about changes
applied to Docker resources such as images, containers, volumes, and networks. This is particularly useful for building
user interfaces, logging systems, or monitoring tools that need to track the progress of Compose operations.
### Understanding `EventProcessor`
A Compose operation, such as `up`, `down`, `build`, performs a series of changes to Docker resources. The
`EventProcessor` receives notifications about these changes through three key methods:
- `Start(ctx, operation)` - Called when a Compose operation begins, for example `up`
- `On(events...)` - Called with progress events for individual resource changes, for example, container starting, image
being pulled
- `Done(operation, success)` - Called when the operation completes, indicating success or failure
Each event contains information about the resource being modified, its current status, and progress indicators when
applicable (such as download progress for image pulls).
### Event status types
Events report resource changes with the following status types:
- Working - Operation is in progress, for example, creating, starting, pulling
- Done - Operation completed successfully
- Warning - Operation completed with warnings
- Error - Operation failed
Common status text values include: `Creating`, `Created`, `Starting`, `Started`, `Running`, `Stopping`, `Stopped`,
`Removing`, `Removed`, `Building`, `Built`, `Pulling`, `Pulled`, and more.
### Built-in `EventProcessor` implementations
The SDK provides three ready-to-use `EventProcessor` implementations:
- `progress.NewTTYWriter(io.Writer)` - Renders an interactive terminal UI with progress bars and task lists
(similar to the Docker Compose CLI output)
- `progress.NewPlainWriter(io.Writer)` - Outputs simple text-based progress messages suitable for non-interactive
environments or log files
- `progress.NewJSONWriter()` - Render events as JSON objects
- `progress.NewQuietWriter()` - (Default) Silently processes events without producing any output
Using `EventProcessor`, a custom UI can be plugged into `docker/compose`.

View File

@@ -23,9 +23,8 @@ import (
clidocstool "github.com/docker/cli-docs-tool"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/cmd/compose"
"github.com/spf13/cobra"
"github.com/docker/compose/v5/cmd/compose"
)
func generateDocs(opts *options) error {

174
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/docker/compose/v5
module github.com/docker/compose/v2
go 1.24.3
go 1.24.9
require (
github.com/AlecAivazis/survey/v2 v2.3.7
@@ -8,95 +8,123 @@ require (
github.com/Microsoft/go-winio v0.6.2
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
github.com/buger/goterm v1.0.4
github.com/compose-spec/compose-go/v2 v2.10.1
github.com/containerd/console v1.0.5
github.com/containerd/containerd/v2 v2.2.1
github.com/compose-spec/compose-go/v2 v2.9.1
github.com/containerd/containerd/v2 v2.1.4
github.com/containerd/errdefs v1.0.0
github.com/containerd/platforms v1.0.0-rc.2
github.com/containerd/platforms v1.0.0-rc.1
github.com/davecgh/go-spew v1.1.1
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/cli-docs-tool v0.11.0
github.com/docker/docker v28.5.2+incompatible
github.com/docker/buildx v0.29.1
github.com/docker/cli v28.5.1+incompatible
github.com/docker/cli-docs-tool v0.10.0
github.com/docker/docker v28.5.1+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
github.com/go-viper/mapstructure/v2 v2.5.0
github.com/go-viper/mapstructure/v2 v2.4.0
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/hashicorp/go-version v1.8.0
github.com/hashicorp/go-version v1.7.0
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/buildkit v0.25.1
github.com/moby/go-archive v0.1.0
github.com/moby/patternmatcher v0.6.0
github.com/moby/sys/atomicwriter v0.1.0
github.com/morikuni/aec v1.1.0
github.com/moby/term v0.5.2
github.com/morikuni/aec v1.0.0
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1
github.com/otiai10/copy v1.14.1
github.com/sirupsen/logrus v1.9.4
github.com/sirupsen/logrus v1.9.3
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/spf13/cobra v1.10.2
github.com/spf13/cobra v1.10.1
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0
go.opentelemetry.io/otel v1.38.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0
go.opentelemetry.io/otel/metric v1.38.0
go.opentelemetry.io/otel/sdk v1.38.0
go.opentelemetry.io/otel/trace v1.38.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0
go.opentelemetry.io/otel v1.36.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0
go.opentelemetry.io/otel/metric v1.36.0
go.opentelemetry.io/otel/sdk v1.36.0
go.opentelemetry.io/otel/trace v1.36.0
go.uber.org/goleak v1.3.0
go.uber.org/mock v0.6.0
go.yaml.in/yaml/v4 v4.0.0-rc.4
golang.org/x/sync v0.19.0
golang.org/x/sys v0.41.0
google.golang.org/grpc v1.78.0
golang.org/x/sync v0.17.0
golang.org/x/sys v0.37.0
google.golang.org/grpc v1.74.2
gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.5.2
tags.cncf.io/container-device-interface v1.1.0
tags.cncf.io/container-device-interface v1.0.1
)
require (
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect
github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
github.com/aws/smithy-go v1.20.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/containerd/containerd/api v1.10.0 // indirect
github.com/containerd/console v1.0.5 // indirect
github.com/containerd/containerd/api v1.9.0 // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/ttrpc v1.2.7 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect
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/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fvbommel/sortorder v1.1.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gofrs/flock v0.13.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/gofrs/flock v0.12.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/gofuzz v1.2.0 // 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/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // 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/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/magiconair/properties v1.8.9 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // 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
@@ -105,27 +133,32 @@ require (
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/spdystream v0.5.0 // indirect
github.com/moby/sys/capability v0.4.0 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/signal v0.7.1 // indirect
github.com/moby/sys/symlink v0.3.0 // indirect
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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // 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/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rivo/uniseg v0.2.0 // 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/secure-systems-lab/go-securesystemslib v0.6.0 // indirect
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect
github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/theupdateframework/notary v0.7.0 // indirect
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
@@ -133,25 +166,46 @@ require (
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
github.com/x448/float16 v0.8.4 // indirect
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect
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
github.com/zclconf/go-cty v1.17.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // 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/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
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/time v0.11.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.32.3 // indirect
k8s.io/apimachinery v0.32.3 // indirect
k8s.io/client-go v0.32.3 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
sigs.k8s.io/yaml v1.4.0 // 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
)

378
go.sum
View File

@@ -8,10 +8,12 @@ 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/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.14.0-rc.1 h1:qAPXKwGOkVn8LlqgBN8GS0bxZ83hOJpcjxzmlQKxKsQ=
github.com/Microsoft/hcsshim v0.14.0-rc.1/go.mod h1:hTKFGbnDtQb1wHiOWv4v0eN+7boSWAHyK/tNAaYZL0c=
github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA=
github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok=
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=
@@ -22,6 +24,36 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
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/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY=
github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90=
github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg=
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI=
github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw=
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE=
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ=
github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
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=
@@ -38,24 +70,24 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembj
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/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
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=
github.com/compose-spec/compose-go/v2 v2.10.1/go.mod h1:Ohac1SzhO/4fXXrzWIztIVB6ckmKBv1Nt5Z5mGVESUg=
github.com/containerd/cgroups/v3 v3.1.2 h1:OSosXMtkhI6Qove637tg1XgK4q+DhR0mX8Wi8EhrHa4=
github.com/containerd/cgroups/v3 v3.1.2/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw=
github.com/compose-spec/compose-go/v2 v2.9.1 h1:8UwI+ujNU+9Ffkf/YgAm/qM9/eU7Jn8nHzWG721W4rs=
github.com/compose-spec/compose-go/v2 v2.9.1/go.mod h1:Oky9AZGTRB4E+0VbTPZTUu4Kp+oEMMuwZXZtPPVT1iE=
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc=
github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/containerd/containerd/api v1.10.0 h1:5n0oHYVBwN4VhoX9fFykCV9dF1/BvAXeg2F8W6UYq1o=
github.com/containerd/containerd/api v1.10.0/go.mod h1:NBm1OAk8ZL+LG8R0ceObGxT5hbUYj7CzTmR3xh0DlMM=
github.com/containerd/containerd/v2 v2.2.1 h1:TpyxcY4AL5A+07dxETevunVS5zxqzuq7ZqJXknM11yk=
github.com/containerd/containerd/v2 v2.2.1/go.mod h1:NR70yW1iDxe84F2iFWbR9xfAN0N2F0NcjTi1OVth4nU=
github.com/containerd/containerd/api v1.9.0 h1:HZ/licowTRazus+wt9fM6r/9BQO7S0vD5lMcWspGIg0=
github.com/containerd/containerd/api v1.9.0/go.mod h1:GhghKFmTR3hNtyznBoQ0EMWr9ju5AqHjcZPsSpTKutI=
github.com/containerd/containerd/v2 v2.1.4 h1:/hXWjiSFd6ftrBOBGfAZ6T30LJcx1dBjdKEeI8xucKQ=
github.com/containerd/containerd/v2 v2.1.4/go.mod h1:8C5QV9djwsYDNhxfTCFjWtTBZrqjditQ4/ghHSYjnHM=
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
@@ -66,15 +98,15 @@ 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/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/nydus-snapshotter v0.15.2 h1:qsHI4M+Wwrf6Jr4eBqhNx8qh+YU0dSiJ+WPmcLFWNcg=
github.com/containerd/nydus-snapshotter v0.15.2/go.mod h1:FfwH2KBkNYoisK/e+KsmNr7xTU53DmnavQHMFOcXwfM=
github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E=
github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4=
github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y=
github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8=
github.com/containerd/stargz-snapshotter v0.17.0 h1:djNS4KU8ztFhLdEDZ1bsfzOiYuVHT6TgSU5qwRk+cNc=
github.com/containerd/stargz-snapshotter/estargz v0.17.0 h1:+TyQIsR/zSFI1Rm31EQBwpAA1ovYgIKHy7kctL3sLcE=
github.com/containerd/stargz-snapshotter/estargz v0.17.0/go.mod h1:s06tWAiJcXQo9/8AReBCIo/QxcXFZ2n4qfsRnpl71SM=
github.com/containerd/stargz-snapshotter v0.16.3 h1:zbQMm8dRuPHEOD4OqAYGajJJUwCeUzt4j7w9Iaw58u4=
github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8=
github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU=
github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ=
github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o=
github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=
@@ -86,8 +118,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
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/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=
@@ -96,17 +126,17 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
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/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/buildx v0.29.1 h1:58hxM5Z4mnNje3G5NKfULT9xCr8ooM8XFtlfUK9bKaA=
github.com/docker/buildx v0.29.1/go.mod h1:J4EFv6oxlPiV1MjO0VyJx2u5tLM7ImDEl9zyB8d4wPI=
github.com/docker/cli v28.5.1+incompatible h1:ESutzBALAD6qyCLqbQSEf1a/U8Ybms5agw59yGVc+yY=
github.com/docker/cli v28.5.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli-docs-tool v0.10.0 h1:bOD6mKynPQgojQi3s2jgcUWGp/Ebqy1SeCr9VfKQLLU=
github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09fzRHP4aX1qwp1U=
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 v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM=
github.com/docker/docker v28.5.1+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=
@@ -124,6 +154,8 @@ github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNE
github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM=
github.com/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/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/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=
@@ -132,6 +164,8 @@ github.com/fsnotify/fsevents v0.2.0/go.mod h1:B3eEk39i4hz8y1zaWS/wPrAP4O6wkIl7HQ
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/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
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=
@@ -140,22 +174,32 @@ 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-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
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-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/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
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-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
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/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/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=
@@ -164,10 +208,17 @@ 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/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
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=
@@ -175,17 +226,21 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
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/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
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/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=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.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=
@@ -203,20 +258,25 @@ github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d/go.mod h1:h+uFLl
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/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
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/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
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=
@@ -229,10 +289,13 @@ github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTRe
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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
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=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
@@ -253,8 +316,8 @@ github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/z
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.25.1 h1:j7IlVkeNbEo+ZLoxdudYCHpmTsbwKvhgc/6UJ/mY/o8=
github.com/moby/buildkit v0.25.1/go.mod h1:phM8sdqnvgK2y1dPDnbwI6veUCXHOZ6KFSl6E164tkc=
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=
@@ -263,6 +326,8 @@ 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/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk=
@@ -282,29 +347,39 @@ github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcY
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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
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/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
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 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
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/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
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/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww=
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8=
github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U=
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=
@@ -325,41 +400,43 @@ github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go
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_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
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/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
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/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
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/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/secure-systems-lab/go-securesystemslib v0.9.1 h1:nZZaNz4DiERIQguNy0cL5qTdn9lR8XKHf4RUyG1Sx3g=
github.com/secure-systems-lab/go-securesystemslib v0.9.1/go.mod h1:np53YzT0zXGMv6x4iEWc9Z59uR+x+ndLwCLqPYpLXVU=
github.com/secure-systems-lab/go-securesystemslib v0.6.0 h1:T65atpAVCJQK14UA57LMdZGpHi4QYSH/9FZyNGqMYIA=
github.com/secure-systems-lab/go-securesystemslib v0.6.0/go.mod h1:8Mtpo9JKks/qhPG4HGZ2LGMvrPbzuxwfz/f/zLfEWkk=
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b h1:h+3JX2VoWTFuyQEo87pStk/a99dzIO1mM9KxIyLPGTU=
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc=
github.com/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/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
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=
@@ -367,8 +444,8 @@ github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYec
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/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
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=
@@ -380,11 +457,17 @@ github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8
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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
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=
@@ -401,55 +484,55 @@ github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/
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/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/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
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=
github.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0=
github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U=
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=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 h1:2pn7OzMewmYRiNtv1doZnLo3gONcnMHlFnmOR8Vgt+8=
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0/go.mod h1:rjbQTDEPQymPE0YnRQp9/NuPwwtL0sesz/fnqRW/v84=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 h1:0tY123n7CdWMem7MOVdKOt0YfshufLCwfE5Bob+hQuM=
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0/go.mod h1:CosX/aS4eHnG9D7nESYpV753l4j9q5j3SL/PUYd2lR8=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0/go.mod h1:CXIWhUomyWBG/oY2/r/kLp6K/cmx9e/7DLpBuuGdLCA=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0 h1:0NIXxOCFx+SKbhCVxwl3ETG8ClLPAa0KuKV6p3yhxP8=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0/go.mod h1:ChZSJbbfbl/DcRZNc9Gqh6DYGlfjw4PvO1pEOZH1ZsE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
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/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.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-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -458,13 +541,13 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
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.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
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/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -474,8 +557,10 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
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/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
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=
@@ -483,8 +568,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
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/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.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=
@@ -501,44 +586,46 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
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.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
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/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ=
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
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=
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
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=
@@ -547,8 +634,12 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
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/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
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/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
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=
@@ -557,12 +648,31 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
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.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
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=
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=
k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls=
k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU=
k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
tags.cncf.io/container-device-interface v1.0.1 h1:KqQDr4vIlxwfYh0Ed/uJGVgX+CHAkahrgabg6Q8GYxc=
tags.cncf.io/container-device-interface v1.0.1/go.mod h1:JojJIOeW3hNbcnOH2q0NrWNha/JuHoDZcmYxAZwb2i0=

View File

@@ -25,10 +25,9 @@ import (
"net/http"
"strings"
"github.com/docker/compose/v2/internal"
"github.com/docker/compose/v2/internal/memnet"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"github.com/docker/compose/v5/internal"
"github.com/docker/compose/v5/internal/memnet"
)
// identify this client in the logs

View File

@@ -17,6 +17,7 @@
package desktop
import (
"context"
"os"
"testing"
"time"
@@ -33,6 +34,9 @@ func TestClientPing(t *testing.T) {
t.Skip("Skipping - COMPOSE_TEST_DESKTOP_ENDPOINT not defined")
}
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
client := NewClient(desktopEndpoint)
t.Cleanup(func() {
_ = client.Close()
@@ -40,7 +44,7 @@ func TestClientPing(t *testing.T) {
now := time.Now()
ret, err := client.Ping(t.Context())
ret, err := client.Ping(ctx)
require.NoError(t, err)
serverTime := time.Unix(0, ret.ServerTime)

View File

@@ -21,7 +21,7 @@ import (
"os"
"strconv"
"github.com/docker/compose/v5/internal/desktop"
"github.com/docker/compose/v2/internal/desktop"
)
// envComposeExperimentalGlobal can be set to a falsy value (e.g. 0, false) to

View File

@@ -29,11 +29,10 @@ import (
"github.com/containerd/containerd/v2/core/remotes"
pusherrors "github.com/containerd/containerd/v2/core/remotes/errors"
"github.com/distribution/reference"
"github.com/docker/compose/v2/pkg/api"
"github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/docker/compose/v5/pkg/api"
)
const (

View File

@@ -20,7 +20,7 @@ import (
"context"
"io"
"net/url"
"slices"
"os"
"strings"
"github.com/containerd/containerd/v2/core/remotes"
@@ -29,14 +29,13 @@ import (
"github.com/containerd/errdefs"
"github.com/distribution/reference"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/compose/v2/internal/registry"
"github.com/moby/buildkit/util/contentutil"
spec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/docker/compose/v5/internal/registry"
)
// NewResolver setup an OCI Resolver based on docker/cli config to provide registry credentials
func NewResolver(config *configfile.ConfigFile, insecureRegistries ...string) remotes.Resolver {
func NewResolver(config *configfile.ConfigFile) remotes.Resolver {
return docker.NewResolver(docker.ResolverOptions{
Hosts: docker.ConfigureDefaultRegistries(
docker.WithAuthorizer(docker.NewDockerAuthorizer(
@@ -52,9 +51,10 @@ func NewResolver(config *configfile.ConfigFile, insecureRegistries ...string) re
return auth.Username, auth.Password, nil
}),
)),
docker.WithPlainHTTP(func(domain string) (bool, error) {
// Should be used for testing **only**
return slices.Contains(insecureRegistries, domain), nil
docker.WithPlainHTTP(func(s string) (bool, error) {
// Used for testing **only**
_, b := os.LookupEnv("__TEST__INSECURE__REGISTRY__")
return b, nil
}),
),
})

View File

@@ -22,12 +22,11 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/context/store"
"github.com/docker/compose/v2/internal/memnet"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"github.com/docker/compose/v5/internal/memnet"
)
const otelConfigFieldName = "otel"
@@ -84,18 +83,18 @@ func ConfigFromDockerContext(st store.Store, name string) (OTLPConfig, error) {
return OTLPConfig{}, err
}
var otelCfg any
var otelCfg interface{}
switch m := meta.Metadata.(type) {
case command.DockerContext:
otelCfg = m.AdditionalFields[otelConfigFieldName]
case map[string]any:
case map[string]interface{}:
otelCfg = m[otelConfigFieldName]
}
if otelCfg == nil {
return OTLPConfig{}, nil
}
otelMap, ok := otelCfg.(map[string]any)
otelMap, ok := otelCfg.(map[string]interface{})
if !ok {
return OTLPConfig{}, fmt.Errorf(
"unexpected type for field %q: %T (expected: %T)",
@@ -115,7 +114,7 @@ func ConfigFromDockerContext(st store.Store, name string) (OTLPConfig, error) {
// valueOrDefault returns the type-cast value at the specified key in the map
// if present and the correct type; otherwise, it returns the default value for
// T.
func valueOrDefault[T any](m map[string]any, key string) T {
func valueOrDefault[T any](m map[string]interface{}, key string) T {
if v, ok := m[key].(T); ok {
return v
}

View File

@@ -23,19 +23,19 @@ import (
"os"
"strings"
"github.com/docker/compose/v2/internal"
"go.opentelemetry.io/otel/attribute"
"github.com/docker/cli/cli/command"
"github.com/moby/buildkit/util/tracing/detect"
_ "github.com/moby/buildkit/util/tracing/env" //nolint:blank-imports
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
"github.com/docker/compose/v5/internal"
)
func init() {

View File

@@ -23,12 +23,12 @@ import (
"github.com/docker/cli/cli/context/store"
"github.com/stretchr/testify/require"
"github.com/docker/compose/v5/internal/tracing"
"github.com/docker/compose/v2/internal/tracing"
)
var testStoreCfg = store.NewConfig(
func() any {
return &map[string]any{}
func() interface{} {
return &map[string]interface{}{}
},
)
@@ -44,13 +44,13 @@ func TestExtractOtelFromContext(t *testing.T) {
Name: "test",
Metadata: command.DockerContext{
Description: t.Name(),
AdditionalFields: map[string]any{
"otel": map[string]any{
AdditionalFields: map[string]interface{}{
"otel": map[string]interface{}{
"OTEL_EXPORTER_OTLP_ENDPOINT": "localhost:1234",
},
},
},
Endpoints: make(map[string]any),
Endpoints: make(map[string]interface{}),
})
require.NoError(t, err)

View File

@@ -24,59 +24,12 @@ import (
"strings"
"time"
"github.com/compose-spec/compose-go/v2/cli"
"github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/platforms"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/volume"
)
// LoadListener receives events during project loading.
// Events include:
// - "extends": when a service extends another (metadata: service info)
// - "include": when including external compose files (metadata: {"path": StringList})
//
// Multiple listeners can be registered, and all will be notified of events.
type LoadListener func(event string, metadata map[string]any)
// ProjectLoadOptions configures how a Compose project should be loaded
type ProjectLoadOptions struct {
// ProjectName to use, or empty to infer from directory
ProjectName string
// ConfigPaths are paths to compose files
ConfigPaths []string
// WorkingDir is the project directory
WorkingDir string
// EnvFiles are paths to .env files
EnvFiles []string
// Profiles to activate
Profiles []string
// Services to select (empty = all)
Services []string
// Offline mode disables remote resource loading
Offline bool
// All includes all resources (not just those used by services)
All bool
// Compatibility enables v1 compatibility mode
Compatibility bool
// ProjectOptionsFns are compose-go project options to apply.
// Use cli.WithInterpolation(false), cli.WithNormalization(false), etc.
// This is optional - pass nil or empty slice to use defaults.
ProjectOptionsFns []cli.ProjectOptionsFn
// LoadListeners receive events during project loading.
// All registered listeners will be notified of events.
// This is optional - pass nil or empty slice if not needed.
LoadListeners []LoadListener
OCI OCIOptions
}
type OCIOptions struct {
InsecureRegistries []string
}
// Compose is the API interface one can use to programmatically use docker/compose in a third-party software
// Use [compose.NewComposeService] to get an actual instance
type Compose interface {
@@ -130,6 +83,10 @@ type Compose interface {
Publish(ctx context.Context, project *types.Project, repository string, options PublishOptions) error
// Images executes the equivalent of a `compose images`
Images(ctx context.Context, projectName string, options ImagesOptions) (map[string]ImageSummary, error)
// MaxConcurrency defines upper limit for concurrent operations against engine API
MaxConcurrency(parallel int)
// DryRunMode defines if dry run applies to the command
DryRunMode(ctx context.Context, dryRun bool) (context.Context, error)
// Watch services' development context and sync/notify/rebuild/restart on changes
Watch(ctx context.Context, project *types.Project, options WatchOptions) error
// Viz generates a graphviz graph of the project services
@@ -146,8 +103,6 @@ type Compose interface {
Generate(ctx context.Context, options GenerateOptions) (*types.Project, error)
// Volumes executes the equivalent to a `docker volume ls`
Volumes(ctx context.Context, project string, options VolumesOptions) ([]VolumesSummary, error)
// LoadProject loads and validates a Compose project from configuration files.
LoadProject(ctx context.Context, options ProjectLoadOptions) (*types.Project, error)
}
type VolumesOptions struct {
@@ -277,6 +232,8 @@ type CreateOptions struct {
Timeout *time.Duration
// QuietPull makes the pulling process quiet
QuietPull bool
// AssumeYes assume "yes" as answer to all prompts and run non-interactively
AssumeYes bool
}
// StartOptions group options of the Start API
@@ -490,9 +447,9 @@ type PublishOptions struct {
ResolveImageDigests bool
Application bool
WithEnvironment bool
OCIVersion OCIVersion
// Use plain HTTP to access registry. Should only be used for testing purpose
InsecureRegistry bool
AssumeYes bool
OCIVersion OCIVersion
}
func (e Event) String() string {
@@ -603,7 +560,7 @@ type ImageSummary struct {
Tag string
Platform platforms.Platform
Size int64
Created *time.Time
Created time.Time
LastTagTime time.Time
}
@@ -742,7 +699,7 @@ const (
ContainerEventRecreated
// ContainerEventExited is a ContainerEvent of type exit. ExitCode is set
ContainerEventExited
// UserCancel user canceled compose up, we are stopping containers
// UserCancel user cancelled compose up, we are stopping containers
HookEventLog
)

View File

@@ -1,32 +0,0 @@
/*
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 api
// ContextInfo provides Docker context information for advanced scenarios
type ContextInfo interface {
// CurrentContext returns the name of the current Docker context
// Returns "default" for simple clients without context support
CurrentContext() string
// ServerOSType returns the Docker daemon's operating system (linux/windows/darwin)
// Used for OS-specific compatibility checks
ServerOSType() string
// BuildKitEnabled determines whether BuildKit should be used for builds
// Checks DOCKER_BUILDKIT env var, config, and daemon capabilities
BuildKitEnabled() (bool, error)
}

View File

@@ -14,7 +14,7 @@
limitations under the License.
*/
package dryrun
package api
import (
"bytes"
@@ -50,8 +50,14 @@ import (
specs "github.com/opencontainers/image-spec/specs-go/v1"
)
const (
DRYRUN_PREFIX = " DRY-RUN MODE - "
)
var _ client.APIClient = &DryRunClient{}
type DryRunKey struct{}
// DryRunClient implements APIClient by delegating to implementation functions. This allows lazy init and per-method overrides
type DryRunClient struct {
apiClient client.APIClient
@@ -188,17 +194,28 @@ func (d *DryRunClient) ContainerUnpause(ctx context.Context, container string) e
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)
return rc, containerType.PathStat{}, fmt.Errorf(" %s Could not find the file %s in container %s", DRYRUN_PREFIX, srcPath, container)
}
return rc, containerType.PathStat{}, nil
}
func (d *DryRunClient) CopyToContainer(ctx context.Context, container, path string, content io.Reader, options containerType.CopyToContainerOptions) error {
if _, err := d.ContainerStatPath(ctx, container, path); err != nil {
return fmt.Errorf(" %s Could not find the file %s in container %s", DRYRUN_PREFIX, path, container)
}
return nil
}
func (d *DryRunClient) ImageBuild(ctx context.Context, reader io.Reader, options build.ImageBuildOptions) (build.ImageBuildResponse, error) {
rc := io.NopCloser(bytes.NewReader(nil))
jsonMessage, err := json.Marshal(&jsonmessage.JSONMessage{
Status: fmt.Sprintf("%[1]sSuccessfully built: dryRunID\n%[1]sSuccessfully tagged: %[2]s\n", DRYRUN_PREFIX, options.Tags[0]),
Progress: &jsonmessage.JSONProgress{},
ID: "",
})
if err != nil {
return build.ImageBuildResponse{}, err
}
rc := io.NopCloser(bytes.NewReader(jsonMessage))
return build.ImageBuildResponse{
Body: rc,
@@ -306,14 +323,16 @@ func (d *DryRunClient) ContainerExecCreate(ctx context.Context, container string
}
func (d *DryRunClient) ContainerExecStart(ctx context.Context, execID string, config containerType.ExecStartOptions) error {
_, ok := d.execs.LoadAndDelete(execID)
v, ok := d.execs.LoadAndDelete(execID)
if !ok {
return fmt.Errorf("invalid exec ID %q", execID)
}
details := v.(execDetails)
fmt.Printf("%sExecuting command %q in %s (detached mode)\n", DRYRUN_PREFIX, details.command, details.container)
return nil
}
// Functions delegated to original APIClient (not used by Compose or not modifying the Compose stack)
// 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) {
return d.apiClient.ConfigList(ctx, options)

View File

@@ -35,7 +35,12 @@ var (
ErrForbidden = errors.New("forbidden")
// ErrUnknown is returned when the error type is unmapped
ErrUnknown = errors.New("unknown")
// ErrNotImplemented is returned when a backend doesn't implement an action
// ErrLoginFailed is returned when login failed
ErrLoginFailed = errors.New("login failed")
// ErrLoginRequired is returned when login is required for a specific action
ErrLoginRequired = errors.New("login required")
// ErrNotImplemented is returned when a backend doesn't implement
// an action
ErrNotImplemented = errors.New("not implemented")
// ErrUnsupportedFlag is returned when a backend doesn't support a flag
ErrUnsupportedFlag = errors.New("unsupported flag")
@@ -43,8 +48,9 @@ var (
ErrCanceled = errors.New("canceled")
// ErrParsingFailed is returned when a string cannot be parsed
ErrParsingFailed = errors.New("parsing failed")
// ErrNoResources is returned when operation didn't selected any resource
ErrNoResources = errors.New("no resources")
// ErrWrongContextType is returned when the caller tries to get a context
// with the wrong type
ErrWrongContextType = errors.New("wrong context type")
)
// IsNotFoundError returns true if the unwrapped error is ErrNotFound

View File

@@ -1,107 +0,0 @@
/*
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 api
import (
"context"
)
// EventStatus indicates the status of an action
type EventStatus int
const (
// Working means that the current task is working
Working EventStatus = iota
// Done means that the current task is done
Done
// Warning means that the current task has warning
Warning
// Error means that the current task has errored
Error
)
// ResourceCompose is a special resource ID used when event applies to all resources in the application
const ResourceCompose = "Compose"
const (
StatusError = "Error"
StatusCreating = "Creating"
StatusStarting = "Starting"
StatusStarted = "Started"
StatusWaiting = "Waiting"
StatusHealthy = "Healthy"
StatusExited = "Exited"
StatusRestarting = "Restarting"
StatusRestarted = "Restarted"
StatusRunning = "Running"
StatusCreated = "Created"
StatusStopping = "Stopping"
StatusStopped = "Stopped"
StatusKilling = "Killing"
StatusKilled = "Killed"
StatusRemoving = "Removing"
StatusRemoved = "Removed"
StatusBuilding = "Building"
StatusBuilt = "Built"
StatusPulling = "Pulling"
StatusPulled = "Pulled"
StatusCommitting = "Committing"
StatusCommitted = "Committed"
StatusCopying = "Copying"
StatusCopied = "Copied"
StatusExporting = "Exporting"
StatusExported = "Exported"
StatusDownloading = "Downloading"
StatusDownloadComplete = "Download complete"
StatusConfiguring = "Configuring"
StatusConfigured = "Configured"
)
// Resource represents status change and progress for a compose resource.
type Resource struct {
ID string
ParentID string
Text string
Details string
Status EventStatus
Current int64
Percent int
Total int64
}
func (e *Resource) StatusText() string {
switch e.Status {
case Working:
return "Working"
case Warning:
return "Warning"
case Done:
return "Done"
default:
return "Error"
}
}
// EventProcessor is notified about Compose operations and tasks
type EventProcessor interface {
// Start is triggered as a Compose operation is starting with context
Start(ctx context.Context, operation string)
// On notify about (sub)task and progress processing operation
On(events ...Resource)
// Done is triggered as a Compose operation completed
Done(operation string, success bool)
}

View File

@@ -16,5 +16,13 @@
package api
// ComposeCompatibility try to mimic compose v1 as much as possible
const ComposeCompatibility = "COMPOSE_COMPATIBILITY"
import (
"github.com/docker/cli/cli/streams"
)
// Streams defines the standard streams (stdin, stdout, stderr) used by the CLI.
type Streams interface {
Out() *streams.Out
Err() *streams.Out
In() *streams.In
}

View File

@@ -19,7 +19,7 @@ package api
import (
"github.com/hashicorp/go-version"
"github.com/docker/compose/v5/internal"
"github.com/docker/compose/v2/internal"
)
const (

View File

@@ -19,10 +19,9 @@ package api
import (
"testing"
"github.com/docker/compose/v2/internal"
"github.com/hashicorp/go-version"
"gotest.tools/v3/assert"
"github.com/docker/compose/v5/internal"
)
func TestComposeVersionInitialization(t *testing.T) {

View File

@@ -30,16 +30,14 @@ import (
"github.com/containerd/errdefs"
"github.com/docker/cli/cli/command"
cli "github.com/docker/cli/cli/command/container"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/utils"
"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/sirupsen/logrus"
"go.yaml.in/yaml/v4"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/utils"
"gopkg.in/yaml.v3"
)
type ConvertOptions struct {
@@ -86,17 +84,7 @@ func convert(ctx context.Context, dockerCli command.Cli, model map[string]any, o
return err
}
dir, err := os.MkdirTemp("", "compose-convert-*")
if err != nil {
return err
}
defer func() {
err := os.RemoveAll(dir)
if err != nil {
logrus.Warnf("failed to remove temp dir %s: %v", dir, err)
}
}()
dir := os.TempDir()
composeYaml := filepath.Join(dir, "compose.yaml")
err = os.WriteFile(composeYaml, raw, 0o600)
if err != nil {

View File

@@ -41,7 +41,7 @@ func (s *composeService) useAPISocket(project *types.Project) (*types.Project, e
return project, nil
}
if s.getContextInfo().ServerOSType() == "windows" {
if s.dockerCli.ServerInfo().OSType == "windows" {
return nil, errors.New("use_api_socket can't be used with a Windows Docker Engine")
}
@@ -49,7 +49,6 @@ func (s *composeService) useAPISocket(project *types.Project) (*types.Project, e
if err != nil {
return nil, fmt.Errorf("resolving credentials failed: %w", err)
}
newConfig := &configfile.ConfigFile{
AuthConfigs: creds,
}

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