mirror of
https://github.com/docker/compose.git
synced 2026-02-17 14:02:39 +08:00
Compare commits
139 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d474515d45 | ||
|
|
2b21c5df93 | ||
|
|
1f3c10eb4b | ||
|
|
68ad165a59 | ||
|
|
3060ed2795 | ||
|
|
be09b2e8ce | ||
|
|
571a1af013 | ||
|
|
8f644eea73 | ||
|
|
56e92e34b6 | ||
|
|
a42a04dfe8 | ||
|
|
34bcd03a76 | ||
|
|
ed61e42f93 | ||
|
|
65696bb1cb | ||
|
|
446e00520c | ||
|
|
c01c9c29f4 | ||
|
|
038c81f34e | ||
|
|
a20b69ac5b | ||
|
|
977530c22a | ||
|
|
d4db8b6b12 | ||
|
|
f8ce0f04e6 | ||
|
|
8e0520e71e | ||
|
|
332311358e | ||
|
|
df9e420ddd | ||
|
|
142f5dba84 | ||
|
|
700c586bcf | ||
|
|
fc566509d5 | ||
|
|
e73c2303fb | ||
|
|
624303233a | ||
|
|
a1729c52de | ||
|
|
254224c182 | ||
|
|
0861e68450 | ||
|
|
af5b748500 | ||
|
|
32a22c1f4f | ||
|
|
e6ea8fb962 | ||
|
|
043465448f | ||
|
|
1d08390864 | ||
|
|
69a83d1303 | ||
|
|
781b9f1cd5 | ||
|
|
cbff0e5559 | ||
|
|
e4222bff53 | ||
|
|
25197fe6dd | ||
|
|
85cdaf9ddf | ||
|
|
a8469db83f | ||
|
|
08488dae59 | ||
|
|
cc3a216f2f | ||
|
|
6e818b9ae0 | ||
|
|
6b3e57503e | ||
|
|
8e497a1289 | ||
|
|
5aed704379 | ||
|
|
1ff9b758d2 | ||
|
|
9eaba55973 | ||
|
|
a85f8a40a9 | ||
|
|
095f65cb42 | ||
|
|
208e57ded8 | ||
|
|
2d148faedf | ||
|
|
43ac1e31c6 | ||
|
|
5561a778c9 | ||
|
|
ae48f488d1 | ||
|
|
5e3a095380 | ||
|
|
a2a3eb72e2 | ||
|
|
3513b42423 | ||
|
|
d4fa63fdcb | ||
|
|
c21d4cfb40 | ||
|
|
61f1d4f69b | ||
|
|
f7cce281de | ||
|
|
bcaacc7f23 | ||
|
|
3f5898f8d0 | ||
|
|
2bb67f2700 | ||
|
|
bf521fe3ab | ||
|
|
11e9621da5 | ||
|
|
a9de9abcfb | ||
|
|
799ab842a0 | ||
|
|
2f65ace2aa | ||
|
|
aa0a4189ee | ||
|
|
eba3ff8f37 | ||
|
|
6313365ba4 | ||
|
|
dbd51745c4 | ||
|
|
a8bfbc147a | ||
|
|
fbbd6f83d7 | ||
|
|
a000978980 | ||
|
|
361c0893a9 | ||
|
|
513b6128c2 | ||
|
|
eececb9add | ||
|
|
501b5acde6 | ||
|
|
f51bc4cd00 | ||
|
|
517f87a372 | ||
|
|
718049cbd7 | ||
|
|
02371f3127 | ||
|
|
a7c9de82b2 | ||
|
|
51ebeb5441 | ||
|
|
fafaa9c5b8 | ||
|
|
fc9c3cde06 | ||
|
|
73bfbab54b | ||
|
|
2ac081b4c4 | ||
|
|
eeea049f17 | ||
|
|
26064d4b60 | ||
|
|
7c46beb8af | ||
|
|
aa1ec4524c | ||
|
|
a4ee6ca7a5 | ||
|
|
5617eff0c2 | ||
|
|
fa24ab8e25 | ||
|
|
0aad9595a5 | ||
|
|
813900180e | ||
|
|
82417bd5bc | ||
|
|
0cbb73c024 | ||
|
|
38e3d670a9 | ||
|
|
24c78728e0 | ||
|
|
9eeb2d3154 | ||
|
|
8da82c98ef | ||
|
|
1a8c855489 | ||
|
|
15bd0b0c5f | ||
|
|
39d0f6477e | ||
|
|
3a95a0872d | ||
|
|
f794c79eb3 | ||
|
|
407d825705 | ||
|
|
82b41b9ebd | ||
|
|
6c06170eb0 | ||
|
|
60c1311f67 | ||
|
|
17add87e4e | ||
|
|
bf0418bac1 | ||
|
|
b9d0c77cde | ||
|
|
bdb8545611 | ||
|
|
41df35c1f4 | ||
|
|
3ef5045a08 | ||
|
|
d9df7aab6e | ||
|
|
c9d96b449b | ||
|
|
1744b45765 | ||
|
|
87f457e7dc | ||
|
|
abcc91e2b9 | ||
|
|
8b9fe89842 | ||
|
|
34b18194f7 | ||
|
|
ce27dba52e | ||
|
|
d2b9456137 | ||
|
|
9c60fe67df | ||
|
|
c16df17e1f | ||
|
|
20404db12b | ||
|
|
f2ff7fd75e | ||
|
|
cb00aaad28 | ||
|
|
e885bc084d |
44
.github/SECURITY.md
vendored
Normal file
44
.github/SECURITY.md
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
# Security Policy
|
||||
|
||||
The maintainers of Docker Compose take security seriously. If you discover
|
||||
a security issue, please bring it to their attention right away!
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please **DO NOT** file a public issue, instead send your report privately
|
||||
to [security@docker.com](mailto:security@docker.com).
|
||||
|
||||
Reporter(s) can expect a response within 72 hours, acknowledging the issue was
|
||||
received.
|
||||
|
||||
## Review Process
|
||||
|
||||
After receiving the report, an initial triage and technical analysis is
|
||||
performed to confirm the report and determine its scope. We may request
|
||||
additional information in this stage of the process.
|
||||
|
||||
Once a reviewer has confirmed the relevance of the report, a draft security
|
||||
advisory will be created on GitHub. The draft advisory will be used to discuss
|
||||
the issue with maintainers, the reporter(s), and where applicable, other
|
||||
affected parties under embargo.
|
||||
|
||||
If the vulnerability is accepted, a timeline for developing a patch, public
|
||||
disclosure, and patch release will be determined. If there is an embargo period
|
||||
on public disclosure before the patch release, the reporter(s) are expected to
|
||||
participate in the discussion of the timeline and abide by agreed upon dates
|
||||
for public disclosure.
|
||||
|
||||
## Accreditation
|
||||
|
||||
Security reports are greatly appreciated and we will publicly thank you,
|
||||
although we will keep your name confidential if you request it. We also like to
|
||||
send gifts - if you're into swag, make sure to let us know. We do not currently
|
||||
offer a paid security bounty program at this time.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
This project docs not provide long-term supported versions, and only the current
|
||||
release and `main` branch are actively maintained. Docker Compose v1, and the
|
||||
corresponding [v1 branch](https://github.com/docker/compose/tree/v1) reached
|
||||
EOL and are no longer supported.
|
||||
|
||||
2
.github/stale.yml
vendored
2
.github/stale.yml
vendored
@@ -1,7 +1,7 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||
daysUntilStale: 180
|
||||
daysUntilStale: 90
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||
|
||||
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Run
|
||||
run: |
|
||||
@@ -81,13 +81,13 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@v2
|
||||
uses: docker/bake-action@v5
|
||||
with:
|
||||
targets: release
|
||||
set: |
|
||||
@@ -110,10 +110,10 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Test
|
||||
uses: docker/bake-action@v2
|
||||
uses: docker/bake-action@v5
|
||||
with:
|
||||
targets: test
|
||||
set: |
|
||||
@@ -141,10 +141,8 @@ jobs:
|
||||
- plugin
|
||||
- standalone
|
||||
engine:
|
||||
- 24.0.9
|
||||
- 25.0.5
|
||||
- 26.1.4
|
||||
- 27.3.0
|
||||
- 26
|
||||
- 27
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
@@ -165,7 +163,7 @@ jobs:
|
||||
run: docker --version
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
@@ -175,7 +173,7 @@ jobs:
|
||||
cache: true
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@v2
|
||||
uses: docker/bake-action@v5
|
||||
with:
|
||||
targets: binary-with-coverage
|
||||
set: |
|
||||
|
||||
58
.github/workflows/codeql.yml
vendored
58
.github/workflows/codeql.yml
vendored
@@ -1,58 +0,0 @@
|
||||
name: codeql
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.txt'
|
||||
- '**/*.yaml'
|
||||
- '**/*_test.go'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.txt'
|
||||
- '**/*.yaml'
|
||||
- '**/*_test.go'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: 'ubuntu-latest'
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language:
|
||||
- go
|
||||
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
-
|
||||
name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
-
|
||||
name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
-
|
||||
name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
9
.github/workflows/docs-upstream.yml
vendored
9
.github/workflows/docs-upstream.yml
vendored
@@ -2,6 +2,15 @@
|
||||
# to check if yaml reference docs used in this repo are valid
|
||||
name: docs-upstream
|
||||
|
||||
# Default to 'contents: read', which grants actions to read commits.
|
||||
#
|
||||
# If any permission is set, any permission not included in the list is
|
||||
# implicitly set to "none".
|
||||
#
|
||||
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
22
.github/workflows/merge.yml
vendored
22
.github/workflows/merge.yml
vendored
@@ -84,14 +84,14 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.REPO_SLUG }}
|
||||
@@ -102,13 +102,13 @@ jobs:
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERPUBLICBOT_USERNAME }}
|
||||
password: ${{ secrets.DOCKERPUBLICBOT_WRITE_PAT }}
|
||||
-
|
||||
name: Build and push image
|
||||
uses: docker/bake-action@v2
|
||||
uses: docker/bake-action@v5
|
||||
id: bake
|
||||
with:
|
||||
files: |
|
||||
@@ -129,14 +129,16 @@ jobs:
|
||||
-
|
||||
name: Generate Token
|
||||
id: generate_token
|
||||
uses: tibdex/github-app-token@v1
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app_id: ${{ vars.DOCKERDESKTOP_APP_ID }}
|
||||
private_key: ${{ secrets.DOCKERDESKTOP_APP_PRIVATEKEY }}
|
||||
repository: docker/${{ secrets.DOCKERDESKTOP_REPO }}
|
||||
app-id: ${{ vars.DOCKERDESKTOP_APP_ID }}
|
||||
private-key: ${{ secrets.DOCKERDESKTOP_APP_PRIVATEKEY }}
|
||||
owner: docker
|
||||
repositories: |
|
||||
${{ secrets.DOCKERDESKTOP_REPO }}
|
||||
-
|
||||
name: Trigger Docker Desktop e2e with edge version
|
||||
uses: actions/github-script@v6
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ steps.generate_token.outputs.token }}
|
||||
script: |
|
||||
|
||||
8
.github/workflows/scorecards.yml
vendored
8
.github/workflows/scorecards.yml
vendored
@@ -22,12 +22,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 # tag=v3.0.0
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # tag=v4.4.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@99c53751e09b9529366343771cc321ec74e9bd3d # tag=v2.0.6
|
||||
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # tag=v2.4.0
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # tag=v3.0.0
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # tag=v4.5.0
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
@@ -49,6 +49,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@5f532563584d71fdef14ee64d17bafb34f751ce5 # tag=v1.0.26
|
||||
uses: github/codeql-action/upload-sarif@3096afedf9873361b2b2f65e1445b13272c83eb8 # tag=v2.20.00
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
10
.github/workflows/stale.yml
vendored
10
.github/workflows/stale.yml
vendored
@@ -1,4 +1,14 @@
|
||||
name: 'Close stale issues'
|
||||
|
||||
# Default to 'contents: read', which grants actions to read commits.
|
||||
#
|
||||
# If any permission is set, any permission not included in the list is
|
||||
# implicitly set to "none".
|
||||
#
|
||||
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0,3' # at midnight UTC every Sunday and Wednesday
|
||||
|
||||
@@ -10,7 +10,7 @@ linters:
|
||||
- errorlint
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
- gomodguard
|
||||
- revive
|
||||
@@ -22,6 +22,7 @@ linters:
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- staticcheck
|
||||
- testifylint
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
@@ -71,3 +72,11 @@ 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
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
ARG GO_VERSION=1.22.7
|
||||
ARG XX_VERSION=1.2.1
|
||||
ARG GO_VERSION=1.22.10
|
||||
ARG XX_VERSION=1.6.1
|
||||
ARG GOLANGCI_LINT_VERSION=v1.60.2
|
||||
ARG ADDLICENSE_VERSION=v1.0.0
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
people = [
|
||||
"glours",
|
||||
"jhrotko",
|
||||
"milas",
|
||||
"ndeloof",
|
||||
"nicksieger",
|
||||
@@ -72,6 +73,11 @@
|
||||
Email = "guillaume.tardif@docker.com"
|
||||
GitHub = "gtardif"
|
||||
|
||||
[people.jhrotko]
|
||||
Name = "Joana Hrotko"
|
||||
Email = "joana.hrotko@docker.com"
|
||||
Github = "jhrotko"
|
||||
|
||||
[people.laurazard]
|
||||
Name = "Laura Brehm"
|
||||
Email = "laura.brehm@docker.com"
|
||||
|
||||
5
Makefile
5
Makefile
@@ -116,6 +116,11 @@ cache-clear: ## Clear the builder cache
|
||||
lint: ## run linter(s)
|
||||
$(BUILDX_CMD) bake lint
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
gofumpt --version >/dev/null 2>&1 || go install mvdan.cc/gofumpt@latest
|
||||
gofumpt -w .
|
||||
|
||||
.PHONY: docs
|
||||
docs: ## generate documentation
|
||||
$(eval $@_TMP_OUT := $(shell mktemp -d -t compose-output.XXXXXXXXXX))
|
||||
|
||||
@@ -60,5 +60,4 @@ func TestGetFlags(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ func alphaCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
cmd.AddCommand(
|
||||
vizCommand(p, dockerCli, backend),
|
||||
publishCommand(p, dockerCli, backend),
|
||||
generateCommand(p, backend),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
||||
93
cmd/compose/commit.go
Normal file
93
cmd/compose/commit.go
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type commitOptions struct {
|
||||
*ProjectOptions
|
||||
|
||||
service string
|
||||
reference string
|
||||
|
||||
pause bool
|
||||
comment string
|
||||
author string
|
||||
changes opts.ListOpts
|
||||
|
||||
index int
|
||||
}
|
||||
|
||||
func commitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
options := commitOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "commit [OPTIONS] SERVICE [REPOSITORY[:TAG]]",
|
||||
Short: "Create a new image from a service container's changes",
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
options.service = args[0]
|
||||
if len(args) > 1 {
|
||||
options.reference = args[1]
|
||||
}
|
||||
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runCommit(ctx, dockerCli, backend, options)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.IntVar(&options.index, "index", 0, "index of the container if service has multiple replicas.")
|
||||
|
||||
flags.BoolVarP(&options.pause, "pause", "p", true, "Pause container during commit")
|
||||
flags.StringVarP(&options.comment, "message", "m", "", "Commit message")
|
||||
flags.StringVarP(&options.author, "author", "a", "", `Author (e.g., "John Hannibal Smith <hannibal@a-team.com>")`)
|
||||
options.changes = opts.NewListOpts(nil)
|
||||
flags.VarP(&options.changes, "change", "c", "Apply Dockerfile instruction to the created image")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runCommit(ctx context.Context, dockerCli command.Cli, backend api.Service, options commitOptions) error {
|
||||
projectName, err := options.toProjectName(ctx, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
commitOptions := api.CommitOptions{
|
||||
Service: options.service,
|
||||
Reference: options.reference,
|
||||
Pause: options.pause,
|
||||
Comment: options.comment,
|
||||
Author: options.author,
|
||||
Changes: options.changes,
|
||||
Index: options.index,
|
||||
}
|
||||
|
||||
return backend.Commit(ctx, projectName, commitOptions)
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
@@ -37,6 +38,7 @@ import (
|
||||
dockercli "github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/pkg/kvfile"
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/internal/desktop"
|
||||
"github.com/docker/compose/v2/internal/experimental"
|
||||
@@ -70,6 +72,26 @@ const (
|
||||
ComposeMenu = "COMPOSE_MENU"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
lines, err := kvfile.ParseFromReader(r, lookup)
|
||||
if err != nil {
|
||||
return nil, 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
|
||||
}
|
||||
|
||||
func init() {
|
||||
// compose evaluates env file values for interpolation
|
||||
// `raw` format allows to load env_file with the same parser used by docker run --env-file
|
||||
dotenv.RegisterFormat("raw", rawEnv)
|
||||
}
|
||||
|
||||
type Backend interface {
|
||||
api.Service
|
||||
|
||||
@@ -580,7 +602,7 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
|
||||
}
|
||||
|
||||
c.AddCommand(
|
||||
upCommand(&opts, dockerCli, backend, experiments),
|
||||
upCommand(&opts, dockerCli, backend),
|
||||
downCommand(&opts, dockerCli, backend),
|
||||
startCommand(&opts, dockerCli, backend),
|
||||
restartCommand(&opts, dockerCli, backend),
|
||||
@@ -594,6 +616,8 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
|
||||
removeCommand(&opts, dockerCli, backend),
|
||||
execCommand(&opts, dockerCli, backend),
|
||||
attachCommand(&opts, dockerCli, backend),
|
||||
exportCommand(&opts, dockerCli, backend),
|
||||
commitCommand(&opts, dockerCli, backend),
|
||||
pauseCommand(&opts, dockerCli, backend),
|
||||
unpauseCommand(&opts, dockerCli, backend),
|
||||
topCommand(&opts, dockerCli, backend),
|
||||
|
||||
@@ -279,6 +279,22 @@ func formatModel(model map[string]any, format string) (content []byte, err error
|
||||
}
|
||||
|
||||
func runServices(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
|
||||
if opts.noInterpolate {
|
||||
// we can't use ToProject, so the model we render here is only partially resolved
|
||||
data, err := opts.ToModel(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, ok := data["services"]; ok {
|
||||
for serviceName := range data["services"].(map[string]any) {
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), serviceName)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -287,6 +303,7 @@ func runServices(ctx context.Context, dockerCli command.Cli, opts configOptions)
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), serviceName)
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -331,7 +348,6 @@ func runHash(ctx context.Context, dockerCli command.Cli, opts configOptions) err
|
||||
}
|
||||
|
||||
hash, err := compose.ServiceHash(s)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -66,9 +66,7 @@ func copyCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
|
||||
flags := copyCmd.Flags()
|
||||
flags.IntVar(&opts.index, "index", 0, "Index of the container if service has multiple replicas")
|
||||
flags.BoolVar(&opts.all, "all", false, "Copy to all the containers of the service")
|
||||
flags.MarkHidden("all") //nolint:errcheck
|
||||
flags.MarkDeprecated("all", "By default all the containers of the service will get the source file/directory to be copied") //nolint:errcheck
|
||||
flags.BoolVar(&opts.all, "all", false, "Include containers created by the run command")
|
||||
flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH")
|
||||
flags.BoolVarP(&opts.copyUIDGID, "archive", "a", false, "Archive mode (copy all uid/gid information)")
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ type createOptions struct {
|
||||
timeout int
|
||||
quietPull bool
|
||||
scale []string
|
||||
AssumeYes bool
|
||||
}
|
||||
|
||||
func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
@@ -80,6 +81,7 @@ 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`)
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -107,6 +109,7 @@ func runCreate(ctx context.Context, _ command.Cli, backend api.Service, createOp
|
||||
Inherit: !createOpts.noInherit,
|
||||
Timeout: createOpts.GetTimeout(),
|
||||
QuietPull: createOpts.quietPull,
|
||||
AssumeYes: createOpts.AssumeYes,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -195,7 +198,9 @@ func applyScaleOpts(project *types.Project, opts []string) error {
|
||||
}
|
||||
|
||||
func (opts createOptions) isPullPolicyValid() bool {
|
||||
pullPolicies := []string{types.PullPolicyAlways, types.PullPolicyNever, types.PullPolicyBuild,
|
||||
types.PullPolicyMissing, types.PullPolicyIfNotPresent}
|
||||
pullPolicies := []string{
|
||||
types.PullPolicyAlways, types.PullPolicyNever, types.PullPolicyBuild,
|
||||
types.PullPolicyMissing, types.PullPolicyIfNotPresent,
|
||||
}
|
||||
return slices.Contains(pullPolicies, opts.Pull)
|
||||
}
|
||||
|
||||
74
cmd/compose/export.go
Normal file
74
cmd/compose/export.go
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
type exportOptions struct {
|
||||
*ProjectOptions
|
||||
|
||||
service string
|
||||
output string
|
||||
index int
|
||||
}
|
||||
|
||||
func exportCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
options := exportOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "export [OPTIONS] SERVICE",
|
||||
Short: "Export a service container's filesystem as a tar archive",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
options.service = args[0]
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runExport(ctx, dockerCli, backend, options)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.IntVar(&options.index, "index", 0, "index of the container if service has multiple replicas.")
|
||||
flags.StringVarP(&options.output, "output", "o", "", "Write to a file, instead of STDOUT")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runExport(ctx context.Context, dockerCli command.Cli, backend api.Service, options exportOptions) error {
|
||||
projectName, err := options.toProjectName(ctx, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
exportOptions := api.ExportOptions{
|
||||
Service: options.service,
|
||||
Index: options.index,
|
||||
Output: options.output,
|
||||
}
|
||||
|
||||
return backend.Export(ctx, projectName, exportOptions)
|
||||
}
|
||||
82
cmd/compose/generate.go
Normal file
82
cmd/compose/generate.go
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
Copyright 2023 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type generateOptions struct {
|
||||
*ProjectOptions
|
||||
Format string
|
||||
}
|
||||
|
||||
func generateCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := generateOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "generate [OPTIONS] [CONTAINERS...]",
|
||||
Short: "EXPERIMENTAL - Generate a Compose file from existing containers",
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runGenerate(ctx, backend, opts, args)
|
||||
}),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&opts.ProjectName, "name", "", "Project name to set in the Compose file")
|
||||
cmd.Flags().StringVar(&opts.ProjectDir, "project-dir", "", "Directory to use for the project")
|
||||
cmd.Flags().StringVar(&opts.Format, "format", "yaml", "Format the output. Values: [yaml | json]")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runGenerate(ctx context.Context, backend api.Service, opts generateOptions, containers []string) error {
|
||||
_, _ = fmt.Fprintln(os.Stderr, "generate command is EXPERIMENTAL")
|
||||
if len(containers) == 0 {
|
||||
return fmt.Errorf("at least one container must be specified")
|
||||
}
|
||||
project, err := backend.Generate(ctx, api.GenerateOptions{
|
||||
Containers: containers,
|
||||
ProjectName: opts.ProjectName,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var content []byte
|
||||
switch opts.Format {
|
||||
case "json":
|
||||
content, err = project.MarshalJSON()
|
||||
case "yaml":
|
||||
content, err = project.MarshalYAML()
|
||||
default:
|
||||
return fmt.Errorf("unsupported format %q", opts.Format)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(content))
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
@@ -74,13 +75,13 @@ func TestPsTable(t *testing.T) {
|
||||
cli.EXPECT().Out().Return(stdout).AnyTimes()
|
||||
cli.EXPECT().ConfigFile().Return(&configfile.ConfigFile{}).AnyTimes()
|
||||
err = runPs(ctx, cli, backend, nil, opts)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = f.Seek(0, 0)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
output, err := os.ReadFile(out)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, string(output), "8080/tcp, 8443/tcp")
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "publish [OPTIONS] [REPOSITORY]",
|
||||
Use: "publish [OPTIONS] REPOSITORY[:TAG]",
|
||||
Short: "Publish compose application",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPublish(ctx, dockerCli, backend, opts, args[0])
|
||||
|
||||
@@ -63,6 +63,7 @@ type runOptions struct {
|
||||
name string
|
||||
noDeps bool
|
||||
ignoreOrphans bool
|
||||
removeOrphans bool
|
||||
quietPull bool
|
||||
}
|
||||
|
||||
@@ -153,6 +154,7 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
options.noTty = !options.tty
|
||||
}
|
||||
}
|
||||
createOpts.pullChanged = cmd.Flags().Changed("pull")
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
@@ -187,9 +189,10 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
flags.StringArrayVarP(&options.publish, "publish", "p", []string{}, "Publish a container's port(s) to the host")
|
||||
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.BoolVar(&options.quietPull, "quiet-pull", false, "Pull without printing progress information")
|
||||
flags.BoolVar(&createOpts.Build, "build", false, "Build image before starting container")
|
||||
flags.BoolVar(&createOpts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file")
|
||||
flags.BoolVar(&options.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file")
|
||||
|
||||
cmd.Flags().BoolVarP(&options.interactive, "interactive", "i", true, "Keep STDIN open even if not attached")
|
||||
cmd.Flags().BoolVarP(&options.tty, "tty", "t", true, "Allocate a pseudo-TTY")
|
||||
@@ -314,6 +317,7 @@ func startDependencies(ctx context.Context, backend api.Service, project types.P
|
||||
err := backend.Create(ctx, &project, api.CreateOptions{
|
||||
Build: buildOpts,
|
||||
IgnoreOrphans: options.ignoreOrphans,
|
||||
RemoveOrphans: options.removeOrphans,
|
||||
QuietPull: options.quietPull,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -92,7 +92,6 @@ func parseServicesReplicasArgs(args []string) (map[string]int, error) {
|
||||
return nil, fmt.Errorf("invalid scale specifier: %s", arg)
|
||||
}
|
||||
intValue, err := strconv.Atoi(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid scale specifier: can't parse replica value as int: %v", arg)
|
||||
}
|
||||
|
||||
@@ -26,11 +26,10 @@ import (
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/internal/experimental"
|
||||
xprogress "github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
ui "github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
@@ -81,13 +80,19 @@ func (opts upOptions) apply(project *types.Project, services []string) (*types.P
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func (opts *upOptions) validateNavigationMenu(dockerCli command.Cli, experimentals *experimental.State) {
|
||||
func (opts *upOptions) validateNavigationMenu(dockerCli command.Cli) {
|
||||
if !dockerCli.Out().IsTerminal() {
|
||||
opts.navigationMenu = false
|
||||
return
|
||||
}
|
||||
// If --menu flag was not set
|
||||
if !opts.navigationMenuChanged {
|
||||
opts.navigationMenu = SetUnchangedOption(ComposeMenu, experimentals.NavBar())
|
||||
if envVar, ok := os.LookupEnv(ComposeMenu); ok {
|
||||
opts.navigationMenu = utils.StringToBool(envVar)
|
||||
return
|
||||
}
|
||||
// ...and COMPOSE_MENU env var is not defined we want the default value to be true
|
||||
opts.navigationMenu = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +107,7 @@ func (opts upOptions) OnExit() api.Cascade {
|
||||
}
|
||||
}
|
||||
|
||||
func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service, experiments *experimental.State) *cobra.Command {
|
||||
func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
up := upOptions{}
|
||||
create := createOptions{}
|
||||
build := buildOptions{ProjectOptions: p}
|
||||
@@ -127,7 +132,7 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service, ex
|
||||
return errors.New("cannot combine --attach and --attach-dependencies")
|
||||
}
|
||||
|
||||
up.validateNavigationMenu(dockerCli, experiments)
|
||||
up.validateNavigationMenu(dockerCli)
|
||||
|
||||
if !p.All && len(project.Services) == 0 {
|
||||
return fmt.Errorf("no service selected")
|
||||
@@ -140,6 +145,7 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service, ex
|
||||
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")
|
||||
@@ -162,7 +168,7 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service, ex
|
||||
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")
|
||||
flags.BoolVar(&up.wait, "wait", false, "Wait for services to be running|healthy. Implies detached mode.")
|
||||
flags.IntVar(&up.waitTimeout, "wait-timeout", 0, "Maximum duration to wait for the project to be running|healthy")
|
||||
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.")
|
||||
|
||||
@@ -250,6 +256,7 @@ func runUp(
|
||||
Inherit: !createOptions.noInherit,
|
||||
Timeout: createOptions.GetTimeout(),
|
||||
QuietPull: createOptions.quietPull,
|
||||
AssumeYes: createOptions.AssumeYes,
|
||||
}
|
||||
|
||||
if upOptions.noStart {
|
||||
|
||||
@@ -47,5 +47,4 @@ func TestApplyScaleOpt(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, *bar.Scale, 3)
|
||||
assert.Equal(t, *bar.Deploy.Replicas, 3)
|
||||
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPreferredIndentationStr(t *testing.T) {
|
||||
@@ -83,10 +83,12 @@ func TestPreferredIndentationStr(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := preferredIndentationStr(tt.args.size, tt.args.useSpace)
|
||||
if tt.wantErr && assert.NotNilf(t, err, fmt.Sprintf("preferredIndentationStr(%v, %v)", tt.args.size, tt.args.useSpace)) {
|
||||
return
|
||||
if tt.wantErr {
|
||||
require.Errorf(t, err, "preferredIndentationStr(%v, %v)", tt.args.size, tt.args.useSpace)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equalf(t, tt.want, got, "preferredIndentationStr(%v, %v)", tt.args.size, tt.args.useSpace)
|
||||
}
|
||||
assert.Equalf(t, tt.want, got, "preferredIndentationStr(%v, %v)", tt.args.size, tt.args.useSpace)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,42 +27,49 @@ var disableAnsi bool
|
||||
func ansi(code string) string {
|
||||
return fmt.Sprintf("\033%s", code)
|
||||
}
|
||||
|
||||
func SaveCursor() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi("7"))
|
||||
}
|
||||
|
||||
func RestoreCursor() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi("8"))
|
||||
}
|
||||
|
||||
func HideCursor() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi("[?25l"))
|
||||
}
|
||||
|
||||
func ShowCursor() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi("[?25h"))
|
||||
}
|
||||
|
||||
func MoveCursor(y, x int) {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi(fmt.Sprintf("[%d;%dH", y, x)))
|
||||
}
|
||||
|
||||
func MoveCursorX(pos int) {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi(fmt.Sprintf("[%dG", pos)))
|
||||
}
|
||||
|
||||
func ClearLine() {
|
||||
if disableAnsi {
|
||||
return
|
||||
@@ -70,6 +77,7 @@ func ClearLine() {
|
||||
// Does not move cursor from its current position
|
||||
fmt.Print(ansi("[2K"))
|
||||
}
|
||||
|
||||
func MoveCursorUp(lines int) {
|
||||
if disableAnsi {
|
||||
return
|
||||
@@ -77,6 +85,7 @@ func MoveCursorUp(lines int) {
|
||||
// Does not add new lines
|
||||
fmt.Print(ansi(fmt.Sprintf("[%dA", lines)))
|
||||
}
|
||||
|
||||
func MoveCursorDown(lines int) {
|
||||
if disableAnsi {
|
||||
return
|
||||
@@ -84,10 +93,12 @@ func MoveCursorDown(lines int) {
|
||||
// Does not add new lines
|
||||
fmt.Print(ansi(fmt.Sprintf("[%dB", lines)))
|
||||
}
|
||||
|
||||
func NewLine() {
|
||||
// Like \n
|
||||
fmt.Print("\012")
|
||||
}
|
||||
|
||||
func lenAnsi(s string) int {
|
||||
// len has into consideration ansi codes, if we want
|
||||
// the len of the actual len(string) we need to strip
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/cli/cli/command"
|
||||
)
|
||||
|
||||
var names = []string{
|
||||
@@ -59,7 +59,7 @@ const (
|
||||
)
|
||||
|
||||
// SetANSIMode configure formatter for colored output on ANSI-compliant console
|
||||
func SetANSIMode(streams api.Streams, ansi string) {
|
||||
func SetANSIMode(streams command.Streams, ansi string) {
|
||||
if !useAnsi(streams, ansi) {
|
||||
nextColor = func() colorFunc {
|
||||
return monochrome
|
||||
@@ -68,7 +68,7 @@ func SetANSIMode(streams api.Streams, ansi string) {
|
||||
}
|
||||
}
|
||||
|
||||
func useAnsi(streams api.Streams, ansi string) bool {
|
||||
func useAnsi(streams command.Streams, ansi string) bool {
|
||||
switch ansi {
|
||||
case Always:
|
||||
return true
|
||||
@@ -104,10 +104,12 @@ func makeColorFunc(code string) colorFunc {
|
||||
}
|
||||
}
|
||||
|
||||
var nextColor = rainbowColor
|
||||
var rainbow []colorFunc
|
||||
var currentIndex = 0
|
||||
var mutex sync.Mutex
|
||||
var (
|
||||
nextColor = rainbowColor
|
||||
rainbow []colorFunc
|
||||
currentIndex = 0
|
||||
mutex sync.Mutex
|
||||
)
|
||||
|
||||
func rainbowColor() colorFunc {
|
||||
mutex.Lock()
|
||||
|
||||
@@ -61,21 +61,32 @@ func (l *logConsumer) Register(name string) {
|
||||
}
|
||||
|
||||
func (l *logConsumer) register(name string) *presenter {
|
||||
cf := monochrome
|
||||
if l.color {
|
||||
if name == api.WatchLogger {
|
||||
cf = makeColorFunc("92")
|
||||
} else {
|
||||
cf = nextColor()
|
||||
var p *presenter
|
||||
root, _, found := strings.Cut(name, " ")
|
||||
if found {
|
||||
parent := l.getPresenter(root)
|
||||
p = &presenter{
|
||||
colors: parent.colors,
|
||||
name: name,
|
||||
prefix: parent.prefix,
|
||||
}
|
||||
} else {
|
||||
cf := monochrome
|
||||
if l.color {
|
||||
if name == api.WatchLogger {
|
||||
cf = makeColorFunc("92")
|
||||
} else {
|
||||
cf = nextColor()
|
||||
}
|
||||
}
|
||||
p = &presenter{
|
||||
colors: cf,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
p := &presenter{
|
||||
colors: cf,
|
||||
name: name,
|
||||
}
|
||||
l.presenters.Store(name, p)
|
||||
l.computeWidth()
|
||||
if l.prefix {
|
||||
l.computeWidth()
|
||||
l.presenters.Range(func(key, value interface{}) bool {
|
||||
p := value.(*presenter)
|
||||
p.setPrefix(l.width)
|
||||
|
||||
@@ -106,15 +106,16 @@ type LogKeyboard struct {
|
||||
Watch KeyboardWatch
|
||||
IsDockerDesktopActive bool
|
||||
IsWatchConfigured bool
|
||||
IsDDComposeUIActive bool
|
||||
logLevel KEYBOARD_LOG_LEVEL
|
||||
signalChannel chan<- os.Signal
|
||||
}
|
||||
|
||||
var KeyboardManager *LogKeyboard
|
||||
var eg multierror.Group
|
||||
var (
|
||||
KeyboardManager *LogKeyboard
|
||||
eg multierror.Group
|
||||
)
|
||||
|
||||
func NewKeyboardManager(ctx context.Context, isDockerDesktopActive, isWatchConfigured, isDockerDesktopConfigActive bool,
|
||||
func NewKeyboardManager(ctx context.Context, isDockerDesktopActive, isWatchConfigured bool,
|
||||
sc chan<- os.Signal,
|
||||
watchFn func(ctx context.Context,
|
||||
doneCh chan bool,
|
||||
@@ -126,7 +127,6 @@ func NewKeyboardManager(ctx context.Context, isDockerDesktopActive, isWatchConfi
|
||||
km := LogKeyboard{}
|
||||
km.IsDockerDesktopActive = isDockerDesktopActive
|
||||
km.IsWatchConfigured = isWatchConfigured
|
||||
km.IsDDComposeUIActive = isDockerDesktopConfigActive
|
||||
km.logLevel = INFO
|
||||
|
||||
km.Watch.Watching = false
|
||||
@@ -200,14 +200,15 @@ func (lk *LogKeyboard) navigationMenu() string {
|
||||
if openDDInfo != "" {
|
||||
openDDUI = navColor(" ")
|
||||
}
|
||||
if lk.IsDDComposeUIActive {
|
||||
if lk.IsDockerDesktopActive {
|
||||
openDDUI = openDDUI + shortcutKeyColor("o") + navColor(" View Config")
|
||||
}
|
||||
|
||||
var watchInfo string
|
||||
if openDDInfo != "" || openDDUI != "" {
|
||||
watchInfo = navColor(" ")
|
||||
}
|
||||
var isEnabled = " Enable"
|
||||
isEnabled := " Enable"
|
||||
if lk.Watch.Watching {
|
||||
isEnabled = " Disable"
|
||||
}
|
||||
@@ -246,7 +247,7 @@ func (lk *LogKeyboard) openDockerDesktop(ctx context.Context, project *types.Pro
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) openDDComposeUI(ctx context.Context, project *types.Project) {
|
||||
if !lk.IsDDComposeUIActive {
|
||||
if !lk.IsDockerDesktopActive {
|
||||
return
|
||||
}
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/gui/composeview", tracing.SpanOptions{},
|
||||
@@ -261,6 +262,7 @@ func (lk *LogKeyboard) openDDComposeUI(ctx context.Context, project *types.Proje
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -288,19 +290,7 @@ 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 {
|
||||
if lk.IsDDComposeUIActive {
|
||||
// we try to open watch docs
|
||||
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
|
||||
|
||||
}
|
||||
lk.Watch.switchWatching()
|
||||
if !lk.Watch.isWatching() {
|
||||
@@ -317,10 +307,15 @@ func (lk *LogKeyboard) StartWatch(ctx context.Context, doneCh chan bool, project
|
||||
lk.Watch.newContext(ctx)
|
||||
buildOpts := *options.Create.Build
|
||||
buildOpts.Quiet = true
|
||||
return lk.Watch.WatchFn(lk.Watch.Ctx, doneCh, project, options.Start.Services, api.WatchOptions{
|
||||
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
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -330,6 +325,20 @@ func (lk *LogKeyboard) HandleKeyEvents(event keyboard.KeyEvent, ctx context.Cont
|
||||
case 'v':
|
||||
lk.openDockerDesktop(ctx, project)
|
||||
case 'w':
|
||||
if !lk.IsWatchConfigured {
|
||||
// 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
|
||||
}
|
||||
lk.StartWatch(ctx, doneCh, project, options)
|
||||
case 'o':
|
||||
lk.openDDComposeUI(ctx, project)
|
||||
|
||||
@@ -13,12 +13,14 @@ Define and run multi-container applications with Docker
|
||||
|:--------------------------------|:----------------------------------------------------------------------------------------|
|
||||
| [`attach`](compose_attach.md) | Attach local standard input, output, and error streams to a service's running container |
|
||||
| [`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 |
|
||||
| [`cp`](compose_cp.md) | Copy files/folders between a service container and the local filesystem |
|
||||
| [`create`](compose_create.md) | Creates containers for a service |
|
||||
| [`down`](compose_down.md) | Stop and remove containers, networks |
|
||||
| [`events`](compose_events.md) | Receive real time events from containers |
|
||||
| [`exec`](compose_exec.md) | Execute a command in a running container |
|
||||
| [`export`](compose_export.md) | Export a service container's filesystem as a tar archive |
|
||||
| [`images`](compose_images.md) | List images used by the created containers |
|
||||
| [`kill`](compose_kill.md) | Force stop service containers |
|
||||
| [`logs`](compose_logs.md) | View output from containers |
|
||||
|
||||
17
docs/reference/compose_alpha_generate.md
Normal file
17
docs/reference/compose_alpha_generate.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# docker compose alpha generate
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
EXPERIMENTAL - Generate a Compose file from existing containers
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------|:---------|:--------|:------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--format` | `string` | `yaml` | Format the output. Values: [yaml \| json] |
|
||||
| `--name` | `string` | | Project name to set in the Compose file |
|
||||
| `--project-dir` | `string` | | Directory to use for the project |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
19
docs/reference/compose_commit.md
Normal file
19
docs/reference/compose_commit.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# docker compose commit
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Create a new image from a service container's changes
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------------|:---------|:--------|:-----------------------------------------------------------|
|
||||
| `-a`, `--author` | `string` | | Author (e.g., "John Hannibal Smith <hannibal@a-team.com>") |
|
||||
| `-c`, `--change` | `list` | | Apply Dockerfile instruction to the created image |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--index` | `int` | `0` | index of the container if service has multiple replicas. |
|
||||
| `-m`, `--message` | `string` | | Commit message |
|
||||
| `-p`, `--pause` | `bool` | `true` | Pause container during commit |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -7,6 +7,7 @@ Copy files/folders between a service container and the local filesystem
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------------|:-------|:--------|:--------------------------------------------------------|
|
||||
| `--all` | `bool` | | Include containers created by the run command |
|
||||
| `-a`, `--archive` | `bool` | | Archive mode (copy all uid/gid information) |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `-L`, `--follow-link` | `bool` | | Always follow symbol link in SRC_PATH |
|
||||
|
||||
@@ -16,6 +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 |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
16
docs/reference/compose_export.md
Normal file
16
docs/reference/compose_export.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# docker compose export
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Export a service container's filesystem as a tar archive
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-----------------|:---------|:--------|:---------------------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--index` | `int` | `0` | index of the container if service has multiple replicas. |
|
||||
| `-o`, `--output` | `string` | | Write to a file, instead of STDOUT |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -57,29 +57,30 @@ specified in the service configuration.
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------------------|:--------------|:--------|:---------------------------------------------------------------------------------|
|
||||
| `--build` | `bool` | | Build image before starting container |
|
||||
| `--cap-add` | `list` | | Add Linux capabilities |
|
||||
| `--cap-drop` | `list` | | Drop Linux capabilities |
|
||||
| `-d`, `--detach` | `bool` | | Run container in background and print container ID |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--entrypoint` | `string` | | Override the entrypoint of the image |
|
||||
| `-e`, `--env` | `stringArray` | | Set environment variables |
|
||||
| `-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 |
|
||||
| `-T`, `--no-TTY` | `bool` | `true` | Disable pseudo-TTY allocation (default: auto-detected) |
|
||||
| `--no-deps` | `bool` | | Don't start linked services |
|
||||
| `-p`, `--publish` | `stringArray` | | Publish a container's port(s) to the host |
|
||||
| `--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 |
|
||||
| `-P`, `--service-ports` | `bool` | | Run command with all service's ports enabled and mapped to the host |
|
||||
| `--use-aliases` | `bool` | | Use the service's network useAliases in the network(s) the container connects to |
|
||||
| `-u`, `--user` | `string` | | Run as specified username or uid |
|
||||
| `-v`, `--volume` | `stringArray` | | Bind mount a volume |
|
||||
| `-w`, `--workdir` | `string` | | Working directory inside the container |
|
||||
| Name | Type | Default | Description |
|
||||
|:------------------------|:--------------|:---------|:---------------------------------------------------------------------------------|
|
||||
| `--build` | `bool` | | Build image before starting container |
|
||||
| `--cap-add` | `list` | | Add Linux capabilities |
|
||||
| `--cap-drop` | `list` | | Drop Linux capabilities |
|
||||
| `-d`, `--detach` | `bool` | | Run container in background and print container ID |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--entrypoint` | `string` | | Override the entrypoint of the image |
|
||||
| `-e`, `--env` | `stringArray` | | Set environment variables |
|
||||
| `-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 |
|
||||
| `-T`, `--no-TTY` | `bool` | `true` | Disable pseudo-TTY allocation (default: auto-detected) |
|
||||
| `--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") |
|
||||
| `--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 |
|
||||
| `-P`, `--service-ports` | `bool` | | Run command with all service's ports enabled and mapped to the host |
|
||||
| `--use-aliases` | `bool` | | Use the service's network useAliases in the network(s) the container connects to |
|
||||
| `-u`, `--user` | `string` | | Run as specified username or uid |
|
||||
| `-v`, `--volume` | `stringArray` | | Bind mount a volume |
|
||||
| `-w`, `--workdir` | `string` | | Working directory inside the container |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -51,8 +51,9 @@ If the process is interrupted using `SIGINT` (ctrl + C) or `SIGTERM`, the contai
|
||||
| `-t`, `--timeout` | `int` | `0` | Use this timeout in seconds for container shutdown when attached or when containers are already running |
|
||||
| `--timestamps` | `bool` | | Show timestamps |
|
||||
| `--wait` | `bool` | | Wait for services to be running\|healthy. Implies detached mode. |
|
||||
| `--wait-timeout` | `int` | `0` | Maximum duration to wait for the project to be running\|healthy |
|
||||
| `--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 |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -7,12 +7,14 @@ plink: docker.yaml
|
||||
cname:
|
||||
- docker compose attach
|
||||
- docker compose build
|
||||
- docker compose commit
|
||||
- docker compose config
|
||||
- docker compose cp
|
||||
- docker compose create
|
||||
- docker compose down
|
||||
- docker compose events
|
||||
- docker compose exec
|
||||
- docker compose export
|
||||
- docker compose images
|
||||
- docker compose kill
|
||||
- docker compose logs
|
||||
@@ -38,12 +40,14 @@ cname:
|
||||
clink:
|
||||
- docker_compose_attach.yaml
|
||||
- docker_compose_build.yaml
|
||||
- docker_compose_commit.yaml
|
||||
- docker_compose_config.yaml
|
||||
- docker_compose_cp.yaml
|
||||
- docker_compose_create.yaml
|
||||
- docker_compose_down.yaml
|
||||
- docker_compose_events.yaml
|
||||
- docker_compose_exec.yaml
|
||||
- docker_compose_export.yaml
|
||||
- docker_compose_images.yaml
|
||||
- docker_compose_kill.yaml
|
||||
- docker_compose_logs.yaml
|
||||
|
||||
@@ -4,9 +4,11 @@ long: Experimental commands
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
cname:
|
||||
- docker compose alpha generate
|
||||
- docker compose alpha publish
|
||||
- docker compose alpha viz
|
||||
clink:
|
||||
- docker_compose_alpha_generate.yaml
|
||||
- docker_compose_alpha_publish.yaml
|
||||
- docker_compose_alpha_viz.yaml
|
||||
inherited_options:
|
||||
|
||||
53
docs/reference/docker_compose_alpha_generate.yaml
Normal file
53
docs/reference/docker_compose_alpha_generate.yaml
Normal file
@@ -0,0 +1,53 @@
|
||||
command: docker compose alpha generate
|
||||
short: EXPERIMENTAL - Generate a Compose file from existing containers
|
||||
long: EXPERIMENTAL - Generate a Compose file from existing containers
|
||||
usage: docker compose alpha generate [OPTIONS] [CONTAINERS...]
|
||||
pname: docker compose alpha
|
||||
plink: docker_compose_alpha.yaml
|
||||
options:
|
||||
- option: format
|
||||
value_type: string
|
||||
default_value: yaml
|
||||
description: 'Format the output. Values: [yaml | json]'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: name
|
||||
value_type: string
|
||||
description: Project name to set in the Compose file
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: project-dir
|
||||
value_type: string
|
||||
description: Directory to use for the project
|
||||
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: true
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
command: docker compose alpha publish
|
||||
short: Publish compose application
|
||||
long: Publish compose application
|
||||
usage: docker compose alpha publish [OPTIONS] [REPOSITORY]
|
||||
usage: docker compose alpha publish [OPTIONS] REPOSITORY[:TAG]
|
||||
pname: docker compose alpha
|
||||
plink: docker_compose_alpha.yaml
|
||||
options:
|
||||
|
||||
76
docs/reference/docker_compose_commit.yaml
Normal file
76
docs/reference/docker_compose_commit.yaml
Normal file
@@ -0,0 +1,76 @@
|
||||
command: docker compose commit
|
||||
short: Create a new image from a service container's changes
|
||||
long: Create a new image from a service container's changes
|
||||
usage: docker compose commit [OPTIONS] SERVICE [REPOSITORY[:TAG]]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
- option: author
|
||||
shorthand: a
|
||||
value_type: string
|
||||
description: Author (e.g., "John Hannibal Smith <hannibal@a-team.com>")
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: change
|
||||
shorthand: c
|
||||
value_type: list
|
||||
description: Apply Dockerfile instruction to the created image
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: index
|
||||
value_type: int
|
||||
default_value: "0"
|
||||
description: index of the container if service has multiple replicas.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: message
|
||||
shorthand: m
|
||||
value_type: string
|
||||
description: Commit message
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: pause
|
||||
shorthand: p
|
||||
value_type: bool
|
||||
default_value: "true"
|
||||
description: Pause container during commit
|
||||
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
|
||||
|
||||
@@ -10,9 +10,9 @@ options:
|
||||
- option: all
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Copy to all the containers of the service
|
||||
deprecated: true
|
||||
hidden: true
|
||||
description: Include containers created by the run command
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -88,6 +88,17 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: "y"
|
||||
shorthand: "y"
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Assume "yes" as answer to all prompts and run non-interactively
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
|
||||
45
docs/reference/docker_compose_export.yaml
Normal file
45
docs/reference/docker_compose_export.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
command: docker compose export
|
||||
short: Export a service container's filesystem as a tar archive
|
||||
long: Export a service container's filesystem as a tar archive
|
||||
usage: docker compose export [OPTIONS] SERVICE
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
- option: index
|
||||
value_type: int
|
||||
default_value: "0"
|
||||
description: index of the container if service has multiple replicas.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: output
|
||||
shorthand: o
|
||||
value_type: string
|
||||
description: Write to a file, instead of STDOUT
|
||||
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
|
||||
|
||||
@@ -180,6 +180,16 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: pull
|
||||
value_type: string
|
||||
default_value: policy
|
||||
description: Pull image before running ("always"|"missing"|"never")
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: quiet-pull
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
|
||||
@@ -289,7 +289,8 @@ options:
|
||||
- option: wait-timeout
|
||||
value_type: int
|
||||
default_value: "0"
|
||||
description: Maximum duration to wait for the project to be running|healthy
|
||||
description: |
|
||||
Maximum duration in seconds to wait for the project to be running|healthy
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
@@ -308,6 +309,17 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: "y"
|
||||
shorthand: "y"
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Assume "yes" as answer to all prompts and run non-interactively
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
|
||||
107
go.mod
107
go.mod
@@ -1,21 +1,23 @@
|
||||
module github.com/docker/compose/v2
|
||||
|
||||
go 1.21.0
|
||||
go 1.22.10
|
||||
|
||||
toolchain go1.23.4
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||
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.2.0
|
||||
github.com/containerd/containerd v1.7.22
|
||||
github.com/compose-spec/compose-go/v2 v2.4.7
|
||||
github.com/containerd/containerd v1.7.24
|
||||
github.com/containerd/platforms v0.2.1
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/docker/buildx v0.17.1
|
||||
github.com/docker/cli v27.3.0-rc.1+incompatible
|
||||
github.com/docker/buildx v0.19.2
|
||||
github.com/docker/cli v27.4.0+incompatible
|
||||
github.com/docker/cli-docs-tool v0.8.0
|
||||
github.com/docker/docker v27.3.0-rc.1+incompatible
|
||||
github.com/docker/docker v27.4.0+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
|
||||
@@ -27,7 +29,7 @@ require (
|
||||
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.16.0
|
||||
github.com/moby/buildkit v0.18.1
|
||||
github.com/moby/patternmatcher v0.6.0
|
||||
github.com/moby/term v0.5.0
|
||||
github.com/morikuni/aec v1.0.0
|
||||
@@ -39,24 +41,25 @@ require (
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.9.0
|
||||
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.46.1
|
||||
go.opentelemetry.io/otel v1.21.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0
|
||||
go.opentelemetry.io/otel/metric v1.21.0
|
||||
go.opentelemetry.io/otel/sdk v1.21.0
|
||||
go.opentelemetry.io/otel/trace v1.21.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0
|
||||
go.opentelemetry.io/otel v1.28.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0
|
||||
go.opentelemetry.io/otel/metric v1.28.0
|
||||
go.opentelemetry.io/otel/sdk v1.28.0
|
||||
go.opentelemetry.io/otel/trace v1.28.0
|
||||
go.uber.org/goleak v1.3.0
|
||||
go.uber.org/mock v0.4.0
|
||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/sys v0.25.0
|
||||
google.golang.org/grpc v1.62.0
|
||||
go.uber.org/mock v0.5.0
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
|
||||
golang.org/x/sync v0.10.0
|
||||
golang.org/x/sys v0.28.0
|
||||
google.golang.org/grpc v1.68.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gotest.tools/v3 v3.5.1
|
||||
tags.cncf.io/container-device-interface v0.8.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -78,16 +81,16 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
|
||||
github.com/aws/smithy-go v1.19.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // 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.7.19 // indirect
|
||||
github.com/containerd/continuity v0.4.3 // indirect
|
||||
github.com/containerd/errdefs v0.1.0 // indirect
|
||||
github.com/containerd/continuity v0.4.5 // indirect
|
||||
github.com/containerd/errdefs v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/ttrpc v1.2.5 // indirect
|
||||
github.com/containerd/typeurl/v2 v2.2.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/containerd/typeurl/v2 v2.2.3 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.2 // indirect
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
||||
@@ -95,14 +98,13 @@ require (
|
||||
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/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // indirect
|
||||
github.com/gogo/googleapis v1.4.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
@@ -111,7 +113,7 @@ 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.16.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // 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
|
||||
@@ -120,22 +122,22 @@ require (
|
||||
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.9 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // 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/matttproud/golang_protobuf_extensions v1.0.4 // 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.2.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/symlink v0.2.0 // indirect
|
||||
github.com/moby/sys/user v0.3.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
@@ -144,40 +146,42 @@ require (
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.17.0 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.44.0 // indirect
|
||||
github.com/prometheus/client_golang v1.20.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.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/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect
|
||||
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20240424095704-91a3fc46842c // indirect
|
||||
github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20241121093142-31cf1f437184 // indirect
|
||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 // indirect
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
||||
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // 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
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.21.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/oauth2 v0.22.0 // indirect
|
||||
golang.org/x/term v0.23.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.53.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.28.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.23.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 v0.0.0-20240123012728-ef4313101c80 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
google.golang.org/protobuf v1.35.1 // indirect
|
||||
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
@@ -190,5 +194,4 @@ require (
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
tags.cncf.io/container-device-interface v0.8.0 // indirect
|
||||
)
|
||||
|
||||
233
go.sum
233
go.sum
@@ -1,7 +1,3 @@
|
||||
cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM=
|
||||
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
|
||||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
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-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
|
||||
@@ -18,8 +14,8 @@ github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0
|
||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
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.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ=
|
||||
github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU=
|
||||
github.com/Microsoft/hcsshim v0.12.8 h1:BtDWYlFMcWhorrvSSo2M7z0csPdw6t7no/C3FsSvqiI=
|
||||
github.com/Microsoft/hcsshim v0.12.8/go.mod h1:cibQ4BqhJ32FXDwPdQhKhwrwophnh3FuT4nwQZF907w=
|
||||
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/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
||||
@@ -75,30 +71,29 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembj
|
||||
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
|
||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o=
|
||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e85keuznYcH5rqI438v41pKcBl4ZxQ=
|
||||
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
|
||||
github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ=
|
||||
github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM=
|
||||
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.2.0 h1:VsQosGhuO+H9wh5laiIiAe4TVd73kQ5NWwmNrdm0HRA=
|
||||
github.com/compose-spec/compose-go/v2 v2.2.0/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc=
|
||||
github.com/compose-spec/compose-go/v2 v2.4.7 h1:WNpz5bIbKG+G+w9pfu72B1ZXr+Og9jez8TMEo8ecXPk=
|
||||
github.com/compose-spec/compose-go/v2 v2.4.7/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc=
|
||||
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
|
||||
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
|
||||
github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0=
|
||||
github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0=
|
||||
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 v1.7.22 h1:nZuNnNRA6T6jB975rx2RRNqqH2k6ELYKDZfqTHqwyy0=
|
||||
github.com/containerd/containerd v1.7.22/go.mod h1:e3Jz1rYRUZ2Lt51YrH9Rz0zPyJBOlSvB3ghr2jbVD8g=
|
||||
github.com/containerd/containerd v1.7.24 h1:zxszGrGjrra1yYJW/6rhm9cJ1ZQ8rkKBR48brqsa7nA=
|
||||
github.com/containerd/containerd v1.7.24/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw=
|
||||
github.com/containerd/containerd/api v1.7.19 h1:VWbJL+8Ap4Ju2mx9c9qS1uFSB1OVYr5JJrW2yT5vFoA=
|
||||
github.com/containerd/containerd/api v1.7.19/go.mod h1:fwGavl3LNwAV5ilJ0sbrABL44AQxmNjDRcwheXDb6Ig=
|
||||
github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8=
|
||||
github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
|
||||
github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM=
|
||||
github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0=
|
||||
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 v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4=
|
||||
github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
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=
|
||||
@@ -112,10 +107,11 @@ github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk=
|
||||
github.com/containerd/ttrpc v1.2.5 h1:IFckT1EFQoFBMG4c3sMdT8EP3/aKfumK1msY+Ze4oLU=
|
||||
github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o=
|
||||
github.com/containerd/typeurl/v2 v2.2.0 h1:6NBDbQzr7I5LHgp34xAXYF5DOTQDn05X58lsPEmzLso=
|
||||
github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=
|
||||
github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
|
||||
@@ -126,17 +122,17 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/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.17.1 h1:9ob2jGp4+W9PxWw68GsoNFp+eYFc7eUoRL9VljLCSM4=
|
||||
github.com/docker/buildx v0.17.1/go.mod h1:kJOhOhS47LRvrLFRulFiO5SE6VJf54yYMn7DzjgO5W0=
|
||||
github.com/docker/cli v27.3.0-rc.1+incompatible h1:yc++0aWWMlq4QBYrvuuzYhVHc+Y/MRrWa4T/g/0mVPQ=
|
||||
github.com/docker/cli v27.3.0-rc.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/buildx v0.19.2 h1:2zXzgP2liQKgQ5BiOqMc+wz7hfWgAIMWw5MR6QDG++I=
|
||||
github.com/docker/buildx v0.19.2/go.mod h1:k4WP+XmGRYL0a7l4RZAI2TqpwhuAuSQ5U/rosRgFmAA=
|
||||
github.com/docker/cli v27.4.0+incompatible h1:/nJzWkcI1MDMN+U+px/YXnQWJqnu4J+QKGTfD6ptiTc=
|
||||
github.com/docker/cli v27.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli-docs-tool v0.8.0 h1:YcDWl7rQJC3lJ7WVZRwSs3bc9nka97QLWfyJQli8yJU=
|
||||
github.com/docker/cli-docs-tool v0.8.0/go.mod h1:8TQQ3E7mOXoYUs811LiPdUnAhXrcVsBIrW21a5pUbdk=
|
||||
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 v27.3.0-rc.1+incompatible h1:rqvkkh1Ukw3IYyI0DYbMuLd0r5EvIRC4bAaFg4uC8sU=
|
||||
github.com/docker/docker v27.3.0-rc.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v27.4.0+incompatible h1:I9z7sQ5qyzO0BfAb9IMOawRkAGxhYsidKiTMcm0DU+A=
|
||||
github.com/docker/docker v27.4.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/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
||||
@@ -158,8 +154,6 @@ github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJ
|
||||
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
@@ -173,8 +167,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
|
||||
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.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
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/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=
|
||||
@@ -192,15 +186,11 @@ github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAp
|
||||
github.com/go-viper/mapstructure/v2 v2.0.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/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0=
|
||||
github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4=
|
||||
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-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
|
||||
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -232,8 +222,8 @@ github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWS
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
|
||||
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=
|
||||
@@ -274,8 +264,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.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
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=
|
||||
@@ -287,6 +277,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/magiconair/properties v1.5.3 h1:C8fxWnhYyME3n0klPOhVM7PtYUB3eV1W3DeFmN3j53Y=
|
||||
github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
@@ -305,8 +297,6 @@ github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebG
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
@@ -319,8 +309,8 @@ github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/z
|
||||
github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/buildkit v0.16.0 h1:wOVBj1o5YNVad/txPQNXUXdelm7Hs/i0PUFjzbK0VKE=
|
||||
github.com/moby/buildkit v0.16.0/go.mod h1:Xqx/5GlrqE1yIRORk0NSCVDFpQAU1WjlT6KHYZdisIQ=
|
||||
github.com/moby/buildkit v0.18.1 h1:Iwrz2F/Za2Gjkpwu3aM2LX92AFfJCJe2oNnvGNvh2Rc=
|
||||
github.com/moby/buildkit v0.18.1/go.mod h1:vCR5CX8NGsPTthTg681+9kdmfvkvqJBXEv71GZe5msU=
|
||||
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/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
||||
@@ -329,14 +319,16 @@ github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkV
|
||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
|
||||
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
||||
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=
|
||||
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||
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/symlink v0.2.0 h1:tk1rOM+Ljp0nFmfOIBtlV3rTDlWOwFRhjEeAhZB0nZc=
|
||||
github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs=
|
||||
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/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
@@ -389,24 +381,26 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
|
||||
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
|
||||
github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
|
||||
github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
|
||||
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
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=
|
||||
@@ -417,8 +411,8 @@ github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc h1:zAsgcP8MhzAbhMnB1QQ2
|
||||
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.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
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=
|
||||
@@ -462,14 +456,16 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
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/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/fsutil v0.0.0-20240424095704-91a3fc46842c h1:+6wg/4ORAbnSoGDzg2Q1i3CeMcT/jjhye/ZfnBHy7/M=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20240424095704-91a3fc46842c/go.mod h1:vbbYqJlnswsbJqWUcJN8fKtBhnEgldDrcagTgnBVKKM=
|
||||
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-20241121093142-31cf1f437184 h1:RgyoSI38Y36zjQaszel/0RAcIehAnjA1B0RiUV9SDO4=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20241121093142-31cf1f437184/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/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
|
||||
@@ -490,38 +486,38 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
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.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1 h1:gbhw/u49SS3gkPWiYweQNJGm/uJN5GkI/FrosxSHT7A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1/go.mod h1:GnOaBaFQ2we3b9AGWJpsBa7v1S5RlQzlC3O7dRMxZhM=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
|
||||
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
|
||||
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 h1:jd0+5t/YynESZqsSyPz+7PAFdEop0dlN0+PkyHYo8oI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0/go.mod h1:U707O40ee1FpQGyhvqnzmCJm1Wh6OX6GGBVn0E6Uyyk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 h1:bflGWrfYyuulcdxf14V6n9+CoQcu5SAAdHmDPAJnlps=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0/go.mod h1:qcTO4xHAxZLaLxPd60TdE88rxtItPHgHWqOhOGRr0as=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I=
|
||||
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
|
||||
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.21.0 h1:smhI5oD714d6jHE6Tie36fPx4WDFIg+Y6RfAY4ICcR0=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.21.0/go.mod h1:FJ8RAsoPGv/wYMgBdUJXOm+6pzFY3YdljnXtv1SBE8Q=
|
||||
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
|
||||
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.53.0 h1:IVtyPth4Rs5P8wIf0mP2KVKFNTJ4paX9qQ4Hkh5gFdc=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.53.0/go.mod h1:ImRBLMJv177/pwiLZ7tU7HDGNdBv7rS0HQ99eN/zBl8=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
|
||||
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
||||
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 h1:aLmmtjRke7LPDQ3lvpFz+kNEH43faFhzW7v8BFIEydg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0/go.mod h1:TC1pyCt6G9Sjb4bQpShH+P5R53pO6ZuGnHuuln9xMeE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 h1:j9+03ymgYhPKmeXGk5Zu+cIZOlVzd9Zv7QIiyItjFBU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk=
|
||||
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
|
||||
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
|
||||
go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
|
||||
go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg=
|
||||
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
||||
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
||||
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.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.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
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=
|
||||
@@ -530,15 +526,15 @@ 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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
|
||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
|
||||
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-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
|
||||
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.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
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=
|
||||
@@ -549,10 +545,10 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
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.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
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=
|
||||
@@ -560,8 +556,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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
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=
|
||||
@@ -576,25 +572,26 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-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.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
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.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -602,23 +599,23 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
|
||||
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
|
||||
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 v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ=
|
||||
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
|
||||
google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0=
|
||||
google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
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=
|
||||
|
||||
@@ -27,11 +27,15 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// identify this client in the logs
|
||||
var userAgent = "compose/" + internal.Version
|
||||
|
||||
// Client for integration with Docker Desktop features.
|
||||
type Client struct {
|
||||
apiEndpoint string
|
||||
@@ -76,6 +80,7 @@ func (c *Client) Ping(ctx context.Context) (*PingResponse, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -105,6 +110,7 @@ func (c *Client) FeatureFlags(ctx context.Context) (FeatureFlagResponse, error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -135,6 +141,7 @@ func (c *Client) GetFileSharesConfig(ctx context.Context) (*GetFileSharesConfigR
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -170,6 +177,7 @@ func (c *Client) CreateFileShare(ctx context.Context, r CreateFileShareRequest)
|
||||
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
|
||||
@@ -212,6 +220,7 @@ func (c *Client) ListFileShares(ctx context.Context) ([]FileShareSession, error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -236,6 +245,7 @@ func (c *Client) DeleteFileShare(ctx context.Context, id string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -268,6 +278,7 @@ func (c *Client) StreamFileShares(ctx context.Context) (<-chan EventMessage[[]Fi
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -66,20 +66,3 @@ func (s *State) Load(ctx context.Context, client *desktop.Client) error {
|
||||
s.desktopValues = desktopValues
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) NavBar() bool {
|
||||
return s.determineFeatureState("ComposeNav")
|
||||
}
|
||||
|
||||
func (s *State) ComposeUI() bool {
|
||||
return s.determineFeatureState("ComposeUIView")
|
||||
}
|
||||
|
||||
func (s *State) determineFeatureState(name string) bool {
|
||||
if s == nil || !s.active || s.desktopValues == nil {
|
||||
return false
|
||||
}
|
||||
// TODO(milas): we should add individual environment variable overrides
|
||||
// per-experiment in a generic way here
|
||||
return s.desktopValues[name].Enabled
|
||||
}
|
||||
|
||||
@@ -88,6 +88,10 @@ func PushManifest(
|
||||
layers []Pushable,
|
||||
ociVersion api.OCIVersion,
|
||||
) 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("{}")})
|
||||
}
|
||||
// prepare to push the manifest by pushing the layers
|
||||
layerDescriptors := make([]v1.Descriptor, len(layers))
|
||||
for i := range layers {
|
||||
@@ -179,7 +183,7 @@ func generateManifest(layers []v1.Descriptor, ociCompat api.OCIVersion) ([]Pusha
|
||||
config = v1.DescriptorEmptyJSON
|
||||
artifactType = ComposeProjectArtifactType
|
||||
// N.B. the descriptor has the data embedded in it
|
||||
toPush = append(toPush, Pushable{Descriptor: config, Data: nil})
|
||||
toPush = append(toPush, Pushable{Descriptor: config, Data: make([]byte, len(config.Data))})
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported OCI version: %s", ociCompat)
|
||||
}
|
||||
|
||||
@@ -17,10 +17,8 @@
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/context/store"
|
||||
@@ -63,11 +61,7 @@ func traceClientFromDockerContext(dockerCli command.Cli, otelEnv envMap) (otlptr
|
||||
}
|
||||
}
|
||||
|
||||
dialCtx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
defer cancel()
|
||||
conn, err := grpc.DialContext(
|
||||
dialCtx,
|
||||
cfg.Endpoint,
|
||||
conn, err := grpc.NewClient(cfg.Endpoint,
|
||||
grpc.WithContextDialer(memnet.DialEndpoint),
|
||||
// this dial is restricted to using a local Unix socket / named pipe,
|
||||
// so there is no need for TLS
|
||||
|
||||
@@ -22,19 +22,16 @@ import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
func KeyboardMetrics(ctx context.Context, enabled, isDockerDesktopActive, isWatchConfigured, isDockerDesktopComposeUI bool) {
|
||||
func KeyboardMetrics(ctx context.Context, enabled, isDockerDesktopActive, isWatchConfigured bool) {
|
||||
commandAvailable := []string{}
|
||||
if isDockerDesktopActive {
|
||||
commandAvailable = append(commandAvailable, "gui")
|
||||
commandAvailable = append(commandAvailable, "gui/composeview")
|
||||
}
|
||||
if isWatchConfigured {
|
||||
commandAvailable = append(commandAvailable, "watch")
|
||||
}
|
||||
|
||||
if isDockerDesktopComposeUI {
|
||||
commandAvailable = append(commandAvailable, "gui/composeview")
|
||||
}
|
||||
|
||||
AddAttributeToSpan(ctx,
|
||||
attribute.Bool("navmenu.enabled", enabled),
|
||||
attribute.StringSlice("navmenu.command_available", commandAvailable))
|
||||
|
||||
@@ -16,7 +16,5 @@
|
||||
|
||||
package internal
|
||||
|
||||
var (
|
||||
// Version is the version of the CLI injected in compilation time
|
||||
Version = "dev"
|
||||
)
|
||||
// Version is the version of the CLI injected in compilation time
|
||||
var Version = "dev"
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -90,6 +91,12 @@ type Service interface {
|
||||
Wait(ctx context.Context, projectName string, options WaitOptions) (int64, error)
|
||||
// Scale manages numbers of container instances running per service
|
||||
Scale(ctx context.Context, project *types.Project, options ScaleOptions) error
|
||||
// Export a service container's filesystem as a tar archive
|
||||
Export(ctx context.Context, projectName string, options ExportOptions) error
|
||||
// Create a new image from a service container's changes
|
||||
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)
|
||||
}
|
||||
|
||||
type ScaleOptions struct {
|
||||
@@ -200,6 +207,8 @@ type CreateOptions struct {
|
||||
Timeout *time.Duration
|
||||
// QuietPull makes the pulling process quiet
|
||||
QuietPull bool
|
||||
// AssumeYes assume "yes" as answer to all prompts and run non-interactively
|
||||
AssumeYes bool
|
||||
}
|
||||
|
||||
// StartOptions group options of the Start API
|
||||
@@ -289,6 +298,7 @@ type ConfigOptions struct {
|
||||
type PushOptions struct {
|
||||
Quiet bool
|
||||
IgnoreFailures bool
|
||||
ImageMandatory bool
|
||||
}
|
||||
|
||||
// PullOptions group options of the Pull API
|
||||
@@ -421,7 +431,6 @@ func (e Event) String() string {
|
||||
attr = append(attr, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
return fmt.Sprintf("%s container %s %s (%s)\n", t, e.Status, e.Container, strings.Join(attr, ", "))
|
||||
|
||||
}
|
||||
|
||||
// ListOptions group options of the ls API
|
||||
@@ -553,6 +562,33 @@ type PauseOptions struct {
|
||||
Project *types.Project
|
||||
}
|
||||
|
||||
// ExportOptions group options of the Export API
|
||||
type ExportOptions struct {
|
||||
Service string
|
||||
Index int
|
||||
Output string
|
||||
}
|
||||
|
||||
// CommitOptions group options of the Commit API
|
||||
type CommitOptions struct {
|
||||
Service string
|
||||
Reference string
|
||||
|
||||
Pause bool
|
||||
Comment string
|
||||
Author string
|
||||
Changes opts.ListOpts
|
||||
|
||||
Index int
|
||||
}
|
||||
|
||||
type GenerateOptions struct {
|
||||
// ProjectName to set in the Compose file
|
||||
ProjectName string
|
||||
// Containers passed in the command line to be used as reference for service definition
|
||||
Containers []string
|
||||
}
|
||||
|
||||
const (
|
||||
// STARTING indicates that stack is being deployed
|
||||
STARTING string = "Starting"
|
||||
@@ -628,6 +664,8 @@ const (
|
||||
ContainerEventExit
|
||||
// UserCancel user cancelled compose up, we are stopping containers
|
||||
UserCancel
|
||||
// HookEventLog is a ContainerEvent of type log on stdout by service hook
|
||||
HookEventLog
|
||||
)
|
||||
|
||||
// Separator is used for naming components
|
||||
|
||||
@@ -101,7 +101,8 @@ func (d *DryRunClient) ContainerAttach(ctx context.Context, container string, op
|
||||
}
|
||||
|
||||
func (d *DryRunClient) ContainerCreate(ctx context.Context, config *containerType.Config, hostConfig *containerType.HostConfig,
|
||||
networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) (containerType.CreateResponse, error) {
|
||||
networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string,
|
||||
) (containerType.CreateResponse, error) {
|
||||
d.containers = append(d.containers, moby.Container{
|
||||
ID: containerName,
|
||||
Names: []string{containerName},
|
||||
@@ -229,7 +230,6 @@ func (d *DryRunClient) ImageInspectWithRaw(ctx context.Context, imageName string
|
||||
default:
|
||||
return d.apiClient.ImageInspectWithRaw(ctx, imageName)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (d *DryRunClient) ImagePull(ctx context.Context, ref string, options image.PullOptions) (io.ReadCloser, error) {
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/containerd/platforms"
|
||||
@@ -38,7 +37,6 @@ import (
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/builder/remotecontext/urlutil"
|
||||
bclient "github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/session"
|
||||
"github.com/moby/buildkit/session/auth/authprovider"
|
||||
@@ -64,26 +62,16 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti
|
||||
}, s.stdinfo(), "Building")
|
||||
}
|
||||
|
||||
type serviceToBuild struct {
|
||||
name string
|
||||
service types.ServiceConfig
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (s *composeService) build(ctx context.Context, project *types.Project, options api.BuildOptions, localImages map[string]string) (map[string]string, error) {
|
||||
buildkitEnabled, err := s.dockerCli.BuildKitEnabled()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
imageIDs := map[string]string{}
|
||||
serviceToBeBuild := map[string]serviceToBuild{}
|
||||
serviceToBuild := types.Services{}
|
||||
|
||||
var policy types.DependencyOption = types.IgnoreDependencies
|
||||
if options.Deps {
|
||||
policy = types.IncludeDependencies
|
||||
}
|
||||
err = project.ForEachService(options.Services, func(serviceName string, service *types.ServiceConfig) error {
|
||||
err := project.ForEachService(options.Services, func(serviceName string, service *types.ServiceConfig) error {
|
||||
if service.Build == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -92,14 +80,26 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
if localImagePresent && service.PullPolicy != types.PullPolicyBuild {
|
||||
return nil
|
||||
}
|
||||
serviceToBeBuild[serviceName] = serviceToBuild{name: serviceName, service: *service}
|
||||
serviceToBuild[serviceName] = *service
|
||||
return nil
|
||||
}, policy)
|
||||
if err != nil || len(serviceToBeBuild) == 0 {
|
||||
if err != nil || len(serviceToBuild) == 0 {
|
||||
return imageIDs, err
|
||||
}
|
||||
|
||||
bake, err := buildWithBake(s.dockerCli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if bake {
|
||||
return s.doBuildBake(ctx, project, serviceToBuild, options)
|
||||
}
|
||||
|
||||
// Initialize buildkit nodes
|
||||
buildkitEnabled, err := s.dockerCli.BuildKitEnabled()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
b *builder.Builder
|
||||
nodes []builder.Node
|
||||
@@ -134,7 +134,6 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
fmt.Sprintf("building with %q instance using %s driver", b.Name, b.Driver),
|
||||
fmt.Sprintf("%s:%s", b.Driver, b.Name),
|
||||
))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -152,17 +151,20 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
return -1
|
||||
}
|
||||
err = InDependencyOrder(ctx, project, func(ctx context.Context, name string) error {
|
||||
serviceToBuild, ok := serviceToBeBuild[name]
|
||||
service, ok := serviceToBuild[name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
service := serviceToBuild.service
|
||||
cw := progress.ContextWriter(ctx)
|
||||
serviceName := fmt.Sprintf("Service %s", name)
|
||||
|
||||
if !buildkitEnabled {
|
||||
cw.Event(progress.BuildingEvent(serviceName))
|
||||
id, err := s.doBuildClassic(ctx, project, service, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cw.Event(progress.BuiltEvent(serviceName))
|
||||
builtDigests[getServiceIndex(name)] = id
|
||||
|
||||
if options.Push {
|
||||
@@ -180,10 +182,12 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
return err
|
||||
}
|
||||
|
||||
cw.Event(progress.BuildingEvent(serviceName))
|
||||
digest, err := s.doBuildBuildkit(ctx, name, buildOptions, w, nodes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cw.Event(progress.BuiltEvent(serviceName))
|
||||
builtDigests[getServiceIndex(name)] = digest
|
||||
|
||||
return nil
|
||||
@@ -204,7 +208,8 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
|
||||
for i, imageDigest := range builtDigests {
|
||||
if imageDigest != "" {
|
||||
imageRef := api.GetImageNameOrDefault(project.Services[names[i]], project.Name)
|
||||
service := project.Services[names[i]]
|
||||
imageRef := api.GetImageNameOrDefault(service, project.Name)
|
||||
imageIDs[imageRef] = imageDigest
|
||||
}
|
||||
}
|
||||
@@ -274,7 +279,7 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
|
||||
imageNames = append(imageNames, imgName)
|
||||
}
|
||||
}
|
||||
imgs, err := s.getImages(ctx, imageNames)
|
||||
imgs, err := s.getImageSummaries(ctx, imageNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -327,12 +332,7 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
|
||||
//
|
||||
// Finally, standard proxy variables based on the Docker client configuration are added, but will not overwrite
|
||||
// any values if already present.
|
||||
func resolveAndMergeBuildArgs(
|
||||
dockerCli command.Cli,
|
||||
project *types.Project,
|
||||
service types.ServiceConfig,
|
||||
opts api.BuildOptions,
|
||||
) types.MappingWithEquals {
|
||||
func resolveAndMergeBuildArgs(dockerCli command.Cli, project *types.Project, service types.ServiceConfig, opts api.BuildOptions) types.MappingWithEquals {
|
||||
result := make(types.MappingWithEquals).
|
||||
OverrideBy(service.Build.Args).
|
||||
OverrideBy(opts.Args).
|
||||
@@ -472,16 +472,6 @@ func flatten(in types.MappingWithEquals) types.Mapping {
|
||||
return out
|
||||
}
|
||||
|
||||
func dockerFilePath(ctxName string, dockerfile string) string {
|
||||
if dockerfile == "" {
|
||||
return ""
|
||||
}
|
||||
if urlutil.IsGitURL(ctxName) || filepath.IsAbs(dockerfile) {
|
||||
return dockerfile
|
||||
}
|
||||
return filepath.Join(ctxName, dockerfile)
|
||||
}
|
||||
|
||||
func sshAgentProvider(sshKeys types.SSHConfig) (session.Attachable, error) {
|
||||
sshConfig := make([]sshprovider.AgentConfig, 0, len(sshKeys))
|
||||
for _, sshKey := range sshKeys {
|
||||
|
||||
307
pkg/compose/build_bake.go
Normal file
307
pkg/compose/build_bake.go
Normal file
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
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"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
"github.com/docker/docker/builder/remotecontext/urlutil"
|
||||
"github.com/moby/buildkit/client"
|
||||
"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
|
||||
}
|
||||
bake, err := strconv.ParseBool(b)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !bake {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
enabled, err := dockerCli.BuildKitEnabled()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !enabled {
|
||||
logrus.Warnf("Docker Compose is configured to build using Bake, but buildkit isn't enabled")
|
||||
}
|
||||
|
||||
_, err = manager.GetPlugin("buildx", dockerCli, &cobra.Command{})
|
||||
if err != nil {
|
||||
if manager.IsNotFound(err) {
|
||||
logrus.Warnf("Docker Compose is configured to build using Bake, but buildx isn't installed")
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
|
||||
// We _could_ use bake.* types from github.com/docker/buildx but long term plan is to remove buildx as a dependency
|
||||
type bakeConfig struct {
|
||||
Groups map[string]bakeGroup `json:"group"`
|
||||
Targets map[string]bakeTarget `json:"target"`
|
||||
}
|
||||
|
||||
type bakeGroup struct {
|
||||
Targets []string `json:"targets"`
|
||||
}
|
||||
|
||||
type bakeTarget struct {
|
||||
Context string `json:"context,omitempty"`
|
||||
Dockerfile string `json:"dockerfile,omitempty"`
|
||||
Args map[string]string `json:"args,omitempty"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
CacheFrom []string `json:"cache-from,omitempty"`
|
||||
CacheTo []string `json:"cache-to,omitempty"`
|
||||
Secrets []string `json:"secret,omitempty"`
|
||||
SSH []string `json:"ssh,omitempty"`
|
||||
Platforms []string `json:"platforms,omitempty"`
|
||||
Target string `json:"target,omitempty"`
|
||||
Pull bool `json:"pull,omitempty"`
|
||||
NoCache bool `json:"no-cache,omitempty"`
|
||||
}
|
||||
|
||||
type bakeMetadata map[string]buildStatus
|
||||
|
||||
type buildStatus struct {
|
||||
Digest string `json:"containerimage.digest"`
|
||||
}
|
||||
|
||||
func (s *composeService) doBuildBake(ctx context.Context, project *types.Project, serviceToBeBuild types.Services, options api.BuildOptions) (map[string]string, error) { //nolint:gocyclo
|
||||
cw := progress.ContextWriter(ctx)
|
||||
for name := range serviceToBeBuild {
|
||||
cw.Event(progress.BuildingEvent(name))
|
||||
}
|
||||
|
||||
eg := errgroup.Group{}
|
||||
ch := make(chan *client.SolveStatus)
|
||||
display, err := progressui.NewDisplay(os.Stdout, progressui.DisplayMode(options.Progress))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
eg.Go(func() error {
|
||||
_, err := display.UpdateFrom(ctx, ch)
|
||||
return err
|
||||
})
|
||||
|
||||
cfg := bakeConfig{
|
||||
Groups: map[string]bakeGroup{},
|
||||
Targets: map[string]bakeTarget{},
|
||||
}
|
||||
var group bakeGroup
|
||||
|
||||
for _, service := range serviceToBeBuild {
|
||||
if service.Build == nil {
|
||||
continue
|
||||
}
|
||||
build := *service.Build
|
||||
|
||||
args := types.Mapping{}
|
||||
for k, v := range resolveAndMergeBuildArgs(s.dockerCli, project, service, options) {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
args[k] = *v
|
||||
}
|
||||
|
||||
image := api.GetImageNameOrDefault(service, project.Name)
|
||||
|
||||
cfg.Targets[image] = bakeTarget{
|
||||
Context: build.Context,
|
||||
Dockerfile: dockerFilePath(build.Context, build.Dockerfile),
|
||||
Args: args,
|
||||
Labels: build.Labels,
|
||||
Tags: append(build.Tags, image),
|
||||
|
||||
CacheFrom: build.CacheFrom,
|
||||
// CacheTo: TODO
|
||||
Platforms: build.Platforms,
|
||||
Target: build.Target,
|
||||
Secrets: toBakeSecrets(project, build.Secrets),
|
||||
SSH: toBakeSSH(build.SSH),
|
||||
Pull: options.Pull,
|
||||
NoCache: options.NoCache,
|
||||
}
|
||||
group.Targets = append(group.Targets, image)
|
||||
}
|
||||
|
||||
cfg.Groups["default"] = group
|
||||
|
||||
b, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metadata, err := os.CreateTemp(os.TempDir(), "compose")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buildx, err := manager.GetPlugin("buildx", s.dockerCli, &cobra.Command{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd := exec.CommandContext(ctx, buildx.Path, "bake", "--file", "-", "--progress", "rawjson", "--metadata-file", metadata.Name())
|
||||
// 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())
|
||||
}
|
||||
|
||||
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()...)
|
||||
|
||||
cmd.Stdout = s.stdout()
|
||||
cmd.Stdin = bytes.NewBuffer(b)
|
||||
pipe, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
eg.Go(cmd.Wait)
|
||||
for {
|
||||
decoder := json.NewDecoder(pipe)
|
||||
var s client.SolveStatus
|
||||
err := decoder.Decode(&s)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
// bake displays build details at the end of a build, which isn't a json SolveStatus
|
||||
continue
|
||||
}
|
||||
ch <- &s
|
||||
}
|
||||
close(ch) // stop build progress UI
|
||||
|
||||
err = eg.Wait()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, err = os.ReadFile(metadata.Name())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var md bakeMetadata
|
||||
err = json.Unmarshal(b, &md)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := map[string]string{}
|
||||
for name, m := range md {
|
||||
results[name] = m.Digest
|
||||
cw.Event(progress.BuiltEvent(name))
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func toBakeSSH(ssh types.SSHConfig) []string {
|
||||
var s []string
|
||||
for _, key := range ssh {
|
||||
s = append(s, fmt.Sprintf("%s=%s", key.ID, key.Path))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func toBakeSecrets(project *types.Project, secrets []types.ServiceSecretConfig) []string {
|
||||
var s []string
|
||||
for _, ref := range secrets {
|
||||
def := project.Secrets[ref.Source]
|
||||
switch {
|
||||
case def.Environment != "":
|
||||
s = append(s, fmt.Sprintf("id=%s,type=env,env=%s", ref.Source, def.Environment))
|
||||
case def.File != "":
|
||||
s = append(s, fmt.Sprintf("id=%s,type=file,src=%s", ref.Source, def.File))
|
||||
}
|
||||
}
|
||||
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) {
|
||||
return dockerfile
|
||||
}
|
||||
return filepath.Join(ctxName, dockerfile)
|
||||
}
|
||||
@@ -45,7 +45,7 @@ func (s *composeService) doBuildBuildkit(ctx context.Context, service string, op
|
||||
response, err = build.Build(ctx, nodes,
|
||||
map[string]build.Options{service: opts},
|
||||
dockerutil.NewClient(s.dockerCli),
|
||||
confutil.ConfigDir(s.dockerCli),
|
||||
confutil.NewConfig(s.dockerCli),
|
||||
buildx.WithPrefix(p, service, true))
|
||||
if err != nil {
|
||||
return "", WrapCategorisedComposeError(err, BuildFailure)
|
||||
@@ -70,11 +70,6 @@ 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("build service %s", name),
|
||||
})
|
||||
w.Event(progress.Event{
|
||||
ID: "==>",
|
||||
Status: progress.Done,
|
||||
|
||||
87
pkg/compose/commit.go
Normal file
87
pkg/compose/commit.go
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
containerType "github.com/docker/docker/api/types/container"
|
||||
)
|
||||
|
||||
func (s *composeService) Commit(ctx context.Context, projectName string, options api.CommitOptions) error {
|
||||
return progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
||||
return s.commit(ctx, projectName, options)
|
||||
}, s.stdinfo(), "Committing")
|
||||
}
|
||||
|
||||
func (s *composeService) commit(ctx context.Context, projectName string, options api.CommitOptions) error {
|
||||
projectName = strings.ToLower(projectName)
|
||||
|
||||
container, err := s.getSpecifiedContainer(ctx, projectName, oneOffInclude, false, options.Service, options.Index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clnt := s.dockerCli.Client()
|
||||
|
||||
w := progress.ContextWriter(ctx)
|
||||
|
||||
name := getCanonicalContainerName(container)
|
||||
msg := fmt.Sprintf("Commit %s", name)
|
||||
|
||||
w.Event(progress.Event{
|
||||
ID: name,
|
||||
Text: msg,
|
||||
Status: progress.Working,
|
||||
StatusText: "Committing",
|
||||
})
|
||||
|
||||
if s.dryRun {
|
||||
w.Event(progress.Event{
|
||||
ID: name,
|
||||
Text: msg,
|
||||
Status: progress.Done,
|
||||
StatusText: "Committed",
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
response, err := clnt.ContainerCommit(ctx, container.ID, containerType.CommitOptions{
|
||||
Reference: options.Reference,
|
||||
Comment: options.Comment,
|
||||
Author: options.Author,
|
||||
Changes: options.Changes.GetAll(),
|
||||
Pause: options.Pause,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Event(progress.Event{
|
||||
ID: name,
|
||||
Text: msg,
|
||||
Status: progress.Done,
|
||||
StatusText: fmt.Sprintf("Committed as %s", response.ID),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -176,7 +176,10 @@ func (s *composeService) projectFromName(containers Containers, projectName stri
|
||||
}
|
||||
set := types.Services{}
|
||||
for _, c := range containers {
|
||||
serviceLabel := c.Labels[api.ServiceLabel]
|
||||
serviceLabel, ok := c.Labels[api.ServiceLabel]
|
||||
if !ok {
|
||||
serviceLabel = getCanonicalContainerName(c)
|
||||
}
|
||||
service, ok := set[serviceLabel]
|
||||
if !ok {
|
||||
service = types.ServiceConfig{
|
||||
@@ -184,7 +187,6 @@ func (s *composeService) projectFromName(containers Containers, projectName stri
|
||||
Image: c.Image,
|
||||
Labels: c.Labels,
|
||||
}
|
||||
|
||||
}
|
||||
service.Scale = increment(service.Scale)
|
||||
set[serviceLabel] = service
|
||||
@@ -318,16 +320,8 @@ func (s *composeService) RuntimeVersion(ctx context.Context) (string, error) {
|
||||
runtimeVersion.val = version.APIVersion
|
||||
})
|
||||
return runtimeVersion.val, runtimeVersion.err
|
||||
|
||||
}
|
||||
|
||||
func (s *composeService) isDesktopIntegrationActive() bool {
|
||||
return s.desktopCli != nil
|
||||
}
|
||||
|
||||
func (s *composeService) isDesktopUIEnabled() bool {
|
||||
if !s.isDesktopIntegrationActive() {
|
||||
return false
|
||||
}
|
||||
return s.experiments.ComposeUI()
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -34,6 +35,7 @@ import (
|
||||
"github.com/docker/compose/v2/internal/tracing"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
containerType "github.com/docker/docker/api/types/container"
|
||||
mmount "github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -56,24 +58,26 @@ const (
|
||||
// Cross services dependencies are managed by creating services in expected order and updating `service:xx` reference
|
||||
// when a service has converged, so dependent ones can be managed with resolved containers references.
|
||||
type convergence struct {
|
||||
service *composeService
|
||||
observedState map[string]Containers
|
||||
stateMutex sync.Mutex
|
||||
service *composeService
|
||||
services map[string]Containers
|
||||
networks map[string]string
|
||||
volumes map[string]string
|
||||
stateMutex sync.Mutex
|
||||
}
|
||||
|
||||
func (c *convergence) getObservedState(serviceName string) Containers {
|
||||
c.stateMutex.Lock()
|
||||
defer c.stateMutex.Unlock()
|
||||
return c.observedState[serviceName]
|
||||
return c.services[serviceName]
|
||||
}
|
||||
|
||||
func (c *convergence) setObservedState(serviceName string, containers Containers) {
|
||||
c.stateMutex.Lock()
|
||||
defer c.stateMutex.Unlock()
|
||||
c.observedState[serviceName] = containers
|
||||
c.services[serviceName] = containers
|
||||
}
|
||||
|
||||
func newConvergence(services []string, state Containers, s *composeService) *convergence {
|
||||
func newConvergence(services []string, state Containers, networks map[string]string, volumes map[string]string, s *composeService) *convergence {
|
||||
observedState := map[string]Containers{}
|
||||
for _, s := range services {
|
||||
observedState[s] = Containers{}
|
||||
@@ -83,8 +87,10 @@ func newConvergence(services []string, state Containers, s *composeService) *con
|
||||
observedState[service] = append(observedState[service], c)
|
||||
}
|
||||
return &convergence{
|
||||
service: s,
|
||||
observedState: observedState,
|
||||
service: s,
|
||||
services: observedState,
|
||||
networks: networks,
|
||||
volumes: volumes,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,11 +129,11 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||
|
||||
sort.Slice(containers, func(i, j int) bool {
|
||||
// select obsolete containers first, so they get removed as we scale down
|
||||
if obsolete, _ := mustRecreate(service, containers[i], recreate); obsolete {
|
||||
if obsolete, _ := c.mustRecreate(service, containers[i], recreate); obsolete {
|
||||
// i is obsolete, so must be first in the list
|
||||
return true
|
||||
}
|
||||
if obsolete, _ := mustRecreate(service, containers[j], recreate); obsolete {
|
||||
if obsolete, _ := c.mustRecreate(service, containers[j], recreate); obsolete {
|
||||
// j is obsolete, so must be first in the list
|
||||
return false
|
||||
}
|
||||
@@ -136,25 +142,27 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||
ni, erri := strconv.Atoi(containers[i].Labels[api.ContainerNumberLabel])
|
||||
nj, errj := strconv.Atoi(containers[j].Labels[api.ContainerNumberLabel])
|
||||
if erri == nil && errj == nil {
|
||||
return ni < nj
|
||||
return ni > nj
|
||||
}
|
||||
|
||||
// If we don't get a container number (?) just sort by creation date
|
||||
return containers[i].Created < containers[j].Created
|
||||
})
|
||||
|
||||
slices.Reverse(containers)
|
||||
for i, container := range containers {
|
||||
if i >= expected {
|
||||
// Scale Down
|
||||
// As we sorted containers, obsolete ones and/or highest number will be removed
|
||||
container := container
|
||||
traceOpts := append(tracing.ServiceOptions(service), tracing.ContainerOptions(container)...)
|
||||
eg.Go(tracing.SpanWrapFuncForErrGroup(ctx, "service/scale/down", traceOpts, func(ctx context.Context) error {
|
||||
return c.service.stopAndRemoveContainer(ctx, container, timeout, false)
|
||||
return c.service.stopAndRemoveContainer(ctx, container, &service, timeout, false)
|
||||
}))
|
||||
continue
|
||||
}
|
||||
|
||||
mustRecreate, err := mustRecreate(service, container, recreate)
|
||||
mustRecreate, err := c.mustRecreate(service, container, recreate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -219,20 +227,26 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||
}
|
||||
|
||||
func (c *convergence) stopDependentContainers(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
|
||||
w := progress.ContextWriter(ctx)
|
||||
// Stop dependent containers, so they will be restarted after service is re-created
|
||||
dependents := project.GetDependentsForService(service)
|
||||
if len(dependents) == 0 {
|
||||
return nil
|
||||
}
|
||||
err := c.service.stop(ctx, project.Name, api.StopOptions{
|
||||
Services: dependents,
|
||||
Project: project,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, name := range dependents {
|
||||
dependents := c.observedState[name]
|
||||
err := c.service.stopContainers(ctx, w, dependents, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dependents := c.getObservedState(name)
|
||||
for i, dependent := range dependents {
|
||||
dependent.State = ContainerExited
|
||||
dependents[i] = dependent
|
||||
}
|
||||
c.observedState[name] = dependents
|
||||
c.setObservedState(name, dependents)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -312,7 +326,7 @@ func (c *convergence) resolveSharedNamespaces(service *types.ServiceConfig) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
func mustRecreate(expected types.ServiceConfig, actual moby.Container, policy string) (bool, error) {
|
||||
func (c *convergence) mustRecreate(expected types.ServiceConfig, actual moby.Container, policy string) (bool, error) {
|
||||
if policy == api.RecreateNever {
|
||||
return false, nil
|
||||
}
|
||||
@@ -325,7 +339,74 @@ func mustRecreate(expected types.ServiceConfig, actual moby.Container, policy st
|
||||
}
|
||||
configChanged := actual.Labels[api.ConfigHashLabel] != configHash
|
||||
imageUpdated := actual.Labels[api.ImageDigestLabel] != expected.CustomLabels[api.ImageDigestLabel]
|
||||
return configChanged || imageUpdated, nil
|
||||
if configChanged || imageUpdated {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if c.networks != nil && actual.State == "running" {
|
||||
if checkExpectedNetworks(expected, actual, c.networks) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
if c.volumes != nil {
|
||||
if checkExpectedVolumes(expected, actual, c.volumes) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func checkExpectedNetworks(expected types.ServiceConfig, actual moby.Container, networks map[string]string) bool {
|
||||
// check the networks container is connected to are the expected ones
|
||||
for net := range expected.Networks {
|
||||
id := networks[net]
|
||||
if id == "swarm" {
|
||||
// corner-case : swarm overlay network isn't visible until a container is attached
|
||||
continue
|
||||
}
|
||||
found := false
|
||||
for _, settings := range actual.NetworkSettings.Networks {
|
||||
if settings.NetworkID == id {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
// config is up-to-date but container is not connected to network
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func checkExpectedVolumes(expected types.ServiceConfig, actual moby.Container, volumes map[string]string) bool {
|
||||
// check container's volume mounts and search for the expected ones
|
||||
for _, vol := range expected.Volumes {
|
||||
if vol.Type != string(mmount.TypeVolume) {
|
||||
continue
|
||||
}
|
||||
if vol.Source == "" {
|
||||
continue
|
||||
}
|
||||
id := volumes[vol.Source]
|
||||
found := false
|
||||
for _, mount := range actual.Mounts {
|
||||
if mount.Type != mmount.TypeVolume {
|
||||
continue
|
||||
}
|
||||
if mount.Name == id {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
// config is up-to-date but container doesn't have volume mounted
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getContainerName(projectName string, service types.ServiceConfig, number int) string {
|
||||
@@ -364,7 +445,12 @@ func containerReasonEvents(containers Containers, eventFunc func(string, string)
|
||||
const ServiceConditionRunningOrHealthy = "running_or_healthy"
|
||||
|
||||
//nolint:gocyclo
|
||||
func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, dependant string, dependencies types.DependsOnConfig, containers Containers) error {
|
||||
func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, dependant string, dependencies types.DependsOnConfig, containers Containers, timeout time.Duration) error {
|
||||
if timeout > 0 {
|
||||
withTimeout, cancelFunc := context.WithTimeout(ctx, timeout)
|
||||
defer cancelFunc()
|
||||
ctx = withTimeout
|
||||
}
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
w := progress.ContextWriter(ctx)
|
||||
for dep, config := range dependencies {
|
||||
@@ -454,7 +540,11 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
|
||||
}
|
||||
})
|
||||
}
|
||||
return eg.Wait()
|
||||
err := eg.Wait()
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
return fmt.Errorf("timeout waiting for dependencies")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func shouldWaitForDependency(serviceName string, dependencyConfig types.ServiceDependency, project *types.Project) (bool, error) {
|
||||
@@ -494,11 +584,11 @@ func nextContainerNumber(containers []moby.Container) int {
|
||||
}
|
||||
}
|
||||
return maxNumber + 1
|
||||
|
||||
}
|
||||
|
||||
func (s *composeService) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig,
|
||||
name string, number int, opts createOptions) (container moby.Container, err error) {
|
||||
name string, number int, opts createOptions,
|
||||
) (container moby.Container, err error) {
|
||||
w := progress.ContextWriter(ctx)
|
||||
eventName := "Container " + name
|
||||
w.Event(progress.CreatingEvent(eventName))
|
||||
@@ -511,7 +601,8 @@ func (s *composeService) createContainer(ctx context.Context, project *types.Pro
|
||||
}
|
||||
|
||||
func (s *composeService) recreateContainer(ctx context.Context, project *types.Project, service types.ServiceConfig,
|
||||
replaced moby.Container, inherit bool, timeout *time.Duration) (moby.Container, error) {
|
||||
replaced moby.Container, inherit bool, timeout *time.Duration,
|
||||
) (moby.Container, error) {
|
||||
var created moby.Container
|
||||
w := progress.ContextWriter(ctx)
|
||||
w.Event(progress.NewEvent(getContainerProgressName(replaced), progress.Working, "Recreate"))
|
||||
@@ -580,7 +671,6 @@ func (s *composeService) createMobyContainer(ctx context.Context,
|
||||
) (moby.Container, error) {
|
||||
var created moby.Container
|
||||
cfgs, err := s.getCreateConfigs(ctx, project, service, number, inherit, opts)
|
||||
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
@@ -760,12 +850,16 @@ func (s *composeService) isServiceCompleted(ctx context.Context, containers Cont
|
||||
return false, 0, nil
|
||||
}
|
||||
|
||||
func (s *composeService) startService(ctx context.Context, project *types.Project, service types.ServiceConfig, containers Containers) error {
|
||||
func (s *composeService) startService(ctx context.Context,
|
||||
project *types.Project, service types.ServiceConfig,
|
||||
containers Containers, listener api.ContainerEventListener,
|
||||
timeout time.Duration,
|
||||
) error {
|
||||
if service.Deploy != nil && service.Deploy.Replicas != nil && *service.Deploy.Replicas == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := s.waitDependencies(ctx, project, service.Name, service.DependsOn, containers)
|
||||
err := s.waitDependencies(ctx, project, service.Name, service.DependsOn, containers, timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -784,10 +878,18 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
|
||||
}
|
||||
eventName := getContainerProgressName(container)
|
||||
w.Event(progress.StartingEvent(eventName))
|
||||
err := s.apiClient().ContainerStart(ctx, container.ID, containerType.StartOptions{})
|
||||
err = s.apiClient().ContainerStart(ctx, container.ID, containerType.StartOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, hook := range service.PostStart {
|
||||
err = s.runHook(ctx, container, service, hook, listener)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
w.Event(progress.StartedEvent(eventName))
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
containerType "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"go.uber.org/mock/gomock"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
@@ -238,7 +239,7 @@ func TestWaitDependencies(t *testing.T) {
|
||||
"db": {Condition: ServiceConditionRunningOrHealthy},
|
||||
"redis": {Condition: ServiceConditionRunningOrHealthy},
|
||||
}
|
||||
assert.NilError(t, tested.waitDependencies(context.Background(), &project, "", dependencies, nil))
|
||||
assert.NilError(t, tested.waitDependencies(context.Background(), &project, "", dependencies, nil, 0))
|
||||
})
|
||||
t.Run("should skip dependencies with condition service_started", func(t *testing.T) {
|
||||
dbService := types.ServiceConfig{Name: "db", Scale: intPtr(1)}
|
||||
@@ -251,7 +252,7 @@ func TestWaitDependencies(t *testing.T) {
|
||||
"db": {Condition: types.ServiceConditionStarted, Required: true},
|
||||
"redis": {Condition: types.ServiceConditionStarted, Required: true},
|
||||
}
|
||||
assert.NilError(t, tested.waitDependencies(context.Background(), &project, "", dependencies, nil))
|
||||
assert.NilError(t, tested.waitDependencies(context.Background(), &project, "", dependencies, nil, 0))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -300,10 +301,17 @@ func TestCreateMobyContainer(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
apiClient.EXPECT().ContainerCreate(gomock.Any(), gomock.Any(), gomock.Cond(func(x any) bool {
|
||||
v := x.(*containerType.HostConfig)
|
||||
return v.NetworkMode == "b-moby-name"
|
||||
}), gomock.Eq(
|
||||
var falseBool bool
|
||||
apiClient.EXPECT().ContainerCreate(gomock.Any(), gomock.Any(), gomock.Eq(
|
||||
&containerType.HostConfig{
|
||||
PortBindings: nat.PortMap{},
|
||||
ExtraHosts: []string{},
|
||||
Tmpfs: map[string]string{},
|
||||
Resources: containerType.Resources{
|
||||
OomKillDisable: &falseBool,
|
||||
},
|
||||
NetworkMode: "b-moby-name",
|
||||
}), gomock.Eq(
|
||||
&network.NetworkingConfig{
|
||||
EndpointsConfig: map[string]*network.EndpointSettings{
|
||||
"b-moby-name": {
|
||||
@@ -382,10 +390,17 @@ func TestCreateMobyContainer(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
apiClient.EXPECT().ContainerCreate(gomock.Any(), gomock.Any(), gomock.Cond(func(x any) bool {
|
||||
v := x.(*containerType.HostConfig)
|
||||
return v.NetworkMode == "b-moby-name"
|
||||
}), gomock.Eq(
|
||||
var falseBool bool
|
||||
apiClient.EXPECT().ContainerCreate(gomock.Any(), gomock.Any(), gomock.Eq(
|
||||
&containerType.HostConfig{
|
||||
PortBindings: nat.PortMap{},
|
||||
ExtraHosts: []string{},
|
||||
Tmpfs: map[string]string{},
|
||||
Resources: containerType.Resources{
|
||||
OomKillDisable: &falseBool,
|
||||
},
|
||||
NetworkMode: "b-moby-name",
|
||||
}), gomock.Eq(
|
||||
&network.NetworkingConfig{
|
||||
EndpointsConfig: map[string]*network.EndpointSettings{
|
||||
"a-moby-name": {
|
||||
@@ -417,5 +432,4 @@ func TestCreateMobyContainer(t *testing.T) {
|
||||
}, progress.ContextWriter(context.TODO()))
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -61,11 +61,6 @@ func (s *composeService) copy(ctx context.Context, projectName string, options a
|
||||
direction |= fromService
|
||||
serviceName = srcService
|
||||
copyFunc = s.copyFromContainer
|
||||
|
||||
// copying from multiple containers of a services doesn't make sense.
|
||||
if options.All {
|
||||
return errors.New("cannot use the --all flag when copying from a service")
|
||||
}
|
||||
}
|
||||
if destService != "" {
|
||||
direction |= toService
|
||||
@@ -80,7 +75,7 @@ func (s *composeService) copy(ctx context.Context, projectName string, options a
|
||||
return errors.New("unknown copy direction")
|
||||
}
|
||||
|
||||
containers, err := s.listContainersTargetedForCopy(ctx, projectName, options.Index, direction, serviceName)
|
||||
containers, err := s.listContainersTargetedForCopy(ctx, projectName, options, direction, serviceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -119,18 +114,22 @@ func (s *composeService) copy(ctx context.Context, projectName string, options a
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
func (s *composeService) listContainersTargetedForCopy(ctx context.Context, projectName string, index int, direction copyDirection, serviceName string) (Containers, error) {
|
||||
func (s *composeService) listContainersTargetedForCopy(ctx context.Context, projectName string, options api.CopyOptions, direction copyDirection, serviceName string) (Containers, error) {
|
||||
var containers Containers
|
||||
var err error
|
||||
switch {
|
||||
case index > 0:
|
||||
ctr, err := s.getSpecifiedContainer(ctx, projectName, oneOffExclude, true, serviceName, index)
|
||||
case options.Index > 0:
|
||||
ctr, err := s.getSpecifiedContainer(ctx, projectName, oneOffExclude, true, serviceName, options.Index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(containers, ctr), nil
|
||||
default:
|
||||
containers, err = s.getContainers(ctx, projectName, oneOffExclude, true, serviceName)
|
||||
withOneOff := oneOffExclude
|
||||
if options.All {
|
||||
withOneOff = oneOffInclude
|
||||
}
|
||||
containers, err = s.getContainers(ctx, projectName, withOneOff, true, serviceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -140,7 +139,6 @@ func (s *composeService) listContainersTargetedForCopy(ctx context.Context, proj
|
||||
}
|
||||
if direction == fromService {
|
||||
return containers[:1], err
|
||||
|
||||
}
|
||||
return containers, err
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -35,6 +34,7 @@ import (
|
||||
pathutil "github.com/docker/compose/v2/internal/paths"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/prompt"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/blkiodev"
|
||||
@@ -48,6 +48,7 @@ import (
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/sirupsen/logrus"
|
||||
cdi "tags.cncf.io/container-device-interface/pkg/parser"
|
||||
)
|
||||
|
||||
type createOptions struct {
|
||||
@@ -80,12 +81,6 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
|
||||
return err
|
||||
}
|
||||
|
||||
var observedState Containers
|
||||
observedState, err = s.getContainers(ctx, project.Name, oneOffInclude, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.ensureImagesExists(ctx, project, options.Build, options.QuietPull)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -93,18 +88,25 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
|
||||
|
||||
prepareNetworks(project)
|
||||
|
||||
if err := s.ensureNetworks(ctx, project.Networks); err != nil {
|
||||
networks, err := s.ensureNetworks(ctx, project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.ensureProjectVolumes(ctx, project); err != nil {
|
||||
volumes, err := s.ensureProjectVolumes(ctx, project, options.AssumeYes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var observedState Containers
|
||||
observedState, err = s.getContainers(ctx, project.Name, oneOffInclude, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
orphans := observedState.filter(isOrphaned(project))
|
||||
if len(orphans) > 0 && !options.IgnoreOrphans {
|
||||
if options.RemoveOrphans {
|
||||
err := s.removeContainers(ctx, orphans, nil, false)
|
||||
err := s.removeContainers(ctx, orphans, nil, nil, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -115,38 +117,43 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
|
||||
"--remove-orphans flag to clean it up.", orphans.names())
|
||||
}
|
||||
}
|
||||
return newConvergence(options.Services, observedState, s).apply(ctx, project, options)
|
||||
return newConvergence(options.Services, observedState, networks, volumes, s).apply(ctx, project, options)
|
||||
}
|
||||
|
||||
func prepareNetworks(project *types.Project) {
|
||||
for k, nw := range project.Networks {
|
||||
nw.Labels = nw.Labels.Add(api.NetworkLabel, k)
|
||||
nw.Labels = nw.Labels.Add(api.ProjectLabel, project.Name)
|
||||
nw.Labels = nw.Labels.Add(api.VersionLabel, api.ComposeVersion)
|
||||
nw.CustomLabels = nw.CustomLabels.
|
||||
Add(api.NetworkLabel, k).
|
||||
Add(api.ProjectLabel, project.Name).
|
||||
Add(api.VersionLabel, api.ComposeVersion)
|
||||
project.Networks[k] = nw
|
||||
}
|
||||
}
|
||||
|
||||
func (s *composeService) ensureNetworks(ctx context.Context, networks types.Networks) error {
|
||||
for i, nw := range networks {
|
||||
err := s.ensureNetwork(ctx, &nw)
|
||||
func (s *composeService) ensureNetworks(ctx context.Context, project *types.Project) (map[string]string, error) {
|
||||
networks := map[string]string{}
|
||||
for name, nw := range project.Networks {
|
||||
id, err := s.ensureNetwork(ctx, project, name, &nw)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
networks[i] = nw
|
||||
networks[name] = id
|
||||
project.Networks[name] = nw
|
||||
}
|
||||
return nil
|
||||
return networks, nil
|
||||
}
|
||||
|
||||
func (s *composeService) ensureProjectVolumes(ctx context.Context, project *types.Project) error {
|
||||
func (s *composeService) ensureProjectVolumes(ctx context.Context, project *types.Project, assumeYes bool) (map[string]string, error) {
|
||||
ids := map[string]string{}
|
||||
for k, volume := range project.Volumes {
|
||||
volume.Labels = volume.Labels.Add(api.VolumeLabel, k)
|
||||
volume.Labels = volume.Labels.Add(api.ProjectLabel, project.Name)
|
||||
volume.Labels = volume.Labels.Add(api.VersionLabel, api.ComposeVersion)
|
||||
err := s.ensureVolume(ctx, volume, project.Name)
|
||||
volume.CustomLabels = volume.CustomLabels.Add(api.VolumeLabel, k)
|
||||
volume.CustomLabels = volume.CustomLabels.Add(api.ProjectLabel, project.Name)
|
||||
volume.CustomLabels = volume.CustomLabels.Add(api.VersionLabel, api.ComposeVersion)
|
||||
id, err := s.ensureVolume(ctx, k, volume, project, assumeYes)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
ids[k] = id
|
||||
}
|
||||
|
||||
err := func() error {
|
||||
@@ -198,11 +205,10 @@ func (s *composeService) ensureProjectVolumes(ctx context.Context, project *type
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
progress.ContextWriter(ctx).TailMsgf("Failed to prepare Synchronized file shares: %v", err)
|
||||
}
|
||||
return nil
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (s *composeService) getCreateConfigs(ctx context.Context,
|
||||
@@ -252,7 +258,7 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
|
||||
if err != nil {
|
||||
return createConfigs{}, err
|
||||
}
|
||||
var containerConfig = container.Config{
|
||||
containerConfig := container.Config{
|
||||
Hostname: service.Hostname,
|
||||
Domainname: service.DomainName,
|
||||
User: service.User,
|
||||
@@ -505,7 +511,10 @@ func (s *composeService) prepareLabels(labels types.Labels, service types.Servic
|
||||
}
|
||||
labels[api.ConfigHashLabel] = hash
|
||||
|
||||
labels[api.ContainerNumberLabel] = strconv.Itoa(number)
|
||||
if number > 0 {
|
||||
// One-off containers are not indexed
|
||||
labels[api.ContainerNumberLabel] = strconv.Itoa(number)
|
||||
}
|
||||
|
||||
var dependencies []string
|
||||
for s, d := range service.DependsOn {
|
||||
@@ -539,23 +548,28 @@ func defaultNetworkSettings(
|
||||
primaryNetworkKey = "default"
|
||||
}
|
||||
primaryNetworkMobyNetworkName := project.Networks[primaryNetworkKey].Name
|
||||
endpointsConfig := map[string]*network.EndpointSettings{
|
||||
primaryNetworkMobyNetworkName: createEndpointSettings(project, service, serviceIndex, primaryNetworkKey, links, useNetworkAliases),
|
||||
}
|
||||
primaryNetworkEndpoint := createEndpointSettings(project, service, serviceIndex, primaryNetworkKey, links, useNetworkAliases)
|
||||
endpointsConfig := map[string]*network.EndpointSettings{}
|
||||
|
||||
// Starting from API version 1.44, the Engine will take several EndpointsConfigs
|
||||
// so we can pass all the extra networks we want the container to be connected to
|
||||
// in the network configuration instead of connecting the container to each extra
|
||||
// network individually after creation.
|
||||
if versions.GreaterThanOrEqualTo(version, "1.44") && len(service.Networks) > 1 {
|
||||
serviceNetworks := service.NetworksByPriority()
|
||||
for _, networkKey := range serviceNetworks[1:] {
|
||||
mobyNetworkName := project.Networks[networkKey].Name
|
||||
epSettings := createEndpointSettings(project, service, serviceIndex, networkKey, links, useNetworkAliases)
|
||||
endpointsConfig[mobyNetworkName] = epSettings
|
||||
if versions.GreaterThanOrEqualTo(version, "1.44") {
|
||||
if len(service.Networks) > 1 {
|
||||
serviceNetworks := service.NetworksByPriority()
|
||||
for _, networkKey := range serviceNetworks[1:] {
|
||||
mobyNetworkName := project.Networks[networkKey].Name
|
||||
epSettings := createEndpointSettings(project, service, serviceIndex, networkKey, links, useNetworkAliases)
|
||||
endpointsConfig[mobyNetworkName] = epSettings
|
||||
}
|
||||
}
|
||||
if primaryNetworkEndpoint.MacAddress == "" {
|
||||
primaryNetworkEndpoint.MacAddress = service.MacAddress
|
||||
}
|
||||
}
|
||||
|
||||
endpointsConfig[primaryNetworkMobyNetworkName] = primaryNetworkEndpoint
|
||||
networkConfig := &network.NetworkingConfig{
|
||||
EndpointsConfig: endpointsConfig,
|
||||
}
|
||||
@@ -645,7 +659,14 @@ func getDeployResources(s types.ServiceConfig) container.Resources {
|
||||
setReservations(s.Deploy.Resources.Reservations, &resources)
|
||||
}
|
||||
|
||||
var cdiDeviceNames []string
|
||||
for _, device := range s.Devices {
|
||||
|
||||
if device.Source == device.Target && cdi.IsQualifiedName(device.Source) {
|
||||
cdiDeviceNames = append(cdiDeviceNames, device.Source)
|
||||
continue
|
||||
}
|
||||
|
||||
resources.Devices = append(resources.Devices, container.DeviceMapping{
|
||||
PathOnHost: device.Source,
|
||||
PathInContainer: device.Target,
|
||||
@@ -653,6 +674,23 @@ func getDeployResources(s types.ServiceConfig) container.Resources {
|
||||
})
|
||||
}
|
||||
|
||||
if len(cdiDeviceNames) > 0 {
|
||||
resources.DeviceRequests = append(resources.DeviceRequests, container.DeviceRequest{
|
||||
Driver: "cdi",
|
||||
DeviceIDs: cdiDeviceNames,
|
||||
})
|
||||
}
|
||||
|
||||
for _, gpus := range s.Gpus {
|
||||
resources.DeviceRequests = append(resources.DeviceRequests, container.DeviceRequest{
|
||||
Driver: gpus.Driver,
|
||||
Count: int(gpus.Count),
|
||||
DeviceIDs: gpus.IDs,
|
||||
Capabilities: [][]string{append(gpus.Capabilities, "gpu")},
|
||||
Options: gpus.Options,
|
||||
})
|
||||
}
|
||||
|
||||
ulimits := toUlimits(s.Ulimits)
|
||||
resources.Ulimits = ulimits
|
||||
return resources
|
||||
@@ -694,6 +732,7 @@ func setReservations(reservations *types.Resource, resources *container.Resource
|
||||
Count: int(device.Count),
|
||||
DeviceIDs: device.IDs,
|
||||
Driver: device.Driver,
|
||||
Options: device.Options,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -806,23 +845,23 @@ func (s *composeService) buildContainerVolumes(
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
version, err := s.RuntimeVersion(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if versions.GreaterThan(version, "1.42") {
|
||||
// We can fully leverage `Mount` API as a replacement for legacy `Bind`
|
||||
return nil, mountOptions, nil
|
||||
}
|
||||
|
||||
MOUNTS:
|
||||
for _, m := range mountOptions {
|
||||
if m.Type == mount.TypeNamedPipe {
|
||||
mounts = append(mounts, m)
|
||||
continue
|
||||
}
|
||||
if m.Type == mount.TypeBind {
|
||||
// `Mount` does not offer option to created host path if missing
|
||||
// `Mount` is preferred but does not offer option to created host path if missing
|
||||
// so `Bind` API is used here with raw volume string
|
||||
// see https://github.com/moby/moby/issues/43483
|
||||
for _, v := range service.Volumes {
|
||||
if v.Target == m.Target {
|
||||
if v.Bind != nil && v.Bind.CreateHostPath {
|
||||
switch {
|
||||
case string(m.Type) != v.Type:
|
||||
v.Source = m.Source
|
||||
fallthrough
|
||||
case !requireMountAPI(v.Bind):
|
||||
binds = append(binds, v.String())
|
||||
continue MOUNTS
|
||||
}
|
||||
@@ -834,8 +873,25 @@ MOUNTS:
|
||||
return binds, mounts, nil
|
||||
}
|
||||
|
||||
// requireMountAPI check if Bind declaration can be implemented by the plain old Bind API or uses any of the advanced
|
||||
// options which require use of Mount API
|
||||
func requireMountAPI(bind *types.ServiceVolumeBind) bool {
|
||||
switch {
|
||||
case bind == nil:
|
||||
return false
|
||||
case !bind.CreateHostPath:
|
||||
return true
|
||||
case bind.Propagation != "":
|
||||
return true
|
||||
case bind.Recursive != "":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby.ImageInspect, inherit *moby.Container) ([]mount.Mount, error) {
|
||||
var mounts = map[string]mount.Mount{}
|
||||
mounts := map[string]mount.Mount{}
|
||||
if inherit != nil {
|
||||
for _, m := range inherit.Mounts {
|
||||
if m.Type == "tmpfs" {
|
||||
@@ -845,7 +901,6 @@ func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby
|
||||
if m.Type == "volume" {
|
||||
src = m.Name
|
||||
}
|
||||
m.Destination = path.Clean(m.Destination)
|
||||
|
||||
if img.Config != nil {
|
||||
if _, ok := img.Config.Volumes[m.Destination]; ok {
|
||||
@@ -922,7 +977,7 @@ func fillBindMounts(p types.Project, s types.ServiceConfig, m map[string]mount.M
|
||||
}
|
||||
|
||||
func buildContainerConfigMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
|
||||
var mounts = map[string]mount.Mount{}
|
||||
mounts := map[string]mount.Mount{}
|
||||
|
||||
configsBaseDir := "/"
|
||||
for _, config := range s.Configs {
|
||||
@@ -933,10 +988,6 @@ func buildContainerConfigMounts(p types.Project, s types.ServiceConfig) ([]mount
|
||||
target = configsBaseDir + config.Target
|
||||
}
|
||||
|
||||
if config.UID != "" || config.GID != "" || config.Mode != nil {
|
||||
logrus.Warn("config `uid`, `gid` and `mode` are not supported, they will be ignored")
|
||||
}
|
||||
|
||||
definedConfig := p.Configs[config.Source]
|
||||
if definedConfig.External {
|
||||
return nil, fmt.Errorf("unsupported external config %s", definedConfig.Name)
|
||||
@@ -953,6 +1004,10 @@ func buildContainerConfigMounts(p types.Project, s types.ServiceConfig) ([]mount
|
||||
continue
|
||||
}
|
||||
|
||||
if config.UID != "" || config.GID != "" || config.Mode != nil {
|
||||
logrus.Warn("config `uid`, `gid` and `mode` are not supported, they will be ignored")
|
||||
}
|
||||
|
||||
bindMount, err := buildMount(p, types.ServiceVolumeConfig{
|
||||
Type: types.VolumeTypeBind,
|
||||
Source: definedConfig.File,
|
||||
@@ -972,7 +1027,7 @@ func buildContainerConfigMounts(p types.Project, s types.ServiceConfig) ([]mount
|
||||
}
|
||||
|
||||
func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
|
||||
var mounts = map[string]mount.Mount{}
|
||||
mounts := map[string]mount.Mount{}
|
||||
|
||||
secretsDir := "/run/secrets/"
|
||||
for _, secret := range s.Secrets {
|
||||
@@ -983,10 +1038,6 @@ func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount
|
||||
target = secretsDir + secret.Target
|
||||
}
|
||||
|
||||
if secret.UID != "" || secret.GID != "" || secret.Mode != nil {
|
||||
logrus.Warn("secrets `uid`, `gid` and `mode` are not supported, they will be ignored")
|
||||
}
|
||||
|
||||
definedSecret := p.Secrets[secret.Source]
|
||||
if definedSecret.External {
|
||||
return nil, fmt.Errorf("unsupported external secret %s", definedSecret.Name)
|
||||
@@ -1003,11 +1054,22 @@ func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount
|
||||
continue
|
||||
}
|
||||
|
||||
if secret.UID != "" || secret.GID != "" || secret.Mode != nil {
|
||||
logrus.Warn("secrets `uid`, `gid` and `mode` are not supported, they will be ignored")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(definedSecret.File); os.IsNotExist(err) {
|
||||
logrus.Warnf("secret file %s does not exist", definedSecret.Name)
|
||||
}
|
||||
|
||||
mnt, err := buildMount(p, types.ServiceVolumeConfig{
|
||||
Type: types.VolumeTypeBind,
|
||||
Source: definedSecret.File,
|
||||
Target: target,
|
||||
ReadOnly: true,
|
||||
Bind: &types.ServiceVolumeBind{
|
||||
CreateHostPath: false,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1062,8 +1124,6 @@ func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.
|
||||
|
||||
bind, vol, tmpfs := buildMountOptions(volume)
|
||||
|
||||
volume.Target = path.Clean(volume.Target)
|
||||
|
||||
if bind != nil {
|
||||
volume.Type = types.VolumeTypeBind
|
||||
}
|
||||
@@ -1114,11 +1174,19 @@ func buildBindOption(bind *types.ServiceVolumeBind) *mount.BindOptions {
|
||||
if bind == nil {
|
||||
return nil
|
||||
}
|
||||
return &mount.BindOptions{
|
||||
opts := &mount.BindOptions{
|
||||
Propagation: mount.Propagation(bind.Propagation),
|
||||
CreateMountpoint: bind.CreateHostPath,
|
||||
// NonRecursive: false, FIXME missing from model ?
|
||||
}
|
||||
switch bind.Recursive {
|
||||
case "disabled":
|
||||
opts.NonRecursive = true
|
||||
case "writable":
|
||||
opts.ReadOnlyNonRecursive = true
|
||||
case "readonly":
|
||||
opts.ReadOnlyForceRecursive = true
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func buildVolumeOptions(vol *types.ServiceVolumeVolume) *mount.VolumeOptions {
|
||||
@@ -1143,24 +1211,21 @@ func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *composeService) ensureNetwork(ctx context.Context, n *types.NetworkConfig) error {
|
||||
func (s *composeService) ensureNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) (string, error) {
|
||||
if n.External {
|
||||
return s.resolveExternalNetwork(ctx, n)
|
||||
}
|
||||
|
||||
err := s.resolveOrCreateNetwork(ctx, n)
|
||||
id, err := s.resolveOrCreateNetwork(ctx, project, name, n)
|
||||
if errdefs.IsConflict(err) {
|
||||
// Maybe another execution of `docker compose up|run` created same network
|
||||
// let's retry once
|
||||
return s.resolveOrCreateNetwork(ctx, n)
|
||||
return s.resolveOrCreateNetwork(ctx, project, name, n)
|
||||
}
|
||||
return err
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.NetworkConfig) error { //nolint:gocyclo
|
||||
expectedNetworkLabel := n.Labels[api.NetworkLabel]
|
||||
expectedProjectLabel := n.Labels[api.ProjectLabel]
|
||||
|
||||
func (s *composeService) resolveOrCreateNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) (string, error) { //nolint:gocyclo
|
||||
// First, try to find a unique network matching by name or ID
|
||||
inspect, err := s.apiClient().NetworkInspect(ctx, n.Name, network.InspectOptions{})
|
||||
if err == nil {
|
||||
@@ -1171,14 +1236,33 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.Ne
|
||||
if !ok {
|
||||
logrus.Warnf("a network with name %s exists but was not created by compose.\n"+
|
||||
"Set `external: true` to use an existing network", n.Name)
|
||||
} else if p != expectedProjectLabel {
|
||||
} else if p != project.Name {
|
||||
logrus.Warnf("a network with name %s exists but was not created for project %q.\n"+
|
||||
"Set `external: true` to use an existing network", n.Name, expectedProjectLabel)
|
||||
"Set `external: true` to use an existing network", n.Name, project.Name)
|
||||
}
|
||||
if inspect.Labels[api.NetworkLabel] != expectedNetworkLabel {
|
||||
return fmt.Errorf("network %s was found but has incorrect label %s set to %q", n.Name, api.NetworkLabel, inspect.Labels[api.NetworkLabel])
|
||||
if inspect.Labels[api.NetworkLabel] != name {
|
||||
return "", fmt.Errorf(
|
||||
"network %s was found but has incorrect label %s set to %q (expected: %q)",
|
||||
n.Name,
|
||||
api.NetworkLabel,
|
||||
inspect.Labels[api.NetworkLabel],
|
||||
name,
|
||||
)
|
||||
}
|
||||
|
||||
hash := inspect.Labels[api.ConfigHashLabel]
|
||||
expected, err := NetworkHash(n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if hash == "" || hash == expected {
|
||||
return inspect.ID, nil
|
||||
}
|
||||
|
||||
err = s.removeDivergedNetwork(ctx, project, name, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// ignore other errors. Typically, an ambiguous request by name results in some generic `invalidParameter` error
|
||||
@@ -1188,7 +1272,7 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.Ne
|
||||
Filters: filters.NewArgs(filters.Arg("name", n.Name)),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
// NetworkList Matches all or part of a network name, so we have to filter for a strict match
|
||||
@@ -1197,9 +1281,9 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.Ne
|
||||
})
|
||||
|
||||
for _, net := range networks {
|
||||
if net.Labels[api.ProjectLabel] == expectedProjectLabel &&
|
||||
net.Labels[api.NetworkLabel] == expectedNetworkLabel {
|
||||
return nil
|
||||
if net.Labels[api.ProjectLabel] == project.Name &&
|
||||
net.Labels[api.NetworkLabel] == name {
|
||||
return net.ID, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1209,7 +1293,7 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.Ne
|
||||
if len(networks) > 0 {
|
||||
logrus.Warnf("a network with name %s exists but was not created by compose.\n"+
|
||||
"Set `external: true` to use an existing network", n.Name)
|
||||
return nil
|
||||
return networks[0].ID, nil
|
||||
}
|
||||
|
||||
var ipam *network.IPAM
|
||||
@@ -1228,8 +1312,13 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.Ne
|
||||
Config: config,
|
||||
}
|
||||
}
|
||||
hash, err := NetworkHash(n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
n.CustomLabels = n.CustomLabels.Add(api.ConfigHashLabel, hash)
|
||||
createOpts := network.CreateOptions{
|
||||
Labels: n.Labels,
|
||||
Labels: mergeLabels(n.Labels, n.CustomLabels),
|
||||
Driver: n.Driver,
|
||||
Options: n.DriverOpts,
|
||||
Internal: n.Internal,
|
||||
@@ -1259,16 +1348,42 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.Ne
|
||||
w := progress.ContextWriter(ctx)
|
||||
w.Event(progress.CreatingEvent(networkEventName))
|
||||
|
||||
_, err = s.apiClient().NetworkCreate(ctx, n.Name, createOpts)
|
||||
resp, err := s.apiClient().NetworkCreate(ctx, n.Name, createOpts)
|
||||
if err != nil {
|
||||
w.Event(progress.ErrorEvent(networkEventName))
|
||||
return fmt.Errorf("failed to create network %s: %w", n.Name, err)
|
||||
return "", fmt.Errorf("failed to create network %s: %w", n.Name, err)
|
||||
}
|
||||
w.Event(progress.CreatedEvent(networkEventName))
|
||||
return nil
|
||||
return resp.ID, nil
|
||||
}
|
||||
|
||||
func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.NetworkConfig) error {
|
||||
func (s *composeService) removeDivergedNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) error {
|
||||
// Remove services attached to this network to force recreation
|
||||
var services []string
|
||||
for _, service := range project.Services.Filter(func(config types.ServiceConfig) bool {
|
||||
_, ok := config.Networks[name]
|
||||
return ok
|
||||
}) {
|
||||
services = append(services, service.Name)
|
||||
}
|
||||
|
||||
// Stop containers so we can remove network
|
||||
// They will be restarted (actually: recreated) with the updated network
|
||||
err := s.stop(ctx, project.Name, api.StopOptions{
|
||||
Services: services,
|
||||
Project: project,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.apiClient().NetworkRemove(ctx, n.Name)
|
||||
eventName := fmt.Sprintf("Network %s", n.Name)
|
||||
progress.ContextWriter(ctx).Event(progress.RemovedEvent(eventName))
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.NetworkConfig) (string, error) {
|
||||
// NetworkInspect will match on ID prefix, so NetworkList with a name
|
||||
// filter is used to look for an exact match to prevent e.g. a network
|
||||
// named `db` from getting erroneously matched to a network with an ID
|
||||
@@ -1276,16 +1391,15 @@ func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.Ne
|
||||
networks, err := s.apiClient().NetworkList(ctx, network.ListOptions{
|
||||
Filters: filters.NewArgs(filters.Arg("name", n.Name)),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(networks) == 0 {
|
||||
// in this instance, n.Name is really an ID
|
||||
sn, err := s.apiClient().NetworkInspect(ctx, n.Name, network.InspectOptions{})
|
||||
if err != nil && !errdefs.IsNotFound(err) {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
networks = append(networks, sn)
|
||||
}
|
||||
@@ -1300,41 +1414,40 @@ func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.Ne
|
||||
|
||||
switch len(networks) {
|
||||
case 1:
|
||||
n.Name = networks[0].ID
|
||||
return nil
|
||||
return networks[0].ID, nil
|
||||
case 0:
|
||||
enabled, err := s.isSWarmEnabled(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
if enabled {
|
||||
// Swarm nodes do not register overlay networks that were
|
||||
// created on a different node unless they're in use.
|
||||
// So we can't preemptively check network exists, but
|
||||
// networkAttach will later fail anyway if network actually doesn't exist
|
||||
return nil
|
||||
return "swarm", nil
|
||||
}
|
||||
return fmt.Errorf("network %s declared as external, but could not be found", n.Name)
|
||||
return "", fmt.Errorf("network %s declared as external, but could not be found", n.Name)
|
||||
default:
|
||||
return fmt.Errorf("multiple networks with name %q were found. Use network ID as `name` to avoid ambiguity", n.Name)
|
||||
return "", fmt.Errorf("multiple networks with name %q were found. Use network ID as `name` to avoid ambiguity", n.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig, project string) error {
|
||||
func (s *composeService) ensureVolume(ctx context.Context, name string, volume types.VolumeConfig, project *types.Project, assumeYes bool) (string, error) {
|
||||
inspected, err := s.apiClient().VolumeInspect(ctx, volume.Name)
|
||||
if err != nil {
|
||||
if !errdefs.IsNotFound(err) {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
if volume.External {
|
||||
return fmt.Errorf("external volume %q not found", volume.Name)
|
||||
return "", fmt.Errorf("external volume %q not found", volume.Name)
|
||||
}
|
||||
err := s.createVolume(ctx, volume)
|
||||
return err
|
||||
err = s.createVolume(ctx, volume)
|
||||
return volume.Name, err
|
||||
}
|
||||
|
||||
if volume.External {
|
||||
return nil
|
||||
return volume.Name, nil
|
||||
}
|
||||
|
||||
// Volume exists with name, but let's double-check this is the expected one
|
||||
@@ -1342,18 +1455,86 @@ func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeCo
|
||||
if !ok {
|
||||
logrus.Warnf("volume %q already exists but was not created by Docker Compose. Use `external: true` to use an existing volume", volume.Name)
|
||||
}
|
||||
if ok && p != project {
|
||||
logrus.Warnf("volume %q already exists but was created for project %q (expected %q). Use `external: true` to use an existing volume", volume.Name, p, project)
|
||||
if ok && p != project.Name {
|
||||
logrus.Warnf("volume %q already exists but was created for project %q (expected %q). Use `external: true` to use an existing volume", volume.Name, p, project.Name)
|
||||
}
|
||||
return nil
|
||||
|
||||
expected, err := VolumeHash(volume)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
actual, ok := inspected.Labels[api.ConfigHashLabel]
|
||||
if ok && actual != expected {
|
||||
confirm := assumeYes
|
||||
if !assumeYes {
|
||||
msg := fmt.Sprintf("Volume %q exists but doesn't match configuration in compose file. Recreate (data will be lost)?", volume.Name)
|
||||
confirm, err = prompt.NewPrompt(s.stdin(), s.stdout()).Confirm(msg, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if confirm {
|
||||
err = s.removeDivergedVolume(ctx, name, volume, project)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return volume.Name, s.createVolume(ctx, volume)
|
||||
}
|
||||
}
|
||||
return inspected.Name, nil
|
||||
}
|
||||
|
||||
func (s *composeService) removeDivergedVolume(ctx context.Context, name string, volume types.VolumeConfig, project *types.Project) error {
|
||||
// Remove services mounting divergent volume
|
||||
var services []string
|
||||
for _, service := range project.Services.Filter(func(config types.ServiceConfig) bool {
|
||||
for _, cfg := range config.Volumes {
|
||||
if cfg.Source == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}) {
|
||||
services = append(services, service.Name)
|
||||
}
|
||||
|
||||
err := s.stop(ctx, project.Name, api.StopOptions{
|
||||
Services: services,
|
||||
Project: project,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
containers, err := s.getContainers(ctx, project.Name, oneOffExclude, true, services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// FIXME (ndeloof) we have to remove container so we can recreate volume
|
||||
// but doing so we can't inherit anonymous volumes from previous instance
|
||||
err = s.remove(ctx, containers, api.RemoveOptions{
|
||||
Services: services,
|
||||
Project: project,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.apiClient().VolumeRemove(ctx, volume.Name, true)
|
||||
}
|
||||
|
||||
func (s *composeService) createVolume(ctx context.Context, volume types.VolumeConfig) error {
|
||||
eventName := fmt.Sprintf("Volume %q", volume.Name)
|
||||
w := progress.ContextWriter(ctx)
|
||||
w.Event(progress.CreatingEvent(eventName))
|
||||
_, err := s.apiClient().VolumeCreate(ctx, volumetypes.CreateOptions{
|
||||
Labels: volume.Labels,
|
||||
hash, err := VolumeHash(volume)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
volume.CustomLabels.Add(api.ConfigHashLabel, hash)
|
||||
_, err = s.apiClient().VolumeCreate(ctx, volumetypes.CreateOptions{
|
||||
Labels: mergeLabels(volume.Labels, volume.CustomLabels),
|
||||
Name: volume.Name,
|
||||
Driver: volume.Driver,
|
||||
DriverOpts: volume.DriverOpts,
|
||||
|
||||
@@ -92,7 +92,7 @@ func TestPrepareNetworkLabels(t *testing.T) {
|
||||
Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{"skynet": {}}),
|
||||
}
|
||||
prepareNetworks(&project)
|
||||
assert.DeepEqual(t, project.Networks["skynet"].Labels, composetypes.Labels(map[string]string{
|
||||
assert.DeepEqual(t, project.Networks["skynet"].CustomLabels, composetypes.Labels(map[string]string{
|
||||
"com.docker.compose.network": "skynet",
|
||||
"com.docker.compose.project": "myProject",
|
||||
"com.docker.compose.version": api.ComposeVersion,
|
||||
@@ -278,33 +278,26 @@ func TestDefaultNetworkSettings(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreateEndpointSettings(t *testing.T) {
|
||||
eps := createEndpointSettings(
|
||||
&composetypes.Project{
|
||||
Name: "projName",
|
||||
},
|
||||
composetypes.ServiceConfig{
|
||||
Name: "serviceName",
|
||||
ContainerName: "containerName",
|
||||
Networks: map[string]*composetypes.ServiceNetworkConfig{
|
||||
"netName": {
|
||||
Priority: 100,
|
||||
Aliases: []string{"alias1", "alias2"},
|
||||
Ipv4Address: "10.16.17.18",
|
||||
Ipv6Address: "fdb4:7a7f:373a:3f0c::42",
|
||||
LinkLocalIPs: []string{"169.254.10.20"},
|
||||
MacAddress: "10:00:00:00:01",
|
||||
DriverOpts: composetypes.Options{
|
||||
"driverOpt1": "optval1",
|
||||
"driverOpt2": "optval2",
|
||||
},
|
||||
eps := createEndpointSettings(&composetypes.Project{
|
||||
Name: "projName",
|
||||
}, composetypes.ServiceConfig{
|
||||
Name: "serviceName",
|
||||
ContainerName: "containerName",
|
||||
Networks: map[string]*composetypes.ServiceNetworkConfig{
|
||||
"netName": {
|
||||
Priority: 100,
|
||||
Aliases: []string{"alias1", "alias2"},
|
||||
Ipv4Address: "10.16.17.18",
|
||||
Ipv6Address: "fdb4:7a7f:373a:3f0c::42",
|
||||
LinkLocalIPs: []string{"169.254.10.20"},
|
||||
MacAddress: "10:00:00:00:01",
|
||||
DriverOpts: composetypes.Options{
|
||||
"driverOpt1": "optval1",
|
||||
"driverOpt2": "optval2",
|
||||
},
|
||||
},
|
||||
},
|
||||
0, // serviceIndex
|
||||
"netName", // networkKey
|
||||
[]string{"link1", "link2"}, // links
|
||||
true, // useNetworkAliases
|
||||
)
|
||||
}, 0, "netName", []string{"link1", "link2"}, true)
|
||||
assert.Check(t, cmp.DeepEqual(eps, &network.EndpointSettings{
|
||||
IPAMConfig: &network.EndpointIPAMConfig{
|
||||
IPv4Address: "10.16.17.18",
|
||||
|
||||
@@ -438,7 +438,6 @@ func (g *Graph) HasCycles() (bool, error) {
|
||||
if !utils.StringContains(discovered, vertex.Key) && !utils.StringContains(finished, vertex.Key) {
|
||||
var err error
|
||||
discovered, finished, err = g.visit(vertex.Key, path, discovered, finished)
|
||||
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
@@ -67,18 +67,26 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||
}
|
||||
|
||||
// Check requested services exists in model
|
||||
options.Services, err = checkSelectedServices(options, project)
|
||||
services, err := checkSelectedServices(options, project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(options.Services) > 0 && len(services) == 0 {
|
||||
logrus.Infof("Any of the services %v not running in project %q", options.Services, projectName)
|
||||
return nil
|
||||
}
|
||||
|
||||
options.Services = services
|
||||
|
||||
if len(containers) > 0 {
|
||||
resourceToRemove = true
|
||||
}
|
||||
|
||||
err = InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error {
|
||||
serviceContainers := containers.filter(isService(service))
|
||||
err := s.removeContainers(ctx, serviceContainers, options.Timeout, options.Volumes)
|
||||
serv := project.Services[service]
|
||||
err := s.removeContainers(ctx, serviceContainers, &serv, options.Timeout, options.Volumes)
|
||||
return err
|
||||
}, WithRootNodesAndDown(options.Services))
|
||||
if err != nil {
|
||||
@@ -87,7 +95,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||
|
||||
orphans := containers.filter(isOrphaned(project))
|
||||
if options.RemoveOrphans && len(orphans) > 0 {
|
||||
err := s.removeContainers(ctx, orphans, options.Timeout, false)
|
||||
err := s.removeContainers(ctx, orphans, nil, options.Timeout, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -289,9 +297,19 @@ func (s *composeService) removeVolume(ctx context.Context, id string, w progress
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *composeService) stopContainer(ctx context.Context, w progress.Writer, container moby.Container, timeout *time.Duration) error {
|
||||
func (s *composeService) stopContainer(ctx context.Context, w progress.Writer, service *types.ServiceConfig, container moby.Container, timeout *time.Duration) error {
|
||||
eventName := getContainerProgressName(container)
|
||||
w.Event(progress.StoppingEvent(eventName))
|
||||
|
||||
if service != nil {
|
||||
for _, hook := range service.PreStop {
|
||||
err := s.runHook(ctx, container, *service, hook, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timeoutInSecond := utils.DurationSecondToInt(timeout)
|
||||
err := s.apiClient().ContainerStop(ctx, container.ID, containerType.StopOptions{Timeout: timeoutInSecond})
|
||||
if err != nil {
|
||||
@@ -302,32 +320,32 @@ func (s *composeService) stopContainer(ctx context.Context, w progress.Writer, c
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *composeService) stopContainers(ctx context.Context, w progress.Writer, containers []moby.Container, timeout *time.Duration) error {
|
||||
func (s *composeService) stopContainers(ctx context.Context, w progress.Writer, serv *types.ServiceConfig, containers []moby.Container, timeout *time.Duration) error {
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
for _, container := range containers {
|
||||
container := container
|
||||
eg.Go(func() error {
|
||||
return s.stopContainer(ctx, w, container, timeout)
|
||||
return s.stopContainer(ctx, w, serv, container, timeout)
|
||||
})
|
||||
}
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
func (s *composeService) removeContainers(ctx context.Context, containers []moby.Container, timeout *time.Duration, volumes bool) error {
|
||||
func (s *composeService) removeContainers(ctx context.Context, containers []moby.Container, service *types.ServiceConfig, timeout *time.Duration, volumes bool) error {
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
for _, container := range containers {
|
||||
container := container
|
||||
eg.Go(func() error {
|
||||
return s.stopAndRemoveContainer(ctx, container, timeout, volumes)
|
||||
return s.stopAndRemoveContainer(ctx, container, service, timeout, volumes)
|
||||
})
|
||||
}
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
func (s *composeService) stopAndRemoveContainer(ctx context.Context, container moby.Container, timeout *time.Duration, volumes bool) error {
|
||||
func (s *composeService) stopAndRemoveContainer(ctx context.Context, container moby.Container, service *types.ServiceConfig, timeout *time.Duration, volumes bool) error {
|
||||
w := progress.ContextWriter(ctx)
|
||||
eventName := getContainerProgressName(container)
|
||||
err := s.stopContainer(ctx, w, container, timeout)
|
||||
err := s.stopContainer(ctx, w, service, container, timeout)
|
||||
if errdefs.IsNotFound(err) {
|
||||
w.Event(progress.RemovedEvent(eventName))
|
||||
return nil
|
||||
|
||||
@@ -96,6 +96,95 @@ func TestDown(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func TestDownWithGivenServices(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
api, cli := prepareMocks(mockCtrl)
|
||||
tested := composeService{
|
||||
dockerCli: cli,
|
||||
}
|
||||
|
||||
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(false)).Return(
|
||||
[]moby.Container{
|
||||
testContainer("service1", "123", false),
|
||||
testContainer("service2", "456", false),
|
||||
testContainer("service2", "789", false),
|
||||
testContainer("service_orphan", "321", true),
|
||||
}, nil)
|
||||
api.EXPECT().VolumeList(
|
||||
gomock.Any(),
|
||||
volume.ListOptions{
|
||||
Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject))),
|
||||
}).
|
||||
Return(volume.ListResponse{}, nil)
|
||||
|
||||
// network names are not guaranteed to be unique, ensure Compose handles
|
||||
// cleanup properly if duplicates are inadvertently created
|
||||
api.EXPECT().NetworkList(gomock.Any(), network.ListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
|
||||
Return([]network.Summary{
|
||||
{ID: "abc123", Name: "myProject_default", Labels: map[string]string{compose.NetworkLabel: "default"}},
|
||||
{ID: "def456", Name: "myProject_default", Labels: map[string]string{compose.NetworkLabel: "default"}},
|
||||
}, nil)
|
||||
|
||||
stopOptions := containerType.StopOptions{}
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "123", stopOptions).Return(nil)
|
||||
|
||||
api.EXPECT().ContainerRemove(gomock.Any(), "123", containerType.RemoveOptions{Force: true}).Return(nil)
|
||||
|
||||
api.EXPECT().NetworkList(gomock.Any(), network.ListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
projectFilter(strings.ToLower(testProject)),
|
||||
networkFilter("default")),
|
||||
}).Return([]network.Summary{
|
||||
{ID: "abc123", Name: "myProject_default"},
|
||||
}, nil)
|
||||
api.EXPECT().NetworkInspect(gomock.Any(), "abc123", gomock.Any()).Return(network.Inspect{ID: "abc123"}, nil)
|
||||
api.EXPECT().NetworkRemove(gomock.Any(), "abc123").Return(nil)
|
||||
|
||||
err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{
|
||||
Services: []string{"service1", "not-running-service"},
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func TestDownWithSpecifiedServiceButTheServicesAreNotRunning(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
api, cli := prepareMocks(mockCtrl)
|
||||
tested := composeService{
|
||||
dockerCli: cli,
|
||||
}
|
||||
|
||||
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(false)).Return(
|
||||
[]moby.Container{
|
||||
testContainer("service1", "123", false),
|
||||
testContainer("service2", "456", false),
|
||||
testContainer("service2", "789", false),
|
||||
testContainer("service_orphan", "321", true),
|
||||
}, nil)
|
||||
api.EXPECT().VolumeList(
|
||||
gomock.Any(),
|
||||
volume.ListOptions{
|
||||
Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject))),
|
||||
}).
|
||||
Return(volume.ListResponse{}, nil)
|
||||
|
||||
// network names are not guaranteed to be unique, ensure Compose handles
|
||||
// cleanup properly if duplicates are inadvertently created
|
||||
api.EXPECT().NetworkList(gomock.Any(), network.ListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
|
||||
Return([]network.Summary{
|
||||
{ID: "abc123", Name: "myProject_default", Labels: map[string]string{compose.NetworkLabel: "default"}},
|
||||
{ID: "def456", Name: "myProject_default", Labels: map[string]string{compose.NetworkLabel: "default"}},
|
||||
}, nil)
|
||||
|
||||
err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{
|
||||
Services: []string{"not-running-service1", "not-running-service2"},
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func TestDownRemoveOrphans(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
@@ -122,7 +211,8 @@ func TestDownRemoveOrphans(t *testing.T) {
|
||||
{
|
||||
Name: "myProject_default",
|
||||
Labels: map[string]string{compose.NetworkLabel: "default"},
|
||||
}}, nil)
|
||||
},
|
||||
}, nil)
|
||||
|
||||
stopOptions := containerType.StopOptions{}
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "123", stopOptions).Return(nil)
|
||||
|
||||
@@ -21,10 +21,8 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// isCaseInsensitiveEnvVars is true on platforms where environment variable names are treated case-insensitively.
|
||||
isCaseInsensitiveEnvVars = (runtime.GOOS == "windows")
|
||||
)
|
||||
// isCaseInsensitiveEnvVars is true on platforms where environment variable names are treated case-insensitively.
|
||||
var isCaseInsensitiveEnvVars = (runtime.GOOS == "windows")
|
||||
|
||||
// envResolver returns resolver for environment variables suitable for the current platform.
|
||||
// Expected to be used with `MappingWithEquals.Resolve`.
|
||||
|
||||
101
pkg/compose/export.go
Normal file
101
pkg/compose/export.go
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
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"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
)
|
||||
|
||||
func (s *composeService) Export(ctx context.Context, projectName string, options api.ExportOptions) error {
|
||||
return progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
||||
return s.export(ctx, projectName, options)
|
||||
}, s.stdinfo(), "Exporting")
|
||||
}
|
||||
|
||||
func (s *composeService) export(ctx context.Context, projectName string, options api.ExportOptions) error {
|
||||
projectName = strings.ToLower(projectName)
|
||||
|
||||
container, err := s.getSpecifiedContainer(ctx, projectName, oneOffInclude, false, options.Service, options.Index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if options.Output == "" && s.dockerCli.Out().IsTerminal() {
|
||||
return fmt.Errorf("output option is required when exporting to terminal")
|
||||
}
|
||||
|
||||
if err := command.ValidateOutputPath(options.Output); err != nil {
|
||||
return fmt.Errorf("failed to export container: %w", err)
|
||||
}
|
||||
|
||||
clnt := s.dockerCli.Client()
|
||||
|
||||
w := progress.ContextWriter(ctx)
|
||||
|
||||
name := getCanonicalContainerName(container)
|
||||
msg := fmt.Sprintf("export %s to %s", name, options.Output)
|
||||
|
||||
w.Event(progress.Event{
|
||||
ID: name,
|
||||
Text: msg,
|
||||
Status: progress.Working,
|
||||
StatusText: "Exporting",
|
||||
})
|
||||
|
||||
responseBody, err := clnt.ContainerExport(ctx, container.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := responseBody.Close(); err != nil {
|
||||
w.Event(progress.Event{
|
||||
ID: name,
|
||||
Text: msg,
|
||||
Status: progress.Error,
|
||||
StatusText: fmt.Sprintf("Failed to close response body: %v", err),
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
if !s.dryRun {
|
||||
if options.Output == "" {
|
||||
_, err := io.Copy(s.dockerCli.Out(), responseBody)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := command.CopyToFile(options.Output, responseBody); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
w.Event(progress.Event{
|
||||
ID: name,
|
||||
Text: msg,
|
||||
Status: progress.Done,
|
||||
StatusText: "Exported",
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
246
pkg/compose/generate.go
Normal file
246
pkg/compose/generate.go
Normal file
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
Copyright 2023 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
containerType "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
func (s *composeService) Generate(ctx context.Context, options api.GenerateOptions) (*types.Project, error) {
|
||||
filtersListNames := filters.NewArgs()
|
||||
filtersListIDs := filters.NewArgs()
|
||||
for _, containerName := range options.Containers {
|
||||
filtersListNames.Add("name", containerName)
|
||||
filtersListIDs.Add("id", containerName)
|
||||
}
|
||||
containers, err := s.apiClient().ContainerList(ctx, containerType.ListOptions{
|
||||
Filters: filtersListNames,
|
||||
All: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
containersByIds, err := s.apiClient().ContainerList(ctx, containerType.ListOptions{
|
||||
Filters: filtersListIDs,
|
||||
All: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, container := range containersByIds {
|
||||
if !utils.Contains(containers, container) {
|
||||
containers = append(containers, container)
|
||||
}
|
||||
}
|
||||
|
||||
if len(containers) == 0 {
|
||||
return nil, fmt.Errorf("no container(s) found with the following name(s): %s", strings.Join(options.Containers, ","))
|
||||
}
|
||||
|
||||
return s.createProjectFromContainers(containers, options.ProjectName)
|
||||
}
|
||||
|
||||
func (s *composeService) createProjectFromContainers(containers []moby.Container, projectName string) (*types.Project, error) {
|
||||
project := &types.Project{}
|
||||
services := types.Services{}
|
||||
networks := types.Networks{}
|
||||
volumes := types.Volumes{}
|
||||
secrets := types.Secrets{}
|
||||
|
||||
if projectName != "" {
|
||||
project.Name = projectName
|
||||
}
|
||||
|
||||
for _, c := range containers {
|
||||
// if the container is from a previous Compose application, use the existing service name
|
||||
serviceLabel, ok := c.Labels[api.ServiceLabel]
|
||||
if !ok {
|
||||
serviceLabel = getCanonicalContainerName(c)
|
||||
}
|
||||
service, ok := services[serviceLabel]
|
||||
if !ok {
|
||||
service = types.ServiceConfig{
|
||||
Name: serviceLabel,
|
||||
Image: c.Image,
|
||||
Labels: c.Labels,
|
||||
}
|
||||
}
|
||||
service.Scale = increment(service.Scale)
|
||||
|
||||
inspect, err := s.apiClient().ContainerInspect(context.Background(), c.ID)
|
||||
if err != nil {
|
||||
services[serviceLabel] = service
|
||||
continue
|
||||
}
|
||||
s.extractComposeConfiguration(&service, inspect, volumes, secrets, networks)
|
||||
service.Labels = cleanDockerPreviousLabels(service.Labels)
|
||||
services[serviceLabel] = service
|
||||
}
|
||||
|
||||
project.Services = services
|
||||
project.Networks = networks
|
||||
project.Volumes = volumes
|
||||
project.Secrets = secrets
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func (s *composeService) extractComposeConfiguration(service *types.ServiceConfig, inspect moby.ContainerJSON, volumes types.Volumes, secrets types.Secrets, networks types.Networks) {
|
||||
service.Environment = types.NewMappingWithEquals(inspect.Config.Env)
|
||||
if inspect.Config.Healthcheck != nil {
|
||||
healthConfig := inspect.Config.Healthcheck
|
||||
service.HealthCheck = s.toComposeHealthCheck(healthConfig)
|
||||
}
|
||||
if len(inspect.Mounts) > 0 {
|
||||
detectedVolumes, volumeConfigs, detectedSecrets, secretsConfigs := s.toComposeVolumes(inspect.Mounts)
|
||||
service.Volumes = append(service.Volumes, volumeConfigs...)
|
||||
service.Secrets = append(service.Secrets, secretsConfigs...)
|
||||
maps.Copy(volumes, detectedVolumes)
|
||||
maps.Copy(secrets, detectedSecrets)
|
||||
}
|
||||
if len(inspect.NetworkSettings.Networks) > 0 {
|
||||
detectedNetworks, networkConfigs := s.toComposeNetwork(inspect.NetworkSettings.Networks)
|
||||
service.Networks = networkConfigs
|
||||
maps.Copy(networks, detectedNetworks)
|
||||
}
|
||||
if len(inspect.HostConfig.PortBindings) > 0 {
|
||||
for key, portBindings := range inspect.HostConfig.PortBindings {
|
||||
for _, portBinding := range portBindings {
|
||||
service.Ports = append(service.Ports, types.ServicePortConfig{
|
||||
Target: uint32(key.Int()),
|
||||
Published: portBinding.HostPort,
|
||||
Protocol: key.Proto(),
|
||||
HostIP: portBinding.HostIP,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *composeService) toComposeHealthCheck(healthConfig *containerType.HealthConfig) *types.HealthCheckConfig {
|
||||
var healthCheck types.HealthCheckConfig
|
||||
healthCheck.Test = healthConfig.Test
|
||||
if healthConfig.Timeout != 0 {
|
||||
timeout := types.Duration(healthConfig.Timeout)
|
||||
healthCheck.Timeout = &timeout
|
||||
}
|
||||
if healthConfig.Interval != 0 {
|
||||
interval := types.Duration(healthConfig.Interval)
|
||||
healthCheck.Interval = &interval
|
||||
}
|
||||
if healthConfig.StartPeriod != 0 {
|
||||
startPeriod := types.Duration(healthConfig.StartPeriod)
|
||||
healthCheck.StartPeriod = &startPeriod
|
||||
}
|
||||
if healthConfig.StartInterval != 0 {
|
||||
startInterval := types.Duration(healthConfig.StartInterval)
|
||||
healthCheck.StartInterval = &startInterval
|
||||
}
|
||||
if healthConfig.Retries != 0 {
|
||||
retries := uint64(healthConfig.Retries)
|
||||
healthCheck.Retries = &retries
|
||||
}
|
||||
return &healthCheck
|
||||
}
|
||||
|
||||
func (s *composeService) toComposeVolumes(volumes []moby.MountPoint) (map[string]types.VolumeConfig,
|
||||
[]types.ServiceVolumeConfig, map[string]types.SecretConfig, []types.ServiceSecretConfig,
|
||||
) {
|
||||
volumeConfigs := make(map[string]types.VolumeConfig)
|
||||
secretConfigs := make(map[string]types.SecretConfig)
|
||||
var serviceVolumeConfigs []types.ServiceVolumeConfig
|
||||
var serviceSecretConfigs []types.ServiceSecretConfig
|
||||
|
||||
for _, volume := range volumes {
|
||||
serviceVC := types.ServiceVolumeConfig{
|
||||
Type: string(volume.Type),
|
||||
Source: volume.Source,
|
||||
Target: volume.Destination,
|
||||
ReadOnly: !volume.RW,
|
||||
}
|
||||
switch volume.Type {
|
||||
case mount.TypeVolume:
|
||||
serviceVC.Source = volume.Name
|
||||
vol := types.VolumeConfig{}
|
||||
if volume.Driver != "local" {
|
||||
vol.Driver = volume.Driver
|
||||
vol.Name = volume.Name
|
||||
}
|
||||
volumeConfigs[volume.Name] = vol
|
||||
serviceVolumeConfigs = append(serviceVolumeConfigs, serviceVC)
|
||||
case mount.TypeBind:
|
||||
if strings.HasPrefix(volume.Destination, "/run/secrets") {
|
||||
destination := strings.Split(volume.Destination, "/")
|
||||
secret := types.SecretConfig{
|
||||
Name: destination[len(destination)-1],
|
||||
File: strings.TrimPrefix(volume.Source, "/host_mnt"),
|
||||
}
|
||||
secretConfigs[secret.Name] = secret
|
||||
serviceSecretConfigs = append(serviceSecretConfigs, types.ServiceSecretConfig{
|
||||
Source: secret.Name,
|
||||
Target: volume.Destination,
|
||||
})
|
||||
} else {
|
||||
serviceVolumeConfigs = append(serviceVolumeConfigs, serviceVC)
|
||||
}
|
||||
}
|
||||
}
|
||||
return volumeConfigs, serviceVolumeConfigs, secretConfigs, serviceSecretConfigs
|
||||
}
|
||||
|
||||
func (s *composeService) toComposeNetwork(networks map[string]*network.EndpointSettings) (map[string]types.NetworkConfig, map[string]*types.ServiceNetworkConfig) {
|
||||
networkConfigs := make(map[string]types.NetworkConfig)
|
||||
serviceNetworkConfigs := make(map[string]*types.ServiceNetworkConfig)
|
||||
|
||||
for name, net := range networks {
|
||||
inspect, err := s.apiClient().NetworkInspect(context.Background(), name, network.InspectOptions{})
|
||||
if err != nil {
|
||||
networkConfigs[name] = types.NetworkConfig{}
|
||||
} else {
|
||||
networkConfigs[name] = types.NetworkConfig{
|
||||
Internal: inspect.Internal,
|
||||
}
|
||||
}
|
||||
serviceNetworkConfigs[name] = &types.ServiceNetworkConfig{
|
||||
Aliases: net.Aliases,
|
||||
}
|
||||
}
|
||||
return networkConfigs, serviceNetworkConfigs
|
||||
}
|
||||
|
||||
func cleanDockerPreviousLabels(labels types.Labels) types.Labels {
|
||||
cleanedLabels := types.Labels{}
|
||||
for key, value := range labels {
|
||||
if !strings.HasPrefix(key, "com.docker.compose.") && !strings.HasPrefix(key, "desktop.docker.io") {
|
||||
cleanedLabels[key] = value
|
||||
}
|
||||
}
|
||||
return cleanedLabels
|
||||
}
|
||||
@@ -33,6 +33,7 @@ func ServiceHash(o types.ServiceConfig) (string, error) {
|
||||
o.Deploy.Replicas = nil
|
||||
}
|
||||
o.DependsOn = nil
|
||||
o.Profiles = nil
|
||||
|
||||
bytes, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
@@ -40,3 +41,24 @@ func ServiceHash(o types.ServiceConfig) (string, error) {
|
||||
}
|
||||
return digest.SHA256.FromBytes(bytes).Encoded(), nil
|
||||
}
|
||||
|
||||
// NetworkHash computes the configuration hash for a network.
|
||||
func NetworkHash(o *types.NetworkConfig) (string, error) {
|
||||
bytes, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return digest.SHA256.FromBytes(bytes).Encoded(), nil
|
||||
}
|
||||
|
||||
// VolumeHash computes the configuration hash for a volume.
|
||||
func VolumeHash(o types.VolumeConfig) (string, error) {
|
||||
if o.Driver == "" { // (TODO: jhrotko) This probably should be fixed in compose-go
|
||||
o.Driver = "local"
|
||||
}
|
||||
bytes, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return digest.SHA256.FromBytes(bytes).Encoded(), nil
|
||||
}
|
||||
|
||||
122
pkg/compose/hook.go
Normal file
122
pkg/compose/hook.go
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
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"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
containerType "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
)
|
||||
|
||||
func (s composeService) runHook(ctx context.Context, container moby.Container, service types.ServiceConfig, hook types.ServiceHook, listener api.ContainerEventListener) error {
|
||||
wOut := utils.GetWriter(func(line string) {
|
||||
listener(api.ContainerEvent{
|
||||
Type: api.HookEventLog,
|
||||
Container: getContainerNameWithoutProject(container) + " ->",
|
||||
ID: container.ID,
|
||||
Service: service.Name,
|
||||
Line: line,
|
||||
})
|
||||
})
|
||||
defer wOut.Close() //nolint:errcheck
|
||||
|
||||
detached := listener == nil
|
||||
exec, err := s.apiClient().ContainerExecCreate(ctx, container.ID, containerType.ExecOptions{
|
||||
User: hook.User,
|
||||
Privileged: hook.Privileged,
|
||||
Env: ToMobyEnv(hook.Environment),
|
||||
WorkingDir: hook.WorkingDir,
|
||||
Cmd: hook.Command,
|
||||
Detach: detached,
|
||||
AttachStdout: !detached,
|
||||
AttachStderr: !detached,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if detached {
|
||||
return s.runWaitExec(ctx, exec, service, listener)
|
||||
}
|
||||
|
||||
height, width := s.stdout().GetTtySize()
|
||||
consoleSize := &[2]uint{height, width}
|
||||
attach, err := s.apiClient().ContainerExecAttach(ctx, exec.ID, containerType.ExecAttachOptions{
|
||||
Tty: service.Tty,
|
||||
ConsoleSize: consoleSize,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer attach.Close()
|
||||
|
||||
if service.Tty {
|
||||
_, err = io.Copy(wOut, attach.Reader)
|
||||
} else {
|
||||
_, err = stdcopy.StdCopy(wOut, wOut, attach.Reader)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
inspected, err := s.apiClient().ContainerExecInspect(ctx, exec.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if inspected.ExitCode != 0 {
|
||||
return fmt.Errorf("%s hook exited with status %d", service.Name, inspected.ExitCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s composeService) runWaitExec(ctx context.Context, exec moby.IDResponse, service types.ServiceConfig, listener api.ContainerEventListener) error {
|
||||
err := s.apiClient().ContainerExecStart(ctx, exec.ID, containerType.ExecStartOptions{
|
||||
Detach: listener == nil,
|
||||
Tty: service.Tty,
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We miss a ContainerExecWait API
|
||||
tick := time.NewTicker(100 * time.Millisecond)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-tick.C:
|
||||
inspect, err := s.apiClient().ContainerExecInspect(ctx, exec.ID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if !inspect.Running {
|
||||
if inspect.ExitCode != 0 {
|
||||
return fmt.Errorf("%s hook exited with status %d", service.Name, inspect.ExitCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,27 +48,25 @@ func (s *composeService) Images(ctx context.Context, projectName string, options
|
||||
for _, c := range allContainers {
|
||||
if utils.StringContains(options.Services, c.Labels[api.ServiceLabel]) {
|
||||
containers = append(containers, c)
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
containers = allContainers
|
||||
}
|
||||
|
||||
imageIDs := []string{}
|
||||
// aggregate image IDs
|
||||
images := []string{}
|
||||
for _, c := range containers {
|
||||
if !utils.StringContains(imageIDs, c.ImageID) {
|
||||
imageIDs = append(imageIDs, c.ImageID)
|
||||
if !utils.StringContains(images, c.Image) {
|
||||
images = append(images, c.Image)
|
||||
}
|
||||
}
|
||||
images, err := s.getImages(ctx, imageIDs)
|
||||
imageSummaries, err := s.getImageSummaries(ctx, images)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
summary := make([]api.ImageSummary, len(containers))
|
||||
for i, container := range containers {
|
||||
img, ok := images[container.ImageID]
|
||||
img, ok := imageSummaries[container.Image]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to retrieve image for container %s", getCanonicalContainerName(container))
|
||||
}
|
||||
@@ -79,34 +77,32 @@ func (s *composeService) Images(ctx context.Context, projectName string, options
|
||||
return summary, nil
|
||||
}
|
||||
|
||||
func (s *composeService) getImages(ctx context.Context, images []string) (map[string]api.ImageSummary, error) {
|
||||
func (s *composeService) getImageSummaries(ctx context.Context, repoTags []string) (map[string]api.ImageSummary, error) {
|
||||
summary := map[string]api.ImageSummary{}
|
||||
l := sync.Mutex{}
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
for _, img := range images {
|
||||
img := img
|
||||
for _, repoTag := range repoTags {
|
||||
repoTag := repoTag
|
||||
eg.Go(func() error {
|
||||
inspect, _, err := s.apiClient().ImageInspectWithRaw(ctx, img)
|
||||
inspect, _, err := s.apiClient().ImageInspectWithRaw(ctx, repoTag)
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unable to get image '%s': %w", img, err)
|
||||
return fmt.Errorf("unable to get image '%s': %w", repoTag, err)
|
||||
}
|
||||
tag := ""
|
||||
repository := ""
|
||||
if len(inspect.RepoTags) > 0 {
|
||||
ref, err := reference.ParseDockerRef(inspect.RepoTags[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repository = reference.FamiliarName(ref)
|
||||
if tagged, ok := ref.(reference.Tagged); ok {
|
||||
tag = tagged.Tag()
|
||||
}
|
||||
ref, err := reference.ParseDockerRef(repoTag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repository = reference.FamiliarName(ref)
|
||||
if tagged, ok := ref.(reference.Tagged); ok {
|
||||
tag = tagged.Tag()
|
||||
}
|
||||
l.Lock()
|
||||
summary[img] = api.ImageSummary{
|
||||
summary[repoTag] = api.ImageSummary{
|
||||
ID: inspect.ID,
|
||||
Repository: repository,
|
||||
Tag: tag,
|
||||
|
||||
103
pkg/compose/images_test.go
Normal file
103
pkg/compose/images_test.go
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
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 (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
containerType "github.com/docker/docker/api/types/container"
|
||||
"go.uber.org/mock/gomock"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
compose "github.com/docker/compose/v2/pkg/api"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
)
|
||||
|
||||
func TestImages(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
api, cli := prepareMocks(mockCtrl)
|
||||
tested := composeService{
|
||||
dockerCli: cli,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
args := filters.NewArgs(projectFilter(strings.ToLower(testProject)))
|
||||
listOpts := containerType.ListOptions{All: true, Filters: args}
|
||||
image1 := imageInspect("image1", "foo:1", 12345)
|
||||
image2 := imageInspect("image2", "bar:2", 67890)
|
||||
api.EXPECT().ImageInspectWithRaw(anyCancellableContext(), "foo:1").Return(image1, nil, nil)
|
||||
api.EXPECT().ImageInspectWithRaw(anyCancellableContext(), "bar:2").Return(image2, nil, nil)
|
||||
c1 := containerDetail("service1", "123", "running", "foo:1")
|
||||
c2 := containerDetail("service1", "456", "running", "bar:2")
|
||||
c2.Ports = []moby.Port{{PublicPort: 80, PrivatePort: 90, IP: "localhost"}}
|
||||
c3 := containerDetail("service2", "789", "exited", "foo:1")
|
||||
api.EXPECT().ContainerList(ctx, listOpts).Return([]moby.Container{c1, c2, c3}, nil)
|
||||
|
||||
images, err := tested.Images(ctx, strings.ToLower(testProject), compose.ImagesOptions{})
|
||||
|
||||
expected := []compose.ImageSummary{
|
||||
{
|
||||
ID: "image1",
|
||||
ContainerName: "123",
|
||||
Repository: "foo",
|
||||
Tag: "1",
|
||||
Size: 12345,
|
||||
},
|
||||
{
|
||||
ID: "image2",
|
||||
ContainerName: "456",
|
||||
Repository: "bar",
|
||||
Tag: "2",
|
||||
Size: 67890,
|
||||
},
|
||||
{
|
||||
ID: "image1",
|
||||
ContainerName: "789",
|
||||
Repository: "foo",
|
||||
Tag: "1",
|
||||
Size: 12345,
|
||||
},
|
||||
}
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, images, expected)
|
||||
}
|
||||
|
||||
func imageInspect(id string, image string, size int64) moby.ImageInspect {
|
||||
return moby.ImageInspect{
|
||||
ID: id,
|
||||
RepoTags: []string{
|
||||
"someRepo:someTag",
|
||||
image,
|
||||
},
|
||||
Size: size,
|
||||
}
|
||||
}
|
||||
|
||||
func containerDetail(service string, id string, status string, image string) moby.Container {
|
||||
return moby.Container{
|
||||
ID: id,
|
||||
Names: []string{"/" + id},
|
||||
Image: image,
|
||||
Labels: containerLabels(service, false),
|
||||
State: status,
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,8 @@ func containerLabels(service string, oneOff bool) map[string]string {
|
||||
compose.ServiceLabel: service,
|
||||
compose.ConfigFilesLabel: composefile,
|
||||
compose.WorkingDirLabel: workingdir,
|
||||
compose.ProjectLabel: strings.ToLower(testProject)}
|
||||
compose.ProjectLabel: strings.ToLower(testProject),
|
||||
}
|
||||
if oneOff {
|
||||
labels[compose.OneoffLabel] = "True"
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ func (s *composeService) Logs(
|
||||
consumer api.LogConsumer,
|
||||
options api.LogOptions,
|
||||
) error {
|
||||
|
||||
var containers Containers
|
||||
var err error
|
||||
|
||||
|
||||
@@ -54,7 +54,6 @@ func (s *composeService) pause(ctx context.Context, projectName string, options
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
})
|
||||
return eg.Wait()
|
||||
}
|
||||
@@ -86,7 +85,6 @@ func (s *composeService) unPause(ctx context.Context, projectName string, option
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
})
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ func (p *printer) Run(cascade api.Cascade, exitCodeFrom string, stopFn func() er
|
||||
case api.UserCancel:
|
||||
aborting = true
|
||||
case api.ContainerEventAttach:
|
||||
if _, ok := containers[id]; ok {
|
||||
if attached, ok := containers[id]; ok && attached {
|
||||
continue
|
||||
}
|
||||
containers[id] = true
|
||||
@@ -148,7 +148,7 @@ func (p *printer) Run(cascade api.Cascade, exitCodeFrom string, stopFn func() er
|
||||
// Last container terminated, done
|
||||
return exitCode, nil
|
||||
}
|
||||
case api.ContainerEventLog:
|
||||
case api.ContainerEventLog, api.HookEventLog:
|
||||
if !aborting {
|
||||
p.consumer.Log(container, event.Line)
|
||||
}
|
||||
|
||||
@@ -55,7 +55,8 @@ func TestPs(t *testing.T) {
|
||||
containers, err := tested.Ps(ctx, strings.ToLower(testProject), compose.PsOptions{})
|
||||
|
||||
expected := []compose.ContainerSummary{
|
||||
{ID: "123", Name: "123", Names: []string{"/123"}, Image: "foo", Project: strings.ToLower(testProject), Service: "service1",
|
||||
{
|
||||
ID: "123", Name: "123", Names: []string{"/123"}, Image: "foo", Project: strings.ToLower(testProject), Service: "service1",
|
||||
State: "running", Health: "healthy", Publishers: []compose.PortPublisher{},
|
||||
Labels: map[string]string{
|
||||
compose.ProjectLabel: strings.ToLower(testProject),
|
||||
@@ -64,7 +65,8 @@ func TestPs(t *testing.T) {
|
||||
compose.ServiceLabel: "service1",
|
||||
},
|
||||
},
|
||||
{ID: "456", Name: "456", Names: []string{"/456"}, Image: "foo", Project: strings.ToLower(testProject), Service: "service1",
|
||||
{
|
||||
ID: "456", Name: "456", Names: []string{"/456"}, Image: "foo", Project: strings.ToLower(testProject), Service: "service1",
|
||||
State: "running", Health: "",
|
||||
Publishers: []compose.PortPublisher{{URL: "localhost", TargetPort: 90, PublishedPort: 80}},
|
||||
Labels: map[string]string{
|
||||
@@ -74,7 +76,8 @@ func TestPs(t *testing.T) {
|
||||
compose.ServiceLabel: "service1",
|
||||
},
|
||||
},
|
||||
{ID: "789", Name: "789", Names: []string{"/789"}, Image: "foo", Project: strings.ToLower(testProject), Service: "service2",
|
||||
{
|
||||
ID: "789", Name: "789", Names: []string{"/789"}, Image: "foo", Project: strings.ToLower(testProject), Service: "service2",
|
||||
State: "exited", Health: "", ExitCode: 130, Publishers: []compose.PortPublisher{},
|
||||
Labels: map[string]string{
|
||||
compose.ProjectLabel: strings.ToLower(testProject),
|
||||
|
||||
@@ -35,7 +35,7 @@ func (s *composeService) Publish(ctx context.Context, project *types.Project, re
|
||||
}
|
||||
|
||||
func (s *composeService) publish(ctx context.Context, project *types.Project, repository string, options api.PublishOptions) error {
|
||||
err := s.Push(ctx, project, api.PushOptions{})
|
||||
err := s.Push(ctx, project, api.PushOptions{IgnoreFailures: true, ImageMandatory: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -42,9 +42,6 @@ import (
|
||||
)
|
||||
|
||||
func (s *composeService) Pull(ctx context.Context, project *types.Project, options api.PullOptions) error {
|
||||
if options.Quiet {
|
||||
return s.pull(ctx, project, options)
|
||||
}
|
||||
return progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
||||
return s.pull(ctx, project, options)
|
||||
}, s.stdinfo(), "Pulling")
|
||||
@@ -118,7 +115,7 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
|
||||
|
||||
idx, name, service := i, name, service
|
||||
eg.Go(func() error {
|
||||
_, err := s.pullServiceImage(ctx, service, s.configFile(), w, false, project.Environment["DOCKER_DEFAULT_PLATFORM"])
|
||||
_, err := s.pullServiceImage(ctx, service, s.configFile(), w, opts.Quiet, project.Environment["DOCKER_DEFAULT_PLATFORM"])
|
||||
if err != nil {
|
||||
pullErrors[idx] = err
|
||||
if service.Build != nil {
|
||||
@@ -178,7 +175,8 @@ func getUnwrappedErrorMessage(err error) string {
|
||||
}
|
||||
|
||||
func (s *composeService) pullServiceImage(ctx context.Context, service types.ServiceConfig,
|
||||
configFile driver.Auth, w progress.Writer, quietPull bool, defaultPlatform string) (string, error) {
|
||||
configFile driver.Auth, w progress.Writer, quietPull bool, defaultPlatform string,
|
||||
) (string, error) {
|
||||
w.Event(progress.Event{
|
||||
ID: service.Name,
|
||||
Status: progress.Working,
|
||||
|
||||
@@ -62,6 +62,9 @@ func (s *composeService) push(ctx context.Context, project *types.Project, optio
|
||||
w := progress.ContextWriter(ctx)
|
||||
for _, service := range project.Services {
|
||||
if service.Build == nil || service.Image == "" {
|
||||
if options.ImageMandatory && service.Image == "" {
|
||||
return fmt.Errorf("%q attribute is mandatory to push an image for service %q", "service.image", service.Name)
|
||||
}
|
||||
w.Event(progress.Event{
|
||||
ID: service.Name,
|
||||
Status: progress.Done,
|
||||
|
||||
@@ -81,6 +81,7 @@ func (s *composeService) Remove(ctx context.Context, projectName string, options
|
||||
_, _ = fmt.Fprintln(s.stdinfo(), "No stopped containers")
|
||||
return nil
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("Going to remove %s", strings.Join(names, ", "))
|
||||
if options.Force {
|
||||
_, _ = fmt.Fprintln(s.stdout(), msg)
|
||||
|
||||
@@ -93,7 +93,7 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
|
||||
}
|
||||
|
||||
if !opts.NoDeps {
|
||||
if err := s.waitDependencies(ctx, project, service.Name, service.DependsOn, observedState); err != nil {
|
||||
if err := s.waitDependencies(ctx, project, service.Name, service.DependsOn, observedState, 0); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
@@ -104,12 +104,12 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
|
||||
Labels: mergeLabels(service.Labels, service.CustomLabels),
|
||||
}
|
||||
|
||||
err = newConvergence(project.ServiceNames(), observedState, s).resolveServiceReferences(&service)
|
||||
err = newConvergence(project.ServiceNames(), observedState, nil, nil, s).resolveServiceReferences(&service)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
created, err := s.createContainer(ctx, project, service, service.ContainerName, 1, createOpts)
|
||||
created, err := s.createContainer(ctx, project, service, service.ContainerName, -1, createOpts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -31,6 +31,5 @@ func (s *composeService) Scale(ctx context.Context, project *types.Project, opti
|
||||
return err
|
||||
}
|
||||
return s.start(ctx, project.Name, api.StartOptions{Project: project, Services: options.Services}, nil)
|
||||
|
||||
}), s.stdinfo())
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ func (s *composeService) start(ctx context.Context, projectName string, options
|
||||
return err
|
||||
}
|
||||
|
||||
return s.startService(ctx, project, service, containers)
|
||||
return s.startService(ctx, project, service, containers, listener, options.WaitTimeout)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -149,7 +149,7 @@ func (s *composeService) start(ctx context.Context, projectName string, options
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
err = s.waitDependencies(ctx, project, project.Name, depends, containers)
|
||||
err = s.waitDependencies(ctx, project, project.Name, depends, containers, 0)
|
||||
if err != nil {
|
||||
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
||||
return fmt.Errorf("application not healthy after %s", options.WaitTimeout)
|
||||
@@ -180,7 +180,8 @@ type containerWatchFn func(container moby.Container, t time.Time) error
|
||||
// watchContainers uses engine events to capture container start/die and notify ContainerEventListener
|
||||
func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
|
||||
projectName string, services, required []string,
|
||||
listener api.ContainerEventListener, containers Containers, onStart, onRecreate containerWatchFn) error {
|
||||
listener api.ContainerEventListener, containers Containers, onStart, onRecreate containerWatchFn,
|
||||
) error {
|
||||
if len(containers) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -214,13 +215,13 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
|
||||
}
|
||||
|
||||
var (
|
||||
expected []string
|
||||
expected = utils.NewSet[string]()
|
||||
watched = map[string]int{}
|
||||
replaced []string
|
||||
)
|
||||
for _, c := range containers {
|
||||
if isRequired(c) {
|
||||
expected = append(expected, c.ID)
|
||||
expected.Add(c.ID)
|
||||
}
|
||||
watched[c.ID] = 0
|
||||
}
|
||||
@@ -242,7 +243,7 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
|
||||
// be able to inspect in time before they're gone from the
|
||||
// API, so just remove the watch without erroring
|
||||
delete(watched, event.Container)
|
||||
expected = utils.Remove(expected, event.Container)
|
||||
expected.Remove(event.Container)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
@@ -253,7 +254,6 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
|
||||
Labels: inspected.Config.Labels,
|
||||
}
|
||||
name := getContainerNameWithoutProject(container)
|
||||
|
||||
service := container.Labels[api.ServiceLabel]
|
||||
switch event.Status {
|
||||
case "stop":
|
||||
@@ -278,7 +278,7 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
|
||||
}
|
||||
|
||||
delete(watched, container.ID)
|
||||
expected = utils.Remove(expected, container.ID)
|
||||
expected.Remove(container.ID)
|
||||
case "die":
|
||||
restarted := watched[container.ID]
|
||||
watched[container.ID] = restarted + 1
|
||||
@@ -308,7 +308,7 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
|
||||
if !willRestart {
|
||||
// we're done with this one
|
||||
delete(watched, container.ID)
|
||||
expected = utils.Remove(expected, container.ID)
|
||||
expected.Remove(container.ID)
|
||||
}
|
||||
case "start":
|
||||
count, ok := watched[container.ID]
|
||||
@@ -316,7 +316,7 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
|
||||
if !ok {
|
||||
// A new container has just been added to service by scale
|
||||
watched[container.ID] = 0
|
||||
expected = append(expected, container.ID)
|
||||
expected.Add(container.ID)
|
||||
mustAttach = true
|
||||
}
|
||||
if mustAttach {
|
||||
@@ -333,17 +333,15 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if utils.StringContains(expected, id) {
|
||||
expected = append(expected, inspected.ID)
|
||||
if expected.Has(id) {
|
||||
expected.Add(inspected.ID)
|
||||
expected.Add(container.ID)
|
||||
}
|
||||
watched[container.ID] = 1
|
||||
if utils.Contains(expected, id) {
|
||||
expected = append(expected, container.ID)
|
||||
}
|
||||
} else if ofInterest(container) {
|
||||
watched[container.ID] = 1
|
||||
if isRequired(container) {
|
||||
expected = append(expected, container.ID)
|
||||
expected.Add(container.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ func (s *composeService) stop(ctx context.Context, projectName string, options a
|
||||
if !utils.StringContains(options.Services, service) {
|
||||
return nil
|
||||
}
|
||||
return s.stopContainers(ctx, w, containers.filter(isService(service)).filter(isNotOneOff), options.Timeout)
|
||||
serv := project.Services[service]
|
||||
return s.stopContainers(ctx, w, &serv, containers.filter(isService(service)).filter(isNotOneOff), options.Timeout)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -98,10 +98,9 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
|
||||
defer keyboard.Close() //nolint:errcheck
|
||||
isWatchConfigured := s.shouldWatch(project)
|
||||
isDockerDesktopActive := s.isDesktopIntegrationActive()
|
||||
isDockerDesktopComposeUI := s.isDesktopUIEnabled()
|
||||
tracing.KeyboardMetrics(ctx, options.Start.NavigationMenu, isDockerDesktopActive, isWatchConfigured, isDockerDesktopComposeUI)
|
||||
tracing.KeyboardMetrics(ctx, options.Start.NavigationMenu, isDockerDesktopActive, isWatchConfigured)
|
||||
|
||||
formatter.NewKeyboardManager(ctx, isDockerDesktopActive, isWatchConfigured, isDockerDesktopComposeUI, signalChan, s.watch)
|
||||
formatter.NewKeyboardManager(ctx, isDockerDesktopActive, isWatchConfigured, signalChan, s.watch)
|
||||
if options.Start.Watch {
|
||||
formatter.KeyboardManager.StartWatch(ctx, doneCh, project, options)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
compose "github.com/docker/compose/v2/pkg/api"
|
||||
@@ -128,7 +129,7 @@ func TestViz(t *testing.T) {
|
||||
IncludeImageName: false,
|
||||
IncludeNetworks: false,
|
||||
})
|
||||
assert.NoError(t, err, "viz command failed")
|
||||
require.NoError(t, err, "viz command failed")
|
||||
|
||||
// check indentation
|
||||
assert.Contains(t, graphStr, "\n ", graphStr)
|
||||
@@ -187,7 +188,7 @@ func TestViz(t *testing.T) {
|
||||
IncludeImageName: true,
|
||||
IncludeNetworks: true,
|
||||
})
|
||||
assert.NoError(t, err, "viz command failed")
|
||||
require.NoError(t, err, "viz command failed")
|
||||
|
||||
// check indentation
|
||||
assert.Contains(t, graphStr, "\n\t", graphStr)
|
||||
|
||||
@@ -23,12 +23,12 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
ccli "github.com/docker/cli/cli/command/container"
|
||||
pathutil "github.com/docker/compose/v2/internal/paths"
|
||||
"github.com/docker/compose/v2/internal/sync"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@@ -48,7 +48,7 @@ const quietPeriod = 500 * time.Millisecond
|
||||
// fileEvent contains the Compose service and modified host system path.
|
||||
type fileEvent struct {
|
||||
sync.PathMapping
|
||||
Action types.WatchAction
|
||||
Trigger types.Trigger
|
||||
}
|
||||
|
||||
// getSyncImplementation returns an appropriate sync implementation for the
|
||||
@@ -69,6 +69,7 @@ func (s *composeService) getSyncImplementation(project *types.Project) (sync.Syn
|
||||
|
||||
return sync.NewTar(project.Name, tarDockerClient{s: s}), nil
|
||||
}
|
||||
|
||||
func (s *composeService) shouldWatch(project *types.Project) bool {
|
||||
var shouldWatch bool
|
||||
for i := range project.Services {
|
||||
@@ -84,6 +85,7 @@ func (s *composeService) shouldWatch(project *types.Project) bool {
|
||||
func (s *composeService) Watch(ctx context.Context, project *types.Project, services []string, options api.WatchOptions) error {
|
||||
return s.watch(ctx, nil, project, services, options)
|
||||
}
|
||||
|
||||
func (s *composeService) watch(ctx context.Context, syncChannel chan bool, project *types.Project, services []string, options api.WatchOptions) error { //nolint: gocyclo
|
||||
var err error
|
||||
if project, err = project.WithSelectedServices(services); err != nil {
|
||||
@@ -119,24 +121,13 @@ func (s *composeService) watch(ctx context.Context, syncChannel chan bool, proje
|
||||
if options.Build == nil {
|
||||
return fmt.Errorf("--no-build is incompatible with watch action %s in service %s", types.WatchActionRebuild, service.Name)
|
||||
}
|
||||
// set the service to always be built - watch triggers `Up()` when it receives a rebuild event
|
||||
service.PullPolicy = types.PullPolicyBuild
|
||||
project.Services[i] = service
|
||||
}
|
||||
}
|
||||
|
||||
if len(services) > 0 && service.Build == nil {
|
||||
// service explicitly selected for watch has no build section
|
||||
return fmt.Errorf("can't watch service %q without a build context", service.Name)
|
||||
}
|
||||
|
||||
if len(services) == 0 && service.Build == nil {
|
||||
logrus.Debugf("service %q has no build context, skipping watch", service.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
// set the service to always be built - watch triggers `Up()` when it receives a rebuild event
|
||||
service.PullPolicy = types.PullPolicyBuild
|
||||
project.Services[i] = service
|
||||
|
||||
dockerIgnores, err := watch.LoadDockerIgnore(service.Build.Context)
|
||||
dockerIgnores, err := watch.LoadDockerIgnore(service.Build)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -156,13 +147,13 @@ func (s *composeService) watch(ctx context.Context, syncChannel chan bool, proje
|
||||
|
||||
var paths, pathLogs []string
|
||||
for _, trigger := range config.Watch {
|
||||
if trigger.Action != types.WatchActionRebuild && checkIfPathAlreadyBindMounted(trigger.Path, service.Volumes) {
|
||||
if isSync(trigger) && checkIfPathAlreadyBindMounted(trigger.Path, service.Volumes) {
|
||||
logrus.Warnf("path '%s' also declared by a bind mount volume, this path won't be monitored!\n", trigger.Path)
|
||||
continue
|
||||
} else {
|
||||
var initialSync bool
|
||||
success, err := trigger.Extensions.Get("x-initialSync", &initialSync)
|
||||
if err == nil && success && initialSync && (trigger.Action == types.WatchActionSync || trigger.Action == types.WatchActionSyncRestart) {
|
||||
if err == nil && success && initialSync && isSync(trigger) {
|
||||
// Need to check initial files are in container that are meant to be synched from watch action
|
||||
err := s.initialSync(ctx, project, service, trigger, ignore, syncer)
|
||||
if err != nil {
|
||||
@@ -213,6 +204,10 @@ func (s *composeService) watch(ctx context.Context, syncChannel chan bool, proje
|
||||
}
|
||||
}
|
||||
|
||||
func isSync(trigger types.Trigger) bool {
|
||||
return trigger.Action == types.WatchActionSync || trigger.Action == types.WatchActionSyncRestart
|
||||
}
|
||||
|
||||
func (s *composeService) watchEvents(ctx context.Context, project *types.Project, name string, options api.WatchOptions, watcher watch.Notify, syncer sync.Syncer, triggers []types.Trigger) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
@@ -294,11 +289,11 @@ func maybeFileEvent(trigger types.Trigger, hostPath string, ignore watch.PathMat
|
||||
return nil
|
||||
}
|
||||
// always use Unix-style paths for inside the container
|
||||
containerPath = path.Join(trigger.Target, rel)
|
||||
containerPath = path.Join(trigger.Target, filepath.ToSlash(rel))
|
||||
}
|
||||
|
||||
return &fileEvent{
|
||||
Action: trigger.Action,
|
||||
Trigger: trigger,
|
||||
PathMapping: sync.PathMapping{
|
||||
HostPath: hostPath,
|
||||
ContainerPath: containerPath,
|
||||
@@ -336,7 +331,10 @@ func loadDevelopmentConfig(service types.ServiceConfig, project *types.Project)
|
||||
}
|
||||
|
||||
if trigger.Action == types.WatchActionRebuild && service.Build == nil {
|
||||
return nil, fmt.Errorf("service %s doesn't have a build section, can't apply 'rebuild' on watch", service.Name)
|
||||
return nil, fmt.Errorf("service %s doesn't have a build section, can't apply %s on watch", types.WatchActionRebuild, service.Name)
|
||||
}
|
||||
if trigger.Action == types.WatchActionSyncExec && len(trigger.Exec.Command) == 0 {
|
||||
return nil, fmt.Errorf("can't watch with action %q on service %s wihtout a command", types.WatchActionSyncExec, service.Name)
|
||||
}
|
||||
|
||||
config.Watch[i] = trigger
|
||||
@@ -352,24 +350,17 @@ func batchDebounceEvents(ctx context.Context, clock clockwork.Clock, delay time.
|
||||
out := make(chan []fileEvent)
|
||||
go func() {
|
||||
defer close(out)
|
||||
seen := make(map[fileEvent]time.Time)
|
||||
seen := make(map[string]fileEvent)
|
||||
flushEvents := func() {
|
||||
if len(seen) == 0 {
|
||||
return
|
||||
}
|
||||
events := make([]fileEvent, 0, len(seen))
|
||||
for e := range seen {
|
||||
for _, e := range seen {
|
||||
events = append(events, e)
|
||||
}
|
||||
// sort batch by oldest -> newest
|
||||
// (if an event is seen > 1 per batch, it gets the latest timestamp)
|
||||
sort.SliceStable(events, func(i, j int) bool {
|
||||
x := events[i]
|
||||
y := events[j]
|
||||
return seen[x].Before(seen[y])
|
||||
})
|
||||
out <- events
|
||||
seen = make(map[fileEvent]time.Time)
|
||||
seen = make(map[string]fileEvent)
|
||||
}
|
||||
|
||||
t := clock.NewTicker(delay)
|
||||
@@ -386,7 +377,10 @@ func batchDebounceEvents(ctx context.Context, clock clockwork.Clock, delay time.
|
||||
flushEvents()
|
||||
return
|
||||
}
|
||||
seen[e] = time.Now()
|
||||
if _, ok := seen[e.HostPath]; !ok {
|
||||
// already know updated path, first rule in watch configuration wins
|
||||
seen[e.HostPath] = e
|
||||
}
|
||||
t.Reset(delay)
|
||||
}
|
||||
}
|
||||
@@ -484,70 +478,130 @@ func (t tarDockerClient) Untar(ctx context.Context, id string, archive io.ReadCl
|
||||
func (s *composeService) handleWatchBatch(ctx context.Context, project *types.Project, serviceName string, options api.WatchOptions, batch []fileEvent, syncer sync.Syncer) error {
|
||||
pathMappings := make([]sync.PathMapping, len(batch))
|
||||
restartService := false
|
||||
syncService := false
|
||||
for i := range batch {
|
||||
if batch[i].Action == types.WatchActionRebuild {
|
||||
options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Rebuilding service %q after changes were detected...", serviceName))
|
||||
// restrict the build to ONLY this service, not any of its dependencies
|
||||
options.Build.Services = []string{serviceName}
|
||||
imageNameToIdMap, err := s.build(ctx, project, *options.Build, nil)
|
||||
|
||||
if err != nil {
|
||||
options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Build failed. Error: %v", err))
|
||||
return err
|
||||
}
|
||||
|
||||
if options.Prune {
|
||||
s.pruneDanglingImagesOnRebuild(ctx, project.Name, imageNameToIdMap)
|
||||
}
|
||||
|
||||
options.LogTo.Log(api.WatchLogger, fmt.Sprintf("service %q successfully built", serviceName))
|
||||
|
||||
err = s.create(ctx, project, api.CreateOptions{
|
||||
Services: []string{serviceName},
|
||||
Inherit: true,
|
||||
Recreate: api.RecreateForce,
|
||||
})
|
||||
if err != nil {
|
||||
options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Failed to recreate service after update. Error: %v", err))
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.start(ctx, project.Name, api.StartOptions{
|
||||
Project: project,
|
||||
Services: []string{serviceName},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Application failed to start after update. Error: %v", err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if batch[i].Action == types.WatchActionSyncRestart {
|
||||
switch batch[i].Trigger.Action {
|
||||
case types.WatchActionRebuild:
|
||||
return s.rebuild(ctx, project, serviceName, options)
|
||||
case types.WatchActionSync, types.WatchActionSyncExec:
|
||||
syncService = true
|
||||
case types.WatchActionSyncRestart:
|
||||
restartService = true
|
||||
syncService = true
|
||||
case types.WatchActionRestart:
|
||||
restartService = true
|
||||
}
|
||||
pathMappings[i] = batch[i].PathMapping
|
||||
}
|
||||
|
||||
writeWatchSyncMessage(options.LogTo, serviceName, pathMappings)
|
||||
writeWatchSyncMessage(options.LogTo, serviceName, pathMappings, restartService)
|
||||
|
||||
service, err := project.GetService(serviceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := syncer.Sync(ctx, service, pathMappings); err != nil {
|
||||
return err
|
||||
if syncService {
|
||||
if err := syncer.Sync(ctx, service, pathMappings); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if restartService {
|
||||
return s.Restart(ctx, project.Name, api.RestartOptions{
|
||||
err = s.restart(ctx, project.Name, api.RestartOptions{
|
||||
Services: []string{serviceName},
|
||||
Project: project,
|
||||
NoDeps: false,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options.LogTo.Log(
|
||||
api.WatchLogger,
|
||||
fmt.Sprintf("service %q restarted", serviceName))
|
||||
}
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
for _, b := range batch {
|
||||
if b.Trigger.Action == types.WatchActionSyncExec {
|
||||
err := s.exec(ctx, project, serviceName, b.Trigger.Exec, eg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
func (s *composeService) exec(ctx context.Context, project *types.Project, serviceName string, x types.ServiceHook, eg *errgroup.Group) error {
|
||||
containers, err := s.getContainers(ctx, project.Name, oneOffExclude, false, serviceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, c := range containers {
|
||||
eg.Go(func() error {
|
||||
exec := ccli.NewExecOptions()
|
||||
exec.User = x.User
|
||||
exec.Privileged = x.Privileged
|
||||
exec.Command = x.Command
|
||||
exec.Workdir = x.WorkingDir
|
||||
for _, v := range x.Environment.ToMapping().Values() {
|
||||
err := exec.Env.Set(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return ccli.RunExec(ctx, s.dockerCli, c.ID, exec)
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *composeService) rebuild(ctx context.Context, project *types.Project, serviceName string, options api.WatchOptions) error {
|
||||
options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Rebuilding service %q after changes were detected...", serviceName))
|
||||
// restrict the build to ONLY this service, not any of its dependencies
|
||||
options.Build.Services = []string{serviceName}
|
||||
imageNameToIdMap, err := s.build(ctx, project, *options.Build, nil)
|
||||
if err != nil {
|
||||
options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Build failed. Error: %v", err))
|
||||
return err
|
||||
}
|
||||
|
||||
if options.Prune {
|
||||
s.pruneDanglingImagesOnRebuild(ctx, project.Name, imageNameToIdMap)
|
||||
}
|
||||
|
||||
options.LogTo.Log(api.WatchLogger, fmt.Sprintf("service %q successfully built", serviceName))
|
||||
|
||||
err = s.create(ctx, project, api.CreateOptions{
|
||||
Services: []string{serviceName},
|
||||
Inherit: true,
|
||||
Recreate: api.RecreateForce,
|
||||
})
|
||||
if err != nil {
|
||||
options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Failed to recreate service after update. Error: %v", err))
|
||||
return err
|
||||
}
|
||||
|
||||
services := []string{serviceName}
|
||||
p, err := project.WithSelectedServices(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.start(ctx, project.Name, api.StartOptions{
|
||||
Project: p,
|
||||
Services: services,
|
||||
AttachTo: services,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Application failed to start after update. Error: %v", err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeWatchSyncMessage prints out a message about the sync for the changed paths.
|
||||
func writeWatchSyncMessage(log api.LogConsumer, serviceName string, pathMappings []sync.PathMapping) {
|
||||
func writeWatchSyncMessage(log api.LogConsumer, serviceName string, pathMappings []sync.PathMapping, restart bool) {
|
||||
action := "Syncing"
|
||||
if restart {
|
||||
action = "Syncing and restarting"
|
||||
}
|
||||
if logrus.IsLevelEnabled(logrus.DebugLevel) {
|
||||
hostPathsToSync := make([]string, len(pathMappings))
|
||||
for i := range pathMappings {
|
||||
@@ -556,7 +610,8 @@ func writeWatchSyncMessage(log api.LogConsumer, serviceName string, pathMappings
|
||||
log.Log(
|
||||
api.WatchLogger,
|
||||
fmt.Sprintf(
|
||||
"Syncing %q after changes were detected: %s",
|
||||
"%s service %q after changes were detected: %s",
|
||||
action,
|
||||
serviceName,
|
||||
strings.Join(hostPathsToSync, ", "),
|
||||
),
|
||||
@@ -564,7 +619,7 @@ func writeWatchSyncMessage(log api.LogConsumer, serviceName string, pathMappings
|
||||
} else {
|
||||
log.Log(
|
||||
api.WatchLogger,
|
||||
fmt.Sprintf("Syncing service %q after %d changes were detected", serviceName, len(pathMappings)),
|
||||
fmt.Sprintf("%s service %q after %d changes were detected", action, serviceName, len(pathMappings)),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -576,7 +631,6 @@ func (s *composeService) pruneDanglingImagesOnRebuild(ctx context.Context, proje
|
||||
filters.Arg("label", api.ProjectLabel+"="+projectName),
|
||||
),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to list images: %v", err)
|
||||
return
|
||||
|
||||
@@ -18,6 +18,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -42,23 +44,32 @@ func TestDebounceBatching(t *testing.T) {
|
||||
ctx, stop := context.WithCancel(context.Background())
|
||||
t.Cleanup(stop)
|
||||
|
||||
trigger := types.Trigger{
|
||||
Path: "/",
|
||||
}
|
||||
matcher := watch.EmptyMatcher{}
|
||||
eventBatchCh := batchDebounceEvents(ctx, clock, quietPeriod, ch)
|
||||
for i := 0; i < 100; i++ {
|
||||
var action types.WatchAction = "a"
|
||||
path := "/a"
|
||||
if i%2 == 0 {
|
||||
action = "b"
|
||||
path = "/b"
|
||||
}
|
||||
ch <- fileEvent{Action: action}
|
||||
|
||||
event := maybeFileEvent(trigger, path, matcher)
|
||||
require.NotNil(t, event)
|
||||
ch <- *event
|
||||
}
|
||||
// we sent 100 events + the debouncer
|
||||
clock.BlockUntil(101)
|
||||
clock.Advance(quietPeriod)
|
||||
select {
|
||||
case batch := <-eventBatchCh:
|
||||
require.ElementsMatch(t, batch, []fileEvent{
|
||||
{Action: "a"},
|
||||
{Action: "b"},
|
||||
slices.SortFunc(batch, func(a, b fileEvent) int {
|
||||
return strings.Compare(a.HostPath, b.HostPath)
|
||||
})
|
||||
assert.Equal(t, len(batch), 2)
|
||||
assert.Equal(t, batch[0].HostPath, "/a")
|
||||
assert.Equal(t, batch[1].HostPath, "/b")
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
t.Fatal("timed out waiting for events")
|
||||
}
|
||||
@@ -110,14 +121,12 @@ func (s stdLogger) Status(container, msg string) {
|
||||
}
|
||||
|
||||
func (s stdLogger) Register(container string) {
|
||||
|
||||
}
|
||||
|
||||
func TestWatch_Sync(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
cli := mocks.NewMockCli(mockCtrl)
|
||||
cli.EXPECT().Err().Return(streams.NewOut(os.Stderr)).AnyTimes()
|
||||
cli.EXPECT().BuildKitEnabled().Return(true, nil)
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
apiClient.EXPECT().ContainerList(gomock.Any(), gomock.Any()).Return([]moby.Container{
|
||||
testContainer("test", "123", false),
|
||||
|
||||
@@ -32,9 +32,8 @@ import (
|
||||
)
|
||||
|
||||
func TestLocalComposeBuild(t *testing.T) {
|
||||
|
||||
for _, env := range []string{"DOCKER_BUILDKIT=0", "DOCKER_BUILDKIT=1"} {
|
||||
c := NewCLI(t, WithEnv(env))
|
||||
for _, env := range []string{"DOCKER_BUILDKIT=0", "DOCKER_BUILDKIT=1", "DOCKER_BUILDKIT=1,COMPOSE-BAKE=1"} {
|
||||
c := NewCLI(t, WithEnv(strings.Split(env, ",")...))
|
||||
|
||||
t.Run(env+" build named and unnamed images", func(t *testing.T) {
|
||||
// ensure local test run does not reuse previously build image
|
||||
@@ -135,7 +134,6 @@ func TestLocalComposeBuild(t *testing.T) {
|
||||
c.RunDockerOrExitError(t, "rmi", "-f", "custom-nginx")
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestBuildSSH(t *testing.T) {
|
||||
@@ -150,7 +148,6 @@ func TestBuildSSH(t *testing.T) {
|
||||
ExitCode: 1,
|
||||
Err: "invalid empty ssh agent socket: make sure SSH_AUTH_SOCK is set",
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
t.Run("build succeed with ssh from Compose file", func(t *testing.T) {
|
||||
@@ -168,17 +165,20 @@ func TestBuildSSH(t *testing.T) {
|
||||
c.RunDockerCmd(t, "image", "inspect", "build-test-ssh")
|
||||
})
|
||||
|
||||
t.Run("build failed with wrong ssh key id from CLI", func(t *testing.T) {
|
||||
c.RunDockerOrExitError(t, "rmi", "build-test-ssh")
|
||||
/*
|
||||
FIXME disabled waiting for https://github.com/moby/buildkit/issues/5558
|
||||
t.Run("build failed with wrong ssh key id from CLI", func(t *testing.T) {
|
||||
c.RunDockerOrExitError(t, "rmi", "build-test-ssh")
|
||||
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/build-test/ssh/compose-without-ssh.yaml",
|
||||
"--project-directory", "fixtures/build-test/ssh", "build", "--no-cache", "--ssh",
|
||||
"wrong-ssh=./fixtures/build-test/ssh/fake_rsa")
|
||||
res.Assert(t, icmd.Expected{
|
||||
ExitCode: 17,
|
||||
Err: "unset ssh forward key fake-ssh",
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/build-test/ssh/compose-without-ssh.yaml",
|
||||
"--project-directory", "fixtures/build-test/ssh", "build", "--no-cache", "--ssh",
|
||||
"wrong-ssh=./fixtures/build-test/ssh/fake_rsa")
|
||||
res.Assert(t, icmd.Expected{
|
||||
ExitCode: 17,
|
||||
Err: "unset ssh forward key fake-ssh",
|
||||
})
|
||||
})
|
||||
})
|
||||
*/
|
||||
|
||||
t.Run("build succeed as part of up with ssh from Compose file", func(t *testing.T) {
|
||||
c.RunDockerOrExitError(t, "rmi", "build-test-ssh")
|
||||
@@ -215,7 +215,6 @@ func TestBuildTags(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
t.Run("build with tags", func(t *testing.T) {
|
||||
|
||||
// ensure local test run does not reuse previously build image
|
||||
c.RunDockerOrExitError(t, "rmi", "build-test-tags")
|
||||
|
||||
@@ -315,7 +314,6 @@ func TestBuildPlatformsWithCorrectBuildxConfig(t *testing.T) {
|
||||
assert.NilError(t, res.Error, res.Stderr())
|
||||
res.Assert(t, icmd.Expected{Out: "I am building for linux/arm64"})
|
||||
res.Assert(t, icmd.Expected{Out: "I am building for linux/amd64"})
|
||||
|
||||
})
|
||||
|
||||
t.Run("multi-arch multi service builds ok", func(t *testing.T) {
|
||||
@@ -352,7 +350,6 @@ func TestBuildPlatformsWithCorrectBuildxConfig(t *testing.T) {
|
||||
assert.NilError(t, res.Error, res.Stderr())
|
||||
res.Assert(t, icmd.Expected{Out: "I am building for linux/386"})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestBuildPrivileged(t *testing.T) {
|
||||
@@ -442,7 +439,6 @@ Switch to a different driver, or turn on the containerd image store, and try aga
|
||||
Err: "the classic builder doesn't support privileged mode, set DOCKER_BUILDKIT=1 to use BuildKit",
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestBuildBuilder(t *testing.T) {
|
||||
@@ -469,7 +465,6 @@ func TestBuildBuilder(t *testing.T) {
|
||||
Err: fmt.Sprintf(`no builder %q found`, "unknown-builder"),
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestBuildEntitlements(t *testing.T) {
|
||||
|
||||
93
pkg/e2e/commit_test.go
Normal file
93
pkg/e2e/commit_test.go
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
Copyright 2023 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCommit(t *testing.T) {
|
||||
const projectName = "e2e-commit-service"
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
cleanup := func() {
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "--timeout=0", "--remove-orphans")
|
||||
}
|
||||
t.Cleanup(cleanup)
|
||||
cleanup()
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/commit/compose.yaml", "--project-name", projectName, "up", "-d", "service")
|
||||
|
||||
c.RunDockerComposeCmd(
|
||||
t,
|
||||
"--project-name",
|
||||
projectName,
|
||||
"commit",
|
||||
"-a",
|
||||
"John Hannibal Smith <hannibal@a-team.com>",
|
||||
"-c",
|
||||
"ENV DEBUG=true",
|
||||
"-m",
|
||||
"sample commit",
|
||||
"service",
|
||||
"service:latest",
|
||||
)
|
||||
}
|
||||
|
||||
func TestCommitWithReplicas(t *testing.T) {
|
||||
const projectName = "e2e-commit-service-with-replicas"
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
cleanup := func() {
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "--timeout=0", "--remove-orphans")
|
||||
}
|
||||
t.Cleanup(cleanup)
|
||||
cleanup()
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/commit/compose.yaml", "--project-name", projectName, "up", "-d", "service-with-replicas")
|
||||
|
||||
c.RunDockerComposeCmd(
|
||||
t,
|
||||
"--project-name",
|
||||
projectName,
|
||||
"commit",
|
||||
"-a",
|
||||
"John Hannibal Smith <hannibal@a-team.com>",
|
||||
"-c",
|
||||
"ENV DEBUG=true",
|
||||
"-m",
|
||||
"sample commit",
|
||||
"--index=1",
|
||||
"service-with-replicas",
|
||||
"service-with-replicas:1",
|
||||
)
|
||||
c.RunDockerComposeCmd(
|
||||
t,
|
||||
"--project-name",
|
||||
projectName,
|
||||
"commit",
|
||||
"-a",
|
||||
"John Hannibal Smith <hannibal@a-team.com>",
|
||||
"-c",
|
||||
"ENV DEBUG=true",
|
||||
"-m",
|
||||
"sample commit",
|
||||
"--index=2",
|
||||
"service-with-replicas",
|
||||
"service-with-replicas:2",
|
||||
)
|
||||
}
|
||||
@@ -65,3 +65,30 @@ func TestLocalComposeExec(t *testing.T) {
|
||||
assert.Check(t, !strings.Contains(res.Stdout(), "FOO="), res.Combined())
|
||||
})
|
||||
}
|
||||
|
||||
func TestLocalComposeExecOneOff(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
const projectName = "compose-e2e-exec-one-off"
|
||||
defer c.cleanupWithDown(t, projectName)
|
||||
cmdArgs := func(cmd string, args ...string) []string {
|
||||
ret := []string{"--project-directory", "fixtures/simple-composefile", "--project-name", projectName, cmd}
|
||||
ret = append(ret, args...)
|
||||
return ret
|
||||
}
|
||||
|
||||
c.RunDockerComposeCmd(t, cmdArgs("run", "-d", "simple")...)
|
||||
|
||||
t.Run("exec in one-off container", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, cmdArgs("exec", "-e", "FOO", "simple", "/usr/bin/env")...)
|
||||
assert.Check(t, !strings.Contains(res.Stdout(), "FOO="), res.Combined())
|
||||
})
|
||||
|
||||
t.Run("exec with index", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmdNoCheck(t, cmdArgs("exec", "--index", "1", "-e", "FOO", "simple", "/usr/bin/env")...)
|
||||
res.Assert(t, icmd.Expected{ExitCode: 1, Err: "service \"simple\" is not running container #1"})
|
||||
})
|
||||
cmdResult := c.RunDockerCmd(t, "ps", "-q", "--filter", "label=com.docker.compose.project=compose-e2e-exec-one-off").Stdout()
|
||||
containerIDs := strings.Split(cmdResult, "\n")
|
||||
_ = c.RunDockerOrExitError(t, append([]string{"stop"}, containerIDs...)...)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user