mirror of
https://github.com/docker/compose.git
synced 2025-07-31 01:24:15 +02:00
Compare commits
313 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0dc9852c67 | ||
|
a478702236 | ||
|
2c12ad19db | ||
|
038ea8441a | ||
|
9e98e6101e | ||
|
52f04229c0 | ||
|
28895d0322 | ||
|
a926f7d717 | ||
|
fe046915eb | ||
|
adbd61e5d6 | ||
|
e37ac04329 | ||
|
cab2c2a44e | ||
|
1946de598d | ||
|
8e29a138aa | ||
|
3c8da0afee | ||
|
1b12c867c5 | ||
|
1a4fc55fd7 | ||
|
efc939dcee | ||
|
d6e9f79ba6 | ||
|
b4c44a431f | ||
|
fb5a8644c3 | ||
|
95660c5e5a | ||
|
f6ddd6ae88 | ||
|
4ae7066955 | ||
|
fd954f266c | ||
|
d62e21025c | ||
|
6a2d16bd10 | ||
|
4d47da6dc2 | ||
|
8f91793fb5 | ||
|
1d2223fb23 | ||
|
d4f6000712 | ||
|
c50d16cd78 | ||
|
3875e13fad | ||
|
c89f30170d | ||
|
41a9b91887 | ||
|
5fc2b2a71c | ||
|
b1cd40c316 | ||
|
362ab0733f | ||
|
f35d2cfb3b | ||
|
17ba6c7188 | ||
|
1c37f1abb6 | ||
|
485b6200ee | ||
|
8c17a35609 | ||
|
6b9667401a | ||
|
9a1e589ce8 | ||
|
5e147e852e | ||
|
29308cb97e | ||
|
0b0242d0ac | ||
|
5a704004d3 | ||
|
cb95910018 | ||
|
f42226e352 | ||
|
0cc3c7a550 | ||
|
f7ee9c8a0c | ||
|
35efa97b7d | ||
|
9e17a091be | ||
|
4bbc6c609f | ||
|
69f1430a49 | ||
|
7cf7c6414f | ||
|
0e0ed91a39 | ||
|
66524e7728 | ||
|
c626befee1 | ||
|
60ee6adcd2 | ||
|
8faf1eb808 | ||
|
3b0601b671 | ||
|
f42374bb18 | ||
|
b046a5ef72 | ||
|
1570c6c076 | ||
|
674e13fb6d | ||
|
6fa173124a | ||
|
2c69fc3d4d | ||
|
317ebcd3b0 | ||
|
5cf1f0e2a4 | ||
|
6198ed5bd2 | ||
|
00ccddbde8 | ||
|
63b441401e | ||
|
3a7982fe45 | ||
|
5430caa172 | ||
|
ee1b1e0a93 | ||
|
26e46d7cc8 | ||
|
a9e76943f6 | ||
|
b6a0df8d3c | ||
|
5a063b7510 | ||
|
ae49bba9be | ||
|
51acc58453 | ||
|
7c999d7f93 | ||
|
ad750d6143 | ||
|
fe382df507 | ||
|
6501d59efc | ||
|
33a782572f | ||
|
65803ea12e | ||
|
f613379373 | ||
|
3553aa26a6 | ||
|
257ea7b75d | ||
|
d219aa66f4 | ||
|
c9ebfad78e | ||
|
8e57362a0f | ||
|
29630f184e | ||
|
6514c680a5 | ||
|
3394bf031b | ||
|
832a08f579 | ||
|
aadce87b16 | ||
|
b3207c455d | ||
|
769b7391ba | ||
|
149b882ebf | ||
|
c97e40e2b8 | ||
|
22e23bd4dc | ||
|
2dde5faeb8 | ||
|
f7825a56bf | ||
|
4cf075ea0a | ||
|
4f491ffa98 | ||
|
ea1c26d22a | ||
|
9a5fa05ad6 | ||
|
276c229458 | ||
|
eef448dc64 | ||
|
343117233b | ||
|
f599a8cdd2 | ||
|
63b06f5563 | ||
|
1d34661e91 | ||
|
0f9e6ab832 | ||
|
15c9651a3a | ||
|
4893a8b9ad | ||
|
97530790fa | ||
|
213c03f99a | ||
|
ebd7b761f2 | ||
|
ea48480d80 | ||
|
8151b59288 | ||
|
ec49baca56 | ||
|
7b9ad96240 | ||
|
9b67a48c33 | ||
|
0d0e12cc85 | ||
|
92fafccfb2 | ||
|
fee8aee8f0 | ||
|
40f5786e68 | ||
|
61e44da936 | ||
|
0bf7d1ea25 | ||
|
80ace63dfb | ||
|
27e90a3fdf | ||
|
3ca75bdf55 | ||
|
eb3074bbda | ||
|
f4fc010d6b | ||
|
693b9ef078 | ||
|
046879a4a2 | ||
|
7c79b23005 | ||
|
ad4cbee498 | ||
|
60256a875c | ||
|
45bd60c33a | ||
|
cf89fd1aa1 | ||
|
23fef850b9 | ||
|
12b73bea73 | ||
|
2e71440bee | ||
|
d49a68ecbf | ||
|
be83f63f26 | ||
|
9a9227ce64 | ||
|
024f8ebdc5 | ||
|
8c622da20b | ||
|
bbb2b76a14 | ||
|
e45e58b3ec | ||
|
f52af4c868 | ||
|
a54814ff39 | ||
|
a2d7548ca9 | ||
|
8a2cb90a39 | ||
|
cc50ada725 | ||
|
5c74f07991 | ||
|
7e198ee6a3 | ||
|
0566431c64 | ||
|
4f6cc2a330 | ||
|
2352a4a016 | ||
|
1f076a3781 | ||
|
009a239510 | ||
|
3059574288 | ||
|
1229a69384 | ||
|
f2a88e02a0 | ||
|
7f9101845d | ||
|
944e5e67a1 | ||
|
23fc76a540 | ||
|
053d225824 | ||
|
93b597ccec | ||
|
4dcaf94c32 | ||
|
07e7619f4e | ||
|
ed81185c5c | ||
|
22f8a7009f | ||
|
91a0aa0265 | ||
|
7cea455c4d | ||
|
559a51e590 | ||
|
480a556bf0 | ||
|
6263361190 | ||
|
9ee03c3fec | ||
|
4bf18d2325 | ||
|
f0f47a8aa8 | ||
|
d6e3fa6d74 | ||
|
16e83f002d | ||
|
2dbef234dc | ||
|
20f0ffec0b | ||
|
cee6a3c660 | ||
|
fc8c56b407 | ||
|
9c998a934f | ||
|
0403f0d76d | ||
|
91d04a5ca9 | ||
|
d2274ebe6c | ||
|
6e35652182 | ||
|
5bb46035cf | ||
|
f8dae06df8 | ||
|
955e4ed94e | ||
|
60385e6065 | ||
|
f5491328bb | ||
|
f46689a75e | ||
|
8fd0c297f5 | ||
|
f3bbfdae58 | ||
|
322c531a8c | ||
|
bf6b447263 | ||
|
a96c305b25 | ||
|
2d7cd2a999 | ||
|
cbb616ca0c | ||
|
640c7deae0 | ||
|
75b48cfc88 | ||
|
047899c3e6 | ||
|
f91b41875e | ||
|
42cccb1fe8 | ||
|
674af0d66d | ||
|
877d232330 | ||
|
4bba13233b | ||
|
a786e70b0e | ||
|
13cd780f30 | ||
|
8e2f799cd7 | ||
|
2a84dfecfd | ||
|
f6913b0866 | ||
|
d629fffa9e | ||
|
7471e16d85 | ||
|
51907d9f72 | ||
|
a3f88a0a1d | ||
|
c83f1285a8 | ||
|
29e642e232 | ||
|
0c37c10964 | ||
|
43cc2be8ca | ||
|
01e83defc2 | ||
|
846161d447 | ||
|
0bcc629fbe | ||
|
482b622282 | ||
|
ee33143026 | ||
|
cb0b5f6e27 | ||
|
1384853538 | ||
|
096b1e32d3 | ||
|
bf71138df6 | ||
|
a1f673dcf5 | ||
|
02c747a7de | ||
|
88f4f265db | ||
|
e67348222f | ||
|
b543380708 | ||
|
2e75185a07 | ||
|
7bedb5a02c | ||
|
f9cd4d0b1d | ||
|
0badcf3c8d | ||
|
ec49db98d4 | ||
|
e5a353b34d | ||
|
43e456145c | ||
|
75368c7859 | ||
|
6e814eac35 | ||
|
a0d1c3f944 | ||
|
0c5bd16da1 | ||
|
b0badf1eb0 | ||
|
342a2a9e71 | ||
|
7814e5798c | ||
|
42b2e11094 | ||
|
6a8c0988cf | ||
|
9129abe516 | ||
|
f38f3f754c | ||
|
ea07ba8e2a | ||
|
432ae23b0e | ||
|
b6f313b8a5 | ||
|
13618756dc | ||
|
6c1e21572a | ||
|
33e863ac6c | ||
|
f70209cf15 | ||
|
62e832eb50 | ||
|
80e8fda14f | ||
|
375a279785 | ||
|
a766e1669a | ||
|
793c6f1715 | ||
|
8e3e1f7f8b | ||
|
83cafe2838 | ||
|
55b5f233c2 | ||
|
c3a0c35681 | ||
|
8615e9a7c1 | ||
|
b23728941d | ||
|
1a7343bc88 | ||
|
41e6094041 | ||
|
66a47169d5 | ||
|
4c72d3a0e3 | ||
|
59f39b9990 | ||
|
7ab65ba127 | ||
|
d9f05d72d2 | ||
|
7b88c5b0ed | ||
|
eaf9800948 | ||
|
4c2ecb542f | ||
|
bcd000ab40 | ||
|
8092ce9414 | ||
|
97595066e3 | ||
|
508309414f | ||
|
b6c8a2b9fc | ||
|
19571c2c81 | ||
|
0ef7bbcddc | ||
|
66dfa7d181 | ||
|
876ecc48be | ||
|
c7bf302c23 | ||
|
7b3bdbe037 | ||
|
094b48fd74 | ||
|
43c52e2a80 | ||
|
6c1ee1069b | ||
|
e38b729a30 | ||
|
145bb8466d | ||
|
acac184135 | ||
|
3292740c19 | ||
|
cae8e84636 |
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -12,6 +12,12 @@ body:
|
||||
Include both the current behavior (what you are seeing) as well as what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
[Docker Swarm](https://www.mirantis.com/software/swarm/) uses a distinct compose file parser and
|
||||
as such doesn't support some of the recent features of Docker Compose. Please contact Mirantis
|
||||
if you need assistance with compose file support in Docker Swarm.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
|
15
.github/workflows/ci.yml
vendored
15
.github/workflows/ci.yml
vendored
@ -77,8 +77,10 @@ jobs:
|
||||
-
|
||||
name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
platform=${MATRIX_PLATFORM}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
env:
|
||||
MATRIX_PLATFORM: ${{ matrix.platform }}
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
@ -181,6 +183,11 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Set up Docker Model
|
||||
run: |
|
||||
sudo apt-get install docker-model-plugin
|
||||
docker model version
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
@ -188,6 +195,9 @@ jobs:
|
||||
check-latest: true
|
||||
cache: true
|
||||
|
||||
- name: Build example provider
|
||||
run: make example-provider
|
||||
|
||||
- name: Build
|
||||
uses: docker/bake-action@v6
|
||||
with:
|
||||
@ -304,9 +314,6 @@ jobs:
|
||||
echo "$sum $file" > ${file#\*}.sha256
|
||||
fi
|
||||
done
|
||||
-
|
||||
name: License
|
||||
run: cp packaging/* ./bin/release/
|
||||
-
|
||||
name: List artifacts
|
||||
run: |
|
||||
|
9
.github/workflows/merge.yml
vendored
9
.github/workflows/merge.yml
vendored
@ -79,6 +79,15 @@ jobs:
|
||||
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
|
||||
|
15
.github/workflows/scorecards.yml
vendored
15
.github/workflows/scorecards.yml
vendored
@ -7,9 +7,6 @@ on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecards analysis
|
||||
@ -19,6 +16,18 @@ jobs:
|
||||
security-events: write
|
||||
# Used to receive a badge.
|
||||
id-token: write
|
||||
# read permissions to all the other objects
|
||||
actions: read
|
||||
attestations: read
|
||||
checks: read
|
||||
contents: read
|
||||
deployments: read
|
||||
issues: read
|
||||
discussions: read
|
||||
packages: read
|
||||
pages: read
|
||||
pull-requests: read
|
||||
statuses: read
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
|
122
.golangci.yml
122
.golangci.yml
@ -1,9 +1,8 @@
|
||||
version: "2"
|
||||
run:
|
||||
concurrency: 2
|
||||
timeout: 10m
|
||||
linters:
|
||||
enable-all: false
|
||||
disable-all: true
|
||||
default: none
|
||||
enable:
|
||||
- copyloopvar
|
||||
- depguard
|
||||
@ -11,73 +10,80 @@ linters:
|
||||
- errorlint
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- gofumpt
|
||||
- goimports
|
||||
- gomodguard
|
||||
- revive
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- revive
|
||||
- staticcheck
|
||||
- testifylint
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
linters-settings:
|
||||
revive:
|
||||
rules:
|
||||
- name: package-comments
|
||||
disabled: true
|
||||
depguard:
|
||||
rules:
|
||||
all:
|
||||
deny:
|
||||
- pkg: io/ioutil
|
||||
desc: 'io/ioutil package has been deprecated'
|
||||
- pkg: gopkg.in/yaml.v2
|
||||
desc: 'compose-go uses yaml.v3'
|
||||
gomodguard:
|
||||
blocked:
|
||||
modules:
|
||||
- github.com/pkg/errors:
|
||||
recommendations:
|
||||
- errors
|
||||
- fmt
|
||||
versions:
|
||||
- github.com/distribution/distribution:
|
||||
reason: "use distribution/reference"
|
||||
- gotest.tools:
|
||||
version: "< 3.0.0"
|
||||
reason: "deprecated, pre-modules version"
|
||||
gocritic:
|
||||
# Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks.
|
||||
# Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- opinionated
|
||||
- style
|
||||
disabled-checks:
|
||||
- paramTypeCombine
|
||||
- unnamedResult
|
||||
- whyNoLint
|
||||
gocyclo:
|
||||
min-complexity: 16
|
||||
lll:
|
||||
line-length: 200
|
||||
settings:
|
||||
depguard:
|
||||
rules:
|
||||
all:
|
||||
deny:
|
||||
- pkg: io/ioutil
|
||||
desc: io/ioutil package has been deprecated
|
||||
- pkg: github.com/docker/docker/errdefs
|
||||
desc: use github.com/containerd/errdefs instead.
|
||||
- pkg: golang.org/x/exp/maps
|
||||
desc: use stdlib maps package
|
||||
- pkg: golang.org/x/exp/slices
|
||||
desc: use stdlib slices package
|
||||
- pkg: gopkg.in/yaml.v2
|
||||
desc: compose-go uses yaml.v3
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- paramTypeCombine
|
||||
- unnamedResult
|
||||
- whyNoLint
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- opinionated
|
||||
- style
|
||||
gocyclo:
|
||||
min-complexity: 16
|
||||
gomodguard:
|
||||
blocked:
|
||||
modules:
|
||||
- github.com/pkg/errors:
|
||||
recommendations:
|
||||
- errors
|
||||
- fmt
|
||||
versions:
|
||||
- github.com/distribution/distribution:
|
||||
reason: use distribution/reference
|
||||
- gotest.tools:
|
||||
version: < 3.0.0
|
||||
reason: deprecated, pre-modules version
|
||||
lll:
|
||||
line-length: 200
|
||||
revive:
|
||||
rules:
|
||||
- name: package-comments
|
||||
disabled: true
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
issues:
|
||||
# golangci hides some golint warnings (the warning about exported things
|
||||
# without documentation for example), this will make it show them anyway.
|
||||
exclude-use-default: false
|
||||
# Maximum issues count per one linter.
|
||||
# Set to 0 to disable.
|
||||
# Default: 50
|
||||
max-issues-per-linter: 0
|
||||
# Maximum count of issues with the same text.
|
||||
# Set to 0 to disable.
|
||||
# Default: 3
|
||||
max-same-issues: 0
|
||||
formatters:
|
||||
enable:
|
||||
- gofumpt
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
@ -15,9 +15,9 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
ARG GO_VERSION=1.22.10
|
||||
ARG GO_VERSION=1.23.10
|
||||
ARG XX_VERSION=1.6.1
|
||||
ARG GOLANGCI_LINT_VERSION=v1.63.4
|
||||
ARG GOLANGCI_LINT_VERSION=v2.0.2
|
||||
ARG ADDLICENSE_VERSION=v1.0.0
|
||||
|
||||
ARG BUILD_TAGS="e2e"
|
||||
|
6
Makefile
6
Makefile
@ -74,7 +74,7 @@ install: binary
|
||||
install $(or $(DESTDIR),./bin/build)/docker-compose ~/.docker/cli-plugins/docker-compose
|
||||
|
||||
.PHONY: e2e-compose
|
||||
e2e-compose: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
|
||||
e2e-compose: example-provider ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
|
||||
go run gotest.tools/gotestsum@latest --format testname --junitfile "/tmp/report/report.xml" -- -v $(TEST_FLAGS) -count=1 ./pkg/e2e
|
||||
|
||||
.PHONY: e2e-compose-standalone
|
||||
@ -87,6 +87,10 @@ build-and-e2e-compose: build e2e-compose ## Compile the compose cli-plugin and r
|
||||
.PHONY: build-and-e2e-compose-standalone
|
||||
build-and-e2e-compose-standalone: build e2e-compose-standalone ## Compile the compose cli-plugin and run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test
|
||||
|
||||
.PHONY: example-provider
|
||||
example-provider: ## build example provider for e2e tests
|
||||
go build -o bin/build/example-provider docs/examples/provider.go
|
||||
|
||||
.PHONY: mocks
|
||||
mocks:
|
||||
mockgen --version >/dev/null 2>&1 || go install go.uber.org/mock/mockgen@v0.4.0
|
||||
|
@ -23,6 +23,12 @@ your application are configured.
|
||||
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 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.
|
||||
|
||||
# Where to get Docker Compose
|
||||
|
||||
### Windows and macOS
|
||||
|
@ -55,7 +55,10 @@ func Setup(cmd *cobra.Command, dockerCli command.Cli, args []string) error {
|
||||
ctx,
|
||||
"cli/"+strings.Join(commandName(cmd), "-"),
|
||||
)
|
||||
cmdSpan.SetAttributes(attribute.StringSlice("cli.flags", getFlags(cmd.Flags())))
|
||||
cmdSpan.SetAttributes(
|
||||
attribute.StringSlice("cli.flags", getFlags(cmd.Flags())),
|
||||
attribute.Bool("cli.isatty", dockerCli.In().IsTerminal()),
|
||||
)
|
||||
|
||||
cmd.SetContext(ctx)
|
||||
wrapRunE(cmd, cmdSpan, tracingShutdown)
|
||||
@ -114,13 +117,14 @@ func wrapRunE(c *cobra.Command, cmdSpan trace.Span, tracingShutdown tracing.Shut
|
||||
}
|
||||
}
|
||||
|
||||
// commandName returns the path components for a given command.
|
||||
// commandName returns the path components for a given command,
|
||||
// in reverse alphabetical order for consistent usage metrics.
|
||||
//
|
||||
// The root Compose command and anything before (i.e. "docker")
|
||||
// are not included.
|
||||
//
|
||||
// For example:
|
||||
// - docker compose alpha watch -> [alpha, watch]
|
||||
// - docker compose alpha watch -> [watch, alpha]
|
||||
// - docker-compose up -> [up]
|
||||
func commandName(cmd *cobra.Command) []string {
|
||||
var name []string
|
||||
|
@ -20,6 +20,8 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
commands "github.com/docker/compose/v2/cmd/compose"
|
||||
"github.com/spf13/cobra"
|
||||
flag "github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
@ -61,3 +63,50 @@ func TestGetFlags(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupCmd func() *cobra.Command
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "docker compose alpha watch -> [watch, alpha]",
|
||||
setupCmd: func() *cobra.Command {
|
||||
dockerCmd := &cobra.Command{Use: "docker"}
|
||||
composeCmd := &cobra.Command{Use: commands.PluginName}
|
||||
alphaCmd := &cobra.Command{Use: "alpha"}
|
||||
watchCmd := &cobra.Command{Use: "watch"}
|
||||
|
||||
dockerCmd.AddCommand(composeCmd)
|
||||
composeCmd.AddCommand(alphaCmd)
|
||||
alphaCmd.AddCommand(watchCmd)
|
||||
|
||||
return watchCmd
|
||||
},
|
||||
want: []string{"watch", "alpha"},
|
||||
},
|
||||
{
|
||||
name: "docker-compose up -> [up]",
|
||||
setupCmd: func() *cobra.Command {
|
||||
dockerComposeCmd := &cobra.Command{Use: commands.PluginName}
|
||||
upCmd := &cobra.Command{Use: "up"}
|
||||
|
||||
dockerComposeCmd.AddCommand(upCmd)
|
||||
|
||||
return upCmd
|
||||
},
|
||||
want: []string{"up"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cmd := tt.setupCmd()
|
||||
got := commandName(cmd)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("commandName() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
149
cmd/compose/bridge.go
Normal file
149
cmd/compose/bridge.go
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
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"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/bridge"
|
||||
)
|
||||
|
||||
func bridgeCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "bridge CMD [OPTIONS]",
|
||||
Short: "Convert compose files into another model",
|
||||
TraverseChildren: true,
|
||||
}
|
||||
cmd.AddCommand(
|
||||
convertCommand(p, dockerCli),
|
||||
transformersCommand(dockerCli),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func convertCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
|
||||
convertOpts := bridge.ConvertOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "convert",
|
||||
Short: "Convert compose files to Kubernetes manifests, Helm charts, or another model",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runConvert(ctx, dockerCli, p, convertOpts)
|
||||
}),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.StringVarP(&convertOpts.Output, "output", "o", "out", "The output directory for the Kubernetes resources")
|
||||
flags.StringArrayVarP(&convertOpts.Transformations, "transformation", "t", nil, "Transformation to apply to compose model (default: docker/compose-bridge-kubernetes)")
|
||||
flags.StringVar(&convertOpts.Templates, "templates", "", "Directory containing transformation templates")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runConvert(ctx context.Context, dockerCli command.Cli, p *ProjectOptions, opts bridge.ConvertOptions) error {
|
||||
project, _, err := p.ToProject(ctx, dockerCli, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bridge.Convert(ctx, dockerCli, project, opts)
|
||||
}
|
||||
|
||||
func transformersCommand(dockerCli command.Cli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "transformations CMD [OPTIONS]",
|
||||
Short: "Manage transformation images",
|
||||
}
|
||||
cmd.AddCommand(
|
||||
listTransformersCommand(dockerCli),
|
||||
createTransformerCommand(dockerCli),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func listTransformersCommand(dockerCli command.Cli) *cobra.Command {
|
||||
options := lsOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Short: "List available transformations",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
transformers, err := bridge.ListTransformers(ctx, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return displayTransformer(dockerCli, transformers, options)
|
||||
}),
|
||||
}
|
||||
cmd.Flags().StringVar(&options.Format, "format", "table", "Format the output. Values: [table | json]")
|
||||
cmd.Flags().BoolVarP(&options.Quiet, "quiet", "q", false, "Only display transformer names")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func displayTransformer(dockerCli command.Cli, transformers []image.Summary, options lsOptions) error {
|
||||
if options.Quiet {
|
||||
for _, t := range transformers {
|
||||
if len(t.RepoTags) > 0 {
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), t.RepoTags[0])
|
||||
} else {
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), t.ID)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return formatter.Print(transformers, options.Format, dockerCli.Out(),
|
||||
func(w io.Writer) {
|
||||
for _, img := range transformers {
|
||||
id := stringid.TruncateID(img.ID)
|
||||
size := units.HumanSizeWithPrecision(float64(img.Size), 3)
|
||||
repo, tag := "<none>", "<none>"
|
||||
if len(img.RepoTags) > 0 {
|
||||
ref, err := reference.ParseDockerRef(img.RepoTags[0])
|
||||
if err == nil {
|
||||
// ParseDockerRef will reject a local image ID
|
||||
repo = reference.FamiliarName(ref)
|
||||
if tagged, ok := ref.(reference.Tagged); ok {
|
||||
tag = tagged.Tag()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", id, repo, tag, size)
|
||||
}
|
||||
},
|
||||
"IMAGE ID", "REPO", "TAGS", "SIZE")
|
||||
}
|
||||
|
||||
func createTransformerCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts bridge.CreateTransformerOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "create [OPTION] PATH",
|
||||
Short: "Create a new transformation",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
opts.Dest = args[0]
|
||||
return bridge.CreateTransformer(ctx, dockerCli, opts)
|
||||
}),
|
||||
}
|
||||
cmd.Flags().StringVarP(&opts.From, "from", "f", "", "Existing transformation to copy (default: docker/compose-bridge-kubernetes)")
|
||||
return cmd
|
||||
}
|
@ -27,7 +27,6 @@ import (
|
||||
"github.com/docker/cli/cli/command"
|
||||
cliopts "github.com/docker/cli/opts"
|
||||
ui "github.com/docker/compose/v2/pkg/progress"
|
||||
buildkit "github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@ -35,15 +34,19 @@ import (
|
||||
|
||||
type buildOptions struct {
|
||||
*ProjectOptions
|
||||
quiet bool
|
||||
pull bool
|
||||
push bool
|
||||
args []string
|
||||
noCache bool
|
||||
memory cliopts.MemBytes
|
||||
ssh string
|
||||
builder string
|
||||
deps bool
|
||||
quiet bool
|
||||
pull bool
|
||||
push bool
|
||||
args []string
|
||||
noCache bool
|
||||
memory cliopts.MemBytes
|
||||
ssh string
|
||||
builder string
|
||||
deps bool
|
||||
print bool
|
||||
check bool
|
||||
sbom string
|
||||
provenance string
|
||||
}
|
||||
|
||||
func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) {
|
||||
@ -67,17 +70,23 @@ func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions,
|
||||
if uiMode == ui.ModeJSON {
|
||||
uiMode = "rawjson"
|
||||
}
|
||||
|
||||
return api.BuildOptions{
|
||||
Pull: opts.pull,
|
||||
Push: opts.push,
|
||||
Progress: uiMode,
|
||||
Args: types.NewMappingWithEquals(opts.args),
|
||||
NoCache: opts.noCache,
|
||||
Quiet: opts.quiet,
|
||||
Services: services,
|
||||
Deps: opts.deps,
|
||||
SSHs: SSHKeys,
|
||||
Builder: builderName,
|
||||
Pull: opts.pull,
|
||||
Push: opts.push,
|
||||
Progress: uiMode,
|
||||
Args: types.NewMappingWithEquals(opts.args),
|
||||
NoCache: opts.noCache,
|
||||
Quiet: opts.quiet,
|
||||
Services: services,
|
||||
Deps: opts.deps,
|
||||
Memory: int64(opts.memory),
|
||||
Print: opts.print,
|
||||
Check: opts.check,
|
||||
SSHs: SSHKeys,
|
||||
Builder: builderName,
|
||||
SBOM: opts.sbom,
|
||||
Provenance: opts.provenance,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -112,12 +121,14 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVar(&opts.push, "push", false, "Push service images")
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Don't print anything to STDOUT")
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress the build output")
|
||||
flags.BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image")
|
||||
flags.StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services")
|
||||
flags.StringVar(&opts.ssh, "ssh", "", "Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)")
|
||||
flags.StringVar(&opts.builder, "builder", "", "Set builder to use")
|
||||
flags.BoolVar(&opts.deps, "with-dependencies", false, "Also build dependencies (transitively)")
|
||||
flags.StringVar(&opts.provenance, "provenance", "", `Add a provenance attestation`)
|
||||
flags.StringVar(&opts.sbom, "sbom", "", `Add a SBOM attestation`)
|
||||
|
||||
flags.Bool("parallel", true, "Build images in parallel. DEPRECATED")
|
||||
flags.MarkHidden("parallel") //nolint:errcheck
|
||||
@ -129,14 +140,17 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
flags.Bool("no-rm", false, "Do not remove intermediate containers after a successful build. DEPRECATED")
|
||||
flags.MarkHidden("no-rm") //nolint:errcheck
|
||||
flags.VarP(&opts.memory, "memory", "m", "Set memory limit for the build container. Not supported by BuildKit.")
|
||||
flags.StringVar(&p.Progress, "progress", string(buildkit.AutoMode), fmt.Sprintf(`Set type of ui output (%s)`, strings.Join(printerModes, ", ")))
|
||||
flags.StringVar(&p.Progress, "progress", "", fmt.Sprintf(`Set type of ui output (%s)`, strings.Join(printerModes, ", ")))
|
||||
flags.MarkHidden("progress") //nolint:errcheck
|
||||
flags.BoolVar(&opts.print, "print", false, "Print equivalent bake file")
|
||||
flags.BoolVar(&opts.check, "check", false, "Check build configuration")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, opts buildOptions, services []string) error {
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, services, cli.WithResolvedPaths(true), cli.WithoutEnvironmentResolution)
|
||||
opts.All = true // do not drop resources as build may involve some dependencies by additional_contexts
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, nil, cli.WithResolvedPaths(true), cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -146,10 +160,10 @@ func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, o
|
||||
}
|
||||
|
||||
apiBuildOptions, err := opts.toAPIBuildOptions(services)
|
||||
apiBuildOptions.Attestations = true
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
apiBuildOptions.Memory = int64(opts.memory)
|
||||
return backend.Build(ctx, project, apiBuildOptions)
|
||||
}
|
||||
|
@ -90,3 +90,13 @@ func completeProfileNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn
|
||||
return values, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
func completeScaleArgs(cli command.Cli, p *ProjectOptions) cobra.CompletionFunc {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
completions, directive := completeServiceNames(cli, p)(cmd, args, toComplete)
|
||||
for i, completion := range completions {
|
||||
completions[i] = completion + "="
|
||||
}
|
||||
return completions, directive
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,6 @@ import (
|
||||
ui "github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/remote"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
buildkit "github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/morikuni/aec"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@ -69,20 +68,21 @@ const (
|
||||
ComposeEnvFiles = "COMPOSE_ENV_FILES"
|
||||
// ComposeMenu defines if the navigation menu should be rendered. Can be also set via --menu
|
||||
ComposeMenu = "COMPOSE_MENU"
|
||||
// ComposeProgress defines type of progress output, if --progress isn't used
|
||||
ComposeProgress = "COMPOSE_PROGRESS"
|
||||
)
|
||||
|
||||
// rawEnv load a dot env file using docker/cli key=value parser, without attempt to interpolate or evaluate values
|
||||
func rawEnv(r io.Reader, filename string, lookup func(key string) (string, bool)) (map[string]string, error) {
|
||||
func rawEnv(r io.Reader, filename string, vars map[string]string, lookup func(key string) (string, bool)) error {
|
||||
lines, err := kvfile.ParseFromReader(r, lookup)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse env_file %s: %w", filename, err)
|
||||
return fmt.Errorf("failed to parse env_file %s: %w", filename, err)
|
||||
}
|
||||
vars := types.Mapping{}
|
||||
for _, line := range lines {
|
||||
key, value, _ := strings.Cut(line, "=")
|
||||
vars[key] = value
|
||||
}
|
||||
return vars, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -170,7 +170,7 @@ func (o *ProjectOptions) WithServices(dockerCli command.Cli, fn ProjectServicesF
|
||||
return Adapt(func(ctx context.Context, args []string) error {
|
||||
options := []cli.ProjectOptionsFn{
|
||||
cli.WithResolvedPaths(true),
|
||||
cli.WithDiscardEnvFile,
|
||||
cli.WithoutEnvironmentResolution,
|
||||
}
|
||||
|
||||
project, metrics, err := o.ToProject(ctx, dockerCli, args, options...)
|
||||
@ -180,6 +180,11 @@ func (o *ProjectOptions) WithServices(dockerCli command.Cli, fn ProjectServicesF
|
||||
|
||||
ctx = context.WithValue(ctx, tracing.MetricsKey{}, metrics)
|
||||
|
||||
project, err = project.WithServicesEnvironmentResolved(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fn(ctx, project, args)
|
||||
})
|
||||
}
|
||||
@ -224,7 +229,7 @@ func (o *ProjectOptions) addProjectFlags(f *pflag.FlagSet) {
|
||||
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)")
|
||||
f.BoolVar(&o.Compatibility, "compatibility", false, "Run compose in backward compatibility mode")
|
||||
f.StringVar(&o.Progress, "progress", string(buildkit.AutoMode), fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
|
||||
f.StringVar(&o.Progress, "progress", os.Getenv(ComposeProgress), fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
|
||||
f.BoolVar(&o.All, "all-resources", false, "Include all resources, even those not used by services")
|
||||
_ = f.MarkHidden("workdir")
|
||||
}
|
||||
@ -372,17 +377,24 @@ func (o *ProjectOptions) remoteLoaders(dockerCli command.Cli) []loader.ResourceL
|
||||
if o.Offline {
|
||||
return nil
|
||||
}
|
||||
git := remote.NewGitRemoteLoader(o.Offline)
|
||||
git := remote.NewGitRemoteLoader(dockerCli, o.Offline)
|
||||
oci := remote.NewOCIRemoteLoader(dockerCli, o.Offline)
|
||||
return []loader.ResourceLoader{git, oci}
|
||||
}
|
||||
|
||||
func (o *ProjectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cli.NewProjectOptions(o.ConfigPaths,
|
||||
append(po,
|
||||
cli.WithWorkingDirectory(o.ProjectDir),
|
||||
// First apply os.Environment, always win
|
||||
cli.WithOsEnv,
|
||||
// set PWD as this variable is not consistently supported on Windows
|
||||
cli.WithEnv([]string{"PWD=" + pwd}),
|
||||
// Load PWD/.env if present and no explicit --env-file has been set
|
||||
cli.WithEnvFiles(o.EnvFiles...),
|
||||
// read dot env file to populate project environment
|
||||
@ -495,8 +507,7 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
|
||||
}
|
||||
|
||||
switch opts.Progress {
|
||||
case ui.ModeAuto:
|
||||
ui.Mode = ui.ModeAuto
|
||||
case "", ui.ModeAuto:
|
||||
if ansi == "never" {
|
||||
ui.Mode = ui.ModePlain
|
||||
}
|
||||
@ -538,10 +549,7 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
|
||||
}
|
||||
|
||||
composeCmd := cmd
|
||||
for {
|
||||
if composeCmd.Name() == PluginName {
|
||||
break
|
||||
}
|
||||
for composeCmd.Name() != PluginName {
|
||||
if !composeCmd.HasParent() {
|
||||
return fmt.Errorf("error parsing command line, expected %q", PluginName)
|
||||
}
|
||||
@ -625,7 +633,10 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
|
||||
scaleCommand(&opts, dockerCli, backend),
|
||||
statsCommand(&opts, dockerCli),
|
||||
watchCommand(&opts, dockerCli, backend),
|
||||
publishCommand(&opts, dockerCli, backend),
|
||||
alphaCommand(&opts, dockerCli, backend),
|
||||
bridgeCommand(&opts, dockerCli),
|
||||
volumesCommand(&opts, dockerCli, backend),
|
||||
)
|
||||
|
||||
c.Flags().SetInterspersed(false)
|
||||
|
@ -47,14 +47,18 @@ type configOptions struct {
|
||||
noInterpolate bool
|
||||
noNormalize bool
|
||||
noResolvePath bool
|
||||
noResolveEnv bool
|
||||
services bool
|
||||
volumes bool
|
||||
networks bool
|
||||
models bool
|
||||
profiles bool
|
||||
images bool
|
||||
hash string
|
||||
noConsistency bool
|
||||
variables bool
|
||||
environment bool
|
||||
lockImageDigests bool
|
||||
}
|
||||
|
||||
func (o *configOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
|
||||
@ -84,9 +88,8 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Aliases: []string{"convert"}, // for backward compatibility with Cloud integrations
|
||||
Use: "config [OPTIONS] [SERVICE...]",
|
||||
Short: "Parse, resolve and render compose file in canonical format",
|
||||
Use: "config [OPTIONS] [SERVICE...]",
|
||||
Short: "Parse, resolve and render compose file in canonical format",
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
if opts.quiet {
|
||||
devnull, err := os.Open(os.DevNull)
|
||||
@ -98,6 +101,9 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
|
||||
if p.Compatibility {
|
||||
opts.noNormalize = true
|
||||
}
|
||||
if opts.lockImageDigests {
|
||||
opts.resolveImageDigests = true
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
@ -107,6 +113,12 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
|
||||
if opts.volumes {
|
||||
return runVolumes(ctx, dockerCli, opts)
|
||||
}
|
||||
if opts.networks {
|
||||
return runNetworks(ctx, dockerCli, opts)
|
||||
}
|
||||
if opts.models {
|
||||
return runModels(ctx, dockerCli, opts)
|
||||
}
|
||||
if opts.hash != "" {
|
||||
return runHash(ctx, dockerCli, opts)
|
||||
}
|
||||
@ -123,21 +135,28 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
|
||||
return runEnvironment(ctx, dockerCli, opts, args)
|
||||
}
|
||||
|
||||
if opts.Format == "" {
|
||||
opts.Format = "yaml"
|
||||
}
|
||||
return runConfig(ctx, dockerCli, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.StringVar(&opts.Format, "format", "yaml", "Format the output. Values: [yaml | json]")
|
||||
flags.StringVar(&opts.Format, "format", "", "Format the output. Values: [yaml | json]")
|
||||
flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests")
|
||||
flags.BoolVar(&opts.lockImageDigests, "lock-image-digests", false, "Produces an override file with image digests")
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only validate the configuration, don't print anything")
|
||||
flags.BoolVar(&opts.noInterpolate, "no-interpolate", false, "Don't interpolate environment variables")
|
||||
flags.BoolVar(&opts.noNormalize, "no-normalize", false, "Don't normalize compose model")
|
||||
flags.BoolVar(&opts.noResolvePath, "no-path-resolution", false, "Don't resolve file paths")
|
||||
flags.BoolVar(&opts.noConsistency, "no-consistency", false, "Don't check model consistency - warning: may produce invalid Compose output")
|
||||
flags.BoolVar(&opts.noResolveEnv, "no-env-resolution", false, "Don't resolve service env files")
|
||||
|
||||
flags.BoolVar(&opts.services, "services", false, "Print the service names, one per line.")
|
||||
flags.BoolVar(&opts.volumes, "volumes", false, "Print the volume names, one per line.")
|
||||
flags.BoolVar(&opts.networks, "networks", false, "Print the network names, one per line.")
|
||||
flags.BoolVar(&opts.models, "models", false, "Print the model names, one per line.")
|
||||
flags.BoolVar(&opts.profiles, "profiles", false, "Print the profile names, one per line.")
|
||||
flags.BoolVar(&opts.images, "images", false, "Print the image names, one per line.")
|
||||
flags.StringVar(&opts.hash, "hash", "", "Print the service config hash, one per line.")
|
||||
@ -190,6 +209,13 @@ func runConfigInterpolate(ctx context.Context, dockerCli command.Cli, opts confi
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.noResolveEnv {
|
||||
project, err = project.WithServicesEnvironmentResolved(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.noConsistency {
|
||||
err := project.CheckContainerNameUnicity()
|
||||
if err != nil {
|
||||
@ -197,6 +223,10 @@ func runConfigInterpolate(ctx context.Context, dockerCli command.Cli, opts confi
|
||||
}
|
||||
}
|
||||
|
||||
if opts.lockImageDigests {
|
||||
project = imagesOnly(project)
|
||||
}
|
||||
|
||||
var content []byte
|
||||
switch opts.Format {
|
||||
case "json":
|
||||
@ -212,6 +242,18 @@ func runConfigInterpolate(ctx context.Context, dockerCli command.Cli, opts confi
|
||||
return content, nil
|
||||
}
|
||||
|
||||
// imagesOnly return project with all attributes removed but service.images
|
||||
func imagesOnly(project *types.Project) *types.Project {
|
||||
digests := types.Services{}
|
||||
for name, config := range project.Services {
|
||||
digests[name] = types.ServiceConfig{
|
||||
Image: config.Image,
|
||||
}
|
||||
}
|
||||
project = &types.Project{Services: digests}
|
||||
return project
|
||||
}
|
||||
|
||||
func runConfigNoInterpolate(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) ([]byte, error) {
|
||||
// we can't use ToProject, so the model we render here is only partially resolved
|
||||
model, err := opts.ToModel(ctx, dockerCli, services)
|
||||
@ -226,6 +268,23 @@ func runConfigNoInterpolate(ctx context.Context, dockerCli command.Cli, opts con
|
||||
}
|
||||
}
|
||||
|
||||
if opts.lockImageDigests {
|
||||
for key, e := range model {
|
||||
if key != "services" {
|
||||
delete(model, key)
|
||||
} else {
|
||||
for _, s := range e.(map[string]any) {
|
||||
service := s.(map[string]any)
|
||||
for key := range service {
|
||||
if key != "image" {
|
||||
delete(service, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return formatModel(model, opts.Format)
|
||||
}
|
||||
|
||||
@ -318,6 +377,30 @@ func runVolumes(ctx context.Context, dockerCli command.Cli, opts configOptions)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runNetworks(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
|
||||
project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for n := range project.Networks {
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runModels(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
|
||||
project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, model := range project.Models {
|
||||
if model.Model != "" {
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), model.Model)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runHash(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
|
||||
var services []string
|
||||
if opts.hash != "*" {
|
||||
@ -399,7 +482,16 @@ func runVariables(ctx context.Context, dockerCli command.Cli, opts configOptions
|
||||
|
||||
variables := template.ExtractVariables(model, template.DefaultPattern)
|
||||
|
||||
return formatter.Print(variables, "", dockerCli.Out(), func(w io.Writer) {
|
||||
if opts.Format == "yaml" {
|
||||
result, err := yaml.Marshal(variables)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Print(string(result))
|
||||
return nil
|
||||
}
|
||||
|
||||
return formatter.Print(variables, opts.Format, dockerCli.Out(), func(w io.Writer) {
|
||||
for name, variable := range variables {
|
||||
_, _ = fmt.Fprintf(w, "%s\t%t\t%s\t%s\n", name, variable.Required, variable.DefaultValue, variable.PresenceValue)
|
||||
}
|
||||
|
@ -26,7 +26,9 @@ import (
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
@ -81,7 +83,15 @@ func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
|
||||
flags.BoolVar(&opts.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
|
||||
flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file")
|
||||
flags.StringArrayVar(&opts.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
|
||||
flags.BoolVarP(&opts.AssumeYes, "y", "y", false, `Assume "yes" as answer to all prompts and run non-interactively`)
|
||||
flags.BoolVarP(&opts.AssumeYes, "yes", "y", false, `Assume "yes" as answer to all prompts and run non-interactively`)
|
||||
flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
// assumeYes was introduced by mistake as `--y`
|
||||
if name == "y" {
|
||||
logrus.Warn("--y is deprecated, please use --yes instead")
|
||||
name = "yes"
|
||||
}
|
||||
return pflag.NormalizedName(name)
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,9 @@ import (
|
||||
|
||||
type eventsOpts struct {
|
||||
*composeOptions
|
||||
json bool
|
||||
json bool
|
||||
since string
|
||||
until string
|
||||
}
|
||||
|
||||
func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
@ -48,6 +50,8 @@ func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&opts.json, "json", false, "Output events as a stream of json objects")
|
||||
cmd.Flags().StringVar(&opts.since, "since", "", "Show all events created since timestamp")
|
||||
cmd.Flags().StringVar(&opts.until, "until", "", "Stream events until this timestamp")
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -59,6 +63,8 @@ func runEvents(ctx context.Context, dockerCli command.Cli, backend api.Service,
|
||||
|
||||
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]interface{}{
|
||||
|
@ -18,12 +18,16 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
@ -59,7 +63,15 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runExec(ctx, dockerCli, backend, opts)
|
||||
err := runExec(ctx, dockerCli, backend, opts)
|
||||
if err != nil {
|
||||
logrus.Debugf("%v", err)
|
||||
var cliError cli.StatusError
|
||||
if ok := errors.As(err, &cliError); ok {
|
||||
os.Exit(err.(cli.StatusError).StatusCode) //nolint: errorlint
|
||||
}
|
||||
}
|
||||
return err
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
@ -86,7 +98,7 @@ func runExec(ctx context.Context, dockerCli command.Cli, backend api.Service, op
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
projectOptions, err := opts.composeOptions.toProjectOptions()
|
||||
projectOptions, err := opts.composeOptions.toProjectOptions() //nolint:staticcheck
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -109,8 +121,8 @@ func runExec(ctx context.Context, dockerCli command.Cli, backend api.Service, op
|
||||
|
||||
exitCode, err := backend.Exec(ctx, projectName, execOpts)
|
||||
if exitCode != 0 {
|
||||
errMsg := ""
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("exit status %d", exitCode)
|
||||
if err != nil && err.Error() != "" {
|
||||
errMsg = err.Error()
|
||||
}
|
||||
return cli.StatusError{StatusCode: exitCode, Status: errMsg}
|
||||
|
@ -20,9 +20,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/go-units"
|
||||
@ -30,7 +33,6 @@ import (
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
type imageOptions struct {
|
||||
@ -76,7 +78,7 @@ func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service,
|
||||
if i := strings.IndexRune(img.ID, ':'); i >= 0 {
|
||||
id = id[i+1:]
|
||||
}
|
||||
if !utils.StringContains(ids, id) {
|
||||
if !slices.Contains(ids, id) {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
@ -85,14 +87,42 @@ func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if opts.Format == "json" {
|
||||
|
||||
sort.Slice(images, func(i, j int) bool {
|
||||
return images[i].ContainerName < images[j].ContainerName
|
||||
})
|
||||
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"`
|
||||
LastTagTime time.Time `json:"LastTagTime"`
|
||||
}
|
||||
// Convert map to slice
|
||||
var imageList []img
|
||||
for ctr, i := range images {
|
||||
imageList = append(imageList, img{
|
||||
ContainerName: ctr,
|
||||
ID: i.ID,
|
||||
Repository: i.Repository,
|
||||
Tag: i.Tag,
|
||||
Platform: platforms.Format(i.Platform),
|
||||
Size: i.Size,
|
||||
LastTagTime: i.LastTagTime,
|
||||
})
|
||||
}
|
||||
json, err := formatter.ToJSON(imageList, "", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintln(dockerCli.Out(), json)
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Print(images, opts.Format, dockerCli.Out(),
|
||||
func(w io.Writer) {
|
||||
for _, img := range images {
|
||||
for _, container := range slices.Sorted(maps.Keys(images)) {
|
||||
img := images[container]
|
||||
id := stringid.TruncateID(img.ID)
|
||||
size := units.HumanSizeWithPrecision(float64(img.Size), 3)
|
||||
repo := img.Repository
|
||||
@ -103,8 +133,10 @@ func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service,
|
||||
if tag == "" {
|
||||
tag = "<none>"
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", img.ContainerName, repo, tag, id, size)
|
||||
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)
|
||||
}
|
||||
},
|
||||
"CONTAINER", "REPOSITORY", "TAG", "IMAGE ID", "SIZE")
|
||||
"CONTAINER", "REPOSITORY", "TAG", "PLATFORM", "IMAGE ID", "SIZE", "CREATED")
|
||||
}
|
||||
|
@ -17,10 +17,22 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/cli"
|
||||
"github.com/compose-spec/compose-go/v2/template"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/internal/tracing"
|
||||
ui "github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/prompt"
|
||||
)
|
||||
|
||||
func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
|
||||
@ -32,7 +44,7 @@ func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
|
||||
|
||||
// default platform only applies if the service doesn't specify
|
||||
if defaultPlatform != "" && service.Platform == "" {
|
||||
if len(service.Build.Platforms) > 0 && !utils.StringContains(service.Build.Platforms, defaultPlatform) {
|
||||
if len(service.Build.Platforms) > 0 && !slices.Contains(service.Build.Platforms, defaultPlatform) {
|
||||
return fmt.Errorf("service %q build.platforms does not support value set by DOCKER_DEFAULT_PLATFORM: %s", name, defaultPlatform)
|
||||
}
|
||||
service.Platform = defaultPlatform
|
||||
@ -40,7 +52,7 @@ func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
|
||||
|
||||
if service.Platform != "" {
|
||||
if len(service.Build.Platforms) > 0 {
|
||||
if !utils.StringContains(service.Build.Platforms, service.Platform) {
|
||||
if !slices.Contains(service.Build.Platforms, service.Platform) {
|
||||
return fmt.Errorf("service %q build configuration does not support platform: %s", name, service.Platform)
|
||||
}
|
||||
}
|
||||
@ -72,3 +84,208 @@ func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isRemoteConfig checks if the main compose file is from a remote source (OCI or Git)
|
||||
func isRemoteConfig(dockerCli command.Cli, options buildOptions) bool {
|
||||
if len(options.ConfigPaths) == 0 {
|
||||
return false
|
||||
}
|
||||
remoteLoaders := options.remoteLoaders(dockerCli)
|
||||
for _, loader := range remoteLoaders {
|
||||
if loader.Accept(options.ConfigPaths[0]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// checksForRemoteStack handles environment variable prompts for remote configurations
|
||||
func checksForRemoteStack(ctx context.Context, dockerCli command.Cli, project *types.Project, options buildOptions, assumeYes bool, cmdEnvs []string) error {
|
||||
if !isRemoteConfig(dockerCli, options) {
|
||||
return nil
|
||||
}
|
||||
if metrics, ok := ctx.Value(tracing.MetricsKey{}).(tracing.Metrics); ok && metrics.CountIncludesRemote > 0 {
|
||||
if err := confirmRemoteIncludes(dockerCli, options, assumeYes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
displayLocationRemoteStack(dockerCli, project, options)
|
||||
return promptForInterpolatedVariables(ctx, dockerCli, options.ProjectOptions, assumeYes, cmdEnvs)
|
||||
}
|
||||
|
||||
// Prepare the values map and collect all variables info
|
||||
type varInfo struct {
|
||||
name string
|
||||
value string
|
||||
source string
|
||||
required bool
|
||||
defaultValue string
|
||||
}
|
||||
|
||||
// promptForInterpolatedVariables displays all variables and their values at once,
|
||||
// then prompts for confirmation
|
||||
func promptForInterpolatedVariables(ctx context.Context, dockerCli command.Cli, projectOptions *ProjectOptions, assumeYes bool, cmdEnvs []string) error {
|
||||
if assumeYes {
|
||||
return nil
|
||||
}
|
||||
|
||||
varsInfo, noVariables, err := extractInterpolationVariablesFromModel(ctx, dockerCli, projectOptions, cmdEnvs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if noVariables {
|
||||
return nil
|
||||
}
|
||||
|
||||
displayInterpolationVariables(dockerCli.Out(), varsInfo)
|
||||
|
||||
// Prompt for confirmation
|
||||
userInput := prompt.NewPrompt(dockerCli.In(), dockerCli.Out())
|
||||
msg := "\nDo you want to proceed with these variables? [Y/n]: "
|
||||
confirmed, err := userInput.Confirm(msg, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !confirmed {
|
||||
return fmt.Errorf("operation cancelled by user")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractInterpolationVariablesFromModel(ctx context.Context, dockerCli command.Cli, projectOptions *ProjectOptions, cmdEnvs []string) ([]varInfo, bool, error) {
|
||||
cmdEnvMap := extractEnvCLIDefined(cmdEnvs)
|
||||
|
||||
// Create a model without interpolation to extract variables
|
||||
opts := configOptions{
|
||||
noInterpolate: true,
|
||||
ProjectOptions: projectOptions,
|
||||
}
|
||||
|
||||
model, err := opts.ToModel(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Extract variables that need interpolation
|
||||
variables := template.ExtractVariables(model, template.DefaultPattern)
|
||||
if len(variables) == 0 {
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
var varsInfo []varInfo
|
||||
proposedValues := make(map[string]string)
|
||||
|
||||
for name, variable := range variables {
|
||||
info := varInfo{
|
||||
name: name,
|
||||
required: variable.Required,
|
||||
defaultValue: variable.DefaultValue,
|
||||
}
|
||||
|
||||
// Determine value and source based on priority
|
||||
if value, exists := cmdEnvMap[name]; exists {
|
||||
info.value = value
|
||||
info.source = "command-line"
|
||||
proposedValues[name] = value
|
||||
} else if value, exists := os.LookupEnv(name); exists {
|
||||
info.value = value
|
||||
info.source = "environment"
|
||||
proposedValues[name] = value
|
||||
} else if variable.DefaultValue != "" {
|
||||
info.value = variable.DefaultValue
|
||||
info.source = "compose file"
|
||||
proposedValues[name] = variable.DefaultValue
|
||||
} else {
|
||||
info.value = "<unset>"
|
||||
info.source = "none"
|
||||
}
|
||||
|
||||
varsInfo = append(varsInfo, info)
|
||||
}
|
||||
return varsInfo, false, nil
|
||||
}
|
||||
|
||||
func extractEnvCLIDefined(cmdEnvs []string) map[string]string {
|
||||
// Parse command-line environment variables
|
||||
cmdEnvMap := make(map[string]string)
|
||||
for _, env := range cmdEnvs {
|
||||
parts := strings.SplitN(env, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
cmdEnvMap[parts[0]] = parts[1]
|
||||
}
|
||||
}
|
||||
return cmdEnvMap
|
||||
}
|
||||
|
||||
func displayInterpolationVariables(writer io.Writer, varsInfo []varInfo) {
|
||||
// Display all variables in a table format
|
||||
_, _ = fmt.Fprintln(writer, "\nFound the following variables in configuration:")
|
||||
|
||||
w := tabwriter.NewWriter(writer, 0, 0, 3, ' ', 0)
|
||||
_, _ = fmt.Fprintln(w, "VARIABLE\tVALUE\tSOURCE\tREQUIRED\tDEFAULT")
|
||||
sort.Slice(varsInfo, func(a, b int) bool {
|
||||
return varsInfo[a].name < varsInfo[b].name
|
||||
})
|
||||
for _, info := range varsInfo {
|
||||
required := "no"
|
||||
if info.required {
|
||||
required = "yes"
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n",
|
||||
info.name,
|
||||
info.value,
|
||||
info.source,
|
||||
required,
|
||||
info.defaultValue,
|
||||
)
|
||||
}
|
||||
_ = w.Flush()
|
||||
}
|
||||
|
||||
func displayLocationRemoteStack(dockerCli command.Cli, project *types.Project, options buildOptions) {
|
||||
mainComposeFile := options.ProjectOptions.ConfigPaths[0] //nolint:staticcheck
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func confirmRemoteIncludes(dockerCli command.Cli, options buildOptions, assumeYes bool) error {
|
||||
if assumeYes {
|
||||
return nil
|
||||
}
|
||||
|
||||
var remoteIncludes []string
|
||||
remoteLoaders := options.ProjectOptions.remoteLoaders(dockerCli) //nolint:staticcheck
|
||||
for _, cf := range options.ProjectOptions.ConfigPaths { //nolint:staticcheck
|
||||
for _, loader := range remoteLoaders {
|
||||
if loader.Accept(cf) {
|
||||
remoteIncludes = append(remoteIncludes, cf)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(remoteIncludes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), "\nWarning: This Compose project includes files from remote sources:")
|
||||
for _, include := range remoteIncludes {
|
||||
_, _ = fmt.Fprintf(dockerCli.Out(), " - %s\n", include)
|
||||
}
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), "\nRemote includes could potentially be malicious. Make sure you trust the source.")
|
||||
|
||||
msg := "Do you want to continue? [y/N]: "
|
||||
confirmed, err := prompt.NewPrompt(dockerCli.In(), dockerCli.Out()).Confirm(msg, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !confirmed {
|
||||
return fmt.Errorf("operation cancelled by user")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -17,10 +17,20 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestApplyPlatforms_InferFromRuntime(t *testing.T) {
|
||||
@ -128,3 +138,257 @@ func TestApplyPlatforms_UnsupportedPlatform(t *testing.T) {
|
||||
`service "test" build.platforms does not support value set by DOCKER_DEFAULT_PLATFORM: commodore/64`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIsRemoteConfig(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
cli := mocks.NewMockCli(ctrl)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
configPaths []string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "empty config paths",
|
||||
configPaths: []string{},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "local file",
|
||||
configPaths: []string{"docker-compose.yaml"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "OCI reference",
|
||||
configPaths: []string{"oci://registry.example.com/stack:latest"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "GIT reference",
|
||||
configPaths: []string{"git://github.com/user/repo.git"},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
opts := buildOptions{
|
||||
ProjectOptions: &ProjectOptions{
|
||||
ConfigPaths: tt.configPaths,
|
||||
},
|
||||
}
|
||||
got := isRemoteConfig(cli, opts)
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisplayLocationRemoteStack(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
cli := mocks.NewMockCli(ctrl)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
cli.EXPECT().Out().Return(streams.NewOut(buf)).AnyTimes()
|
||||
|
||||
project := &types.Project{
|
||||
Name: "test-project",
|
||||
WorkingDir: "/tmp/test",
|
||||
}
|
||||
|
||||
options := buildOptions{
|
||||
ProjectOptions: &ProjectOptions{
|
||||
ConfigPaths: []string{"oci://registry.example.com/stack:latest"},
|
||||
},
|
||||
}
|
||||
|
||||
displayLocationRemoteStack(cli, project, options)
|
||||
|
||||
output := buf.String()
|
||||
require.Equal(t, output, fmt.Sprintf("Your compose stack %q is stored in %q\n", "oci://registry.example.com/stack:latest", "/tmp/test"))
|
||||
}
|
||||
|
||||
func TestDisplayInterpolationVariables(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
// 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 := `
|
||||
services:
|
||||
app:
|
||||
image: nginx
|
||||
environment:
|
||||
- TEST_VAR=${TEST_VAR:?required} # required with default
|
||||
- API_KEY=${API_KEY:?} # required without default
|
||||
- DEBUG=${DEBUG:-true} # optional with default
|
||||
- UNSET_VAR # optional without default
|
||||
`
|
||||
composePath := filepath.Join(tmpDir, "docker-compose.yml")
|
||||
err = os.WriteFile(composePath, []byte(composeContent), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
cli := mocks.NewMockCli(ctrl)
|
||||
cli.EXPECT().Out().Return(streams.NewOut(buf)).AnyTimes()
|
||||
|
||||
// Create ProjectOptions with the temporary compose file
|
||||
projectOptions := &ProjectOptions{
|
||||
ConfigPaths: []string{composePath},
|
||||
}
|
||||
|
||||
// Set up the context with necessary environment variables
|
||||
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(ctx, cli, projectOptions, []string{})
|
||||
require.NoError(t, err)
|
||||
require.False(t, noVariables)
|
||||
|
||||
// Display the variables
|
||||
displayInterpolationVariables(cli.Out(), info)
|
||||
|
||||
// Expected output format with proper spacing
|
||||
expected := "\nFound the following variables in configuration:\n" +
|
||||
"VARIABLE VALUE SOURCE REQUIRED DEFAULT\n" +
|
||||
"API_KEY 123456 environment yes \n" +
|
||||
"DEBUG true compose file no true\n" +
|
||||
"TEST_VAR test-value environment yes \n"
|
||||
|
||||
// Normalize spaces and newlines for comparison
|
||||
normalizeSpaces := func(s string) string {
|
||||
// Replace multiple spaces with a single space
|
||||
s = strings.Join(strings.Fields(strings.TrimSpace(s)), " ")
|
||||
return s
|
||||
}
|
||||
|
||||
actualOutput := buf.String()
|
||||
|
||||
// Compare normalized strings
|
||||
require.Equal(t,
|
||||
normalizeSpaces(expected),
|
||||
normalizeSpaces(actualOutput),
|
||||
"\nExpected:\n%s\nGot:\n%s", expected, actualOutput)
|
||||
}
|
||||
|
||||
func TestConfirmRemoteIncludes(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
cli := mocks.NewMockCli(ctrl)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
opts buildOptions
|
||||
assumeYes bool
|
||||
userInput string
|
||||
wantErr bool
|
||||
errMessage string
|
||||
wantPrompt bool
|
||||
wantOutput string
|
||||
}{
|
||||
{
|
||||
name: "no remote includes",
|
||||
opts: buildOptions{
|
||||
ProjectOptions: &ProjectOptions{
|
||||
ConfigPaths: []string{
|
||||
"docker-compose.yaml",
|
||||
"./local/path/compose.yaml",
|
||||
},
|
||||
},
|
||||
},
|
||||
assumeYes: false,
|
||||
wantErr: false,
|
||||
wantPrompt: false,
|
||||
},
|
||||
{
|
||||
name: "assume yes with remote includes",
|
||||
opts: buildOptions{
|
||||
ProjectOptions: &ProjectOptions{
|
||||
ConfigPaths: []string{
|
||||
"oci://registry.example.com/stack:latest",
|
||||
"git://github.com/user/repo.git",
|
||||
},
|
||||
},
|
||||
},
|
||||
assumeYes: true,
|
||||
wantErr: false,
|
||||
wantPrompt: false,
|
||||
},
|
||||
{
|
||||
name: "user confirms remote includes",
|
||||
opts: buildOptions{
|
||||
ProjectOptions: &ProjectOptions{
|
||||
ConfigPaths: []string{
|
||||
"oci://registry.example.com/stack:latest",
|
||||
"git://github.com/user/repo.git",
|
||||
},
|
||||
},
|
||||
},
|
||||
assumeYes: false,
|
||||
userInput: "y\n",
|
||||
wantErr: false,
|
||||
wantPrompt: true,
|
||||
wantOutput: "\nWarning: This Compose project includes files from remote sources:\n" +
|
||||
" - oci://registry.example.com/stack:latest\n" +
|
||||
" - git://github.com/user/repo.git\n" +
|
||||
"\nRemote includes could potentially be malicious. Make sure you trust the source.\n" +
|
||||
"Do you want to continue? [y/N]: ",
|
||||
},
|
||||
{
|
||||
name: "user rejects remote includes",
|
||||
opts: buildOptions{
|
||||
ProjectOptions: &ProjectOptions{
|
||||
ConfigPaths: []string{
|
||||
"oci://registry.example.com/stack:latest",
|
||||
},
|
||||
},
|
||||
},
|
||||
assumeYes: false,
|
||||
userInput: "n\n",
|
||||
wantErr: true,
|
||||
errMessage: "operation cancelled by user",
|
||||
wantPrompt: true,
|
||||
wantOutput: "\nWarning: This Compose project includes files from remote sources:\n" +
|
||||
" - oci://registry.example.com/stack:latest\n" +
|
||||
"\nRemote includes could potentially be malicious. Make sure you trust the source.\n" +
|
||||
"Do you want to continue? [y/N]: ",
|
||||
},
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cli.EXPECT().Out().Return(streams.NewOut(buf)).AnyTimes()
|
||||
|
||||
if tt.wantPrompt {
|
||||
inbuf := io.NopCloser(bytes.NewBufferString(tt.userInput))
|
||||
cli.EXPECT().In().Return(streams.NewIn(inbuf)).AnyTimes()
|
||||
}
|
||||
|
||||
err := confirmRemoteIncludes(cli, tt.opts, tt.assumeYes)
|
||||
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, tt.errMessage, err.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
if tt.wantOutput != "" {
|
||||
require.Equal(t, tt.wantOutput, buf.String())
|
||||
}
|
||||
buf.Reset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -20,12 +20,12 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
cliformatter "github.com/docker/cli/cli/command/formatter"
|
||||
@ -101,7 +101,7 @@ func runPs(ctx context.Context, dockerCli command.Cli, backend api.Service, serv
|
||||
names := project.ServiceNames()
|
||||
if len(services) > 0 {
|
||||
for _, service := range services {
|
||||
if !utils.StringContains(names, service) {
|
||||
if !slices.Contains(names, service) {
|
||||
return fmt.Errorf("no such service: %s", service)
|
||||
}
|
||||
}
|
||||
@ -139,7 +139,7 @@ func runPs(ctx context.Context, dockerCli command.Cli, backend api.Service, serv
|
||||
services := []string{}
|
||||
for _, c := range containers {
|
||||
s := c.Service
|
||||
if !utils.StringContains(services, s) {
|
||||
if !slices.Contains(services, s) {
|
||||
services = append(services, s)
|
||||
}
|
||||
}
|
||||
|
@ -18,11 +18,14 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
type publishOptions struct {
|
||||
@ -43,23 +46,35 @@ func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPublish(ctx, dockerCli, backend, opts, args[0])
|
||||
}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Args: cli.ExactArgs(1),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests")
|
||||
flags.StringVar(&opts.ociVersion, "oci-version", "", "OCI image/artifact specification version (automatically determined by default)")
|
||||
flags.BoolVar(&opts.withEnvironment, "with-env", false, "Include environment variables in the published OCI artifact")
|
||||
flags.BoolVarP(&opts.assumeYes, "y", "y", false, `Assume "yes" as answer to all prompts`)
|
||||
flags.BoolVarP(&opts.assumeYes, "yes", "y", false, `Assume "yes" as answer to all prompts`)
|
||||
flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
// assumeYes was introduced by mistake as `--y`
|
||||
if name == "y" {
|
||||
logrus.Warn("--y is deprecated, please use --yes instead")
|
||||
name = "yes"
|
||||
}
|
||||
return pflag.NormalizedName(name)
|
||||
})
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runPublish(ctx context.Context, dockerCli command.Cli, backend api.Service, opts publishOptions, repository string) error {
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, nil)
|
||||
project, metrics, err := opts.ToProject(ctx, dockerCli, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if metrics.CountIncludesLocal > 0 {
|
||||
return errors.New("cannot publish compose file with local includes")
|
||||
}
|
||||
|
||||
return backend.Publish(ctx, project, repository, api.PublishOptions{
|
||||
ResolveImageDigests: opts.resolveImageDigests,
|
||||
OCIVersion: api.OCIVersion(opts.ociVersion),
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/cli"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/morikuni/aec"
|
||||
@ -97,7 +98,7 @@ func (opts pullOptions) apply(project *types.Project, services []string) (*types
|
||||
}
|
||||
|
||||
func runPull(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pullOptions, services []string) error {
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, services)
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -19,8 +19,10 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
@ -44,6 +46,7 @@ type runOptions struct {
|
||||
Service string
|
||||
Command []string
|
||||
environment []string
|
||||
envFiles []string
|
||||
Detach bool
|
||||
Remove bool
|
||||
noTty bool
|
||||
@ -64,6 +67,7 @@ type runOptions struct {
|
||||
noDeps bool
|
||||
ignoreOrphans bool
|
||||
removeOrphans bool
|
||||
quiet bool
|
||||
quietPull bool
|
||||
}
|
||||
|
||||
@ -116,6 +120,29 @@ func (options runOptions) apply(project *types.Project) (*types.Project, error)
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func (options runOptions) getEnvironment() (types.Mapping, error) {
|
||||
environment := types.NewMappingWithEquals(options.environment).Resolve(os.LookupEnv).ToMapping()
|
||||
for _, file := range options.envFiles {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vars, err := dotenv.ParseWithLookup(f, func(k string) (string, bool) {
|
||||
value, ok := environment[k]
|
||||
return value, ok
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
for k, v := range vars {
|
||||
if _, ok := environment[k]; !ok {
|
||||
environment[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return environment, nil
|
||||
}
|
||||
|
||||
func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
options := runOptions{
|
||||
composeOptions: &composeOptions{
|
||||
@ -154,11 +181,24 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
options.noTty = !options.tty
|
||||
}
|
||||
}
|
||||
if options.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 {
|
||||
project, _, err := p.ToProject(ctx, dockerCli, []string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithDiscardEnvFile)
|
||||
project, _, err := p.ToProject(ctx, dockerCli, []string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
project, err = project.WithServicesEnvironmentResolved(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -175,6 +215,7 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&options.Detach, "detach", "d", false, "Run container in background and print container ID")
|
||||
flags.StringArrayVarP(&options.environment, "env", "e", []string{}, "Set environment variables")
|
||||
flags.StringArrayVar(&options.envFiles, "env-from-file", []string{}, "Set environment variables from file")
|
||||
flags.StringArrayVarP(&options.labels, "label", "l", []string{}, "Add or override a label")
|
||||
flags.BoolVar(&options.Remove, "rm", false, "Automatically remove the container when it exits")
|
||||
flags.BoolVarP(&options.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected)")
|
||||
@ -190,6 +231,8 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
flags.BoolVar(&options.useAliases, "use-aliases", false, "Use the service's network useAliases in the network(s) the container connects to")
|
||||
flags.BoolVarP(&options.servicePorts, "service-ports", "P", false, "Run command with all service's ports enabled and mapped to the host")
|
||||
flags.StringVar(&createOpts.Pull, "pull", "policy", `Pull image before running ("always"|"missing"|"never")`)
|
||||
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Don't print anything to STDOUT")
|
||||
flags.BoolVar(&buildOpts.quiet, "quiet-build", false, "Suppress progress output from the build process")
|
||||
flags.BoolVar(&options.quietPull, "quiet-pull", false, "Pull without printing progress information")
|
||||
flags.BoolVar(&createOpts.Build, "build", false, "Build image before starting container")
|
||||
flags.BoolVar(&options.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file")
|
||||
@ -224,19 +267,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
return err
|
||||
}
|
||||
|
||||
err = progress.Run(ctx, func(ctx context.Context) error {
|
||||
var buildForDeps *api.BuildOptions
|
||||
if !createOpts.noBuild {
|
||||
// allow dependencies needing build to be implicitly selected
|
||||
bo, err := buildOpts.toAPIBuildOptions(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buildForDeps = &bo
|
||||
}
|
||||
return startDependencies(ctx, backend, *project, buildForDeps, options)
|
||||
}, dockerCli.Err())
|
||||
if err != nil {
|
||||
if err := checksForRemoteStack(ctx, dockerCli, project, buildOpts, createOpts.AssumeYes, []string{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -251,18 +282,26 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
|
||||
var buildForRun *api.BuildOptions
|
||||
if !createOpts.noBuild {
|
||||
// dependencies have already been started above, so only the service
|
||||
// being run might need to be built at this point
|
||||
bo, err := buildOpts.toAPIBuildOptions([]string{options.Service})
|
||||
bo, err := buildOpts.toAPIBuildOptions(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buildForRun = &bo
|
||||
}
|
||||
|
||||
environment, err := options.getEnvironment()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// start container and attach to container streams
|
||||
runOpts := api.RunOptions{
|
||||
Build: buildForRun,
|
||||
CreateOptions: api.CreateOptions{
|
||||
Build: buildForRun,
|
||||
RemoveOrphans: options.removeOrphans,
|
||||
IgnoreOrphans: options.ignoreOrphans,
|
||||
QuietPull: options.quietPull,
|
||||
},
|
||||
Name: options.name,
|
||||
Service: options.Service,
|
||||
Command: options.Command,
|
||||
@ -272,15 +311,14 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
Interactive: options.interactive,
|
||||
WorkingDir: options.workdir,
|
||||
User: options.user,
|
||||
CapAdd: options.capAdd.GetAll(),
|
||||
CapDrop: options.capDrop.GetAll(),
|
||||
Environment: options.environment,
|
||||
CapAdd: options.capAdd.GetSlice(),
|
||||
CapDrop: options.capDrop.GetSlice(),
|
||||
Environment: environment.Values(),
|
||||
Entrypoint: options.entrypointCmd,
|
||||
Labels: labels,
|
||||
UseNetworkAliases: options.useAliases,
|
||||
NoDeps: options.noDeps,
|
||||
Index: 0,
|
||||
QuietPull: options.quietPull,
|
||||
}
|
||||
|
||||
for name, service := range project.Services {
|
||||
@ -300,34 +338,3 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func startDependencies(ctx context.Context, backend api.Service, project types.Project, buildOpts *api.BuildOptions, options runOptions) error {
|
||||
dependencies := types.Services{}
|
||||
var requestedService types.ServiceConfig
|
||||
for name, service := range project.Services {
|
||||
if name != options.Service {
|
||||
dependencies[name] = service
|
||||
} else {
|
||||
requestedService = service
|
||||
}
|
||||
}
|
||||
|
||||
project.Services = dependencies
|
||||
project.DisabledServices[options.Service] = requestedService
|
||||
err := backend.Create(ctx, &project, api.CreateOptions{
|
||||
Build: buildOpts,
|
||||
IgnoreOrphans: options.ignoreOrphans,
|
||||
RemoveOrphans: options.removeOrphans,
|
||||
QuietPull: options.quietPull,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(dependencies) > 0 {
|
||||
return backend.Start(ctx, project.Name, api.StartOptions{
|
||||
Project: &project,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -19,14 +19,13 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -51,7 +50,7 @@ func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
return runScale(ctx, dockerCli, backend, opts, serviceTuples)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: completeScaleArgs(dockerCli, p),
|
||||
}
|
||||
flags := scaleCmd.Flags()
|
||||
flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services")
|
||||
@ -60,7 +59,7 @@ func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
|
||||
func runScale(ctx context.Context, dockerCli command.Cli, backend api.Service, opts scaleOptions, serviceReplicaTuples map[string]int) error {
|
||||
services := maps.Keys(serviceReplicaTuples)
|
||||
services := slices.Sorted(maps.Keys(serviceReplicaTuples))
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, services)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -49,6 +49,11 @@ func topCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
return topCmd
|
||||
}
|
||||
|
||||
type (
|
||||
topHeader map[string]int // maps a proc title to its output index
|
||||
topEntries map[string]string
|
||||
)
|
||||
|
||||
func runTop(ctx context.Context, dockerCli command.Cli, backend api.Service, opts topOptions, services []string) error {
|
||||
projectName, err := opts.toProjectName(ctx, dockerCli)
|
||||
if err != nil {
|
||||
@ -63,30 +68,76 @@ func runTop(ctx context.Context, dockerCli command.Cli, backend api.Service, opt
|
||||
return containers[i].Name < containers[j].Name
|
||||
})
|
||||
|
||||
for _, container := range containers {
|
||||
_, _ = fmt.Fprintf(dockerCli.Out(), "%s\n", container.Name)
|
||||
err := psPrinter(dockerCli.Out(), func(w io.Writer) {
|
||||
for _, proc := range container.Processes {
|
||||
info := []interface{}{}
|
||||
for _, p := range proc {
|
||||
info = append(info, p)
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, strings.Repeat("%s\t", len(info))+"\n", info...)
|
||||
header, entries := collectTop(containers)
|
||||
return topPrint(dockerCli.Out(), header, entries)
|
||||
}
|
||||
|
||||
func collectTop(containers []api.ContainerProcSummary) (topHeader, []topEntries) {
|
||||
// map column name to its header (should keep working if backend.Top returns
|
||||
// varying columns for different containers)
|
||||
header := topHeader{"SERVICE": 0, "#": 1}
|
||||
|
||||
// assume one process per container and grow if needed
|
||||
entries := make([]topEntries, 0, len(containers))
|
||||
|
||||
for _, container := range containers {
|
||||
for _, proc := range container.Processes {
|
||||
entry := topEntries{
|
||||
"SERVICE": container.Service,
|
||||
"#": container.Replica,
|
||||
}
|
||||
_, _ = fmt.Fprintln(w)
|
||||
},
|
||||
container.Titles...)
|
||||
if err != nil {
|
||||
return err
|
||||
for i, title := range container.Titles {
|
||||
if _, exists := header[title]; !exists {
|
||||
header[title] = len(header)
|
||||
}
|
||||
entry[title] = proc[i]
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
// ensure CMD is the right-most column
|
||||
if pos, ok := header["CMD"]; ok {
|
||||
maxPos := pos
|
||||
for h, i := range header {
|
||||
if i > maxPos {
|
||||
maxPos = i
|
||||
}
|
||||
if i > pos {
|
||||
header[h] = i - 1
|
||||
}
|
||||
}
|
||||
header["CMD"] = maxPos
|
||||
}
|
||||
|
||||
return header, entries
|
||||
}
|
||||
|
||||
func psPrinter(out io.Writer, printer func(writer io.Writer), headers ...string) error {
|
||||
w := tabwriter.NewWriter(out, 5, 1, 3, ' ', 0)
|
||||
_, _ = fmt.Fprintln(w, strings.Join(headers, "\t"))
|
||||
printer(w)
|
||||
func topPrint(out io.Writer, headers topHeader, rows []topEntries) error {
|
||||
if len(rows) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(out, 4, 1, 2, ' ', 0)
|
||||
|
||||
// write headers in the order we've encountered them
|
||||
h := make([]string, len(headers))
|
||||
for title, index := range headers {
|
||||
h[index] = title
|
||||
}
|
||||
_, _ = fmt.Fprintln(w, strings.Join(h, "\t"))
|
||||
|
||||
for _, row := range rows {
|
||||
// write proc data in header order
|
||||
r := make([]string, len(headers))
|
||||
for title, index := range headers {
|
||||
if v, ok := row[title]; ok {
|
||||
r[index] = v
|
||||
} else {
|
||||
r[index] = "-"
|
||||
}
|
||||
}
|
||||
_, _ = fmt.Fprintln(w, strings.Join(r, "\t"))
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
|
329
cmd/compose/top_test.go
Normal file
329
cmd/compose/top_test.go
Normal file
@ -0,0 +1,329 @@
|
||||
/*
|
||||
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 compose
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var topTestCases = []struct {
|
||||
name string
|
||||
titles []string
|
||||
procs [][]string
|
||||
|
||||
header topHeader
|
||||
entries []topEntries
|
||||
output string
|
||||
}{
|
||||
{
|
||||
name: "noprocs",
|
||||
titles: []string{"UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"},
|
||||
procs: [][]string{},
|
||||
header: topHeader{"SERVICE": 0, "#": 1},
|
||||
entries: []topEntries{},
|
||||
output: "",
|
||||
},
|
||||
{
|
||||
name: "simple",
|
||||
titles: []string{"UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"},
|
||||
procs: [][]string{{"root", "1", "1", "0", "12:00", "?", "00:00:01", "/entrypoint"}},
|
||||
header: topHeader{
|
||||
"SERVICE": 0,
|
||||
"#": 1,
|
||||
"UID": 2,
|
||||
"PID": 3,
|
||||
"PPID": 4,
|
||||
"C": 5,
|
||||
"STIME": 6,
|
||||
"TTY": 7,
|
||||
"TIME": 8,
|
||||
"CMD": 9,
|
||||
},
|
||||
entries: []topEntries{
|
||||
{
|
||||
"SERVICE": "simple",
|
||||
"#": "1",
|
||||
"UID": "root",
|
||||
"PID": "1",
|
||||
"PPID": "1",
|
||||
"C": "0",
|
||||
"STIME": "12:00",
|
||||
"TTY": "?",
|
||||
"TIME": "00:00:01",
|
||||
"CMD": "/entrypoint",
|
||||
},
|
||||
},
|
||||
output: trim(`
|
||||
SERVICE # UID PID PPID C STIME TTY TIME CMD
|
||||
simple 1 root 1 1 0 12:00 ? 00:00:01 /entrypoint
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "noppid",
|
||||
titles: []string{"UID", "PID", "C", "STIME", "TTY", "TIME", "CMD"},
|
||||
procs: [][]string{{"root", "1", "0", "12:00", "?", "00:00:02", "/entrypoint"}},
|
||||
header: topHeader{
|
||||
"SERVICE": 0,
|
||||
"#": 1,
|
||||
"UID": 2,
|
||||
"PID": 3,
|
||||
"C": 4,
|
||||
"STIME": 5,
|
||||
"TTY": 6,
|
||||
"TIME": 7,
|
||||
"CMD": 8,
|
||||
},
|
||||
entries: []topEntries{
|
||||
{
|
||||
"SERVICE": "noppid",
|
||||
"#": "1",
|
||||
"UID": "root",
|
||||
"PID": "1",
|
||||
"C": "0",
|
||||
"STIME": "12:00",
|
||||
"TTY": "?",
|
||||
"TIME": "00:00:02",
|
||||
"CMD": "/entrypoint",
|
||||
},
|
||||
},
|
||||
output: trim(`
|
||||
SERVICE # UID PID C STIME TTY TIME CMD
|
||||
noppid 1 root 1 0 12:00 ? 00:00:02 /entrypoint
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "extra-hdr",
|
||||
titles: []string{"UID", "GID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"},
|
||||
procs: [][]string{{"root", "1", "1", "1", "0", "12:00", "?", "00:00:03", "/entrypoint"}},
|
||||
header: topHeader{
|
||||
"SERVICE": 0,
|
||||
"#": 1,
|
||||
"UID": 2,
|
||||
"GID": 3,
|
||||
"PID": 4,
|
||||
"PPID": 5,
|
||||
"C": 6,
|
||||
"STIME": 7,
|
||||
"TTY": 8,
|
||||
"TIME": 9,
|
||||
"CMD": 10,
|
||||
},
|
||||
entries: []topEntries{
|
||||
{
|
||||
"SERVICE": "extra-hdr",
|
||||
"#": "1",
|
||||
"UID": "root",
|
||||
"GID": "1",
|
||||
"PID": "1",
|
||||
"PPID": "1",
|
||||
"C": "0",
|
||||
"STIME": "12:00",
|
||||
"TTY": "?",
|
||||
"TIME": "00:00:03",
|
||||
"CMD": "/entrypoint",
|
||||
},
|
||||
},
|
||||
output: trim(`
|
||||
SERVICE # UID GID PID PPID C STIME TTY TIME CMD
|
||||
extra-hdr 1 root 1 1 1 0 12:00 ? 00:00:03 /entrypoint
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "multiple",
|
||||
titles: []string{"UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"},
|
||||
procs: [][]string{
|
||||
{"root", "1", "1", "0", "12:00", "?", "00:00:04", "/entrypoint"},
|
||||
{"root", "123", "1", "0", "12:00", "?", "00:00:42", "sleep infinity"},
|
||||
},
|
||||
header: topHeader{
|
||||
"SERVICE": 0,
|
||||
"#": 1,
|
||||
"UID": 2,
|
||||
"PID": 3,
|
||||
"PPID": 4,
|
||||
"C": 5,
|
||||
"STIME": 6,
|
||||
"TTY": 7,
|
||||
"TIME": 8,
|
||||
"CMD": 9,
|
||||
},
|
||||
entries: []topEntries{
|
||||
{
|
||||
"SERVICE": "multiple",
|
||||
"#": "1",
|
||||
"UID": "root",
|
||||
"PID": "1",
|
||||
"PPID": "1",
|
||||
"C": "0",
|
||||
"STIME": "12:00",
|
||||
"TTY": "?",
|
||||
"TIME": "00:00:04",
|
||||
"CMD": "/entrypoint",
|
||||
},
|
||||
{
|
||||
"SERVICE": "multiple",
|
||||
"#": "1",
|
||||
"UID": "root",
|
||||
"PID": "123",
|
||||
"PPID": "1",
|
||||
"C": "0",
|
||||
"STIME": "12:00",
|
||||
"TTY": "?",
|
||||
"TIME": "00:00:42",
|
||||
"CMD": "sleep infinity",
|
||||
},
|
||||
},
|
||||
output: trim(`
|
||||
SERVICE # UID PID PPID C STIME TTY TIME CMD
|
||||
multiple 1 root 1 1 0 12:00 ? 00:00:04 /entrypoint
|
||||
multiple 1 root 123 1 0 12:00 ? 00:00:42 sleep infinity
|
||||
`),
|
||||
},
|
||||
}
|
||||
|
||||
// TestRunTopCore only tests the core functionality of runTop: formatting
|
||||
// and printing of the output of (api.Service).Top().
|
||||
func TestRunTopCore(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
all := []api.ContainerProcSummary{}
|
||||
|
||||
for _, tc := range topTestCases {
|
||||
summary := api.ContainerProcSummary{
|
||||
Name: "not used",
|
||||
Titles: tc.titles,
|
||||
Processes: tc.procs,
|
||||
Service: tc.name,
|
||||
Replica: "1",
|
||||
}
|
||||
all = append(all, summary)
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
header, entries := collectTop([]api.ContainerProcSummary{summary})
|
||||
assert.Equal(t, tc.header, header)
|
||||
assert.Equal(t, tc.entries, entries)
|
||||
|
||||
var buf bytes.Buffer
|
||||
err := topPrint(&buf, header, entries)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.output, buf.String())
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("all", func(t *testing.T) {
|
||||
header, entries := collectTop(all)
|
||||
assert.Equal(t, topHeader{
|
||||
"SERVICE": 0,
|
||||
"#": 1,
|
||||
"UID": 2,
|
||||
"PID": 3,
|
||||
"PPID": 4,
|
||||
"C": 5,
|
||||
"STIME": 6,
|
||||
"TTY": 7,
|
||||
"TIME": 8,
|
||||
"GID": 9,
|
||||
"CMD": 10,
|
||||
}, header)
|
||||
assert.Equal(t, []topEntries{
|
||||
{
|
||||
"SERVICE": "simple",
|
||||
"#": "1",
|
||||
"UID": "root",
|
||||
"PID": "1",
|
||||
"PPID": "1",
|
||||
"C": "0",
|
||||
"STIME": "12:00",
|
||||
"TTY": "?",
|
||||
"TIME": "00:00:01",
|
||||
"CMD": "/entrypoint",
|
||||
}, {
|
||||
"SERVICE": "noppid",
|
||||
"#": "1",
|
||||
"UID": "root",
|
||||
"PID": "1",
|
||||
"C": "0",
|
||||
"STIME": "12:00",
|
||||
"TTY": "?",
|
||||
"TIME": "00:00:02",
|
||||
"CMD": "/entrypoint",
|
||||
}, {
|
||||
"SERVICE": "extra-hdr",
|
||||
"#": "1",
|
||||
"UID": "root",
|
||||
"GID": "1",
|
||||
"PID": "1",
|
||||
"PPID": "1",
|
||||
"C": "0",
|
||||
"STIME": "12:00",
|
||||
"TTY": "?",
|
||||
"TIME": "00:00:03",
|
||||
"CMD": "/entrypoint",
|
||||
}, {
|
||||
"SERVICE": "multiple",
|
||||
"#": "1",
|
||||
"UID": "root",
|
||||
"PID": "1",
|
||||
"PPID": "1",
|
||||
"C": "0",
|
||||
"STIME": "12:00",
|
||||
"TTY": "?",
|
||||
"TIME": "00:00:04",
|
||||
"CMD": "/entrypoint",
|
||||
}, {
|
||||
"SERVICE": "multiple",
|
||||
"#": "1",
|
||||
"UID": "root",
|
||||
"PID": "123",
|
||||
"PPID": "1",
|
||||
"C": "0",
|
||||
"STIME": "12:00",
|
||||
"TTY": "?",
|
||||
"TIME": "00:00:42",
|
||||
"CMD": "sleep infinity",
|
||||
},
|
||||
}, entries)
|
||||
|
||||
var buf bytes.Buffer
|
||||
err := topPrint(&buf, header, entries)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, trim(`
|
||||
SERVICE # UID PID PPID C STIME TTY TIME GID CMD
|
||||
simple 1 root 1 1 0 12:00 ? 00:00:01 - /entrypoint
|
||||
noppid 1 root 1 - 0 12:00 ? 00:00:02 - /entrypoint
|
||||
extra-hdr 1 root 1 1 0 12:00 ? 00:00:03 1 /entrypoint
|
||||
multiple 1 root 1 1 0 12:00 ? 00:00:04 - /entrypoint
|
||||
multiple 1 root 123 1 0 12:00 ? 00:00:42 - sleep infinity
|
||||
`), buf.String())
|
||||
})
|
||||
}
|
||||
|
||||
func trim(s string) string {
|
||||
var out bytes.Buffer
|
||||
for _, line := range strings.Split(strings.TrimSpace(s), "\n") {
|
||||
out.WriteString(strings.TrimSpace(line))
|
||||
out.WriteRune('\n')
|
||||
}
|
||||
return out.String()
|
||||
}
|
@ -27,7 +27,9 @@ import (
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
xprogress "github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@ -145,7 +147,6 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c
|
||||
flags := upCmd.Flags()
|
||||
flags.BoolVarP(&up.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
|
||||
flags.BoolVar(&create.Build, "build", false, "Build images before starting containers")
|
||||
flags.BoolVarP(&create.AssumeYes, "y", "y", false, `Assume "yes" as answer to all prompts and run non-interactively`)
|
||||
flags.BoolVar(&create.noBuild, "no-build", false, "Don't build an image, even if it's policy")
|
||||
flags.StringVar(&create.Pull, "pull", "policy", `Pull image before running ("always"|"missing"|"never")`)
|
||||
flags.BoolVar(&create.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file")
|
||||
@ -164,6 +165,7 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c
|
||||
flags.BoolVar(&create.recreateDeps, "always-recreate-deps", false, "Recreate dependent containers. Incompatible with --no-recreate.")
|
||||
flags.BoolVarP(&create.noInherit, "renew-anon-volumes", "V", false, "Recreate anonymous volumes instead of retrieving data from the previous containers")
|
||||
flags.BoolVar(&create.quietPull, "quiet-pull", false, "Pull without printing progress information")
|
||||
flags.BoolVar(&build.quiet, "quiet-build", false, "Suppress the build output")
|
||||
flags.StringArrayVar(&up.attach, "attach", []string{}, "Restrict attaching to the specified services. Incompatible with --attach-dependencies.")
|
||||
flags.StringArrayVar(&up.noAttach, "no-attach", []string{}, "Do not attach (stream logs) to the specified services")
|
||||
flags.BoolVar(&up.attachDependencies, "attach-dependencies", false, "Automatically attach to log output of dependent services")
|
||||
@ -171,7 +173,15 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c
|
||||
flags.IntVar(&up.waitTimeout, "wait-timeout", 0, "Maximum duration in seconds to wait for the project to be running|healthy")
|
||||
flags.BoolVarP(&up.watch, "watch", "w", false, "Watch source code and rebuild/refresh containers when files are updated.")
|
||||
flags.BoolVar(&up.navigationMenu, "menu", false, "Enable interactive shortcuts when running attached. Incompatible with --detach. Can also be enable/disable by setting COMPOSE_MENU environment var.")
|
||||
|
||||
flags.BoolVarP(&create.AssumeYes, "yes", "y", false, `Assume "yes" as answer to all prompts and run non-interactively`)
|
||||
flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
// assumeYes was introduced by mistake as `--y`
|
||||
if name == "y" {
|
||||
logrus.Warn("--y is deprecated, please use --yes instead")
|
||||
name = "yes"
|
||||
}
|
||||
return pflag.NormalizedName(name)
|
||||
})
|
||||
return upCmd
|
||||
}
|
||||
|
||||
@ -214,6 +224,7 @@ func validateFlags(up *upOptions, create *createOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func runUp(
|
||||
ctx context.Context,
|
||||
dockerCli command.Cli,
|
||||
@ -224,6 +235,10 @@ func runUp(
|
||||
project *types.Project,
|
||||
services []string,
|
||||
) error {
|
||||
if err := checksForRemoteStack(ctx, dockerCli, project, buildOptions, createOptions.AssumeYes, []string{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := createOptions.Apply(project)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -247,6 +262,8 @@ func runUp(
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bo.Services = services
|
||||
bo.Deps = !upOptions.noDeps
|
||||
build = &bo
|
||||
}
|
||||
|
||||
@ -315,7 +332,7 @@ func runUp(
|
||||
WaitTimeout: timeout,
|
||||
Watch: upOptions.watch,
|
||||
Services: services,
|
||||
NavigationMenu: upOptions.navigationMenu && ui.Mode != "plain",
|
||||
NavigationMenu: upOptions.navigationMenu && ui.Mode != "plain" && dockerCli.In().IsTerminal(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
76
cmd/compose/version_test.go
Normal file
76
cmd/compose/version_test.go
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
Copyright 2025 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 (
|
||||
"bytes"
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestVersionCommand(t *testing.T) {
|
||||
originalVersion := internal.Version
|
||||
defer func() {
|
||||
internal.Version = originalVersion
|
||||
}()
|
||||
internal.Version = "v9.9.9-test"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "default",
|
||||
args: []string{},
|
||||
want: "Docker Compose version v9.9.9-test\n",
|
||||
},
|
||||
{
|
||||
name: "short flag",
|
||||
args: []string{"--short"},
|
||||
want: "9.9.9-test\n",
|
||||
},
|
||||
{
|
||||
name: "json flag",
|
||||
args: []string{"--format", "json"},
|
||||
want: `{"version":"v9.9.9-test"}` + "\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
cli := mocks.NewMockCli(ctrl)
|
||||
cli.EXPECT().Out().Return(streams.NewOut(buf)).AnyTimes()
|
||||
|
||||
cmd := versionCommand(cli)
|
||||
cmd.SetArgs(test.args)
|
||||
err := cmd.Execute()
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, test.want, buf.String())
|
||||
})
|
||||
}
|
||||
}
|
95
cmd/compose/volumes.go
Normal file
95
cmd/compose/volumes.go
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
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"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
type volumesOptions struct {
|
||||
*ProjectOptions
|
||||
Quiet bool
|
||||
Format string
|
||||
}
|
||||
|
||||
func volumesCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
options := volumesOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "volumes [OPTIONS] [SERVICE...]",
|
||||
Short: "List volumes",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runVol(ctx, dockerCli, backend, args, options)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVarP(&options.Quiet, "quiet", "q", false, "Only display volume names")
|
||||
cmd.Flags().StringVar(&options.Format, "format", "table", flags.FormatHelp)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runVol(ctx context.Context, dockerCli command.Cli, backend api.Service, services []string, options volumesOptions) error {
|
||||
project, _, err := options.projectOrName(ctx, dockerCli, services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
names := project.ServiceNames()
|
||||
|
||||
if len(services) == 0 {
|
||||
services = names
|
||||
}
|
||||
|
||||
for _, service := range services {
|
||||
if !slices.Contains(names, service) {
|
||||
return fmt.Errorf("no such service: %s", service)
|
||||
}
|
||||
}
|
||||
|
||||
volumes, err := backend.Volumes(ctx, project, api.VolumesOptions{
|
||||
Services: services,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if options.Quiet {
|
||||
for _, v := range volumes {
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), v.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
volumeCtx := formatter.Context{
|
||||
Output: dockerCli.Out(),
|
||||
Format: formatter.NewVolumeFormat(options.Format, options.Quiet),
|
||||
}
|
||||
|
||||
return formatter.VolumeWrite(volumeCtx, volumes)
|
||||
}
|
@ -59,7 +59,7 @@ func watchCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&buildOpts.quiet, "quiet", false, "hide build output")
|
||||
cmd.Flags().BoolVar(&watchOpts.prune, "prune", false, "Prune dangling images on rebuild")
|
||||
cmd.Flags().BoolVar(&watchOpts.prune, "prune", true, "Prune dangling images on rebuild")
|
||||
cmd.Flags().BoolVar(&watchOpts.noUp, "no-up", false, "Do not build & start services before watching")
|
||||
return cmd
|
||||
}
|
||||
@ -117,9 +117,10 @@ func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Service, w
|
||||
}
|
||||
|
||||
consumer := formatter.NewLogConsumer(ctx, dockerCli.Out(), dockerCli.Err(), false, false, false)
|
||||
return backend.Watch(ctx, project, services, api.WatchOptions{
|
||||
Build: &build,
|
||||
LogTo: consumer,
|
||||
Prune: watchOpts.prune,
|
||||
return backend.Watch(ctx, project, api.WatchOptions{
|
||||
Build: &build,
|
||||
LogTo: consumer,
|
||||
Prune: watchOpts.prune,
|
||||
Services: services,
|
||||
})
|
||||
}
|
||||
|
@ -28,49 +28,42 @@ func ansi(code string) string {
|
||||
return fmt.Sprintf("\033%s", code)
|
||||
}
|
||||
|
||||
func SaveCursor() {
|
||||
func saveCursor() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi("7"))
|
||||
}
|
||||
|
||||
func RestoreCursor() {
|
||||
func restoreCursor() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi("8"))
|
||||
}
|
||||
|
||||
func HideCursor() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi("[?25l"))
|
||||
}
|
||||
|
||||
func ShowCursor() {
|
||||
func showCursor() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi("[?25h"))
|
||||
}
|
||||
|
||||
func MoveCursor(y, x int) {
|
||||
func moveCursor(y, x int) {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi(fmt.Sprintf("[%d;%dH", y, x)))
|
||||
}
|
||||
|
||||
func MoveCursorX(pos int) {
|
||||
func carriageReturn() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi(fmt.Sprintf("[%dG", pos)))
|
||||
fmt.Print(ansi(fmt.Sprintf("[%dG", 0)))
|
||||
}
|
||||
|
||||
func ClearLine() {
|
||||
func clearLine() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
@ -78,7 +71,7 @@ func ClearLine() {
|
||||
fmt.Print(ansi("[2K"))
|
||||
}
|
||||
|
||||
func MoveCursorUp(lines int) {
|
||||
func moveCursorUp(lines int) {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
@ -86,7 +79,7 @@ func MoveCursorUp(lines int) {
|
||||
fmt.Print(ansi(fmt.Sprintf("[%dA", lines)))
|
||||
}
|
||||
|
||||
func MoveCursorDown(lines int) {
|
||||
func moveCursorDown(lines int) {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
@ -94,7 +87,7 @@ func MoveCursorDown(lines int) {
|
||||
fmt.Print(ansi(fmt.Sprintf("[%dB", lines)))
|
||||
}
|
||||
|
||||
func NewLine() {
|
||||
func newLine() {
|
||||
// Like \n
|
||||
fmt.Print("\012")
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package formatter
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
@ -58,6 +59,9 @@ const (
|
||||
Auto = "auto"
|
||||
)
|
||||
|
||||
// ansiColorOffset is the offset for basic foreground colors in ANSI escape codes.
|
||||
const ansiColorOffset = 30
|
||||
|
||||
// SetANSIMode configure formatter for colored output on ANSI-compliant console
|
||||
func SetANSIMode(streams command.Streams, ansi string) {
|
||||
if !useAnsi(streams, ansi) {
|
||||
@ -91,11 +95,15 @@ func ansiColor(code, s string, formatOpts ...string) string {
|
||||
|
||||
// Everything about ansiColorCode color https://hyperskill.org/learn/step/18193
|
||||
func ansiColorCode(code string, formatOpts ...string) string {
|
||||
res := "\033["
|
||||
var sb strings.Builder
|
||||
sb.WriteString("\033[")
|
||||
for _, c := range formatOpts {
|
||||
res = fmt.Sprintf("%s%s;", res, c)
|
||||
sb.WriteString(c)
|
||||
sb.WriteString(";")
|
||||
}
|
||||
return fmt.Sprintf("%s%sm", res, code)
|
||||
sb.WriteString(code)
|
||||
sb.WriteString("m")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func makeColorFunc(code string) colorFunc {
|
||||
@ -122,8 +130,8 @@ func rainbowColor() colorFunc {
|
||||
func init() {
|
||||
colors := map[string]colorFunc{}
|
||||
for i, name := range names {
|
||||
colors[name] = makeColorFunc(strconv.Itoa(30 + i))
|
||||
colors["intense_"+name] = makeColorFunc(strconv.Itoa(30+i) + ";1")
|
||||
colors[name] = makeColorFunc(strconv.Itoa(ansiColorOffset + i))
|
||||
colors["intense_"+name] = makeColorFunc(strconv.Itoa(ansiColorOffset+i) + ";1")
|
||||
}
|
||||
rainbow = []colorFunc{
|
||||
colors["cyan"],
|
||||
|
@ -56,10 +56,6 @@ func NewLogConsumer(ctx context.Context, stdout, stderr io.Writer, color, prefix
|
||||
}
|
||||
}
|
||||
|
||||
func (l *logConsumer) Register(name string) {
|
||||
l.register(name)
|
||||
}
|
||||
|
||||
func (l *logConsumer) register(name string) *presenter {
|
||||
var p *presenter
|
||||
root, _, found := strings.Cut(name, " ")
|
||||
@ -73,9 +69,12 @@ func (l *logConsumer) register(name string) *presenter {
|
||||
} else {
|
||||
cf := monochrome
|
||||
if l.color {
|
||||
if name == api.WatchLogger {
|
||||
switch name {
|
||||
case "":
|
||||
cf = monochrome
|
||||
case api.WatchLogger:
|
||||
cf = makeColorFunc("92")
|
||||
} else {
|
||||
default:
|
||||
cf = nextColor()
|
||||
}
|
||||
}
|
||||
@ -118,23 +117,15 @@ func (l *logConsumer) write(w io.Writer, container, message string) {
|
||||
if l.ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
if KeyboardManager != nil {
|
||||
KeyboardManager.ClearKeyboardInfo()
|
||||
}
|
||||
|
||||
p := l.getPresenter(container)
|
||||
timestamp := time.Now().Format(jsonmessage.RFC3339NanoFixed)
|
||||
for _, line := range strings.Split(message, "\n") {
|
||||
if l.timestamp {
|
||||
_, _ = fmt.Fprintf(w, "%s%s%s\n", p.prefix, timestamp, line)
|
||||
_, _ = fmt.Fprintf(w, "%s%s %s\n", p.prefix, timestamp, line)
|
||||
} else {
|
||||
_, _ = fmt.Fprintf(w, "%s%s\n", p.prefix, line)
|
||||
}
|
||||
}
|
||||
|
||||
if KeyboardManager != nil {
|
||||
KeyboardManager.PrintKeyboardInfo()
|
||||
}
|
||||
}
|
||||
|
||||
func (l *logConsumer) Status(container, msg string) {
|
||||
@ -168,3 +159,27 @@ func (p *presenter) setPrefix(width int) {
|
||||
}
|
||||
p.prefix = p.colors(fmt.Sprintf("%-"+strconv.Itoa(width)+"s | ", p.name))
|
||||
}
|
||||
|
||||
type logDecorator struct {
|
||||
decorated api.LogConsumer
|
||||
Before func()
|
||||
After func()
|
||||
}
|
||||
|
||||
func (l logDecorator) Log(containerName, message string) {
|
||||
l.Before()
|
||||
l.decorated.Log(containerName, message)
|
||||
l.After()
|
||||
}
|
||||
|
||||
func (l logDecorator) Err(containerName, message string) {
|
||||
l.Before()
|
||||
l.decorated.Err(containerName, message)
|
||||
l.After()
|
||||
}
|
||||
|
||||
func (l logDecorator) Status(container, msg string) {
|
||||
l.Before()
|
||||
l.decorated.Status(container, msg)
|
||||
l.After()
|
||||
}
|
||||
|
@ -29,9 +29,7 @@ import (
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/compose/v2/internal/tracing"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/watch"
|
||||
"github.com/eiannone/keyboard"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
)
|
||||
|
||||
@ -50,8 +48,8 @@ func (ke *KeyboardError) printError(height int, info string) {
|
||||
if ke.shouldDisplay() {
|
||||
errMessage := ke.err.Error()
|
||||
|
||||
MoveCursor(height-1-extraLines(info)-extraLines(errMessage), 0)
|
||||
ClearLine()
|
||||
moveCursor(height-1-extraLines(info)-extraLines(errMessage), 0)
|
||||
clearLine()
|
||||
|
||||
fmt.Print(errMessage)
|
||||
}
|
||||
@ -71,26 +69,14 @@ func (ke *KeyboardError) error() string {
|
||||
}
|
||||
|
||||
type KeyboardWatch struct {
|
||||
Watcher watch.Notify
|
||||
Watching bool
|
||||
WatchFn func(ctx context.Context, doneCh chan bool, project *types.Project, services []string, options api.WatchOptions) error
|
||||
Ctx context.Context
|
||||
Cancel context.CancelFunc
|
||||
Watcher Feature
|
||||
}
|
||||
|
||||
func (kw *KeyboardWatch) isWatching() bool {
|
||||
return kw.Watching
|
||||
}
|
||||
|
||||
func (kw *KeyboardWatch) switchWatching() {
|
||||
kw.Watching = !kw.Watching
|
||||
}
|
||||
|
||||
func (kw *KeyboardWatch) newContext(ctx context.Context) context.CancelFunc {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
kw.Ctx = ctx
|
||||
kw.Cancel = cancel
|
||||
return cancel
|
||||
// Feature is an compose feature that can be started/stopped by a menu command
|
||||
type Feature interface {
|
||||
Start(context.Context) error
|
||||
Stop() error
|
||||
}
|
||||
|
||||
type KEYBOARD_LOG_LEVEL int
|
||||
@ -103,42 +89,26 @@ const (
|
||||
|
||||
type LogKeyboard struct {
|
||||
kError KeyboardError
|
||||
Watch KeyboardWatch
|
||||
Watch *KeyboardWatch
|
||||
IsDockerDesktopActive bool
|
||||
IsWatchConfigured bool
|
||||
logLevel KEYBOARD_LOG_LEVEL
|
||||
signalChannel chan<- os.Signal
|
||||
}
|
||||
|
||||
var (
|
||||
KeyboardManager *LogKeyboard
|
||||
eg multierror.Group
|
||||
)
|
||||
|
||||
func NewKeyboardManager(ctx context.Context, isDockerDesktopActive, isWatchConfigured bool,
|
||||
sc chan<- os.Signal,
|
||||
watchFn func(ctx context.Context,
|
||||
doneCh chan bool,
|
||||
project *types.Project,
|
||||
services []string,
|
||||
options api.WatchOptions,
|
||||
) error,
|
||||
) {
|
||||
km := LogKeyboard{}
|
||||
km.IsDockerDesktopActive = isDockerDesktopActive
|
||||
km.IsWatchConfigured = isWatchConfigured
|
||||
km.logLevel = INFO
|
||||
|
||||
km.Watch.Watching = false
|
||||
km.Watch.WatchFn = watchFn
|
||||
|
||||
km.signalChannel = sc
|
||||
|
||||
KeyboardManager = &km
|
||||
func NewKeyboardManager(isDockerDesktopActive bool, sc chan<- os.Signal) *LogKeyboard {
|
||||
return &LogKeyboard{
|
||||
IsDockerDesktopActive: isDockerDesktopActive,
|
||||
logLevel: INFO,
|
||||
signalChannel: sc,
|
||||
}
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) ClearKeyboardInfo() {
|
||||
lk.clearNavigationMenu()
|
||||
func (lk *LogKeyboard) Decorate(l api.LogConsumer) api.LogConsumer {
|
||||
return logDecorator{
|
||||
decorated: l,
|
||||
Before: lk.clearNavigationMenu,
|
||||
After: lk.PrintKeyboardInfo,
|
||||
}
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) PrintKeyboardInfo() {
|
||||
@ -163,7 +133,7 @@ func (lk *LogKeyboard) createBuffer(lines int) {
|
||||
|
||||
if lines > 0 {
|
||||
allocateSpace(lines)
|
||||
MoveCursorUp(lines)
|
||||
moveCursorUp(lines)
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,17 +146,17 @@ func (lk *LogKeyboard) printNavigationMenu() {
|
||||
height := goterm.Height()
|
||||
menu := lk.navigationMenu()
|
||||
|
||||
MoveCursorX(0)
|
||||
SaveCursor()
|
||||
carriageReturn()
|
||||
saveCursor()
|
||||
|
||||
lk.kError.printError(height, menu)
|
||||
|
||||
MoveCursor(height-extraLines(menu), 0)
|
||||
ClearLine()
|
||||
moveCursor(height-extraLines(menu), 0)
|
||||
clearLine()
|
||||
fmt.Print(menu)
|
||||
|
||||
MoveCursorX(0)
|
||||
RestoreCursor()
|
||||
carriageReturn()
|
||||
restoreCursor()
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,7 +179,7 @@ func (lk *LogKeyboard) navigationMenu() string {
|
||||
watchInfo = navColor(" ")
|
||||
}
|
||||
isEnabled := " Enable"
|
||||
if lk.Watch.Watching {
|
||||
if lk.Watch != nil && lk.Watch.Watching {
|
||||
isEnabled = " Disable"
|
||||
}
|
||||
watchInfo = watchInfo + shortcutKeyColor("w") + navColor(isEnabled+" Watch")
|
||||
@ -218,63 +188,66 @@ func (lk *LogKeyboard) navigationMenu() string {
|
||||
|
||||
func (lk *LogKeyboard) clearNavigationMenu() {
|
||||
height := goterm.Height()
|
||||
MoveCursorX(0)
|
||||
SaveCursor()
|
||||
carriageReturn()
|
||||
saveCursor()
|
||||
|
||||
// ClearLine()
|
||||
// clearLine()
|
||||
for i := 0; i < height; i++ {
|
||||
MoveCursorDown(1)
|
||||
ClearLine()
|
||||
moveCursorDown(1)
|
||||
clearLine()
|
||||
}
|
||||
RestoreCursor()
|
||||
restoreCursor()
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) openDockerDesktop(ctx context.Context, project *types.Project) {
|
||||
if !lk.IsDockerDesktopActive {
|
||||
return
|
||||
}
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/gui", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
link := fmt.Sprintf("docker-desktop://dashboard/apps/%s", project.Name)
|
||||
err := open.Run(link)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Could not open Docker Desktop")
|
||||
lk.keyboardError("View", err)
|
||||
}
|
||||
return err
|
||||
}),
|
||||
)
|
||||
go func() {
|
||||
_ = tracing.EventWrapFuncForErrGroup(ctx, "menu/gui", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
link := fmt.Sprintf("docker-desktop://dashboard/apps/%s", project.Name)
|
||||
err := open.Run(link)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not open Docker Desktop")
|
||||
lk.keyboardError("View", err)
|
||||
}
|
||||
return err
|
||||
})()
|
||||
}()
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) openDDComposeUI(ctx context.Context, project *types.Project) {
|
||||
if !lk.IsDockerDesktopActive {
|
||||
return
|
||||
}
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/gui/composeview", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
link := fmt.Sprintf("docker-desktop://dashboard/docker-compose/%s", project.Name)
|
||||
err := open.Run(link)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Could not open Docker Desktop Compose UI")
|
||||
lk.keyboardError("View Config", err)
|
||||
}
|
||||
return err
|
||||
}),
|
||||
)
|
||||
go func() {
|
||||
_ = tracing.EventWrapFuncForErrGroup(ctx, "menu/gui/composeview", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
link := fmt.Sprintf("docker-desktop://dashboard/docker-compose/%s", project.Name)
|
||||
err := open.Run(link)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not open Docker Desktop Compose UI")
|
||||
lk.keyboardError("View Config", err)
|
||||
}
|
||||
return err
|
||||
})()
|
||||
}()
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) openDDWatchDocs(ctx context.Context, project *types.Project) {
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/gui/watch", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
link := fmt.Sprintf("docker-desktop://dashboard/docker-compose/%s/watch", project.Name)
|
||||
err := open.Run(link)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Could not open Docker Desktop Compose UI")
|
||||
lk.keyboardError("Watch Docs", err)
|
||||
}
|
||||
return err
|
||||
}),
|
||||
)
|
||||
go func() {
|
||||
_ = tracing.EventWrapFuncForErrGroup(ctx, "menu/gui/watch", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
link := fmt.Sprintf("docker-desktop://dashboard/docker-compose/%s/watch", project.Name)
|
||||
err := open.Run(link)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not open Docker Desktop Compose UI")
|
||||
lk.keyboardError("Watch Docs", err)
|
||||
}
|
||||
return err
|
||||
})()
|
||||
}()
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) keyboardError(prefix string, err error) {
|
||||
@ -288,58 +261,54 @@ func (lk *LogKeyboard) keyboardError(prefix string, err error) {
|
||||
}()
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) StartWatch(ctx context.Context, doneCh chan bool, project *types.Project, options api.UpOptions) {
|
||||
if !lk.IsWatchConfigured {
|
||||
func (lk *LogKeyboard) ToggleWatch(ctx context.Context, options api.UpOptions) {
|
||||
if lk.Watch == nil {
|
||||
return
|
||||
}
|
||||
lk.Watch.switchWatching()
|
||||
if !lk.Watch.isWatching() {
|
||||
lk.Watch.Cancel()
|
||||
if lk.Watch.Watching {
|
||||
err := lk.Watch.Watcher.Stop()
|
||||
if err != nil {
|
||||
options.Start.Attach.Err(api.WatchLogger, err.Error())
|
||||
} else {
|
||||
lk.Watch.Watching = false
|
||||
}
|
||||
} else {
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/watch", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
if options.Create.Build == nil {
|
||||
err := fmt.Errorf("Cannot run watch mode with flag --no-build")
|
||||
lk.keyboardError("Watch", err)
|
||||
go func() {
|
||||
_ = tracing.EventWrapFuncForErrGroup(ctx, "menu/watch", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
err := lk.Watch.Watcher.Start(ctx)
|
||||
if err != nil {
|
||||
options.Start.Attach.Err(api.WatchLogger, err.Error())
|
||||
} else {
|
||||
lk.Watch.Watching = true
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
lk.Watch.newContext(ctx)
|
||||
buildOpts := *options.Create.Build
|
||||
buildOpts.Quiet = true
|
||||
err := lk.Watch.WatchFn(lk.Watch.Ctx, doneCh, project, options.Start.Services, api.WatchOptions{
|
||||
Build: &buildOpts,
|
||||
LogTo: options.Start.Attach,
|
||||
})
|
||||
if err != nil {
|
||||
lk.Watch.switchWatching()
|
||||
options.Start.Attach.Err(api.WatchLogger, err.Error())
|
||||
}
|
||||
return err
|
||||
}))
|
||||
})()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) HandleKeyEvents(event keyboard.KeyEvent, ctx context.Context, doneCh chan bool, project *types.Project, 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 'v':
|
||||
lk.openDockerDesktop(ctx, project)
|
||||
case 'w':
|
||||
if !lk.IsWatchConfigured {
|
||||
if lk.Watch == nil {
|
||||
// we try to open watch docs if DD is installed
|
||||
if lk.IsDockerDesktopActive {
|
||||
lk.openDDWatchDocs(ctx, project)
|
||||
}
|
||||
// either way we mark menu/watch as an error
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/watch", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
err := fmt.Errorf("watch is not yet configured. Learn more: %s", ansiColor(CYAN, "https://docs.docker.com/compose/file-watch/"))
|
||||
lk.keyboardError("Watch", err)
|
||||
return err
|
||||
}))
|
||||
return
|
||||
go func() {
|
||||
_ = tracing.EventWrapFuncForErrGroup(ctx, "menu/watch", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
err := fmt.Errorf("watch is not yet configured. Learn more: %s", ansiColor(CYAN, "https://docs.docker.com/compose/file-watch/"))
|
||||
lk.keyboardError("Watch", err)
|
||||
return err
|
||||
})()
|
||||
}()
|
||||
}
|
||||
lk.StartWatch(ctx, doneCh, project, options)
|
||||
lk.ToggleWatch(ctx, options)
|
||||
case 'o':
|
||||
lk.openDDComposeUI(ctx, project)
|
||||
}
|
||||
@ -347,26 +316,29 @@ func (lk *LogKeyboard) HandleKeyEvents(event keyboard.KeyEvent, ctx context.Cont
|
||||
case keyboard.KeyCtrlC:
|
||||
_ = keyboard.Close()
|
||||
lk.clearNavigationMenu()
|
||||
ShowCursor()
|
||||
showCursor()
|
||||
|
||||
lk.logLevel = NONE
|
||||
if lk.Watch.Watching && lk.Watch.Cancel != nil {
|
||||
lk.Watch.Cancel()
|
||||
_ = eg.Wait().ErrorOrNil() // Need to print this ?
|
||||
}
|
||||
// will notify main thread to kill and will handle gracefully
|
||||
lk.signalChannel <- syscall.SIGINT
|
||||
case keyboard.KeyEnter:
|
||||
NewLine()
|
||||
newLine()
|
||||
lk.printNavigationMenu()
|
||||
}
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) EnableWatch(enabled bool, watcher Feature) {
|
||||
lk.Watch = &KeyboardWatch{
|
||||
Watching: enabled,
|
||||
Watcher: watcher,
|
||||
}
|
||||
}
|
||||
|
||||
func allocateSpace(lines int) {
|
||||
for i := 0; i < lines; i++ {
|
||||
ClearLine()
|
||||
NewLine()
|
||||
MoveCursorX(0)
|
||||
clearLine()
|
||||
newLine()
|
||||
carriageReturn()
|
||||
}
|
||||
}
|
||||
|
||||
|
152
docs/examples/provider.go
Normal file
152
docs/examples/provider.go
Normal file
@ -0,0 +1,152 @@
|
||||
/*
|
||||
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 main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd := &cobra.Command{
|
||||
Short: "Compose Provider Example",
|
||||
Use: "demo",
|
||||
}
|
||||
cmd.AddCommand(composeCommand())
|
||||
err := cmd.Execute()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
type options struct {
|
||||
db string
|
||||
size int
|
||||
}
|
||||
|
||||
func composeCommand() *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "compose EVENT",
|
||||
TraverseChildren: true,
|
||||
}
|
||||
c.PersistentFlags().String("project-name", "", "compose project name") // unused
|
||||
|
||||
var options options
|
||||
upCmd := &cobra.Command{
|
||||
Use: "up",
|
||||
Run: func(_ *cobra.Command, args []string) {
|
||||
up(options, args)
|
||||
},
|
||||
Args: cobra.ExactArgs(1),
|
||||
}
|
||||
|
||||
upCmd.Flags().StringVar(&options.db, "type", "", "Database type (mysql, postgres, etc.)")
|
||||
_ = upCmd.MarkFlagRequired("type")
|
||||
upCmd.Flags().IntVar(&options.size, "size", 10, "Database size in GB")
|
||||
upCmd.Flags().String("name", "", "Name of the database to be created")
|
||||
_ = upCmd.MarkFlagRequired("name")
|
||||
|
||||
downCmd := &cobra.Command{
|
||||
Use: "down",
|
||||
Run: down,
|
||||
Args: cobra.ExactArgs(1),
|
||||
}
|
||||
downCmd.Flags().String("name", "", "Name of the database to be deleted")
|
||||
_ = downCmd.MarkFlagRequired("name")
|
||||
|
||||
c.AddCommand(upCmd, downCmd)
|
||||
c.AddCommand(metadataCommand(upCmd, downCmd))
|
||||
return c
|
||||
}
|
||||
|
||||
const lineSeparator = "\n"
|
||||
|
||||
func up(options options, args []string) {
|
||||
servicename := args[0]
|
||||
fmt.Printf(`{ "type": "debug", "message": "Starting %s" }%s`, servicename, lineSeparator)
|
||||
|
||||
for i := 0; i < options.size; i += 10 {
|
||||
time.Sleep(1 * time.Second)
|
||||
fmt.Printf(`{ "type": "info", "message": "Processing ... %d%%" }%s`, i*100/options.size, lineSeparator)
|
||||
}
|
||||
fmt.Printf(`{ "type": "setenv", "message": "URL=https://magic.cloud/%s" }%s`, servicename, lineSeparator)
|
||||
}
|
||||
|
||||
func down(_ *cobra.Command, _ []string) {
|
||||
fmt.Printf(`{ "type": "error", "message": "Permission error" }%s`, lineSeparator)
|
||||
}
|
||||
|
||||
func metadataCommand(upCmd, downCmd *cobra.Command) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "metadata",
|
||||
Run: func(cmd *cobra.Command, _ []string) {
|
||||
metadata(upCmd, downCmd)
|
||||
},
|
||||
Args: cobra.NoArgs,
|
||||
}
|
||||
}
|
||||
|
||||
func metadata(upCmd, downCmd *cobra.Command) {
|
||||
metadata := ProviderMetadata{}
|
||||
metadata.Description = "Manage services on AwesomeCloud"
|
||||
metadata.Up = commandParameters(upCmd)
|
||||
metadata.Down = commandParameters(downCmd)
|
||||
jsonMetadata, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(jsonMetadata))
|
||||
}
|
||||
|
||||
func commandParameters(cmd *cobra.Command) CommandMetadata {
|
||||
cmdMetadata := CommandMetadata{}
|
||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
_, isRequired := f.Annotations[cobra.BashCompOneRequiredFlag]
|
||||
cmdMetadata.Parameters = append(cmdMetadata.Parameters, Metadata{
|
||||
Name: f.Name,
|
||||
Description: f.Usage,
|
||||
Required: isRequired,
|
||||
Type: f.Value.Type(),
|
||||
Default: f.DefValue,
|
||||
})
|
||||
})
|
||||
return cmdMetadata
|
||||
}
|
||||
|
||||
type ProviderMetadata struct {
|
||||
Description string `json:"description"`
|
||||
Up CommandMetadata `json:"up"`
|
||||
Down CommandMetadata `json:"down"`
|
||||
}
|
||||
|
||||
type CommandMetadata struct {
|
||||
Parameters []Metadata `json:"parameters"`
|
||||
}
|
||||
|
||||
type Metadata struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Required bool `json:"required"`
|
||||
Type string `json:"type"`
|
||||
Default string `json:"default,omitempty"`
|
||||
}
|
176
docs/extension.md
Normal file
176
docs/extension.md
Normal file
@ -0,0 +1,176 @@
|
||||
# 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 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.
|
||||
|
||||
The Compose extensibility model has been designed to extend the `service` support to runtimes accessible through
|
||||
third-party tooling.
|
||||
|
||||
# Architecture
|
||||
|
||||
Compose extensibility relies on the `provider` attribute to select the actual binary responsible for managing
|
||||
the resource(s) needed to run a service.
|
||||
|
||||
```yaml
|
||||
database:
|
||||
provider:
|
||||
type: awesomecloud
|
||||
options:
|
||||
type: mysql
|
||||
size: 256
|
||||
name: myAwesomeCloudDB
|
||||
```
|
||||
|
||||
`provider.type` tells Compose the binary to run, which can be either:
|
||||
- Another Docker CLI plugin (typically, `model` to run `docker-model`)
|
||||
- An executable in user's `PATH`
|
||||
|
||||
If `provider.type` doesn't resolve into any of those, Compose will report an error and interrupt the `up` command.
|
||||
|
||||
To be a valid Compose extension, provider command *MUST* accept a `compose` command (which can be hidden)
|
||||
with subcommands `up` and `down`.
|
||||
|
||||
## Up lifecycle
|
||||
|
||||
To execute an application's `up` lifecycle, Compose executes the provider's `compose up` command, passing
|
||||
the project name, service name, and additional options. The `provider.options` are translated
|
||||
into command line flags. For example:
|
||||
```console
|
||||
awesomecloud compose --project-name <NAME> up --type=mysql --size=256 "database"
|
||||
```
|
||||
|
||||
> __Note:__ `project-name` _should_ be used by the provider to tag resources
|
||||
> set for project, so that later execution with `down` subcommand releases
|
||||
> all allocated resources set for the project.
|
||||
|
||||
## Communication with Compose
|
||||
|
||||
Providers can interact with Compose using `stdout` as a channel, sending JSON line delimited messages.
|
||||
JSON messages MUST include a `type` and a `message` attribute.
|
||||
```json
|
||||
{ "type": "info", "message": "preparing mysql ..." }
|
||||
```
|
||||
|
||||
`type` can be either:
|
||||
- `info`: Reports status updates to the user. Compose will render message as the service state in the progress UI
|
||||
- `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
|
||||
sequenceDiagram
|
||||
Shell->>Compose: docker compose up
|
||||
Compose->>Provider: compose up --project-name=xx --foo=bar "database"
|
||||
Provider--)Compose: json { "info": "pulling 25%" }
|
||||
Compose-)Shell: pulling 25%
|
||||
Provider--)Compose: json { "info": "pulling 50%" }
|
||||
Compose-)Shell: pulling 50%
|
||||
Provider--)Compose: json { "info": "pulling 75%" }
|
||||
Compose-)Shell: pulling 75%
|
||||
Provider--)Compose: json { "setenv": "URL=http://cloud.com/abcd:1234" }
|
||||
Compose-)Compose: set DATABASE_URL
|
||||
Provider-)Compose: EOF (command complete) exit 0
|
||||
Compose-)Shell: service started
|
||||
```
|
||||
|
||||
## Connection to a service managed by a provider
|
||||
|
||||
A service in the Compose application can declare dependency on a service managed by an external provider:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
image: myapp
|
||||
depends_on:
|
||||
- database
|
||||
|
||||
database:
|
||||
provider:
|
||||
type: awesomecloud
|
||||
```
|
||||
|
||||
When the provider command sends a `setenv` JSON message, Compose injects the specified variable into any dependent service,
|
||||
automatically prefixing it with the service name. For example, if `awesomecloud compose up` returns:
|
||||
```json
|
||||
{"type": "setenv", "message": "URL=https://awesomecloud.com/db:1234"}
|
||||
```
|
||||
Then the `app` service, which depends on the service managed by the provider, will receive a `DATABASE_URL` environment variable injected
|
||||
into its runtime environment.
|
||||
|
||||
> __Note:__ The `compose up` provider command _MUST_ be idempotent. If resource is already running, the command _MUST_ set
|
||||
> the same environment variables to ensure consistent configuration of dependent services.
|
||||
|
||||
## Down lifecycle
|
||||
|
||||
`down` lifecycle is equivalent to `up` with the `<provider> compose --project-name <NAME> down <SERVICE>` command.
|
||||
The provider is responsible for releasing all resources associated with the service.
|
||||
|
||||
## Provide metadata about options
|
||||
|
||||
Compose extensions *MAY* optionally implement a `metadata` subcommand to provide information about the parameters accepted by the `up` and `down` commands.
|
||||
|
||||
The `metadata` subcommand takes no parameters and returns a JSON structure on the `stdout` channel that describes the parameters accepted by both the `up` and `down` commands, including whether each parameter is mandatory or optional.
|
||||
|
||||
```console
|
||||
awesomecloud compose metadata
|
||||
```
|
||||
|
||||
The expected JSON output format is:
|
||||
```json
|
||||
{
|
||||
"description": "Manage services on AwesomeCloud",
|
||||
"up": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "type",
|
||||
"description": "Database type (mysql, postgres, etc.)",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "size",
|
||||
"description": "Database size in GB",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
"default": "10"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Name of the database to be created",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"down": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Name of the database to be removed",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
The top elements are:
|
||||
- `description`: Human-readable description of the provider
|
||||
- `up`: Object describing the parameters accepted by the `up` command
|
||||
- `down`: Object describing the parameters accepted by the `down` command
|
||||
|
||||
And for each command parameter, you should include the following properties:
|
||||
- `name`: The parameter name (without `--` prefix)
|
||||
- `description`: Human-readable description of the parameter
|
||||
- `required`: Boolean indicating if the parameter is mandatory
|
||||
- `type`: Parameter type (`string`, `integer`, `boolean`, etc.)
|
||||
- `default`: Default value (optional, only for non-required parameters)
|
||||
- `enum`: List of possible values supported by the parameter separated by `,` (optional, only for parameters with a limited set of values)
|
||||
|
||||
This metadata allows Compose and other tools to understand the provider's interface and provide better user experience, such as validation, auto-completion, and documentation generation.
|
||||
|
||||
## Examples
|
||||
|
||||
See [example](examples/provider.go) for illustration on implementing this API in a command line
|
@ -12,6 +12,7 @@ Define and run multi-container applications with Docker
|
||||
| Name | Description |
|
||||
|:--------------------------------|:----------------------------------------------------------------------------------------|
|
||||
| [`attach`](compose_attach.md) | Attach local standard input, output, and error streams to a service's running container |
|
||||
| [`bridge`](compose_bridge.md) | Convert compose files into another model |
|
||||
| [`build`](compose_build.md) | Build or rebuild services |
|
||||
| [`commit`](compose_commit.md) | Create a new image from a service container's changes |
|
||||
| [`config`](compose_config.md) | Parse, resolve and render compose file in canonical format |
|
||||
@ -28,6 +29,7 @@ Define and run multi-container applications with Docker
|
||||
| [`pause`](compose_pause.md) | Pause services |
|
||||
| [`port`](compose_port.md) | Print the public port for a port binding |
|
||||
| [`ps`](compose_ps.md) | List containers |
|
||||
| [`publish`](compose_publish.md) | Publish compose application |
|
||||
| [`pull`](compose_pull.md) | Pull service images |
|
||||
| [`push`](compose_push.md) | Push service images |
|
||||
| [`restart`](compose_restart.md) | Restart service containers |
|
||||
@ -41,6 +43,7 @@ Define and run multi-container applications with Docker
|
||||
| [`unpause`](compose_unpause.md) | Unpause services |
|
||||
| [`up`](compose_up.md) | Create and start containers |
|
||||
| [`version`](compose_version.md) | Show the Docker Compose version information |
|
||||
| [`volumes`](compose_volumes.md) | List volumes |
|
||||
| [`wait`](compose_wait.md) | Block until containers of all (or specified) services stop. |
|
||||
| [`watch`](compose_watch.md) | Watch build context for service and rebuild/refresh containers when files are updated |
|
||||
|
||||
@ -57,7 +60,7 @@ Define and run multi-container applications with Docker
|
||||
| `-f`, `--file` | `stringArray` | | Compose configuration files |
|
||||
| `--parallel` | `int` | `-1` | Control max parallelism, -1 for unlimited |
|
||||
| `--profile` | `stringArray` | | Specify a profile to enable |
|
||||
| `--progress` | `string` | `auto` | Set type of progress output (auto, tty, plain, json, quiet) |
|
||||
| `--progress` | `string` | | Set type of progress output (auto, tty, plain, json, quiet) |
|
||||
| `--project-directory` | `string` | | Specify an alternate working directory<br>(default: the path of the, first specified, Compose file) |
|
||||
| `-p`, `--project-name` | `string` | | Project name |
|
||||
|
||||
@ -67,7 +70,7 @@ Define and run multi-container applications with Docker
|
||||
## Examples
|
||||
|
||||
### Use `-f` to specify the name and path of one or more Compose files
|
||||
Use the `-f` flag to specify the location of a Compose configuration file.
|
||||
Use the `-f` flag to specify the location of a Compose [configuration file](/reference/compose-file/).
|
||||
|
||||
#### Specifying multiple Compose files
|
||||
You can supply multiple `-f` configuration files. When you supply multiple files, Compose combines them into a single
|
||||
@ -77,10 +80,10 @@ to their predecessors.
|
||||
For example, consider this command line:
|
||||
|
||||
```console
|
||||
$ docker compose -f docker-compose.yml -f docker-compose.admin.yml run backup_db
|
||||
$ docker compose -f compose.yaml -f compose.admin.yaml run backup_db
|
||||
```
|
||||
|
||||
The `docker-compose.yml` file might specify a `webapp` service.
|
||||
The `compose.yaml` file might specify a `webapp` service.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
@ -91,7 +94,7 @@ services:
|
||||
volumes:
|
||||
- "/data"
|
||||
```
|
||||
If the `docker-compose.admin.yml` also specifies this same service, any matching fields override the previous file.
|
||||
If the `compose.admin.yaml` also specifies this same service, any matching fields override the previous file.
|
||||
New values, add to the `webapp` service configuration.
|
||||
|
||||
```yaml
|
||||
|
@ -11,7 +11,7 @@ Publish compose application
|
||||
| `--oci-version` | `string` | | OCI image/artifact specification version (automatically determined by default) |
|
||||
| `--resolve-image-digests` | `bool` | | Pin image tags to digests |
|
||||
| `--with-env` | `bool` | | Include environment variables in the published OCI artifact |
|
||||
| `-y`, `--y` | `bool` | | Assume "yes" as answer to all prompts |
|
||||
| `-y`, `--yes` | `bool` | | Assume "yes" as answer to all prompts |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
22
docs/reference/compose_bridge.md
Normal file
22
docs/reference/compose_bridge.md
Normal file
@ -0,0 +1,22 @@
|
||||
# docker compose bridge
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Convert compose files into another model
|
||||
|
||||
### Subcommands
|
||||
|
||||
| Name | Description |
|
||||
|:-------------------------------------------------------|:-----------------------------------------------------------------------------|
|
||||
| [`convert`](compose_bridge_convert.md) | Convert compose files to Kubernetes manifests, Helm charts, or another model |
|
||||
| [`transformations`](compose_bridge_transformations.md) | Manage transformation images |
|
||||
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------|:-------|:--------|:--------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
17
docs/reference/compose_bridge_convert.md
Normal file
17
docs/reference/compose_bridge_convert.md
Normal file
@ -0,0 +1,17 @@
|
||||
# docker compose bridge convert
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Convert compose files to Kubernetes manifests, Helm charts, or another model
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-------------------------|:--------------|:--------|:-------------------------------------------------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `-o`, `--output` | `string` | `out` | The output directory for the Kubernetes resources |
|
||||
| `--templates` | `string` | | Directory containing transformation templates |
|
||||
| `-t`, `--transformation` | `stringArray` | | Transformation to apply to compose model (default: docker/compose-bridge-kubernetes) |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
22
docs/reference/compose_bridge_transformations.md
Normal file
22
docs/reference/compose_bridge_transformations.md
Normal file
@ -0,0 +1,22 @@
|
||||
# docker compose bridge transformations
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Manage transformation images
|
||||
|
||||
### Subcommands
|
||||
|
||||
| Name | Description |
|
||||
|:-----------------------------------------------------|:-------------------------------|
|
||||
| [`create`](compose_bridge_transformations_create.md) | Create a new transformation |
|
||||
| [`list`](compose_bridge_transformations_list.md) | List available transformations |
|
||||
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------|:-------|:--------|:--------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
15
docs/reference/compose_bridge_transformations_create.md
Normal file
15
docs/reference/compose_bridge_transformations_create.md
Normal file
@ -0,0 +1,15 @@
|
||||
# docker compose bridge transformations create
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Create a new transformation
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------|:---------|:--------|:----------------------------------------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `-f`, `--from` | `string` | | Existing transformation to copy (default: docker/compose-bridge-kubernetes) |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
20
docs/reference/compose_bridge_transformations_list.md
Normal file
20
docs/reference/compose_bridge_transformations_list.md
Normal file
@ -0,0 +1,20 @@
|
||||
# docker compose bridge transformations list
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
List available transformations
|
||||
|
||||
### Aliases
|
||||
|
||||
`docker compose bridge transformations list`, `docker compose bridge transformations ls`
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------|:---------|:--------|:-------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--format` | `string` | `table` | Format the output. Values: [table \| json] |
|
||||
| `-q`, `--quiet` | `bool` | | Only display transformer names |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
@ -17,12 +17,16 @@ run `docker compose build` to rebuild it.
|
||||
|:----------------------|:--------------|:--------|:------------------------------------------------------------------------------------------------------------|
|
||||
| `--build-arg` | `stringArray` | | Set build-time variables for services |
|
||||
| `--builder` | `string` | | Set builder to use |
|
||||
| `--check` | `bool` | | Check build configuration |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `-m`, `--memory` | `bytes` | `0` | Set memory limit for the build container. Not supported by BuildKit. |
|
||||
| `--no-cache` | `bool` | | Do not use cache when building the image |
|
||||
| `--print` | `bool` | | Print equivalent bake file |
|
||||
| `--provenance` | `string` | | Add a provenance attestation |
|
||||
| `--pull` | `bool` | | Always attempt to pull a newer version of the image |
|
||||
| `--push` | `bool` | | Push service images |
|
||||
| `-q`, `--quiet` | `bool` | | Don't print anything to STDOUT |
|
||||
| `-q`, `--quiet` | `bool` | | Suppress the build output |
|
||||
| `--sbom` | `string` | | Add a SBOM attestation |
|
||||
| `--ssh` | `string` | | Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent) |
|
||||
| `--with-dependencies` | `bool` | | Also build dependencies (transitively) |
|
||||
|
||||
|
@ -5,20 +5,20 @@
|
||||
It merges the Compose files set by `-f` flags, resolves variables in the Compose file, and expands short-notation into
|
||||
the canonical format.
|
||||
|
||||
### Aliases
|
||||
|
||||
`docker compose config`, `docker compose convert`
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:--------------------------|:---------|:--------|:----------------------------------------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--environment` | `bool` | | Print environment used for interpolation. |
|
||||
| `--format` | `string` | `yaml` | Format the output. Values: [yaml \| json] |
|
||||
| `--format` | `string` | | Format the output. Values: [yaml \| json] |
|
||||
| `--hash` | `string` | | Print the service config hash, one per line. |
|
||||
| `--images` | `bool` | | Print the image names, one per line. |
|
||||
| `--lock-image-digests` | `bool` | | Produces an override file with image digests |
|
||||
| `--models` | `bool` | | Print the model names, one per line. |
|
||||
| `--networks` | `bool` | | Print the network names, one per line. |
|
||||
| `--no-consistency` | `bool` | | Don't check model consistency - warning: may produce invalid Compose output |
|
||||
| `--no-env-resolution` | `bool` | | Don't resolve service env files |
|
||||
| `--no-interpolate` | `bool` | | Don't interpolate environment variables |
|
||||
| `--no-normalize` | `bool` | | Don't normalize compose model |
|
||||
| `--no-path-resolution` | `bool` | | Don't resolve file paths |
|
||||
|
@ -16,7 +16,7 @@ Creates containers for a service
|
||||
| `--quiet-pull` | `bool` | | Pull without printing progress information |
|
||||
| `--remove-orphans` | `bool` | | Remove containers for services not defined in the Compose file |
|
||||
| `--scale` | `stringArray` | | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. |
|
||||
| `-y`, `--y` | `bool` | | Assume "yes" as answer to all prompts and run non-interactively |
|
||||
| `-y`, `--yes` | `bool` | | Assume "yes" as answer to all prompts and run non-interactively |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
@ -23,10 +23,12 @@ The events that can be received using this can be seen [here](/reference/cli/doc
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------|:-------|:--------|:------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--json` | `bool` | | Output events as a stream of json objects |
|
||||
| Name | Type | Default | Description |
|
||||
|:------------|:---------|:--------|:------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--json` | `bool` | | Output events as a stream of json objects |
|
||||
| `--since` | `string` | | Show all events created since timestamp |
|
||||
| `--until` | `string` | | Stream events until this timestamp |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
18
docs/reference/compose_publish.md
Normal file
18
docs/reference/compose_publish.md
Normal file
@ -0,0 +1,18 @@
|
||||
# docker compose publish
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Publish compose application
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:--------------------------|:---------|:--------|:-------------------------------------------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--oci-version` | `string` | | OCI image/artifact specification version (automatically determined by default) |
|
||||
| `--resolve-image-digests` | `bool` | | Pin image tags to digests |
|
||||
| `--with-env` | `bool` | | Include environment variables in the published OCI artifact |
|
||||
| `-y`, `--yes` | `bool` | | Assume "yes" as answer to all prompts |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
@ -66,6 +66,7 @@ specified in the service configuration.
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--entrypoint` | `string` | | Override the entrypoint of the image |
|
||||
| `-e`, `--env` | `stringArray` | | Set environment variables |
|
||||
| `--env-from-file` | `stringArray` | | Set environment variables from file |
|
||||
| `-i`, `--interactive` | `bool` | `true` | Keep STDIN open even if not attached |
|
||||
| `-l`, `--label` | `stringArray` | | Add or override a label |
|
||||
| `--name` | `string` | | Assign a name to the container |
|
||||
@ -73,6 +74,8 @@ specified in the service configuration.
|
||||
| `--no-deps` | `bool` | | Don't start linked services |
|
||||
| `-p`, `--publish` | `stringArray` | | Publish a container's port(s) to the host |
|
||||
| `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never") |
|
||||
| `-q`, `--quiet` | `bool` | | Don't print anything to STDOUT |
|
||||
| `--quiet-build` | `bool` | | Suppress progress output from the build process |
|
||||
| `--quiet-pull` | `bool` | | Pull without printing progress information |
|
||||
| `--remove-orphans` | `bool` | | Remove containers for services not defined in the Compose file |
|
||||
| `--rm` | `bool` | | Automatically remove the container when it exits |
|
||||
|
@ -44,6 +44,7 @@ If the process is interrupted using `SIGINT` (ctrl + C) or `SIGTERM`, the contai
|
||||
| `--no-recreate` | `bool` | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
|
||||
| `--no-start` | `bool` | | Don't start the services after creating them |
|
||||
| `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never") |
|
||||
| `--quiet-build` | `bool` | | Suppress the build output |
|
||||
| `--quiet-pull` | `bool` | | Pull without printing progress information |
|
||||
| `--remove-orphans` | `bool` | | Remove containers for services not defined in the Compose file |
|
||||
| `-V`, `--renew-anon-volumes` | `bool` | | Recreate anonymous volumes instead of retrieving data from the previous containers |
|
||||
@ -53,7 +54,7 @@ If the process is interrupted using `SIGINT` (ctrl + C) or `SIGTERM`, the contai
|
||||
| `--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 |
|
||||
| `-w`, `--watch` | `bool` | | Watch source code and rebuild/refresh containers when files are updated. |
|
||||
| `-y`, `--y` | `bool` | | Assume "yes" as answer to all prompts and run non-interactively |
|
||||
| `-y`, `--yes` | `bool` | | Assume "yes" as answer to all prompts and run non-interactively |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
16
docs/reference/compose_volumes.md
Normal file
16
docs/reference/compose_volumes.md
Normal file
@ -0,0 +1,16 @@
|
||||
# docker compose volumes
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
List volumes
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--format` | `string` | `table` | Format output using a custom template:<br>'table': Print output in table format with column headers (default)<br>'table TEMPLATE': Print output in table format using the given Go template<br>'json': Print in JSON format<br>'TEMPLATE': Print output using the given Go template.<br>Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates |
|
||||
| `-q`, `--quiet` | `bool` | | Only display volume names |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
@ -9,7 +9,7 @@ Watch build context for service and rebuild/refresh containers when files are up
|
||||
|:------------|:-------|:--------|:----------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--no-up` | `bool` | | Do not build & start services before watching |
|
||||
| `--prune` | `bool` | | Prune dangling images on rebuild |
|
||||
| `--prune` | `bool` | `true` | Prune dangling images on rebuild |
|
||||
| `--quiet` | `bool` | | hide build output |
|
||||
|
||||
|
||||
|
@ -6,6 +6,7 @@ pname: docker
|
||||
plink: docker.yaml
|
||||
cname:
|
||||
- docker compose attach
|
||||
- docker compose bridge
|
||||
- docker compose build
|
||||
- docker compose commit
|
||||
- docker compose config
|
||||
@ -22,6 +23,7 @@ cname:
|
||||
- docker compose pause
|
||||
- docker compose port
|
||||
- docker compose ps
|
||||
- docker compose publish
|
||||
- docker compose pull
|
||||
- docker compose push
|
||||
- docker compose restart
|
||||
@ -35,10 +37,12 @@ cname:
|
||||
- docker compose unpause
|
||||
- docker compose up
|
||||
- docker compose version
|
||||
- docker compose volumes
|
||||
- docker compose wait
|
||||
- docker compose watch
|
||||
clink:
|
||||
- docker_compose_attach.yaml
|
||||
- docker_compose_bridge.yaml
|
||||
- docker_compose_build.yaml
|
||||
- docker_compose_commit.yaml
|
||||
- docker_compose_config.yaml
|
||||
@ -55,6 +59,7 @@ clink:
|
||||
- docker_compose_pause.yaml
|
||||
- docker_compose_port.yaml
|
||||
- docker_compose_ps.yaml
|
||||
- docker_compose_publish.yaml
|
||||
- docker_compose_pull.yaml
|
||||
- docker_compose_push.yaml
|
||||
- docker_compose_restart.yaml
|
||||
@ -68,6 +73,7 @@ clink:
|
||||
- docker_compose_unpause.yaml
|
||||
- docker_compose_up.yaml
|
||||
- docker_compose_version.yaml
|
||||
- docker_compose_volumes.yaml
|
||||
- docker_compose_wait.yaml
|
||||
- docker_compose_watch.yaml
|
||||
options:
|
||||
@ -165,7 +171,6 @@ options:
|
||||
swarm: false
|
||||
- option: progress
|
||||
value_type: string
|
||||
default_value: auto
|
||||
description: Set type of progress output (auto, tty, plain, json, quiet)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
@ -229,7 +234,7 @@ options:
|
||||
swarm: false
|
||||
examples: |-
|
||||
### Use `-f` to specify the name and path of one or more Compose files
|
||||
Use the `-f` flag to specify the location of a Compose configuration file.
|
||||
Use the `-f` flag to specify the location of a Compose [configuration file](/reference/compose-file/).
|
||||
|
||||
#### Specifying multiple Compose files
|
||||
You can supply multiple `-f` configuration files. When you supply multiple files, Compose combines them into a single
|
||||
@ -239,10 +244,10 @@ examples: |-
|
||||
For example, consider this command line:
|
||||
|
||||
```console
|
||||
$ docker compose -f docker-compose.yml -f docker-compose.admin.yml run backup_db
|
||||
$ docker compose -f compose.yaml -f compose.admin.yaml run backup_db
|
||||
```
|
||||
|
||||
The `docker-compose.yml` file might specify a `webapp` service.
|
||||
The `compose.yaml` file might specify a `webapp` service.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
@ -253,7 +258,7 @@ examples: |-
|
||||
volumes:
|
||||
- "/data"
|
||||
```
|
||||
If the `docker-compose.admin.yml` also specifies this same service, any matching fields override the previous file.
|
||||
If the `compose.admin.yaml` also specifies this same service, any matching fields override the previous file.
|
||||
New values, add to the `webapp` service configuration.
|
||||
|
||||
```yaml
|
||||
|
@ -45,7 +45,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: true
|
||||
kubernetes: false
|
||||
|
@ -35,7 +35,7 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: "y"
|
||||
- option: "yes"
|
||||
shorthand: "y"
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
@ -58,7 +58,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: true
|
||||
kubernetes: false
|
||||
|
@ -69,7 +69,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: true
|
||||
kubernetes: false
|
||||
|
29
docs/reference/docker_compose_bridge.yaml
Normal file
29
docs/reference/docker_compose_bridge.yaml
Normal file
@ -0,0 +1,29 @@
|
||||
command: docker compose bridge
|
||||
short: Convert compose files into another model
|
||||
long: Convert compose files into another model
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
cname:
|
||||
- docker compose bridge convert
|
||||
- docker compose bridge transformations
|
||||
clink:
|
||||
- docker_compose_bridge_convert.yaml
|
||||
- docker_compose_bridge_transformations.yaml
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
|
59
docs/reference/docker_compose_bridge_convert.yaml
Normal file
59
docs/reference/docker_compose_bridge_convert.yaml
Normal file
@ -0,0 +1,59 @@
|
||||
command: docker compose bridge convert
|
||||
short: |
|
||||
Convert compose files to Kubernetes manifests, Helm charts, or another model
|
||||
long: |
|
||||
Convert compose files to Kubernetes manifests, Helm charts, or another model
|
||||
usage: docker compose bridge convert
|
||||
pname: docker compose bridge
|
||||
plink: docker_compose_bridge.yaml
|
||||
options:
|
||||
- option: output
|
||||
shorthand: o
|
||||
value_type: string
|
||||
default_value: out
|
||||
description: The output directory for the Kubernetes resources
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: templates
|
||||
value_type: string
|
||||
description: Directory containing transformation templates
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: transformation
|
||||
shorthand: t
|
||||
value_type: stringArray
|
||||
default_value: '[]'
|
||||
description: |
|
||||
Transformation to apply to compose model (default: docker/compose-bridge-kubernetes)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
|
29
docs/reference/docker_compose_bridge_transformations.yaml
Normal file
29
docs/reference/docker_compose_bridge_transformations.yaml
Normal file
@ -0,0 +1,29 @@
|
||||
command: docker compose bridge transformations
|
||||
short: Manage transformation images
|
||||
long: Manage transformation images
|
||||
pname: docker compose bridge
|
||||
plink: docker_compose_bridge.yaml
|
||||
cname:
|
||||
- docker compose bridge transformations create
|
||||
- docker compose bridge transformations list
|
||||
clink:
|
||||
- docker_compose_bridge_transformations_create.yaml
|
||||
- docker_compose_bridge_transformations_list.yaml
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
|
@ -0,0 +1,36 @@
|
||||
command: docker compose bridge transformations create
|
||||
short: Create a new transformation
|
||||
long: Create a new transformation
|
||||
usage: docker compose bridge transformations create [OPTION] PATH
|
||||
pname: docker compose bridge transformations
|
||||
plink: docker_compose_bridge_transformations.yaml
|
||||
options:
|
||||
- option: from
|
||||
shorthand: f
|
||||
value_type: string
|
||||
description: |
|
||||
Existing transformation to copy (default: docker/compose-bridge-kubernetes)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
|
@ -0,0 +1,47 @@
|
||||
command: docker compose bridge transformations list
|
||||
aliases: docker compose bridge transformations list, docker compose bridge transformations ls
|
||||
short: List available transformations
|
||||
long: List available transformations
|
||||
usage: docker compose bridge transformations list
|
||||
pname: docker compose bridge transformations
|
||||
plink: docker_compose_bridge_transformations.yaml
|
||||
options:
|
||||
- option: format
|
||||
value_type: string
|
||||
default_value: table
|
||||
description: 'Format the output. Values: [table | json]'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: quiet
|
||||
shorthand: q
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Only display transformer names
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
|
@ -33,6 +33,16 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: check
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Check build configuration
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: compress
|
||||
value_type: bool
|
||||
default_value: "true"
|
||||
@ -96,9 +106,18 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: print
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Print equivalent bake file
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: progress
|
||||
value_type: string
|
||||
default_value: auto
|
||||
description: Set type of ui output (auto, tty, plain, json, quiet)
|
||||
deprecated: false
|
||||
hidden: true
|
||||
@ -106,6 +125,15 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: provenance
|
||||
value_type: string
|
||||
description: Add a provenance attestation
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: pull
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
@ -130,7 +158,16 @@ options:
|
||||
shorthand: q
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Don't print anything to STDOUT
|
||||
description: Suppress the build output
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: sbom
|
||||
value_type: string
|
||||
description: Add a SBOM attestation
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
|
@ -1,5 +1,4 @@
|
||||
command: docker compose config
|
||||
aliases: docker compose config, docker compose convert
|
||||
short: Parse, resolve and render compose file in canonical format
|
||||
long: |-
|
||||
`docker compose config` renders the actual data model to be applied on the Docker Engine.
|
||||
@ -21,7 +20,6 @@ options:
|
||||
swarm: false
|
||||
- option: format
|
||||
value_type: string
|
||||
default_value: yaml
|
||||
description: 'Format the output. Values: [yaml | json]'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
@ -48,6 +46,36 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: lock-image-digests
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Produces an override file with image digests
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: models
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Print the model names, one per line.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: networks
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Print the network names, one per line.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: no-consistency
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
@ -59,6 +87,16 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: no-env-resolution
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Don't resolve service env files
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: no-interpolate
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
|
@ -88,7 +88,7 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: "y"
|
||||
- option: "yes"
|
||||
shorthand: "y"
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
|
@ -34,6 +34,24 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: since
|
||||
value_type: string
|
||||
description: Show all events created since timestamp
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: until
|
||||
value_type: string
|
||||
description: Stream events until this timestamp
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
|
66
docs/reference/docker_compose_publish.yaml
Normal file
66
docs/reference/docker_compose_publish.yaml
Normal file
@ -0,0 +1,66 @@
|
||||
command: docker compose publish
|
||||
short: Publish compose application
|
||||
long: Publish compose application
|
||||
usage: docker compose publish [OPTIONS] REPOSITORY[:TAG]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
- option: oci-version
|
||||
value_type: string
|
||||
description: |
|
||||
OCI image/artifact specification version (automatically determined by default)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: resolve-image-digests
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Pin image tags to digests
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: with-env
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Include environment variables in the published OCI artifact
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: "yes"
|
||||
shorthand: "y"
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Assume "yes" as answer to all prompts
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
|
@ -117,6 +117,16 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: env-from-file
|
||||
value_type: stringArray
|
||||
default_value: '[]'
|
||||
description: Set environment variables from file
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: interactive
|
||||
shorthand: i
|
||||
value_type: bool
|
||||
@ -190,6 +200,27 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: quiet
|
||||
shorthand: q
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Don't print anything to STDOUT
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: quiet-build
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Suppress progress output from the build process
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: quiet-pull
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
|
@ -211,6 +211,16 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: quiet-build
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Suppress the build output
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: quiet-pull
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
@ -309,7 +319,7 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: "y"
|
||||
- option: "yes"
|
||||
shorthand: "y"
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
|
52
docs/reference/docker_compose_volumes.yaml
Normal file
52
docs/reference/docker_compose_volumes.yaml
Normal file
@ -0,0 +1,52 @@
|
||||
command: docker compose volumes
|
||||
short: List volumes
|
||||
long: List volumes
|
||||
usage: docker compose volumes [OPTIONS] [SERVICE...]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
- option: format
|
||||
value_type: string
|
||||
default_value: table
|
||||
description: |-
|
||||
Format output using a custom template:
|
||||
'table': Print output in table format with column headers (default)
|
||||
'table TEMPLATE': Print output in table format using the given Go template
|
||||
'json': Print in JSON format
|
||||
'TEMPLATE': Print output using the given Go template.
|
||||
Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: quiet
|
||||
shorthand: q
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Only display volume names
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
|
@ -19,7 +19,7 @@ options:
|
||||
swarm: false
|
||||
- option: prune
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
default_value: "true"
|
||||
description: Prune dangling images on rebuild
|
||||
deprecated: false
|
||||
hidden: false
|
||||
|
171
go.mod
171
go.mod
@ -1,70 +1,70 @@
|
||||
module github.com/docker/compose/v2
|
||||
|
||||
go 1.22.10
|
||||
go 1.23.10
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||
github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e
|
||||
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.4.8
|
||||
github.com/containerd/containerd/v2 v2.0.2
|
||||
github.com/compose-spec/compose-go/v2 v2.8.1
|
||||
github.com/containerd/containerd/v2 v2.1.3
|
||||
github.com/containerd/errdefs v1.0.0
|
||||
github.com/containerd/platforms v1.0.0-rc.1
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/docker/buildx v0.21.1
|
||||
github.com/docker/cli v28.0.0+incompatible
|
||||
github.com/docker/cli-docs-tool v0.9.0
|
||||
github.com/docker/docker v28.0.0+incompatible
|
||||
github.com/docker/buildx v0.26.1
|
||||
github.com/docker/cli v28.3.3+incompatible
|
||||
github.com/docker/cli-docs-tool v0.10.0
|
||||
github.com/docker/docker v28.3.3+incompatible
|
||||
github.com/docker/go-connections v0.5.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/google/go-cmp v0.6.0
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
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/mitchellh/mapstructure v1.5.0
|
||||
github.com/moby/buildkit v0.20.0
|
||||
github.com/moby/buildkit v0.23.0-rc1.0.20250618182037-9b91d20367db // master
|
||||
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/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.0
|
||||
github.com/opencontainers/image-spec v1.1.1
|
||||
github.com/otiai10/copy v1.14.1
|
||||
github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/pflag v1.0.6
|
||||
github.com/spf13/pflag v1.0.7
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/theupdateframework/notary v0.7.0
|
||||
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0
|
||||
go.opentelemetry.io/otel v1.32.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0
|
||||
go.opentelemetry.io/otel/metric v1.32.0
|
||||
go.opentelemetry.io/otel/sdk v1.32.0
|
||||
go.opentelemetry.io/otel/trace v1.32.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.5.0
|
||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f
|
||||
golang.org/x/sync v0.11.0
|
||||
golang.org/x/sys v0.30.0
|
||||
google.golang.org/grpc v1.70.0
|
||||
go.uber.org/mock v0.5.2
|
||||
golang.org/x/sync v0.16.0
|
||||
golang.org/x/sys v0.34.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 v0.8.0
|
||||
tags.cncf.io/container-device-interface v1.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // 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
|
||||
@ -82,31 +82,31 @@ require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/containerd/console v1.0.4 // indirect
|
||||
github.com/containerd/containerd/api v1.8.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 v1.0.0 // 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.6 // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.2 // 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.2 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // 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.22.4 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.3.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.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
|
||||
@ -114,32 +114,33 @@ require (
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.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/imdario/mergo v0.3.16 // indirect
|
||||
github.com/in-toto/in-toto-golang v0.5.0 // indirect
|
||||
github.com/in-toto/in-toto-golang v0.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.17.11 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/magiconair/properties v1.8.9 // 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.17 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/spdystream v0.4.0 // 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.3.0 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
@ -149,52 +150,66 @@ require (
|
||||
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.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.20.5 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.22.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // 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/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.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/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a // indirect
|
||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 // indirect
|
||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f // indirect
|
||||
github.com/tonistiigi/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/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/zclconf/go-cty v1.16.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/oauth2 v0.24.0 // indirect
|
||||
golang.org/x/term v0.27.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.6.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect
|
||||
google.golang.org/protobuf v1.35.2 // indirect
|
||||
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
|
||||
github.com/zclconf/go-cty v1.16.2 // 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.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.6 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/api v0.31.2 // indirect
|
||||
k8s.io/apimachinery v0.31.2 // indirect
|
||||
k8s.io/client-go v0.31.2 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // 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-20240228011516-70dd3763d340 // indirect
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.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
|
||||
)
|
||||
|
366
go.sum
366
go.sum
@ -2,25 +2,24 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 h1:dIScnXFlF784X79oi7MzVT6GWqr/W1uUt0pB5CsDs9M=
|
||||
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2/go.mod h1:gCLVsLfv1egrcZu+GoJATN5ts75F2s62ih/457eWzOw=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
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.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg=
|
||||
github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y=
|
||||
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=
|
||||
github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
|
||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
@ -81,16 +80,16 @@ github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e
|
||||
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.4.8 h1:7Myl8wDRl/4mRz77S+eyDJymGGEHu0diQdGSSeyq90A=
|
||||
github.com/compose-spec/compose-go/v2 v2.4.8/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc=
|
||||
github.com/compose-spec/compose-go/v2 v2.8.1 h1:27O4dzyhiS/UEUKp1zHOHCBWD1WbxGsYGMNNaSejTk4=
|
||||
github.com/compose-spec/compose-go/v2 v2.8.1/go.mod h1:veko/VB7URrg/tKz3vmIAQDaz+CGiXH8vZsW79NmAww=
|
||||
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.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
|
||||
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0=
|
||||
github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc=
|
||||
github.com/containerd/containerd/v2 v2.0.2 h1:GmH/tRBlTvrXOLwSpWE2vNAm8+MqI6nmxKpKBNKY8Wc=
|
||||
github.com/containerd/containerd/v2 v2.0.2/go.mod h1:wIqEvQ/6cyPFUGJ5yMFanspPabMLor+bF865OHvNTTI=
|
||||
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.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.3 h1:eMD2SLcIQPdMlnlNF6fatlrlRLAeDaiGPGwmRKLZKNs=
|
||||
github.com/containerd/containerd/v2 v2.1.3/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=
|
||||
@ -101,8 +100,8 @@ github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY
|
||||
github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/nydus-snapshotter v0.15.0 h1:RqZRs1GPeM6T3wmuxJV9u+2Rg4YETVMwTmiDeX+iWC8=
|
||||
github.com/containerd/nydus-snapshotter v0.15.0/go.mod h1:biq0ijpeZe0I5yZFSJyHzFSjjRZQ7P7y/OuHyd7hYOw=
|
||||
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=
|
||||
@ -121,25 +120,26 @@ github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/buildx v0.21.1 h1:YjV2k6CsSDbkDTOMsjARUIrj2xv+zZR+M2dtrRyzXhg=
|
||||
github.com/docker/buildx v0.21.1/go.mod h1:8V4UMnlKsaGYwz83BygmIbJIFEAYGHT6KAv8akDZmqo=
|
||||
github.com/docker/cli v28.0.0+incompatible h1:ido37VmLUqEp+5NFb9icd6BuBB+SNDgCn+5kPCr2buA=
|
||||
github.com/docker/cli v28.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli-docs-tool v0.9.0 h1:CVwQbE+ZziwlPqrJ7LRyUF6GvCA+6gj7MTCsayaK9t0=
|
||||
github.com/docker/cli-docs-tool v0.9.0/go.mod h1:ClrwlNW+UioiRyH9GiAOe1o3J/TsY3Tr1ipoypjAUtc=
|
||||
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.26.1 h1:nlj3bVhHK9fV7g6floRvGhPcR0u2hxCPMmObCS1ZKL4=
|
||||
github.com/docker/buildx v0.26.1/go.mod h1:oxMC30cSHPaCCkY2j+EqN7uxFikjSzVC0c44lo9b4Fo=
|
||||
github.com/docker/cli v28.3.3+incompatible h1:fp9ZHAr1WWPGdIWBM1b3zLtgCF+83gRdVMTJsUeiyAo=
|
||||
github.com/docker/cli v28.3.3+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.0.0+incompatible h1:Olh0KS820sJ7nPsBKChVhk5pzqcwDR15fumfAd/p9hM=
|
||||
github.com/docker/docker v28.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
|
||||
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
|
||||
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
|
||||
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
@ -163,8 +163,6 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
|
||||
github.com/fsnotify/fsevents v0.2.0 h1:BRlvlqjvNTfogHfeBOFvSC9N0Ddy+wzQCQukyoD7o/c=
|
||||
github.com/fsnotify/fsevents v0.2.0/go.mod h1:B3eEk39i4hz8y1zaWS/wPrAP4O6wkIl7HQwKBr1qH/w=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
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=
|
||||
@ -173,30 +171,33 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
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 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||
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.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
|
||||
github.com/go-openapi/swag v0.22.4/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-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.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc=
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
|
||||
github.com/go-viper/mapstructure/v2 v2.3.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.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-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@ -212,13 +213,13 @@ github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvR
|
||||
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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/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-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM=
|
||||
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
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=
|
||||
@ -228,8 +229,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
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.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
|
||||
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=
|
||||
@ -244,13 +245,13 @@ github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
github.com/in-toto/in-toto-golang v0.5.0 h1:hb8bgwr0M2hGdDsLjkJ3ZqJ8JFLL/tgYdAxF/XEFBbY=
|
||||
github.com/in-toto/in-toto-golang v0.5.0/go.mod h1:/Rq0IZHLV7Ku5gielPT4wPHJfH1GdHMCq8+WPxw8/BE=
|
||||
github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU=
|
||||
github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf h1:FtEj8sfIcaaBfAKrE1Cwb61YDtYq9JxChK1c7AKce7s=
|
||||
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf/go.mod h1:yrqSXGoD/4EKfF26AOGzscPOgTTJcyAwM2rpixWT+t4=
|
||||
github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8 h1:CZkYfurY6KGhVtlalI4QwQ6T0Cu6iuY3e0x5RLu96WE=
|
||||
github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
|
||||
github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d h1:jRQLvyVGL+iVtDElaEIDdKwpPqUIZJfzkNLV34htpEc=
|
||||
@ -270,8 +271,8 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU
|
||||
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.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
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=
|
||||
@ -286,8 +287,9 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/magiconair/properties v1.5.3 h1:C8fxWnhYyME3n0klPOhVM7PtYUB3eV1W3DeFmN3j53Y=
|
||||
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=
|
||||
@ -295,10 +297,10 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec
|
||||
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.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
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=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
@ -315,16 +317,20 @@ 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.20.0 h1:aF5RujjQ310Pn6SLL/wQYIrSsPXy0sQ5KvWifwq1h8Y=
|
||||
github.com/moby/buildkit v0.20.0/go.mod h1:HYFUIK+iGDRxRgdphZ9Nv0y1Fz7mv0HrU7xZoXx217E=
|
||||
github.com/moby/buildkit v0.23.0-rc1.0.20250618182037-9b91d20367db h1:ZzrDuG9G1A/RwJvuogNplxCEKsIUQh1CqEnqbOGFgKE=
|
||||
github.com/moby/buildkit v0.23.0-rc1.0.20250618182037-9b91d20367db/go.mod h1:v5jMDvQgUyidk3wu3NvVAAd5JJo83nfet9Gf/o0+EAQ=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
|
||||
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
|
||||
github.com/moby/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.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8=
|
||||
github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
|
||||
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=
|
||||
github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I=
|
||||
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
|
||||
@ -335,8 +341,8 @@ github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0
|
||||
github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8=
|
||||
github.com/moby/sys/symlink v0.3.0 h1:GZX89mEZ9u53f97npBy4Rc3vJKj7JBDj/PN2I22GrNU=
|
||||
github.com/moby/sys/symlink v0.3.0/go.mod h1:3eNdhduHmYPcgsJtZXW1W4XUJdZGBIkttZ8xKqPUJq0=
|
||||
github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
|
||||
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
@ -359,24 +365,22 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
|
||||
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.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
||||
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.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||
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.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk=
|
||||
github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626 h1:DmNGcqH3WDbV5k8OJ+esPWbqUOX5rMLR2PMvziDMJi0=
|
||||
github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626/go.mod h1:BRHJJd0E+cx42OybVYSgUvZmU0B8P9gZuRXlZUP7TKI=
|
||||
github.com/opencontainers/selinux v1.11.1 h1:nHFvthhM0qY8/m+vfhJylliSshm8G1jJ2jDMcgULaH8=
|
||||
github.com/opencontainers/selinux v1.11.1/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
|
||||
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.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=
|
||||
@ -391,15 +395,14 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
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=
|
||||
@ -408,24 +411,24 @@ github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQy
|
||||
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.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
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.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc h1:zAsgcP8MhzAbhMnB1QQ2O7ZhWYVGYSR2iVcjzQuPV+o=
|
||||
github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc/go.mod h1:S8xSOnV3CgpNrWd0GQ/OoQfMtlg2uPRSuTzcSGrzwK8=
|
||||
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.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/secure-systems-lab/go-securesystemslib v0.4.0 h1:b23VGrQhTA8cN2CbBw7/FulN9fTtqYUdS5+Oxzt+DUE=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.4.0/go.mod h1:FGBZgq2tXWICsxWQW1msNf49F0Pf2Op5Htayx335Qbs=
|
||||
github.com/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.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=
|
||||
@ -437,8 +440,8 @@ 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.3 h1:ialnHeEYUC4+hkm5vJm4qz2x+oEJbS0mAMFrNXdQraY=
|
||||
github.com/spdx/tools-golang v0.5.3/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/uFZm2NTMhI=
|
||||
github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk=
|
||||
github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE=
|
||||
github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94 h1:JmfC365KywYwHB946TTiQWEb8kqPY+pybPLoGE9GgVk=
|
||||
github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||
github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
@ -447,8 +450,9 @@ github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wx
|
||||
github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431 h1:XTHrT015sxHyJ5FnQ0AeemSspZWaDq7DoTRW0EVsDCE=
|
||||
github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c h1:2EejZtjFjKJGk71ANb+wtFK5EjUzUkEM3R0xnp559xg=
|
||||
github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@ -467,24 +471,22 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c=
|
||||
github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw=
|
||||
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 h1:QB54BJwA6x8QU9nHY3xJSZR2kX9bgpZekRKGkLTmEXA=
|
||||
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375/go.mod h1:xRroudyp5iVtxKqZCrA6n2TLFRBf8bmnjr1UD4x+z7g=
|
||||
github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205 h1:eUk79E1w8yMtXeHSzjKorxuC8qJOnyXQnLaJehxpJaI=
|
||||
github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a h1:EfGw4G0x/8qXWgtcZ6KVaPS+wpWOQMaypczzP8ojkMY=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a/go.mod h1:Dl/9oEjK7IqnjAm21Okx/XIxUCFJzvh+XdVHUlBwXTw=
|
||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 h1:7I5c2Ig/5FgqkYOh/N87NzoyI9U15qUPXhDD8uCupv8=
|
||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE=
|
||||
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 h1:r0p7fK56l8WPequOaR3i9LBqfPtEdXIQbUTzT55iqT4=
|
||||
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f h1:MoxeMfHAe5Qj/ySSBfL8A7l1V+hxuluj8owsIEEZipI=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98=
|
||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 h1:2f304B10LaZdB8kkVEaoXvAMVan2tl9AiK4G0odjQtE=
|
||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE=
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk=
|
||||
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab h1:H6aJ0yKQ0gF49Qb2z5hI1UHxSQt4JMyxebFR15KnApw=
|
||||
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc=
|
||||
github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs=
|
||||
github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI=
|
||||
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/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
@ -494,45 +496,51 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
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.16.0 h1:xPKEhst+BW5D0wxebMZkxgapvOE/dw7bFTlgSc9nD6w=
|
||||
github.com/zclconf/go-cty v1.16.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
||||
github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70=
|
||||
github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 h1:yMkBS9yViCc7U7yeLzJPM2XizlfdVvBRSmsQDWu6qc0=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0/go.mod h1:n8MR6/liuGB5EmTETUBeU5ZgqMOlqKRxUaqPQBOANZ8=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0 h1:4BZHA+B1wXEQoGNHxW8mURaLhcdGwvRnmhGbm+odRbc=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0/go.mod h1:3qi2EEwMgB4xnKgPLqsDP3j9qxnHDZeHsnAxfjQqTko=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
|
||||
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
|
||||
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 h1:FZ6ei8GFW7kyPYdxJaV2rgI6M+4tvZzhYsQ2wgyVC08=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0/go.mod h1:MdEu/mC6j3D+tTEfvI15b5Ci2Fn7NneJ71YMoiS3tpI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0 h1:ZsXq73BERAiNuuFXYqP4MR5hBrjXfMGSO+Cx7qoOZiM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0/go.mod h1:hg1zaDMpyZJuUzjFxFsRYBoccE86tM9Uf4IqNMUxvrY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy03iVTXP6ffeN2iXrxfGsZGCjVx0/4KlizjyBwU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4=
|
||||
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
|
||||
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
|
||||
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
|
||||
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
|
||||
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
|
||||
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||
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.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
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=
|
||||
@ -541,29 +549,26 @@ 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.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
|
||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
|
||||
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.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
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=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
|
||||
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
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=
|
||||
@ -571,8 +576,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.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
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=
|
||||
@ -593,55 +598,58 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
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.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
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.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
|
||||
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
|
||||
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
||||
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
||||
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=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a h1:OAiGFfOiA0v9MRYsSidp3ubZaBnteRUyn3xB2ZQ5G/E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
|
||||
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.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
||||
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
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.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
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.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y=
|
||||
gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI=
|
||||
gopkg.in/cenkalti/backoff.v2 v2.2.1 h1:eJ9UAg01/HIHG987TwxvnzK2MgxXq97YY6rYDpY9aII=
|
||||
gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/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=
|
||||
gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1/go.mod h1:WbjuEoo1oadwzQ4apSDU+JTvmllEHtsNHS6y7vFc7iw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
@ -656,25 +664,23 @@ 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=
|
||||
k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0=
|
||||
k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk=
|
||||
k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw=
|
||||
k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
|
||||
k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc=
|
||||
k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs=
|
||||
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-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
|
||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
||||
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 v0.8.0 h1:8bCFo/g9WODjWx3m6EYl3GfUG31eKJbaggyBDxEldRc=
|
||||
tags.cncf.io/container-device-interface v0.8.0/go.mod h1:Apb7N4VdILW0EVdEMRYXIDVRZfNJZ+kmEUss2kRRQ6Y=
|
||||
tags.cncf.io/container-device-interface/specs-go v0.8.0 h1:QYGFzGxvYK/ZLMrjhvY0RjpUavIn4KcmRmVP/JjdBTA=
|
||||
tags.cncf.io/container-device-interface/specs-go v0.8.0/go.mod h1:BhJIkjjPh4qpys+qm4DAYtUyryaTDg9zris+AczXyws=
|
||||
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=
|
||||
|
@ -17,10 +17,8 @@
|
||||
package desktop
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@ -29,7 +27,6 @@ import (
|
||||
|
||||
"github.com/docker/compose/v2/internal"
|
||||
"github.com/docker/compose/v2/internal/memnet"
|
||||
"github.com/r3labs/sse"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
)
|
||||
|
||||
@ -53,11 +50,10 @@ func NewClient(apiEndpoint string) *Client {
|
||||
}
|
||||
transport = otelhttp.NewTransport(transport)
|
||||
|
||||
c := &Client{
|
||||
return &Client{
|
||||
apiEndpoint: apiEndpoint,
|
||||
client: &http.Client{Transport: transport},
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) Endpoint() string {
|
||||
@ -76,11 +72,11 @@ type PingResponse struct {
|
||||
|
||||
// Ping is a minimal API used to ensure that the server is available.
|
||||
func (c *Client) Ping(ctx context.Context) (*PingResponse, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, backendURL("/ping"), http.NoBody)
|
||||
req, err := c.newRequest(ctx, http.MethodGet, "/ping", http.NoBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -88,6 +84,7 @@ func (c *Client) Ping(ctx context.Context) (*PingResponse, error) {
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
}
|
||||
@ -106,11 +103,11 @@ type FeatureFlagValue struct {
|
||||
}
|
||||
|
||||
func (c *Client) FeatureFlags(ctx context.Context) (FeatureFlagResponse, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, backendURL("/features"), http.NoBody)
|
||||
req, err := c.newRequest(ctx, http.MethodGet, "/features", http.NoBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -118,6 +115,7 @@ func (c *Client) FeatureFlags(ctx context.Context) (FeatureFlagResponse, error)
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
}
|
||||
@ -129,209 +127,13 @@ func (c *Client) FeatureFlags(ctx context.Context) (FeatureFlagResponse, error)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
type GetFileSharesConfigResponse struct {
|
||||
Active bool `json:"active"`
|
||||
Compose struct {
|
||||
ManageBindMounts bool `json:"manageBindMounts"`
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetFileSharesConfig(ctx context.Context) (*GetFileSharesConfigResponse, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, backendURL("/mutagen/file-shares/config"), http.NoBody)
|
||||
func (c *Client) newRequest(ctx context.Context, method, path string, body io.Reader) (*http.Request, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, method, backendURL(path), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, newHTTPStatusCodeError(resp)
|
||||
}
|
||||
|
||||
var ret GetFileSharesConfigResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
type CreateFileShareRequest struct {
|
||||
HostPath string `json:"hostPath"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
type CreateFileShareResponse struct {
|
||||
FileShareID string `json:"fileShareID"`
|
||||
}
|
||||
|
||||
func (c *Client) CreateFileShare(ctx context.Context, r CreateFileShareRequest) (*CreateFileShareResponse, error) {
|
||||
rawBody, _ := json.Marshal(r)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, backendURL("/mutagen/file-shares"), bytes.NewReader(rawBody))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
errBody, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, string(errBody))
|
||||
}
|
||||
var ret CreateFileShareResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
type FileShareReceiverState struct {
|
||||
TotalReceivedSize uint64 `json:"totalReceivedSize"`
|
||||
}
|
||||
|
||||
type FileShareEndpoint struct {
|
||||
Path string `json:"path"`
|
||||
TotalFileSize uint64 `json:"totalFileSize,omitempty"`
|
||||
StagingProgress *FileShareReceiverState `json:"stagingProgress"`
|
||||
}
|
||||
|
||||
type FileShareSession struct {
|
||||
SessionID string `json:"identifier"`
|
||||
Alpha FileShareEndpoint `json:"alpha"`
|
||||
Beta FileShareEndpoint `json:"beta"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
func (c *Client) ListFileShares(ctx context.Context) ([]FileShareSession, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, backendURL("/mutagen/file-shares"), http.NoBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, newHTTPStatusCodeError(resp)
|
||||
}
|
||||
|
||||
var ret []FileShareSession
|
||||
if err := json.NewDecoder(resp.Body).Decode(&ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (c *Client) DeleteFileShare(ctx context.Context, id string) error {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, backendURL("/mutagen/file-shares/"+id), http.NoBody)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return newHTTPStatusCodeError(resp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type EventMessage[T any] struct {
|
||||
Value T
|
||||
Error error
|
||||
}
|
||||
|
||||
func newHTTPStatusCodeError(resp *http.Response) error {
|
||||
r := io.LimitReader(resp.Body, 2048)
|
||||
body, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("http status code %d", resp.StatusCode)
|
||||
}
|
||||
return fmt.Errorf("http status code %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
func (c *Client) StreamFileShares(ctx context.Context) (<-chan EventMessage[[]FileShareSession], error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, backendURL("/mutagen/file-shares/stream"), http.NoBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
return nil, newHTTPStatusCodeError(resp)
|
||||
}
|
||||
|
||||
events := make(chan EventMessage[[]FileShareSession])
|
||||
go func(ctx context.Context) {
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
for range events {
|
||||
// drain the channel
|
||||
}
|
||||
close(events)
|
||||
}()
|
||||
if err := readEvents(ctx, resp.Body, events); err != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case events <- EventMessage[[]FileShareSession]{Error: err}:
|
||||
}
|
||||
}
|
||||
}(ctx)
|
||||
return events, nil
|
||||
}
|
||||
|
||||
func readEvents[T any](ctx context.Context, r io.Reader, events chan<- EventMessage[T]) error {
|
||||
eventReader := sse.NewEventStreamReader(r)
|
||||
for {
|
||||
msg, err := eventReader.ReadEvent()
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("reading events: %w", err)
|
||||
}
|
||||
msg = bytes.TrimPrefix(msg, []byte("data: "))
|
||||
|
||||
var event T
|
||||
if err := json.Unmarshal(msg, &event); err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return context.Cause(ctx)
|
||||
case events <- EventMessage[T]{Value: event}:
|
||||
// event was sent to channel, read next
|
||||
}
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// backendURL generates a URL for the given API path.
|
||||
|
@ -1,384 +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 desktop
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/compose/v2/internal/paths"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// fileShareProgressID is the identifier used for the root grouping of file
|
||||
// share events in the progress writer.
|
||||
const fileShareProgressID = "Synchronized File Shares"
|
||||
|
||||
// RemoveFileSharesForProject removes any Synchronized File Shares that were
|
||||
// created by Compose for this project in the past if possible.
|
||||
//
|
||||
// Errors are not propagated; they are only sent to the progress writer.
|
||||
func RemoveFileSharesForProject(ctx context.Context, c *Client, projectName string) {
|
||||
w := progress.ContextWriter(ctx)
|
||||
|
||||
existing, err := c.ListFileShares(ctx)
|
||||
if err != nil {
|
||||
w.TailMsgf("Synchronized File Shares not removed due to error: %v", err)
|
||||
return
|
||||
}
|
||||
// filter the list first, so we can early return and not show the event if
|
||||
// there's no sessions to clean up
|
||||
var toRemove []FileShareSession
|
||||
for _, share := range existing {
|
||||
if share.Labels["com.docker.compose.project"] == projectName {
|
||||
toRemove = append(toRemove, share)
|
||||
}
|
||||
}
|
||||
if len(toRemove) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
w.Event(progress.NewEvent(fileShareProgressID, progress.Working, "Removing"))
|
||||
rootResult := progress.Done
|
||||
defer func() {
|
||||
w.Event(progress.NewEvent(fileShareProgressID, rootResult, ""))
|
||||
}()
|
||||
for _, share := range toRemove {
|
||||
shareID := share.Labels["com.docker.desktop.mutagen.file-share.id"]
|
||||
if shareID == "" {
|
||||
w.Event(progress.Event{
|
||||
ID: share.Alpha.Path,
|
||||
ParentID: fileShareProgressID,
|
||||
Status: progress.Warning,
|
||||
StatusText: "Invalid",
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
w.Event(progress.Event{
|
||||
ID: share.Alpha.Path,
|
||||
ParentID: fileShareProgressID,
|
||||
Status: progress.Working,
|
||||
})
|
||||
|
||||
var status progress.EventStatus
|
||||
var statusText string
|
||||
if err := c.DeleteFileShare(ctx, shareID); err != nil {
|
||||
// TODO(milas): Docker Desktop is doing weird things with error responses,
|
||||
// once fixed, we can return proper error types from the client
|
||||
if strings.Contains(err.Error(), "file share in use") {
|
||||
status = progress.Warning
|
||||
statusText = "Resource is still in use"
|
||||
if rootResult != progress.Error {
|
||||
// error takes precedence over warning
|
||||
rootResult = progress.Warning
|
||||
}
|
||||
} else {
|
||||
logrus.Debugf("Error deleting file share %q: %v", shareID, err)
|
||||
status = progress.Error
|
||||
rootResult = progress.Error
|
||||
}
|
||||
} else {
|
||||
logrus.Debugf("Deleted file share: %s", shareID)
|
||||
status = progress.Done
|
||||
}
|
||||
|
||||
w.Event(progress.Event{
|
||||
ID: share.Alpha.Path,
|
||||
ParentID: fileShareProgressID,
|
||||
Status: status,
|
||||
StatusText: statusText,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// FileShareManager maps between Compose bind mounts and Desktop File Shares
|
||||
// state.
|
||||
type FileShareManager struct {
|
||||
mu sync.Mutex
|
||||
cli *Client
|
||||
projectName string
|
||||
hostPaths []string
|
||||
// state holds session status keyed by file share ID.
|
||||
state map[string]*FileShareSession
|
||||
}
|
||||
|
||||
func NewFileShareManager(cli *Client, projectName string, hostPaths []string) *FileShareManager {
|
||||
return &FileShareManager{
|
||||
cli: cli,
|
||||
projectName: projectName,
|
||||
hostPaths: hostPaths,
|
||||
state: make(map[string]*FileShareSession),
|
||||
}
|
||||
}
|
||||
|
||||
// EnsureExists looks for existing File Shares or creates new ones for the
|
||||
// host paths.
|
||||
//
|
||||
// This function blocks until each share reaches steady state, at which point
|
||||
// flow can continue.
|
||||
func (m *FileShareManager) EnsureExists(ctx context.Context) (err error) {
|
||||
w := progress.ContextWriter(ctx)
|
||||
w.Event(progress.NewEvent(fileShareProgressID, progress.Working, ""))
|
||||
defer func() {
|
||||
if err != nil {
|
||||
w.Event(progress.NewEvent(fileShareProgressID, progress.Error, ""))
|
||||
} else {
|
||||
w.Event(progress.NewEvent(fileShareProgressID, progress.Done, ""))
|
||||
}
|
||||
}()
|
||||
|
||||
wait := &waiter{
|
||||
shareIDs: make(map[string]struct{}),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
handler := m.eventHandler(w, wait)
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
// stream session events to update internal state for project
|
||||
monitorErr := make(chan error, 1)
|
||||
go func() {
|
||||
defer close(monitorErr)
|
||||
if err := m.watch(ctx, handler); err != nil && ctx.Err() == nil {
|
||||
monitorErr <- err
|
||||
}
|
||||
}()
|
||||
|
||||
if err := m.initialize(ctx, wait, handler); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
waitCh := wait.start()
|
||||
if waitCh != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return context.Cause(ctx)
|
||||
case err := <-monitorErr:
|
||||
if err != nil {
|
||||
return fmt.Errorf("watching file share sessions: %w", err)
|
||||
} else if ctx.Err() == nil {
|
||||
// this indicates a bug - it should not stop w/o an error if the context is still active
|
||||
return errors.New("file share session watch stopped unexpectedly")
|
||||
}
|
||||
case <-wait.start():
|
||||
// everything is done
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// initialize finds existing shares or creates new ones for the host paths.
|
||||
//
|
||||
// Once a share is found/created, its progress is monitored via the watch.
|
||||
func (m *FileShareManager) initialize(ctx context.Context, wait *waiter, handler func(FileShareSession)) error {
|
||||
// the watch is already running in the background, so the lock is taken
|
||||
// throughout to prevent interleaving writes
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
existing, err := m.cli.ListFileShares(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, path := range m.hostPaths {
|
||||
var fileShareID string
|
||||
var fss *FileShareSession
|
||||
|
||||
if fss = findExistingShare(path, existing); fss != nil {
|
||||
fileShareID = fss.Beta.Path
|
||||
logrus.Debugf("Found existing suitable file share %s for path %q [%s]", fileShareID, path, fss.Alpha.Path)
|
||||
wait.addShare(fileShareID)
|
||||
handler(*fss)
|
||||
continue
|
||||
} else {
|
||||
req := CreateFileShareRequest{
|
||||
HostPath: path,
|
||||
Labels: map[string]string{
|
||||
"com.docker.compose.project": m.projectName,
|
||||
},
|
||||
}
|
||||
createResp, err := m.cli.CreateFileShare(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating file share: %w", err)
|
||||
}
|
||||
fileShareID = createResp.FileShareID
|
||||
fss = m.state[fileShareID]
|
||||
logrus.Debugf("Created file share %s for path %q", fileShareID, path)
|
||||
}
|
||||
wait.addShare(fileShareID)
|
||||
if fss != nil {
|
||||
handler(*fss)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *FileShareManager) watch(ctx context.Context, handler func(FileShareSession)) error {
|
||||
events, err := m.cli.StreamFileShares(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("streaming file shares: %w", err)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case event := <-events:
|
||||
if event.Error != nil {
|
||||
return fmt.Errorf("reading file share events: %w", event.Error)
|
||||
}
|
||||
// closure for lock
|
||||
func() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for _, fss := range event.Value {
|
||||
handler(fss)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eventHandler updates internal state, keeps track of in-progress syncs, and
|
||||
// prints relevant events to progress.
|
||||
func (m *FileShareManager) eventHandler(w progress.Writer, wait *waiter) func(fss FileShareSession) {
|
||||
return func(fss FileShareSession) {
|
||||
fileShareID := fss.Beta.Path
|
||||
|
||||
shouldPrint := wait.isWatching(fileShareID)
|
||||
forProject := fss.Labels[api.ProjectLabel] == m.projectName
|
||||
|
||||
if shouldPrint || forProject {
|
||||
m.state[fileShareID] = &fss
|
||||
}
|
||||
|
||||
var percent int
|
||||
var current, total int64
|
||||
if fss.Beta.StagingProgress != nil {
|
||||
current = int64(fss.Beta.StagingProgress.TotalReceivedSize)
|
||||
} else {
|
||||
current = int64(fss.Beta.TotalFileSize)
|
||||
}
|
||||
total = int64(fss.Alpha.TotalFileSize)
|
||||
if total != 0 {
|
||||
percent = int(current * 100 / total)
|
||||
}
|
||||
|
||||
var status progress.EventStatus
|
||||
var text string
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(fss.Status, "halted"):
|
||||
wait.shareDone(fileShareID)
|
||||
status = progress.Error
|
||||
case fss.Status == "watching":
|
||||
wait.shareDone(fileShareID)
|
||||
status = progress.Done
|
||||
percent = 100
|
||||
case fss.Status == "staging-beta":
|
||||
status = progress.Working
|
||||
// TODO(milas): the printer doesn't style statuses for children nicely
|
||||
text = fmt.Sprintf(" Syncing (%7s / %-7s)",
|
||||
units.HumanSize(float64(current)),
|
||||
units.HumanSize(float64(total)),
|
||||
)
|
||||
default:
|
||||
// catch-all for various other transitional statuses
|
||||
status = progress.Working
|
||||
}
|
||||
|
||||
evt := progress.Event{
|
||||
ID: fss.Alpha.Path,
|
||||
Status: status,
|
||||
Text: text,
|
||||
ParentID: fileShareProgressID,
|
||||
Current: current,
|
||||
Total: total,
|
||||
Percent: percent,
|
||||
}
|
||||
|
||||
if shouldPrint {
|
||||
w.Event(evt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func findExistingShare(path string, existing []FileShareSession) *FileShareSession {
|
||||
for _, share := range existing {
|
||||
if paths.IsChild(share.Alpha.Path, path) {
|
||||
return &share
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type waiter struct {
|
||||
mu sync.Mutex
|
||||
shareIDs map[string]struct{}
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (w *waiter) addShare(fileShareID string) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
w.shareIDs[fileShareID] = struct{}{}
|
||||
}
|
||||
|
||||
func (w *waiter) isWatching(fileShareID string) bool {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
_, ok := w.shareIDs[fileShareID]
|
||||
return ok
|
||||
}
|
||||
|
||||
// start returns a channel to wait for any outstanding shares to be ready.
|
||||
//
|
||||
// If no shares are registered when this is called, nil is returned.
|
||||
func (w *waiter) start() <-chan struct{} {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
if len(w.shareIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
if w.done == nil {
|
||||
w.done = make(chan struct{})
|
||||
}
|
||||
return w.done
|
||||
}
|
||||
|
||||
func (w *waiter) shareDone(fileShareID string) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
delete(w.shareIDs, fileShareID)
|
||||
if len(w.shareIDs) == 0 && w.done != nil {
|
||||
close(w.done)
|
||||
w.done = nil
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
//go:build linux || openbsd
|
||||
//go:build linux || openbsd || freebsd
|
||||
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
pusherrors "github.com/containerd/containerd/v2/core/remotes/errors"
|
||||
@ -104,7 +105,9 @@ func PushManifest(
|
||||
) error {
|
||||
// Check if we need an extra empty layer for the manifest config
|
||||
if ociVersion == api.OCIVersion1_1 || ociVersion == "" {
|
||||
layers = append(layers, Pushable{Descriptor: v1.DescriptorEmptyJSON, Data: []byte("{}")})
|
||||
if err := resolver.Push(ctx, named, v1.DescriptorEmptyJSON, v1.DescriptorEmptyJSON.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// prepare to push the manifest by pushing the layers
|
||||
layerDescriptors := make([]v1.Descriptor, len(layers))
|
||||
@ -157,14 +160,7 @@ func isNonAuthClientError(statusCode int) bool {
|
||||
// not a client error
|
||||
return false
|
||||
}
|
||||
for _, v := range clientAuthStatusCodes {
|
||||
if statusCode == v {
|
||||
// client auth error
|
||||
return false
|
||||
}
|
||||
}
|
||||
// any other 4xx client error
|
||||
return true
|
||||
return !slices.Contains(clientAuthStatusCodes, statusCode)
|
||||
}
|
||||
|
||||
func generateManifest(layers []v1.Descriptor, ociCompat api.OCIVersion) ([]Pushable, error) {
|
||||
|
@ -33,7 +33,7 @@ import (
|
||||
"github.com/hashicorp/go-multierror"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/moby/go-archive"
|
||||
)
|
||||
|
||||
type archiveEntry struct {
|
||||
@ -229,7 +229,7 @@ func (a *ArchiveBuilder) writeEntry(entry archiveEntry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// tarPath writes the given source path into tarWriter at the given dest (recursively for directories).
|
||||
// entriesForPath writes the given source path into tarWriter at the given dest (recursively for directories).
|
||||
// e.g. tarring my_dir --> dest d: d/file_a, d/file_b
|
||||
// If source path does not exist, quietly skips it and returns no err
|
||||
func (a *ArchiveBuilder) entriesForPath(localPath, containerPath string) ([]archiveEntry, error) {
|
||||
|
@ -77,11 +77,13 @@ func ProjectOptions(ctx context.Context, proj *types.Project) SpanOptions {
|
||||
attribute.StringSlice("project.networks", proj.NetworkNames()),
|
||||
attribute.StringSlice("project.secrets", proj.SecretNames()),
|
||||
attribute.StringSlice("project.configs", proj.ConfigNames()),
|
||||
attribute.StringSlice("project.models", proj.ModelNames()),
|
||||
attribute.StringSlice("project.extensions", keys(proj.Extensions)),
|
||||
attribute.StringSlice("project.services.active", proj.ServiceNames()),
|
||||
attribute.StringSlice("project.services.disabled", proj.DisabledServiceNames()),
|
||||
attribute.StringSlice("project.services.build", proj.ServicesWithBuild()),
|
||||
attribute.StringSlice("project.services.depends_on", proj.ServicesWithDependsOn()),
|
||||
attribute.StringSlice("project.services.models", proj.ServicesWithModels()),
|
||||
attribute.StringSlice("project.services.capabilities", capabilities),
|
||||
attribute.StringSlice("project.services.capabilities.gpu", gpu),
|
||||
attribute.StringSlice("project.services.capabilities.tpu", tpu),
|
||||
@ -110,6 +112,7 @@ func ServiceOptions(service types.ServiceConfig) SpanOptions {
|
||||
attribute.String("service.name", service.Name),
|
||||
attribute.String("service.image", service.Image),
|
||||
attribute.StringSlice("service.networks", keys(service.Networks)),
|
||||
attribute.StringSlice("service.models", keys(service.Models)),
|
||||
}
|
||||
|
||||
configNames := make([]string, len(service.Configs))
|
||||
|
@ -22,15 +22,12 @@ import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
func KeyboardMetrics(ctx context.Context, enabled, isDockerDesktopActive, isWatchConfigured bool) {
|
||||
func KeyboardMetrics(ctx context.Context, enabled, isDockerDesktopActive bool) {
|
||||
commandAvailable := []string{}
|
||||
if isDockerDesktopActive {
|
||||
commandAvailable = append(commandAvailable, "gui")
|
||||
commandAvailable = append(commandAvailable, "gui/composeview")
|
||||
}
|
||||
if isWatchConfigured {
|
||||
commandAvailable = append(commandAvailable, "watch")
|
||||
}
|
||||
|
||||
AddAttributeToSpan(ctx,
|
||||
attribute.Bool("navmenu.enabled", enabled),
|
||||
|
@ -1,2 +0,0 @@
|
||||
The Docker End User License Agreement (https://www.docker.com/legal/docker-software-end-user-license-agreement) describes Docker's Terms for this software.
|
||||
By downloading, accessing, or using this software you expressly accept and agree to the Terms set out in the Docker End User License Agreement.
|
@ -19,12 +19,15 @@ package api
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
)
|
||||
|
||||
// Service manages a compose project
|
||||
@ -78,13 +81,13 @@ type Service interface {
|
||||
// Publish executes the equivalent to a `compose publish`
|
||||
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) ([]ImageSummary, error)
|
||||
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, services []string, options WatchOptions) error
|
||||
Watch(ctx context.Context, project *types.Project, options WatchOptions) error
|
||||
// Viz generates a graphviz graph of the project services
|
||||
Viz(ctx context.Context, project *types.Project, options VizOptions) (string, error)
|
||||
// Wait blocks until at least one of the services' container exits
|
||||
@ -97,8 +100,16 @@ type Service interface {
|
||||
Commit(ctx context.Context, projectName string, options CommitOptions) error
|
||||
// Generate generates a Compose Project from existing containers
|
||||
Generate(ctx context.Context, options GenerateOptions) (*types.Project, error)
|
||||
// Volumes executes the equivalent to a `docker volume ls`
|
||||
Volumes(ctx context.Context, project *types.Project, options VolumesOptions) ([]VolumesSummary, error)
|
||||
}
|
||||
|
||||
type VolumesOptions struct {
|
||||
Services []string
|
||||
}
|
||||
|
||||
type VolumesSummary = *volume.Volume
|
||||
|
||||
type ScaleOptions struct {
|
||||
Services []string
|
||||
}
|
||||
@ -126,9 +137,10 @@ const WatchLogger = "#watch"
|
||||
|
||||
// WatchOptions group options of the Watch API
|
||||
type WatchOptions struct {
|
||||
Build *BuildOptions
|
||||
LogTo LogConsumer
|
||||
Prune bool
|
||||
Build *BuildOptions
|
||||
LogTo LogConsumer
|
||||
Prune bool
|
||||
Services []string
|
||||
}
|
||||
|
||||
// BuildOptions group options of the Build API
|
||||
@ -155,13 +167,25 @@ type BuildOptions struct {
|
||||
Memory int64
|
||||
// Builder name passed in the command line
|
||||
Builder string
|
||||
// Print don't actually run builder but print equivalent build config
|
||||
Print bool
|
||||
// Check let builder validate build configuration
|
||||
Check bool
|
||||
// Attestations allows to enable attestations generation
|
||||
Attestations bool
|
||||
// Provenance generate a provenance attestation
|
||||
Provenance string
|
||||
// SBOM generate a SBOM attestation
|
||||
SBOM string
|
||||
// Out is the stream to write build progress
|
||||
Out io.Writer
|
||||
}
|
||||
|
||||
// Apply mutates project according to build options
|
||||
func (o BuildOptions) Apply(project *types.Project) error {
|
||||
platform := project.Environment["DOCKER_DEFAULT_PLATFORM"]
|
||||
for name, service := range project.Services {
|
||||
if service.Image == "" && service.Build == nil {
|
||||
if service.Provider == nil && service.Image == "" && service.Build == nil {
|
||||
return fmt.Errorf("invalid service %q. Must specify either image or build", name)
|
||||
}
|
||||
|
||||
@ -169,13 +193,13 @@ func (o BuildOptions) Apply(project *types.Project) error {
|
||||
continue
|
||||
}
|
||||
if platform != "" {
|
||||
if len(service.Build.Platforms) > 0 && !utils.StringContains(service.Build.Platforms, platform) {
|
||||
if len(service.Build.Platforms) > 0 && !slices.Contains(service.Build.Platforms, platform) {
|
||||
return fmt.Errorf("service %q build.platforms does not support value set by DOCKER_DEFAULT_PLATFORM: %s", name, platform)
|
||||
}
|
||||
service.Platform = platform
|
||||
}
|
||||
if service.Platform != "" {
|
||||
if len(service.Build.Platforms) > 0 && !utils.StringContains(service.Build.Platforms, service.Platform) {
|
||||
if len(service.Build.Platforms) > 0 && !slices.Contains(service.Build.Platforms, service.Platform) {
|
||||
return fmt.Errorf("service %q build configuration does not support platform: %s", name, service.Platform)
|
||||
}
|
||||
}
|
||||
@ -343,7 +367,7 @@ type RemoveOptions struct {
|
||||
|
||||
// RunOptions group options of the Run API
|
||||
type RunOptions struct {
|
||||
Build *BuildOptions
|
||||
CreateOptions
|
||||
// Project is the compose project used to define this app. Might be nil if user ran command just with project name
|
||||
Project *types.Project
|
||||
Name string
|
||||
@ -363,8 +387,6 @@ type RunOptions struct {
|
||||
Privileged bool
|
||||
UseNetworkAliases bool
|
||||
NoDeps bool
|
||||
// QuietPull makes the pulling process quiet
|
||||
QuietPull bool
|
||||
// used by exec
|
||||
Index int
|
||||
}
|
||||
@ -383,6 +405,8 @@ type AttachOptions struct {
|
||||
type EventsOptions struct {
|
||||
Services []string
|
||||
Consumer func(event Event) error
|
||||
Since string
|
||||
Until string
|
||||
}
|
||||
|
||||
// Event is a container runtime event served by Events API
|
||||
@ -523,15 +547,18 @@ type ContainerProcSummary struct {
|
||||
Name string
|
||||
Processes [][]string
|
||||
Titles []string
|
||||
Service string
|
||||
Replica string
|
||||
}
|
||||
|
||||
// ImageSummary holds container image description
|
||||
type ImageSummary struct {
|
||||
ID string
|
||||
ContainerName string
|
||||
Repository string
|
||||
Tag string
|
||||
Size int64
|
||||
ID string
|
||||
Repository string
|
||||
Tag string
|
||||
Platform platforms.Platform
|
||||
Size int64
|
||||
LastTagTime time.Time
|
||||
}
|
||||
|
||||
// ServiceStatus hold status about a service
|
||||
@ -629,7 +656,6 @@ type LogConsumer interface {
|
||||
Log(containerName, message string)
|
||||
Err(containerName, message string)
|
||||
Status(container, msg string)
|
||||
Register(container string)
|
||||
}
|
||||
|
||||
// ContainerEventListener is a callback to process ContainerEvent from services
|
||||
@ -637,16 +663,18 @@ type ContainerEventListener func(event ContainerEvent)
|
||||
|
||||
// ContainerEvent notify an event has been collected on source container implementing Service
|
||||
type ContainerEvent struct {
|
||||
Type int
|
||||
// Container is the name of the container _without the project prefix_.
|
||||
Type int
|
||||
Time int64
|
||||
Container *ContainerSummary
|
||||
// Source is the name of the container _without the project prefix_.
|
||||
//
|
||||
// This is only suitable for display purposes within Compose, as it's
|
||||
// not guaranteed to be unique across services.
|
||||
Container string
|
||||
ID string
|
||||
Service string
|
||||
Line string
|
||||
// ContainerEventExit only
|
||||
Source string
|
||||
ID string
|
||||
Service string
|
||||
Line string
|
||||
// ExitCode is only set on ContainerEventExited events
|
||||
ExitCode int
|
||||
Restarting bool
|
||||
}
|
||||
@ -656,17 +684,19 @@ const (
|
||||
ContainerEventLog = iota
|
||||
// ContainerEventErr is a ContainerEvent of type log on stderr. Line is set
|
||||
ContainerEventErr
|
||||
// ContainerEventAttach is a ContainerEvent of type attach. First event sent about a container
|
||||
ContainerEventAttach
|
||||
// ContainerEventStarted let consumer know a container has been started
|
||||
ContainerEventStarted
|
||||
// ContainerEventRestarted let consumer know a container has been restarted
|
||||
ContainerEventRestarted
|
||||
// ContainerEventStopped is a ContainerEvent of type stopped.
|
||||
ContainerEventStopped
|
||||
// ContainerEventCreated let consumer know a new container has been created
|
||||
ContainerEventCreated
|
||||
// ContainerEventRecreated let consumer know container stopped but his being replaced
|
||||
ContainerEventRecreated
|
||||
// ContainerEventExit is a ContainerEvent of type exit. ExitCode is set
|
||||
ContainerEventExit
|
||||
// ContainerEventExited is a ContainerEvent of type exit. ExitCode is set
|
||||
ContainerEventExited
|
||||
// UserCancel user cancelled compose up, we are stopping containers
|
||||
UserCancel
|
||||
// HookEventLog is a ContainerEvent of type log on stdout by service hook
|
||||
HookEventLog
|
||||
)
|
||||
|
||||
|
@ -34,6 +34,7 @@ import (
|
||||
"github.com/docker/buildx/util/imagetools"
|
||||
"github.com/docker/cli/cli/command"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/build"
|
||||
"github.com/docker/docker/api/types/checkpoint"
|
||||
containerType "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/events"
|
||||
@ -205,18 +206,18 @@ func (d *DryRunClient) CopyToContainer(ctx context.Context, container, path stri
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DryRunClient) ImageBuild(ctx context.Context, reader io.Reader, options moby.ImageBuildOptions) (moby.ImageBuildResponse, error) {
|
||||
func (d *DryRunClient) ImageBuild(ctx context.Context, reader io.Reader, options build.ImageBuildOptions) (build.ImageBuildResponse, error) {
|
||||
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 moby.ImageBuildResponse{}, err
|
||||
return build.ImageBuildResponse{}, err
|
||||
}
|
||||
rc := io.NopCloser(bytes.NewReader(jsonMessage))
|
||||
|
||||
return moby.ImageBuildResponse{
|
||||
return build.ImageBuildResponse{
|
||||
Body: rc,
|
||||
OSType: "",
|
||||
}, nil
|
||||
@ -334,11 +335,11 @@ func (d *DryRunClient) ContainerExecStart(ctx context.Context, execID string, co
|
||||
|
||||
// Functions delegated to original APIClient (not used by Compose or not modifying the Compose stack
|
||||
|
||||
func (d *DryRunClient) ConfigList(ctx context.Context, options moby.ConfigListOptions) ([]swarm.Config, error) {
|
||||
func (d *DryRunClient) ConfigList(ctx context.Context, options swarm.ConfigListOptions) ([]swarm.Config, error) {
|
||||
return d.apiClient.ConfigList(ctx, options)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (moby.ConfigCreateResponse, error) {
|
||||
func (d *DryRunClient) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (swarm.ConfigCreateResponse, error) {
|
||||
return d.apiClient.ConfigCreate(ctx, config)
|
||||
}
|
||||
|
||||
@ -422,7 +423,7 @@ func (d *DryRunClient) DistributionInspect(ctx context.Context, imageName, encod
|
||||
return d.apiClient.DistributionInspect(ctx, imageName, encodedRegistryAuth)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) BuildCachePrune(ctx context.Context, opts moby.BuildCachePruneOptions) (*moby.BuildCachePruneReport, error) {
|
||||
func (d *DryRunClient) BuildCachePrune(ctx context.Context, opts build.CachePruneOptions) (*build.CachePruneReport, error) {
|
||||
return d.apiClient.BuildCachePrune(ctx, opts)
|
||||
}
|
||||
|
||||
@ -470,11 +471,11 @@ func (d *DryRunClient) NodeInspectWithRaw(ctx context.Context, nodeID string) (s
|
||||
return d.apiClient.NodeInspectWithRaw(ctx, nodeID)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) NodeList(ctx context.Context, options moby.NodeListOptions) ([]swarm.Node, error) {
|
||||
func (d *DryRunClient) NodeList(ctx context.Context, options swarm.NodeListOptions) ([]swarm.Node, error) {
|
||||
return d.apiClient.NodeList(ctx, options)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) NodeRemove(ctx context.Context, nodeID string, options moby.NodeRemoveOptions) error {
|
||||
func (d *DryRunClient) NodeRemove(ctx context.Context, nodeID string, options swarm.NodeRemoveOptions) error {
|
||||
return d.apiClient.NodeRemove(ctx, nodeID, options)
|
||||
}
|
||||
|
||||
@ -538,15 +539,15 @@ func (d *DryRunClient) PluginCreate(ctx context.Context, createContext io.Reader
|
||||
return d.apiClient.PluginCreate(ctx, createContext, options)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options moby.ServiceCreateOptions) (swarm.ServiceCreateResponse, error) {
|
||||
func (d *DryRunClient) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options swarm.ServiceCreateOptions) (swarm.ServiceCreateResponse, error) {
|
||||
return d.apiClient.ServiceCreate(ctx, service, options)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, options moby.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
func (d *DryRunClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
return d.apiClient.ServiceInspectWithRaw(ctx, serviceID, options)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) ServiceList(ctx context.Context, options moby.ServiceListOptions) ([]swarm.Service, error) {
|
||||
func (d *DryRunClient) ServiceList(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return d.apiClient.ServiceList(ctx, options)
|
||||
}
|
||||
|
||||
@ -554,7 +555,7 @@ func (d *DryRunClient) ServiceRemove(ctx context.Context, serviceID string) erro
|
||||
return d.apiClient.ServiceRemove(ctx, serviceID)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options moby.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
|
||||
func (d *DryRunClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
|
||||
return d.apiClient.ServiceUpdate(ctx, serviceID, version, service, options)
|
||||
}
|
||||
|
||||
@ -570,7 +571,7 @@ func (d *DryRunClient) TaskInspectWithRaw(ctx context.Context, taskID string) (s
|
||||
return d.apiClient.TaskInspectWithRaw(ctx, taskID)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) TaskList(ctx context.Context, options moby.TaskListOptions) ([]swarm.Task, error) {
|
||||
func (d *DryRunClient) TaskList(ctx context.Context, options swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
return d.apiClient.TaskList(ctx, options)
|
||||
}
|
||||
|
||||
@ -582,7 +583,7 @@ func (d *DryRunClient) SwarmJoin(ctx context.Context, req swarm.JoinRequest) err
|
||||
return d.apiClient.SwarmJoin(ctx, req)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) SwarmGetUnlockKey(ctx context.Context) (moby.SwarmUnlockKeyResponse, error) {
|
||||
func (d *DryRunClient) SwarmGetUnlockKey(ctx context.Context) (swarm.UnlockKeyResponse, error) {
|
||||
return d.apiClient.SwarmGetUnlockKey(ctx)
|
||||
}
|
||||
|
||||
@ -602,11 +603,11 @@ func (d *DryRunClient) SwarmUpdate(ctx context.Context, version swarm.Version, s
|
||||
return d.apiClient.SwarmUpdate(ctx, version, swarmSpec, flags)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) SecretList(ctx context.Context, options moby.SecretListOptions) ([]swarm.Secret, error) {
|
||||
func (d *DryRunClient) SecretList(ctx context.Context, options swarm.SecretListOptions) ([]swarm.Secret, error) {
|
||||
return d.apiClient.SecretList(ctx, options)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (moby.SecretCreateResponse, error) {
|
||||
func (d *DryRunClient) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (swarm.SecretCreateResponse, error) {
|
||||
return d.apiClient.SecretCreate(ctx, secret)
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ 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
|
||||
|
@ -17,8 +17,6 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
|
||||
"github.com/docker/compose/v2/internal"
|
||||
@ -65,9 +63,6 @@ var ComposeVersion string
|
||||
func init() {
|
||||
v, err := version.NewVersion(internal.Version)
|
||||
if err == nil {
|
||||
segments := v.Segments()
|
||||
if len(segments) > 2 {
|
||||
ComposeVersion = fmt.Sprintf("%d.%d.%d", segments[0], segments[1], segments[2])
|
||||
}
|
||||
ComposeVersion = v.Core().String()
|
||||
}
|
||||
}
|
||||
|
35
pkg/api/labels_test.go
Normal file
35
pkg/api/labels_test.go
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
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 (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/compose/v2/internal"
|
||||
"github.com/hashicorp/go-version"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestComposeVersionInitialization(t *testing.T) {
|
||||
v, err := version.NewVersion(internal.Version)
|
||||
if err != nil {
|
||||
assert.Equal(t, "", ComposeVersion, "ComposeVersion should be empty for a non-semver internal version (e.g. 'devel')")
|
||||
} else {
|
||||
expected := v.Core().String()
|
||||
assert.Equal(t, expected, ComposeVersion, "ComposeVersion should be the core of internal.Version")
|
||||
}
|
||||
}
|
224
pkg/bridge/convert.go
Normal file
224
pkg/bridge/convert.go
Normal file
@ -0,0 +1,224 @@
|
||||
/*
|
||||
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 bridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"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"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type ConvertOptions struct {
|
||||
Output string
|
||||
Templates string
|
||||
Transformations []string
|
||||
}
|
||||
|
||||
func Convert(ctx context.Context, dockerCli command.Cli, project *types.Project, opts ConvertOptions) error {
|
||||
if len(opts.Transformations) == 0 {
|
||||
opts.Transformations = []string{DefaultTransformerImage}
|
||||
}
|
||||
// Load image references, secrets and configs, also expose ports
|
||||
project, err := LoadAdditionalResources(ctx, dockerCli, project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// for user to rely on compose.yaml attribute names, not go struct ones, we marshall back into YAML
|
||||
raw, err := project.MarshalYAML(types.WithSecretContent)
|
||||
// Marshall to YAML
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot render project into yaml: %w", err)
|
||||
}
|
||||
var model map[string]any
|
||||
err = yaml.Unmarshal(raw, &model)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot render project into yaml: %w", err)
|
||||
}
|
||||
|
||||
if opts.Output != "" {
|
||||
_ = os.RemoveAll(opts.Output)
|
||||
err := os.MkdirAll(opts.Output, 0o744)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return fmt.Errorf("cannot create output folder: %w", err)
|
||||
}
|
||||
}
|
||||
// Run Transformers images
|
||||
return convert(ctx, dockerCli, model, opts)
|
||||
}
|
||||
|
||||
func convert(ctx context.Context, dockerCli command.Cli, model map[string]any, opts ConvertOptions) error {
|
||||
raw, err := yaml.Marshal(model)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dir := os.TempDir()
|
||||
composeYaml := filepath.Join(dir, "compose.yaml")
|
||||
err = os.WriteFile(composeYaml, raw, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := filepath.Abs(opts.Output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
binds := []string{
|
||||
fmt.Sprintf("%s:%s", dir, "/in"),
|
||||
fmt.Sprintf("%s:%s", out, "/out"),
|
||||
}
|
||||
if opts.Templates != "" {
|
||||
templateDir, err := filepath.Abs(opts.Templates)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
binds = append(binds, fmt.Sprintf("%s:%s", templateDir, "/templates"))
|
||||
}
|
||||
|
||||
for _, transformation := range opts.Transformations {
|
||||
_, err = inspectWithPull(ctx, dockerCli, transformation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
containerConfig := &container.Config{
|
||||
Image: transformation,
|
||||
Env: []string{"LICENSE_AGREEMENT=true"},
|
||||
}
|
||||
// On POSIX systems, this is a decimal number representing the uid.
|
||||
// On Windows, this is a security identifier (SID) in a string format and the engine isn't able to manage it
|
||||
if runtime.GOOS != "windows" {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
containerConfig.User = usr.Uid
|
||||
}
|
||||
created, err := dockerCli.Client().ContainerCreate(ctx, containerConfig, &container.HostConfig{
|
||||
AutoRemove: true,
|
||||
Binds: binds,
|
||||
}, &network.NetworkingConfig{}, nil, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cli.RunStart(ctx, dockerCli, &cli.StartOptions{
|
||||
Attach: true,
|
||||
Containers: []string{created.ID},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadAdditionalResources loads additional resources from the project, such as image references, secrets, configs and exposed ports
|
||||
func LoadAdditionalResources(ctx context.Context, dockerCLI command.Cli, project *types.Project) (*types.Project, error) {
|
||||
for name, service := range project.Services {
|
||||
imageName := api.GetImageNameOrDefault(service, project.Name)
|
||||
|
||||
inspect, err := inspectWithPull(ctx, dockerCLI, imageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
service.Image = imageName
|
||||
exposed := utils.Set[string]{}
|
||||
exposed.AddAll(service.Expose...)
|
||||
for port := range inspect.Config.ExposedPorts {
|
||||
exposed.Add(nat.Port(port).Port())
|
||||
}
|
||||
for _, port := range service.Ports {
|
||||
exposed.Add(strconv.Itoa(int(port.Target)))
|
||||
}
|
||||
service.Expose = exposed.Elements()
|
||||
project.Services[name] = service
|
||||
}
|
||||
|
||||
for name, secret := range project.Secrets {
|
||||
f, err := loadFileObject(types.FileObjectConfig(secret))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
project.Secrets[name] = types.SecretConfig(f)
|
||||
}
|
||||
|
||||
for name, config := range project.Configs {
|
||||
f, err := loadFileObject(types.FileObjectConfig(config))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
project.Configs[name] = types.ConfigObjConfig(f)
|
||||
}
|
||||
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func loadFileObject(conf types.FileObjectConfig) (types.FileObjectConfig, error) {
|
||||
if !conf.External {
|
||||
switch {
|
||||
case conf.Environment != "":
|
||||
conf.Content = os.Getenv(conf.Environment)
|
||||
case conf.File != "":
|
||||
bytes, err := os.ReadFile(conf.File)
|
||||
if err != nil {
|
||||
return conf, err
|
||||
}
|
||||
conf.Content = string(bytes)
|
||||
}
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func inspectWithPull(ctx context.Context, dockerCli command.Cli, imageName string) (image.InspectResponse, error) {
|
||||
inspect, err := dockerCli.Client().ImageInspect(ctx, imageName)
|
||||
if errdefs.IsNotFound(err) {
|
||||
var stream io.ReadCloser
|
||||
stream, err = dockerCli.Client().ImagePull(ctx, imageName, image.PullOptions{})
|
||||
if err != nil {
|
||||
return image.InspectResponse{}, err
|
||||
}
|
||||
defer func() { _ = stream.Close() }()
|
||||
|
||||
err = jsonmessage.DisplayJSONMessagesToStream(stream, dockerCli.Out(), nil)
|
||||
if err != nil {
|
||||
return image.InspectResponse{}, err
|
||||
}
|
||||
if inspect, err = dockerCli.Client().ImageInspect(ctx, imageName); err != nil {
|
||||
return image.InspectResponse{}, err
|
||||
}
|
||||
}
|
||||
return inspect, err
|
||||
}
|
120
pkg/bridge/transformers.go
Normal file
120
pkg/bridge/transformers.go
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
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 bridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/moby/go-archive"
|
||||
)
|
||||
|
||||
const (
|
||||
TransformerLabel = "com.docker.compose.bridge"
|
||||
DefaultTransformerImage = "docker/compose-bridge-kubernetes"
|
||||
|
||||
templatesPath = "/templates"
|
||||
)
|
||||
|
||||
type CreateTransformerOptions struct {
|
||||
Dest string
|
||||
From string
|
||||
}
|
||||
|
||||
func CreateTransformer(ctx context.Context, dockerCli command.Cli, options CreateTransformerOptions) error {
|
||||
if options.From == "" {
|
||||
options.From = DefaultTransformerImage
|
||||
}
|
||||
out, err := filepath.Abs(options.Dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := os.Stat(out); err == nil {
|
||||
return fmt.Errorf("output folder %s already exists", out)
|
||||
}
|
||||
|
||||
tmpl := filepath.Join(out, "templates")
|
||||
err = os.MkdirAll(tmpl, 0o744)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return fmt.Errorf("cannot create output folder: %w", err)
|
||||
}
|
||||
|
||||
if err := command.ValidateOutputPath(out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
created, err := dockerCli.Client().ContainerCreate(ctx, &container.Config{
|
||||
Image: options.From,
|
||||
}, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
|
||||
defer func() {
|
||||
_ = dockerCli.Client().ContainerRemove(context.Background(), created.ID, container.RemoveOptions{Force: true})
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content, stat, err := dockerCli.Client().CopyFromContainer(ctx, created.ID, templatesPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = content.Close()
|
||||
}()
|
||||
|
||||
srcInfo := archive.CopyInfo{
|
||||
Path: templatesPath,
|
||||
Exists: true,
|
||||
IsDir: stat.Mode.IsDir(),
|
||||
}
|
||||
|
||||
preArchive := content
|
||||
if srcInfo.RebaseName != "" {
|
||||
_, srcBase := archive.SplitPathDirEntry(srcInfo.Path)
|
||||
preArchive = archive.RebaseArchiveEntries(content, srcBase, srcInfo.RebaseName)
|
||||
}
|
||||
|
||||
if err := archive.CopyTo(preArchive, srcInfo, out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dockerfile := `FROM docker/compose-bridge-transformer
|
||||
LABEL com.docker.compose.bridge=transformation
|
||||
COPY templates /templates
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(out, "Dockerfile"), []byte(dockerfile), 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintf(dockerCli.Out(), "Transformer created in %q\n", out)
|
||||
return err
|
||||
}
|
||||
|
||||
func ListTransformers(ctx context.Context, dockerCli command.Cli) ([]image.Summary, error) {
|
||||
api := dockerCli.Client()
|
||||
return api.ImageList(ctx, image.ListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
filters.Arg("label", fmt.Sprintf("%s=%s", TransformerLabel, "transformation")),
|
||||
),
|
||||
})
|
||||
}
|
91
pkg/compose/apiSocket.go
Normal file
91
pkg/compose/apiSocket.go
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
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 (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
)
|
||||
|
||||
// --use-api-socket is not actually supported by the Docker Engine
|
||||
// but is a client-side hack (see https://github.com/docker/cli/blob/master/cli/command/container/create.go#L246)
|
||||
// we replicate here by transforming the project model
|
||||
|
||||
func (s *composeService) useAPISocket(project *types.Project) (*types.Project, error) {
|
||||
useAPISocket := false
|
||||
for _, service := range project.Services {
|
||||
if service.UseAPISocket {
|
||||
useAPISocket = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !useAPISocket {
|
||||
return project, nil
|
||||
}
|
||||
|
||||
if s.dockerCli.ServerInfo().OSType == "windows" {
|
||||
return nil, errors.New("use_api_socket can't be used with a Windows Docker Engine")
|
||||
}
|
||||
|
||||
creds, err := s.dockerCli.ConfigFile().GetAllCredentials()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolving credentials failed: %w", err)
|
||||
}
|
||||
newConfig := &configfile.ConfigFile{
|
||||
AuthConfigs: creds,
|
||||
}
|
||||
var configBuf bytes.Buffer
|
||||
if err := newConfig.SaveToWriter(&configBuf); err != nil {
|
||||
return nil, fmt.Errorf("saving creds for API socket: %w", err)
|
||||
}
|
||||
|
||||
project.Configs["#apisocket"] = types.ConfigObjConfig{
|
||||
Content: configBuf.String(),
|
||||
}
|
||||
|
||||
for name, service := range project.Services {
|
||||
if !service.UseAPISocket {
|
||||
continue
|
||||
}
|
||||
service.Volumes = append(service.Volumes, types.ServiceVolumeConfig{
|
||||
Type: types.VolumeTypeBind,
|
||||
Source: "/var/run/docker.sock",
|
||||
Target: "/var/run/docker.sock",
|
||||
})
|
||||
|
||||
_, envvarPresent := service.Environment["DOCKER_CONFIG"]
|
||||
|
||||
// If the DOCKER_CONFIG env var is already present, we assume the client knows
|
||||
// what they're doing and don't inject the creds.
|
||||
if !envvarPresent {
|
||||
// Set our special little location for the config file.
|
||||
path := "/run/secrets/docker"
|
||||
service.Environment["DOCKER_CONFIG"] = &path
|
||||
}
|
||||
|
||||
service.Configs = append(service.Configs, types.ServiceConfigObjConfig{
|
||||
Source: "#apisocket",
|
||||
Target: "/run/secrets/docker/config.json",
|
||||
})
|
||||
project.Services[name] = service
|
||||
}
|
||||
return project, nil
|
||||
}
|
@ -61,41 +61,37 @@ func (s *composeService) attach(ctx context.Context, project *types.Project, lis
|
||||
}
|
||||
|
||||
func (s *composeService) attachContainer(ctx context.Context, container containerType.Summary, listener api.ContainerEventListener) error {
|
||||
serviceName := container.Labels[api.ServiceLabel]
|
||||
containerName := getContainerNameWithoutProject(container)
|
||||
service := container.Labels[api.ServiceLabel]
|
||||
name := getContainerNameWithoutProject(container)
|
||||
return s.doAttachContainer(ctx, service, container.ID, name, listener)
|
||||
}
|
||||
|
||||
listener(api.ContainerEvent{
|
||||
Type: api.ContainerEventAttach,
|
||||
Container: containerName,
|
||||
ID: container.ID,
|
||||
Service: serviceName,
|
||||
})
|
||||
|
||||
wOut := utils.GetWriter(func(line string) {
|
||||
listener(api.ContainerEvent{
|
||||
Type: api.ContainerEventLog,
|
||||
Container: containerName,
|
||||
ID: container.ID,
|
||||
Service: serviceName,
|
||||
Line: line,
|
||||
})
|
||||
})
|
||||
wErr := utils.GetWriter(func(line string) {
|
||||
listener(api.ContainerEvent{
|
||||
Type: api.ContainerEventErr,
|
||||
Container: containerName,
|
||||
ID: container.ID,
|
||||
Service: serviceName,
|
||||
Line: line,
|
||||
})
|
||||
})
|
||||
|
||||
inspect, err := s.apiClient().ContainerInspect(ctx, container.ID)
|
||||
func (s *composeService) doAttachContainer(ctx context.Context, service, id, name string, listener api.ContainerEventListener) error {
|
||||
inspect, err := s.apiClient().ContainerInspect(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = s.attachContainerStreams(ctx, container.ID, inspect.Config.Tty, nil, wOut, wErr)
|
||||
wOut := utils.GetWriter(func(line string) {
|
||||
listener(api.ContainerEvent{
|
||||
Type: api.ContainerEventLog,
|
||||
Source: name,
|
||||
ID: id,
|
||||
Service: service,
|
||||
Line: line,
|
||||
})
|
||||
})
|
||||
wErr := utils.GetWriter(func(line string) {
|
||||
listener(api.ContainerEvent{
|
||||
Type: api.ContainerEventErr,
|
||||
Source: name,
|
||||
ID: id,
|
||||
Service: service,
|
||||
Line: line,
|
||||
})
|
||||
})
|
||||
|
||||
_, _, err = s.attachContainerStreams(ctx, id, inspect.Config.Tty, nil, wOut, wErr)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -21,19 +21,18 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/controller/pb"
|
||||
"github.com/docker/buildx/store/storeutil"
|
||||
"github.com/docker/buildx/util/buildflags"
|
||||
xprogress "github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/hints"
|
||||
cliopts "github.com/docker/cli/opts"
|
||||
"github.com/docker/compose/v2/internal/tracing"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@ -49,6 +48,8 @@ import (
|
||||
"github.com/moby/buildkit/util/progress/progressui"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
// required to get default driver registered
|
||||
_ "github.com/docker/buildx/driver/docker"
|
||||
@ -60,17 +61,16 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti
|
||||
return err
|
||||
}
|
||||
return progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
||||
_, err := s.build(ctx, project, options, nil)
|
||||
return err
|
||||
return tracing.SpanWrapFunc("project/build", tracing.ProjectOptions(ctx, project),
|
||||
func(ctx context.Context) error {
|
||||
_, err := s.build(ctx, project, options, nil)
|
||||
return err
|
||||
})(ctx)
|
||||
}, s.stdinfo(), "Building")
|
||||
}
|
||||
|
||||
const bakeSuggest = "Compose now can delegate build to bake for better performances\nJust set COMPOSE_BAKE=true"
|
||||
|
||||
var suggest sync.Once
|
||||
|
||||
//nolint:gocyclo
|
||||
func (s *composeService) build(ctx context.Context, project *types.Project, options api.BuildOptions, localImages map[string]string) (map[string]string, error) {
|
||||
func (s *composeService) build(ctx context.Context, project *types.Project, options api.BuildOptions, localImages map[string]api.ImageSummary) (map[string]string, error) {
|
||||
imageIDs := map[string]string{}
|
||||
serviceToBuild := types.Services{}
|
||||
|
||||
@ -79,29 +79,19 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
policy = types.IncludeDependencies
|
||||
}
|
||||
|
||||
serviceDeps := false
|
||||
project, err := project.WithServicesTransform(func(serviceName string, service types.ServiceConfig) (types.ServiceConfig, error) {
|
||||
if service.Build != nil {
|
||||
for _, c := range service.Build.AdditionalContexts {
|
||||
if t, found := strings.CutPrefix(c, types.ServicePrefix); found {
|
||||
serviceDeps = true
|
||||
if service.DependsOn == nil {
|
||||
service.DependsOn = map[string]types.ServiceDependency{}
|
||||
}
|
||||
service.DependsOn[t] = types.ServiceDependency{
|
||||
Condition: "build", // non-canonical, but will force dependency graph ordering
|
||||
}
|
||||
}
|
||||
}
|
||||
var err error
|
||||
if len(options.Services) > 0 {
|
||||
// As user requested some services to be built, also include those used as additional_contexts
|
||||
options.Services = addBuildDependencies(options.Services, project)
|
||||
// Some build dependencies we just introduced may not be enabled
|
||||
project, err = project.WithServicesEnabled(options.Services...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return service, nil
|
||||
})
|
||||
if err != nil {
|
||||
return imageIDs, err
|
||||
}
|
||||
|
||||
if serviceDeps {
|
||||
logrus.Infof(`additional_context with "service:"" is better supported when delegating build go bake. Set COMPOSE_BAKE=true`)
|
||||
project, err = project.WithSelectedServices(options.Services)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = project.ForEachService(options.Services, func(serviceName string, service *types.ServiceConfig) error {
|
||||
@ -124,10 +114,31 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if bake {
|
||||
if bake || options.Print {
|
||||
trace.SpanFromContext(ctx).SetAttributes(attribute.String("builder", "bake"))
|
||||
return s.doBuildBake(ctx, project, serviceToBuild, options)
|
||||
}
|
||||
|
||||
// Not using bake, additional_context: service:xx is implemented by building images in dependency order
|
||||
project, err = project.WithServicesTransform(func(serviceName string, service types.ServiceConfig) (types.ServiceConfig, error) {
|
||||
if service.Build != nil {
|
||||
for _, c := range service.Build.AdditionalContexts {
|
||||
if t, found := strings.CutPrefix(c, types.ServicePrefix); found {
|
||||
if service.DependsOn == nil {
|
||||
service.DependsOn = map[string]types.ServiceDependency{}
|
||||
}
|
||||
service.DependsOn[t] = types.ServiceDependency{
|
||||
Condition: "build", // non-canonical, but will force dependency graph ordering
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return service, nil
|
||||
})
|
||||
if err != nil {
|
||||
return imageIDs, err
|
||||
}
|
||||
|
||||
// Initialize buildkit nodes
|
||||
buildkitEnabled, err := s.dockerCli.BuildKitEnabled()
|
||||
if err != nil {
|
||||
@ -139,11 +150,6 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
w *xprogress.Printer
|
||||
)
|
||||
if buildkitEnabled {
|
||||
if hints.Enabled() && progress.Mode != progress.ModeQuiet && progress.Mode != progress.ModeJSON {
|
||||
suggest.Do(func() {
|
||||
fmt.Fprintln(s.dockerCli.Out(), bakeSuggest) //nolint:errcheck
|
||||
})
|
||||
}
|
||||
builderName := options.Builder
|
||||
if builderName == "" {
|
||||
builderName = os.Getenv("BUILDX_BUILDER")
|
||||
@ -167,7 +173,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
if options.Quiet {
|
||||
options.Progress = progress.ModeQuiet
|
||||
}
|
||||
if options.Progress == "" {
|
||||
if options.Progress == progress.ModeAuto {
|
||||
options.Progress = os.Getenv("BUILDKIT_PROGRESS")
|
||||
}
|
||||
w, err = xprogress.NewPrinter(progressCtx, os.Stdout, progressui.DisplayMode(options.Progress),
|
||||
@ -201,6 +207,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
serviceName := fmt.Sprintf("Service %s", name)
|
||||
|
||||
if !buildkitEnabled {
|
||||
trace.SpanFromContext(ctx).SetAttributes(attribute.String("builder", "classic"))
|
||||
cw.Event(progress.BuildingEvent(serviceName))
|
||||
id, err := s.doBuildClassic(ctx, project, service, options)
|
||||
if err != nil {
|
||||
@ -224,6 +231,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
return err
|
||||
}
|
||||
|
||||
trace.SpanFromContext(ctx).SetAttributes(attribute.String("builder", "buildkit"))
|
||||
digest, err := s.doBuildBuildkit(ctx, name, buildOptions, w, nodes)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -259,7 +267,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
|
||||
func (s *composeService) ensureImagesExists(ctx context.Context, project *types.Project, buildOpts *api.BuildOptions, quietPull bool) error {
|
||||
for name, service := range project.Services {
|
||||
if service.Image == "" && service.Build == nil {
|
||||
if service.Provider == nil && service.Image == "" && service.Build == nil {
|
||||
return fmt.Errorf("invalid service %q. Must specify either image or build", name)
|
||||
}
|
||||
}
|
||||
@ -287,7 +295,11 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
|
||||
}
|
||||
|
||||
for name, digest := range builtImages {
|
||||
images[name] = digest
|
||||
images[name] = api.ImageSummary{
|
||||
Repository: name,
|
||||
ID: digest,
|
||||
LastTagTime: time.Now(),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
@ -300,38 +312,33 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
|
||||
// set digest as com.docker.compose.image label so we can detect outdated containers
|
||||
for name, service := range project.Services {
|
||||
image := api.GetImageNameOrDefault(service, project.Name)
|
||||
digest, ok := images[image]
|
||||
img, ok := images[image]
|
||||
if ok {
|
||||
if service.Labels == nil {
|
||||
service.Labels = types.Labels{}
|
||||
}
|
||||
service.CustomLabels.Add(api.ImageDigestLabel, digest)
|
||||
service.CustomLabels.Add(api.ImageDigestLabel, img.ID)
|
||||
}
|
||||
project.Services[name] = service
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *composeService) getLocalImagesDigests(ctx context.Context, project *types.Project) (map[string]string, error) {
|
||||
var imageNames []string
|
||||
func (s *composeService) getLocalImagesDigests(ctx context.Context, project *types.Project) (map[string]api.ImageSummary, error) {
|
||||
imageNames := utils.Set[string]{}
|
||||
for _, s := range project.Services {
|
||||
imgName := api.GetImageNameOrDefault(s, project.Name)
|
||||
if !utils.StringContains(imageNames, imgName) {
|
||||
imageNames = append(imageNames, imgName)
|
||||
imageNames.Add(api.GetImageNameOrDefault(s, project.Name))
|
||||
for _, volume := range s.Volumes {
|
||||
if volume.Type == types.VolumeTypeImage {
|
||||
imageNames.Add(volume.Source)
|
||||
}
|
||||
}
|
||||
}
|
||||
imgs, err := s.getImageSummaries(ctx, imageNames)
|
||||
imgs, err := s.getImageSummaries(ctx, imageNames.Elements())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
images := map[string]string{}
|
||||
for name, info := range imgs {
|
||||
images[name] = info.ID
|
||||
}
|
||||
|
||||
for i, service := range project.Services {
|
||||
imgName := api.GetImageNameOrDefault(service, project.Name)
|
||||
digest, ok := images[imgName]
|
||||
img, ok := imgs[imgName]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@ -340,7 +347,7 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inspect, err := s.apiClient().ImageInspect(ctx, digest)
|
||||
inspect, err := s.apiClient().ImageInspect(ctx, img.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -350,18 +357,19 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
|
||||
Variant: inspect.Variant,
|
||||
}
|
||||
if !platforms.NewMatcher(platform).Match(actual) {
|
||||
logrus.Debugf("local image %s doesn't match expected platform %s", service.Image, service.Platform)
|
||||
// there is a local image, but it's for the wrong platform, so
|
||||
// pretend it doesn't exist so that we can pull/build an image
|
||||
// for the correct platform instead
|
||||
delete(images, imgName)
|
||||
delete(imgs, imgName)
|
||||
}
|
||||
}
|
||||
|
||||
project.Services[i].CustomLabels.Add(api.ImageDigestLabel, digest)
|
||||
project.Services[i].CustomLabels.Add(api.ImageDigestLabel, img.ID)
|
||||
|
||||
}
|
||||
|
||||
return images, nil
|
||||
return imgs, nil
|
||||
}
|
||||
|
||||
// resolveAndMergeBuildArgs returns the final set of build arguments to use for the service image build.
|
||||
@ -390,6 +398,7 @@ func resolveAndMergeBuildArgs(dockerCli command.Cli, project *types.Project, ser
|
||||
return result
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, options api.BuildOptions) (build.Options, error) {
|
||||
plats, err := parsePlatforms(service)
|
||||
if err != nil {
|
||||
@ -463,15 +472,31 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
|
||||
return build.Options{}, err
|
||||
}
|
||||
|
||||
attests := map[string]*string{}
|
||||
if options.Attestations {
|
||||
if service.Build.Provenance != "" {
|
||||
attests["provenance"] = attestation(service.Build.Provenance, "provenance")
|
||||
}
|
||||
if service.Build.SBOM != "" {
|
||||
attests["sbom"] = attestation(service.Build.SBOM, "sbom")
|
||||
}
|
||||
}
|
||||
if options.Provenance != "" {
|
||||
attests["provenance"] = attestation(options.Provenance, "provenance")
|
||||
}
|
||||
if options.SBOM != "" {
|
||||
attests["sbom"] = attestation(options.SBOM, "sbom")
|
||||
}
|
||||
|
||||
return build.Options{
|
||||
Inputs: build.Inputs{
|
||||
ContextPath: service.Build.Context,
|
||||
DockerfileInline: service.Build.DockerfileInline,
|
||||
DockerfilePath: dockerFilePath(service.Build.Context, service.Build.Dockerfile),
|
||||
NamedContexts: toBuildContexts(service.Build.AdditionalContexts),
|
||||
NamedContexts: toBuildContexts(service, project),
|
||||
},
|
||||
CacheFrom: pb.CreateCaches(cacheFrom.ToPB()),
|
||||
CacheTo: pb.CreateCaches(cacheTo.ToPB()),
|
||||
CacheFrom: build.CreateCaches(cacheFrom),
|
||||
CacheTo: build.CreateCaches(cacheTo),
|
||||
NoCache: service.Build.NoCache,
|
||||
Pull: service.Build.Pull,
|
||||
BuildArgs: flatten(resolveAndMergeBuildArgs(s.dockerCli, project, service, options)),
|
||||
@ -486,9 +511,20 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
|
||||
Session: sessionConfig,
|
||||
Allow: allow,
|
||||
SourcePolicy: sp,
|
||||
Attests: attests,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func attestation(attest string, val string) *string {
|
||||
if b, err := strconv.ParseBool(val); err == nil {
|
||||
s := fmt.Sprintf("type=%s,disabled=%t", attest, b)
|
||||
return &s
|
||||
} else {
|
||||
s := fmt.Sprintf("type=%s,%s", attest, val)
|
||||
return &s
|
||||
}
|
||||
}
|
||||
|
||||
func toUlimitOpt(ulimits map[string]*types.UlimitsConfig) *cliopts.UlimitOpt {
|
||||
ref := map[string]*container.Ulimit{}
|
||||
for _, limit := range toUlimits(ulimits) {
|
||||
@ -573,13 +609,15 @@ func getImageBuildLabels(project *types.Project, service types.ServiceConfig) ty
|
||||
return ret
|
||||
}
|
||||
|
||||
func toBuildContexts(additionalContexts types.Mapping) map[string]build.NamedContext {
|
||||
func toBuildContexts(service types.ServiceConfig, project *types.Project) map[string]build.NamedContext {
|
||||
namedContexts := map[string]build.NamedContext{}
|
||||
for name, contextPath := range additionalContexts {
|
||||
if _, found := strings.CutPrefix(contextPath, types.ServicePrefix); found {
|
||||
// image we depend on has been build previously, as we run in dependency order.
|
||||
// this assumes use of docker engine builder, so that build can access local images
|
||||
continue
|
||||
for name, contextPath := range service.Build.AdditionalContexts {
|
||||
if strings.HasPrefix(contextPath, types.ServicePrefix) {
|
||||
// image we depend on has been built previously, as we run in dependency order.
|
||||
// so we convert the service reference into an image reference
|
||||
target := contextPath[len(types.ServicePrefix):]
|
||||
image := api.GetImageNameOrDefault(project.Services[target], project.Name)
|
||||
contextPath = "docker-image://" + image
|
||||
}
|
||||
namedContexts[name] = build.NamedContext{Path: contextPath}
|
||||
}
|
||||
@ -608,3 +646,25 @@ func parsePlatforms(service types.ServiceConfig) ([]specs.Platform, error) {
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func addBuildDependencies(services []string, project *types.Project) []string {
|
||||
servicesWithDependencies := utils.NewSet(services...)
|
||||
for _, service := range services {
|
||||
s, ok := project.Services[service]
|
||||
if !ok {
|
||||
s = project.DisabledServices[service]
|
||||
}
|
||||
b := s.Build
|
||||
if b != nil {
|
||||
for _, target := range b.AdditionalContexts {
|
||||
if s, found := strings.CutPrefix(target, types.ServicePrefix); found {
|
||||
servicesWithDependencies.Add(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(servicesWithDependencies) > len(services) {
|
||||
return addBuildDependencies(servicesWithDependencies.Elements(), project)
|
||||
}
|
||||
return servicesWithDependencies.Elements()
|
||||
}
|
||||
|
@ -20,9 +20,12 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@ -32,7 +35,6 @@ import (
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli-plugins/socket"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
@ -43,26 +45,22 @@ import (
|
||||
"github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func buildWithBake(dockerCli command.Cli) (bool, error) {
|
||||
b, ok := os.LookupEnv("COMPOSE_BAKE")
|
||||
if !ok {
|
||||
if dockerCli.ConfigFile().Plugins["compose"]["build"] == "bake" {
|
||||
b, ok = "true", true
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return false, nil
|
||||
b = "true"
|
||||
}
|
||||
bake, err := strconv.ParseBool(b)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !bake {
|
||||
if ok {
|
||||
logrus.Warnf("COMPOSE_BAKE=false is deprecated, support for internal compose builder will be removed in next release")
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@ -72,6 +70,7 @@ func buildWithBake(dockerCli command.Cli) (bool, error) {
|
||||
}
|
||||
if !enabled {
|
||||
logrus.Warnf("Docker Compose is configured to build using Bake, but buildkit isn't enabled")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
_, err = manager.GetPlugin("buildx", dockerCli, &cobra.Command{})
|
||||
@ -117,6 +116,7 @@ type bakeTarget struct {
|
||||
Ulimits []string `json:"ulimits,omitempty"`
|
||||
Call string `json:"call,omitempty"`
|
||||
Entitlements []string `json:"entitlements,omitempty"`
|
||||
ExtraHosts map[string]string `json:"extra-hosts,omitempty"`
|
||||
Outputs []string `json:"output,omitempty"`
|
||||
}
|
||||
|
||||
@ -124,12 +124,24 @@ type bakeMetadata map[string]buildStatus
|
||||
|
||||
type buildStatus struct {
|
||||
Digest string `json:"containerimage.digest"`
|
||||
Image string `json:"image.name"`
|
||||
}
|
||||
|
||||
func (s *composeService) doBuildBake(ctx context.Context, project *types.Project, serviceToBeBuild types.Services, options api.BuildOptions) (map[string]string, error) { //nolint:gocyclo
|
||||
eg := errgroup.Group{}
|
||||
ch := make(chan *client.SolveStatus)
|
||||
display, err := progressui.NewDisplay(os.Stdout, progressui.DisplayMode(options.Progress))
|
||||
if options.Progress == progress.ModeAuto {
|
||||
options.Progress = os.Getenv("BUILDKIT_PROGRESS")
|
||||
}
|
||||
displayMode := progressui.DisplayMode(options.Progress)
|
||||
out := options.Out
|
||||
if out == nil {
|
||||
if !s.dockerCli.Out().IsTerminal() {
|
||||
displayMode = progressui.PlainMode
|
||||
}
|
||||
out = os.Stdout // should be s.dockerCli.Out(), but NewDisplay require access to the underlying *File
|
||||
}
|
||||
display, err := progressui.NewDisplay(out, displayMode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -142,15 +154,32 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
Groups: map[string]bakeGroup{},
|
||||
Targets: map[string]bakeTarget{},
|
||||
}
|
||||
var group bakeGroup
|
||||
var privileged bool
|
||||
var read []string
|
||||
var (
|
||||
group bakeGroup
|
||||
privileged bool
|
||||
read []string
|
||||
expectedImages = make(map[string]string, len(serviceToBeBuild)) // service name -> expected image
|
||||
targets = make(map[string]string, len(serviceToBeBuild)) // service name -> build target
|
||||
)
|
||||
|
||||
for serviceName, service := range serviceToBeBuild {
|
||||
// produce a unique ID for service used as bake target
|
||||
for serviceName := range project.Services {
|
||||
t := strings.ReplaceAll(serviceName, ".", "_")
|
||||
for {
|
||||
if _, ok := targets[serviceName]; !ok {
|
||||
targets[serviceName] = t
|
||||
break
|
||||
}
|
||||
t += "_"
|
||||
}
|
||||
}
|
||||
|
||||
for serviceName, service := range project.Services {
|
||||
if service.Build == nil {
|
||||
continue
|
||||
}
|
||||
build := *service.Build
|
||||
labels := getImageBuildLabels(project, service)
|
||||
|
||||
args := types.Mapping{}
|
||||
for k, v := range resolveAndMergeBuildArgs(s.dockerCli, project, service, options) {
|
||||
@ -160,8 +189,6 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
args[k] = *v
|
||||
}
|
||||
|
||||
image := api.GetImageNameOrDefault(service, project.Name)
|
||||
|
||||
entitlements := build.Entitlements
|
||||
if slices.Contains(build.Entitlements, "security.insecure") {
|
||||
privileged = true
|
||||
@ -171,12 +198,20 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
privileged = true
|
||||
}
|
||||
|
||||
var output string
|
||||
var outputs []string
|
||||
var call string
|
||||
push := options.Push && service.Image != ""
|
||||
if len(service.Build.Platforms) > 1 {
|
||||
output = fmt.Sprintf("type=image,push=%t", push)
|
||||
} else {
|
||||
output = fmt.Sprintf("type=docker,load=true,push=%t", push)
|
||||
switch {
|
||||
case options.Check:
|
||||
call = "lint"
|
||||
case len(service.Build.Platforms) > 1:
|
||||
outputs = []string{fmt.Sprintf("type=image,push=%t", push)}
|
||||
default:
|
||||
if push {
|
||||
outputs = []string{"type=registry"}
|
||||
} else {
|
||||
outputs = []string{"type=docker"}
|
||||
}
|
||||
}
|
||||
|
||||
read = append(read, build.Context)
|
||||
@ -187,17 +222,22 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
}
|
||||
}
|
||||
|
||||
cfg.Targets[serviceName] = bakeTarget{
|
||||
image := api.GetImageNameOrDefault(service, project.Name)
|
||||
expectedImages[serviceName] = image
|
||||
|
||||
target := targets[serviceName]
|
||||
cfg.Targets[target] = bakeTarget{
|
||||
Context: build.Context,
|
||||
Contexts: additionalContexts(build.AdditionalContexts),
|
||||
Contexts: additionalContexts(build.AdditionalContexts, targets),
|
||||
Dockerfile: dockerFilePath(build.Context, build.Dockerfile),
|
||||
DockerfileInline: build.DockerfileInline,
|
||||
DockerfileInline: strings.ReplaceAll(build.DockerfileInline, "${", "$${"),
|
||||
Args: args,
|
||||
Labels: build.Labels,
|
||||
Labels: labels,
|
||||
Tags: append(build.Tags, image),
|
||||
|
||||
CacheFrom: build.CacheFrom,
|
||||
// CacheTo: TODO
|
||||
CacheFrom: build.CacheFrom,
|
||||
CacheTo: build.CacheTo,
|
||||
NetworkMode: build.Network,
|
||||
Platforms: build.Platforms,
|
||||
Target: build.Target,
|
||||
Secrets: toBakeSecrets(project, build.Secrets),
|
||||
@ -207,9 +247,19 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
ShmSize: build.ShmSize,
|
||||
Ulimits: toBakeUlimits(build.Ulimits),
|
||||
Entitlements: entitlements,
|
||||
Outputs: []string{output},
|
||||
ExtraHosts: toBakeExtraHosts(build.ExtraHosts),
|
||||
|
||||
Outputs: outputs,
|
||||
Call: call,
|
||||
}
|
||||
group.Targets = append(group.Targets, serviceName)
|
||||
}
|
||||
|
||||
// create a bake group with targets for services to build
|
||||
for serviceName, service := range serviceToBeBuild {
|
||||
if service.Build == nil {
|
||||
continue
|
||||
}
|
||||
group.Targets = append(group.Targets, targets[serviceName])
|
||||
}
|
||||
|
||||
cfg.Groups["default"] = group
|
||||
@ -219,19 +269,31 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logrus.Debugf("bake build config:\n%s", string(b))
|
||||
|
||||
metadata, err := os.CreateTemp(os.TempDir(), "compose")
|
||||
if err != nil {
|
||||
if options.Print {
|
||||
_, err = fmt.Fprintln(s.stdout(), string(b))
|
||||
return nil, err
|
||||
}
|
||||
logrus.Debugf("bake build config:\n%s", string(b))
|
||||
|
||||
var metadataFile string
|
||||
for {
|
||||
// we don't use os.CreateTemp here as we need a temporary file name, but don't want it actually created
|
||||
// as bake relies on atomicwriter and this creates conflict during rename
|
||||
metadataFile = filepath.Join(os.TempDir(), fmt.Sprintf("compose-build-metadataFile-%d.json", rand.Int31()))
|
||||
if _, err = os.Stat(metadataFile); os.IsNotExist(err) {
|
||||
break
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
_ = os.Remove(metadataFile)
|
||||
}()
|
||||
|
||||
buildx, err := manager.GetPlugin("buildx", s.dockerCli, &cobra.Command{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
args := []string{"bake", "--file", "-", "--progress", "rawjson", "--metadata-file", metadata.Name()}
|
||||
args := []string{"bake", "--file", "-", "--progress", "rawjson", "--metadata-file", metadataFile}
|
||||
mustAllow := buildx.Version != "" && versions.GreaterThanOrEqualTo(buildx.Version[1:], "0.17.0")
|
||||
if mustAllow {
|
||||
// FIXME we should prompt user about this, but this is a breaking change in UX
|
||||
@ -246,27 +308,21 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
if options.Builder != "" {
|
||||
args = append(args, "--builder", options.Builder)
|
||||
}
|
||||
if options.Quiet {
|
||||
args = append(args, "--progress=quiet")
|
||||
}
|
||||
|
||||
logrus.Debugf("Executing bake with args: %v", args)
|
||||
|
||||
cmd := exec.CommandContext(ctx, buildx.Path, args...)
|
||||
// Remove DOCKER_CLI_PLUGIN... variable so buildx can detect it run standalone
|
||||
cmd.Env = filter(os.Environ(), manager.ReexecEnvvar)
|
||||
|
||||
// Use docker/cli mechanism to propagate termination signal to child process
|
||||
server, err := socket.NewPluginServer(nil)
|
||||
if err != nil {
|
||||
defer server.Close() //nolint:errcheck
|
||||
cmd.Cancel = server.Close
|
||||
cmd.Env = replace(cmd.Env, socket.EnvKey, server.Addr().String())
|
||||
if s.dryRun {
|
||||
return dryRunBake(ctx, cfg), nil
|
||||
}
|
||||
cmd := exec.CommandContext(ctx, buildx.Path, args...)
|
||||
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("DOCKER_CONTEXT=%s", s.dockerCli.CurrentContext()))
|
||||
|
||||
// propagate opentelemetry context to child process, see https://github.com/open-telemetry/oteps/blob/main/text/0258-env-context-baggage-carriers.md
|
||||
carrier := propagation.MapCarrier{}
|
||||
otel.GetTextMapPropagator().Inject(ctx, &carrier)
|
||||
cmd.Env = append(cmd.Env, types.Mapping(carrier).Values()...)
|
||||
err = s.prepareShellOut(ctx, project.Environment, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd.Stdout = s.stdout()
|
||||
cmd.Stdin = bytes.NewBuffer(b)
|
||||
@ -275,23 +331,31 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var errMessage string
|
||||
scanner := bufio.NewScanner(pipe)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
var errMessage []string
|
||||
reader := bufio.NewReader(pipe)
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
eg.Go(cmd.Wait)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
for {
|
||||
line, readErr := reader.ReadString('\n')
|
||||
if readErr != nil {
|
||||
if readErr == io.EOF {
|
||||
break
|
||||
} else {
|
||||
return nil, fmt.Errorf("failed to execute bake: %w", readErr)
|
||||
}
|
||||
}
|
||||
decoder := json.NewDecoder(strings.NewReader(line))
|
||||
var status client.SolveStatus
|
||||
err := decoder.Decode(&status)
|
||||
if err != nil {
|
||||
if strings.HasPrefix(line, "ERROR: ") {
|
||||
errMessage = line[7:]
|
||||
errMessage = append(errMessage, line[7:])
|
||||
} else {
|
||||
errMessage = append(errMessage, line)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@ -301,13 +365,13 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
|
||||
err = eg.Wait()
|
||||
if err != nil {
|
||||
if errMessage != "" {
|
||||
return nil, errors.New(errMessage)
|
||||
if len(errMessage) > 0 {
|
||||
return nil, errors.New(strings.Join(errMessage, "\n"))
|
||||
}
|
||||
return nil, fmt.Errorf("failed to execute bake: %w", err)
|
||||
}
|
||||
|
||||
b, err = os.ReadFile(metadata.Name())
|
||||
b, err = os.ReadFile(metadataFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -320,18 +384,32 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
|
||||
cw := progress.ContextWriter(ctx)
|
||||
results := map[string]string{}
|
||||
for name, m := range md {
|
||||
results[name] = m.Digest
|
||||
cw.Event(progress.BuiltEvent(name))
|
||||
for name := range serviceToBeBuild {
|
||||
image := expectedImages[name]
|
||||
target := targets[name]
|
||||
built, ok := md[target]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("build result not found in Bake metadata for service %s", name)
|
||||
}
|
||||
results[image] = built.Digest
|
||||
cw.Event(progress.BuiltEvent(image))
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func additionalContexts(contexts types.Mapping) map[string]string {
|
||||
func toBakeExtraHosts(hosts types.HostsList) map[string]string {
|
||||
m := make(map[string]string)
|
||||
for k, v := range hosts {
|
||||
m[k] = strings.Join(v, ",")
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func additionalContexts(contexts types.Mapping, targets map[string]string) map[string]string {
|
||||
ac := map[string]string{}
|
||||
for k, v := range contexts {
|
||||
if target, found := strings.CutPrefix(v, types.ServicePrefix); found {
|
||||
v = "target:" + target
|
||||
v = "target:" + targets[target]
|
||||
}
|
||||
ac[k] = v
|
||||
}
|
||||
@ -376,28 +454,47 @@ func toBakeSecrets(project *types.Project, secrets []types.ServiceSecretConfig)
|
||||
return s
|
||||
}
|
||||
|
||||
func filter(environ []string, variable string) []string {
|
||||
prefix := variable + "="
|
||||
filtered := make([]string, 0, len(environ))
|
||||
for _, val := range environ {
|
||||
if !strings.HasPrefix(val, prefix) {
|
||||
filtered = append(filtered, val)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func replace(environ []string, variable, value string) []string {
|
||||
filtered := filter(environ, variable)
|
||||
return append(filtered, fmt.Sprintf("%s=%s", variable, value))
|
||||
}
|
||||
|
||||
func dockerFilePath(ctxName string, dockerfile string) string {
|
||||
if dockerfile == "" {
|
||||
return ""
|
||||
}
|
||||
if urlutil.IsGitURL(ctxName) || filepath.IsAbs(dockerfile) {
|
||||
if urlutil.IsGitURL(ctxName) {
|
||||
return dockerfile
|
||||
}
|
||||
return filepath.Join(ctxName, dockerfile)
|
||||
if !filepath.IsAbs(dockerfile) {
|
||||
dockerfile = filepath.Join(ctxName, dockerfile)
|
||||
}
|
||||
dir := filepath.Dir(dockerfile)
|
||||
symlinks, err := filepath.EvalSymlinks(dir)
|
||||
if err == nil {
|
||||
return filepath.Join(symlinks, filepath.Base(dockerfile))
|
||||
}
|
||||
return dockerfile
|
||||
}
|
||||
|
||||
func dryRunBake(ctx context.Context, cfg bakeConfig) map[string]string {
|
||||
w := progress.ContextWriter(ctx)
|
||||
bakeResponse := map[string]string{}
|
||||
for name, target := range cfg.Targets {
|
||||
dryRunUUID := fmt.Sprintf("dryRun-%x", sha1.Sum([]byte(name)))
|
||||
displayDryRunBuildEvent(w, name, dryRunUUID, target.Tags[0])
|
||||
bakeResponse[name] = dryRunUUID
|
||||
}
|
||||
for name := range bakeResponse {
|
||||
w.Event(progress.BuiltEvent(name))
|
||||
}
|
||||
return bakeResponse
|
||||
}
|
||||
|
||||
func displayDryRunBuildEvent(w progress.Writer, name string, dryRunUUID, tag string) {
|
||||
w.Event(progress.Event{
|
||||
ID: name + " ==>",
|
||||
Status: progress.Done,
|
||||
Text: fmt.Sprintf("==> writing image %s", dryRunUUID),
|
||||
})
|
||||
w.Event(progress.Event{
|
||||
ID: name + " ==> ==>",
|
||||
Status: progress.Done,
|
||||
Text: fmt.Sprintf(`naming to %s`, tag),
|
||||
})
|
||||
}
|
||||
|
@ -70,16 +70,7 @@ func (s composeService) dryRunBuildResponse(ctx context.Context, name string, op
|
||||
w := progress.ContextWriter(ctx)
|
||||
buildResponse := map[string]*client.SolveResponse{}
|
||||
dryRunUUID := fmt.Sprintf("dryRun-%x", sha1.Sum([]byte(name)))
|
||||
w.Event(progress.Event{
|
||||
ID: "==>",
|
||||
Status: progress.Done,
|
||||
Text: fmt.Sprintf("==> writing image %s", dryRunUUID),
|
||||
})
|
||||
w.Event(progress.Event{
|
||||
ID: "==> ==>",
|
||||
Status: progress.Done,
|
||||
Text: fmt.Sprintf(`naming to %s`, options.Tags[0]),
|
||||
})
|
||||
displayDryRunBuildEvent(w, name, dryRunUUID, options.Tags[0])
|
||||
buildResponse[name] = &client.SolveResponse{ExporterResponse: map[string]string{
|
||||
"containerimage.digest": dryRunUUID,
|
||||
}}
|
||||
|
@ -27,24 +27,19 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/image/build"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
buildtypes "github.com/docker/docker/api/types/build"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/builder/remotecontext/urlutil"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
|
||||
"github.com/moby/go-archive"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@ -131,7 +126,7 @@ func (s *composeService) doBuildClassic(ctx context.Context, project *types.Proj
|
||||
excludes = build.TrimBuildFilesFromExcludes(excludes, relDockerfile, false)
|
||||
buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
|
||||
ExcludePatterns: excludes,
|
||||
ChownOpts: &idtools.Identity{},
|
||||
ChownOpts: &archive.ChownOpts{},
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -180,7 +175,7 @@ func (s *composeService) doBuildClassic(ctx context.Context, project *types.Proj
|
||||
|
||||
imageID := ""
|
||||
aux := func(msg jsonmessage.JSONMessage) {
|
||||
var result dockertypes.BuildResult
|
||||
var result buildtypes.Result
|
||||
if err := json.Unmarshal(*msg.Aux, &result); err != nil {
|
||||
logrus.Errorf("Failed to parse aux message: %s", err)
|
||||
} else {
|
||||
@ -220,10 +215,10 @@ func isLocalDir(c string) bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func imageBuildOptions(dockerCli command.Cli, project *types.Project, service types.ServiceConfig, options api.BuildOptions) dockertypes.ImageBuildOptions {
|
||||
func imageBuildOptions(dockerCli command.Cli, project *types.Project, service types.ServiceConfig, options api.BuildOptions) buildtypes.ImageBuildOptions {
|
||||
config := service.Build
|
||||
return dockertypes.ImageBuildOptions{
|
||||
Version: dockertypes.BuilderV1,
|
||||
return buildtypes.ImageBuildOptions{
|
||||
Version: buildtypes.BuilderV1,
|
||||
Tags: config.Tags,
|
||||
NoCache: config.NoCache,
|
||||
Remove: true,
|
||||
|
57
pkg/compose/build_test.go
Normal file
57
pkg/compose/build_test.go
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
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 (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func Test_addBuildDependencies(t *testing.T) {
|
||||
project := &types.Project{Services: types.Services{
|
||||
"test": types.ServiceConfig{
|
||||
Build: &types.BuildConfig{
|
||||
AdditionalContexts: map[string]string{
|
||||
"foo": "service:foo",
|
||||
"bar": "service:bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
"foo": types.ServiceConfig{
|
||||
Build: &types.BuildConfig{
|
||||
AdditionalContexts: map[string]string{
|
||||
"zot": "service:zot",
|
||||
},
|
||||
},
|
||||
},
|
||||
"bar": types.ServiceConfig{
|
||||
Build: &types.BuildConfig{},
|
||||
},
|
||||
"zot": types.ServiceConfig{
|
||||
Build: &types.BuildConfig{},
|
||||
},
|
||||
}}
|
||||
|
||||
services := addBuildDependencies([]string{"test"}, project)
|
||||
expected := []string{"test", "foo", "bar", "zot"}
|
||||
slices.Sort(services)
|
||||
slices.Sort(expected)
|
||||
assert.DeepEqual(t, services, expected)
|
||||
}
|
@ -69,7 +69,7 @@ func (s *composeService) commit(ctx context.Context, projectName string, options
|
||||
Reference: options.Reference,
|
||||
Comment: options.Comment,
|
||||
Author: options.Author,
|
||||
Changes: options.Changes.GetAll(),
|
||||
Changes: options.Changes.GetSlice(),
|
||||
Pause: options.Pause,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -19,12 +19,12 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"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/filters"
|
||||
)
|
||||
@ -124,13 +124,7 @@ func matches(c container.Summary, predicates ...containerPredicate) bool {
|
||||
func isService(services ...string) containerPredicate {
|
||||
return func(c container.Summary) bool {
|
||||
service := c.Labels[api.ServiceLabel]
|
||||
return utils.StringContains(services, service)
|
||||
}
|
||||
}
|
||||
|
||||
func isRunning() containerPredicate {
|
||||
return func(c container.Summary) bool {
|
||||
return c.State == "running"
|
||||
return slices.Contains(services, service)
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,7 +139,7 @@ func isOrphaned(project *types.Project) containerPredicate {
|
||||
}
|
||||
// Service that is not defined in the compose model
|
||||
service := c.Labels[api.ServiceLabel]
|
||||
return !utils.StringContains(services, service)
|
||||
return !slices.Contains(services, service)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ import (
|
||||
const (
|
||||
doubledContainerNameWarning = "WARNING: The %q service is using the custom container name %q. " +
|
||||
"Docker requires each container to have a unique name. " +
|
||||
"Remove the custom name to scale the service.\n"
|
||||
"Remove the custom name to scale the service"
|
||||
)
|
||||
|
||||
// convergence manages service's container lifecycle.
|
||||
@ -101,7 +101,7 @@ func (c *convergence) apply(ctx context.Context, project *types.Project, options
|
||||
|
||||
return tracing.SpanWrapFunc("service/apply", tracing.ServiceOptions(service), func(ctx context.Context) error {
|
||||
strategy := options.RecreateDependencies
|
||||
if utils.StringContains(options.Services, name) {
|
||||
if slices.Contains(options.Services, name) {
|
||||
strategy = options.Recreate
|
||||
}
|
||||
return c.ensureService(ctx, project, service, strategy, options.Inherit, options.Timeout)
|
||||
@ -110,6 +110,9 @@ func (c *convergence) apply(ctx context.Context, project *types.Project, options
|
||||
}
|
||||
|
||||
func (c *convergence) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig, recreate string, inherit bool, timeout *time.Duration) error { //nolint:gocyclo
|
||||
if service.Provider != nil {
|
||||
return c.service.runPlugin(ctx, project, service, "up")
|
||||
}
|
||||
expected, err := getScale(service)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -188,7 +191,6 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||
case ContainerCreated:
|
||||
case ContainerRestarting:
|
||||
case ContainerExited:
|
||||
w.Event(progress.CreatedEvent(name))
|
||||
default:
|
||||
container := container
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "service/start", tracing.ContainerOptions(container), func(ctx context.Context) error {
|
||||
@ -225,14 +227,16 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||
|
||||
func (c *convergence) stopDependentContainers(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
|
||||
// Stop dependent containers, so they will be restarted after service is re-created
|
||||
dependents := project.GetDependentsForService(service)
|
||||
dependents := project.GetDependentsForService(service, func(dependency types.ServiceDependency) bool {
|
||||
return dependency.Restart
|
||||
})
|
||||
if len(dependents) == 0 {
|
||||
return nil
|
||||
}
|
||||
err := c.service.stop(ctx, project.Name, api.StopOptions{
|
||||
Services: dependents,
|
||||
Project: project,
|
||||
})
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -559,6 +563,9 @@ func shouldWaitForDependency(serviceName string, dependencyConfig types.ServiceD
|
||||
} else if service.GetScale() == 0 {
|
||||
// don't wait for the dependency which configured to have 0 containers running
|
||||
return false, nil
|
||||
} else if service.Provider != nil {
|
||||
// don't wait for provider services
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@ -590,6 +597,13 @@ func (s *composeService) createContainer(ctx context.Context, project *types.Pro
|
||||
w.Event(progress.CreatingEvent(eventName))
|
||||
ctr, err = s.createMobyContainer(ctx, project, service, name, number, nil, opts, w)
|
||||
if err != nil {
|
||||
if ctx.Err() == nil {
|
||||
w.Event(progress.Event{
|
||||
ID: eventName,
|
||||
Status: progress.Error,
|
||||
StatusText: err.Error(),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
w.Event(progress.CreatedEvent(eventName))
|
||||
@ -598,10 +612,19 @@ func (s *composeService) createContainer(ctx context.Context, project *types.Pro
|
||||
|
||||
func (s *composeService) recreateContainer(ctx context.Context, project *types.Project, service types.ServiceConfig,
|
||||
replaced containerType.Summary, inherit bool, timeout *time.Duration,
|
||||
) (containerType.Summary, error) {
|
||||
var created containerType.Summary
|
||||
) (created containerType.Summary, err error) {
|
||||
w := progress.ContextWriter(ctx)
|
||||
w.Event(progress.NewEvent(getContainerProgressName(replaced), progress.Working, "Recreate"))
|
||||
eventName := getContainerProgressName(replaced)
|
||||
w.Event(progress.NewEvent(eventName, progress.Working, "Recreate"))
|
||||
defer func() {
|
||||
if err != nil && ctx.Err() == nil {
|
||||
w.Event(progress.Event{
|
||||
ID: eventName,
|
||||
Status: progress.Error,
|
||||
StatusText: err.Error(),
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
number, err := strconv.Atoi(replaced.Labels[api.ContainerNumberLabel])
|
||||
if err != nil {
|
||||
@ -612,13 +635,18 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
|
||||
if inherit {
|
||||
inherited = &replaced
|
||||
}
|
||||
|
||||
replacedContainerName := service.ContainerName
|
||||
if replacedContainerName == "" {
|
||||
replacedContainerName = service.Name + api.Separator + strconv.Itoa(number)
|
||||
}
|
||||
name := getContainerName(project.Name, service, number)
|
||||
tmpName := fmt.Sprintf("%s_%s", replaced.ID[:12], name)
|
||||
opts := createOptions{
|
||||
AutoRemove: false,
|
||||
AttachStdin: false,
|
||||
UseNetworkAliases: true,
|
||||
Labels: mergeLabels(service.Labels, service.CustomLabels).Add(api.ContainerReplaceLabel, replaced.ID),
|
||||
Labels: mergeLabels(service.Labels, service.CustomLabels).Add(api.ContainerReplaceLabel, replacedContainerName),
|
||||
}
|
||||
created, err = s.createMobyContainer(ctx, project, service, tmpName, number, inherited, opts, w)
|
||||
if err != nil {
|
||||
@ -636,18 +664,23 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
|
||||
return created, err
|
||||
}
|
||||
|
||||
err = s.apiClient().ContainerRename(ctx, created.ID, name)
|
||||
err = s.apiClient().ContainerRename(ctx, tmpName, name)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
|
||||
w.Event(progress.NewEvent(getContainerProgressName(replaced), progress.Done, "Recreated"))
|
||||
w.Event(progress.NewEvent(eventName, progress.Done, "Recreated"))
|
||||
return created, err
|
||||
}
|
||||
|
||||
// force sequential calls to ContainerStart to prevent race condition in engine assigning ports from ranges
|
||||
var startMx sync.Mutex
|
||||
|
||||
func (s *composeService) startContainer(ctx context.Context, ctr containerType.Summary) error {
|
||||
w := progress.ContextWriter(ctx)
|
||||
w.Event(progress.NewEvent(getContainerProgressName(ctr), progress.Working, "Restart"))
|
||||
startMx.Lock()
|
||||
defer startMx.Unlock()
|
||||
err := s.apiClient().ContainerStart(ctx, ctr.ID, containerType.StartOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
@ -731,14 +764,7 @@ func (s *composeService) createMobyContainer(ctx context.Context,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = s.injectSecrets(ctx, project, service, created.ID)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
|
||||
err = s.injectConfigs(ctx, project, service, created.ID)
|
||||
return created, err
|
||||
return created, nil
|
||||
}
|
||||
|
||||
// getLinks mimics V1 compose/service.py::Service::_get_links()
|
||||
@ -872,6 +898,17 @@ func (s *composeService) startService(ctx context.Context,
|
||||
if ctr.State == ContainerRunning {
|
||||
continue
|
||||
}
|
||||
|
||||
err = s.injectSecrets(ctx, project, service, ctr.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.injectConfigs(ctx, project, service, ctr.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
eventName := getContainerProgressName(ctr)
|
||||
w.Event(progress.StartingEvent(eventName))
|
||||
err = s.apiClient().ContainerStart(ctx, ctr.ID, containerType.StartOptions{})
|
||||
|
@ -78,6 +78,10 @@ func (s *composeService) ToMobyHealthCheck(ctx context.Context, check *compose.H
|
||||
} else {
|
||||
startInterval = time.Duration(*check.StartInterval)
|
||||
}
|
||||
if check.StartPeriod == nil {
|
||||
// see https://github.com/moby/moby/issues/48874
|
||||
return nil, errors.New("healthcheck.start_interval requires healthcheck.start_period to be set")
|
||||
}
|
||||
}
|
||||
return &container.HealthConfig{
|
||||
Test: test,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user