mirror of
https://github.com/docker/compose.git
synced 2026-02-12 11:39:23 +08:00
Compare commits
55 Commits
multiplaye
...
v2.20.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8318f66330 | ||
|
|
cb17c3c8a6 | ||
|
|
9174a99d27 | ||
|
|
4eb43c53fa | ||
|
|
150b88ab5d | ||
|
|
5159058c7e | ||
|
|
1ae191a936 | ||
|
|
3b2f3cdce3 | ||
|
|
47778f8b77 | ||
|
|
7d88edaf24 | ||
|
|
636c13f818 | ||
|
|
5a072b1ad5 | ||
|
|
ddceb1ac9d | ||
|
|
d48f28c72c | ||
|
|
2d16a05afa | ||
|
|
bb94ea034e | ||
|
|
0938c7e96f | ||
|
|
f429ee958a | ||
|
|
e9ded2c518 | ||
|
|
54e6e0bd8f | ||
|
|
3bc871e64b | ||
|
|
6ff15d9472 | ||
|
|
49bc0603e3 | ||
|
|
ce8a09b53f | ||
|
|
3dc8734897 | ||
|
|
852e192820 | ||
|
|
d9e7859664 | ||
|
|
e28b223650 | ||
|
|
1964693074 | ||
|
|
dc74e6aa0e | ||
|
|
b182cf6850 | ||
|
|
f330b24632 | ||
|
|
8339269e13 | ||
|
|
ee6aeed84e | ||
|
|
7a9dfa4284 | ||
|
|
29daae3d6e | ||
|
|
8dea7b5cae | ||
|
|
bc6ad2e4a4 | ||
|
|
e6a7694b8d | ||
|
|
46d936c750 | ||
|
|
15bc7850bb | ||
|
|
8a64ab56a0 | ||
|
|
1178c51e6a | ||
|
|
3b3fd3e56c | ||
|
|
b1e10f559e | ||
|
|
baea5a48f5 | ||
|
|
cb3a6ce52b | ||
|
|
28f3802a07 | ||
|
|
fd0e0a2cbd | ||
|
|
e90df62bb0 | ||
|
|
b0af2deb2b | ||
|
|
be22bc735a | ||
|
|
b5f5e27597 | ||
|
|
b1334b8dfc | ||
|
|
25ca75db4d |
@@ -15,7 +15,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
ARG GO_VERSION=1.20.5
|
||||
ARG GO_VERSION=1.20.6
|
||||
ARG XX_VERSION=1.2.1
|
||||
ARG GOLANGCI_LINT_VERSION=v1.53.2
|
||||
ARG ADDLICENSE_VERSION=v1.0.0
|
||||
@@ -91,6 +91,7 @@ FROM build-base AS lint
|
||||
ARG BUILD_TAGS
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=from=golangci-lint,source=/usr/bin/golangci-lint,target=/usr/bin/golangci-lint \
|
||||
golangci-lint run --build-tags "$BUILD_TAGS" ./...
|
||||
|
||||
@@ -129,6 +130,7 @@ FROM base AS docsgen
|
||||
WORKDIR /src
|
||||
RUN --mount=target=. \
|
||||
--mount=target=/root/.cache,type=cache \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
go build -o /out/docsgen ./docs/yaml/main/generate.go
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} alpine AS docs-build
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/compose-spec/compose-go/dotenv"
|
||||
buildx "github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/cli/cli/command"
|
||||
|
||||
@@ -472,7 +473,7 @@ func setEnvWithDotEnv(prjOpts *ProjectOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
envFromFile, err := cli.GetEnvFromFile(composegoutils.GetAsEqualsMap(os.Environ()), workingDir, options.EnvFiles)
|
||||
envFromFile, err := dotenv.GetEnvFromFile(composegoutils.GetAsEqualsMap(os.Environ()), workingDir, options.EnvFiles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -233,11 +233,7 @@ func runConfigImages(streams api.Streams, opts configOptions, services []string)
|
||||
return err
|
||||
}
|
||||
for _, s := range project.Services {
|
||||
if s.Image != "" {
|
||||
fmt.Fprintln(streams.Out(), s.Image)
|
||||
} else {
|
||||
fmt.Fprintf(streams.Out(), "%s%s%s\n", project.Name, api.Separator, s.Name)
|
||||
}
|
||||
fmt.Fprintln(streams.Out(), api.GetImageNameOrDefault(s, project.Name))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -64,10 +64,10 @@ func copyCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
flags := copyCmd.Flags()
|
||||
flags.IntVar(&opts.index, "index", 0, "Index of the container if there are multiple instances of a service .")
|
||||
flags.BoolVar(&opts.all, "all", false, "Copy to all the containers of the service.")
|
||||
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.MarkDeprecated("all", "by default all the containers of the service will get the source file/directory to be copied.") //nolint:errcheck
|
||||
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)")
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ func execCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *c
|
||||
|
||||
runCmd.Flags().BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: Run command in the background.")
|
||||
runCmd.Flags().StringArrayVarP(&opts.environment, "env", "e", []string{}, "Set environment variables")
|
||||
runCmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if there are multiple instances of a service [default: 1].")
|
||||
runCmd.Flags().IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas")
|
||||
runCmd.Flags().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process.")
|
||||
runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user.")
|
||||
runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", !streams.Out().IsTerminal(), "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.")
|
||||
|
||||
@@ -57,7 +57,7 @@ func portCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *c
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
cmd.Flags().StringVar(&opts.protocol, "protocol", "tcp", "tcp or udp")
|
||||
cmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if service has multiple replicas")
|
||||
cmd.Flags().IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,13 @@ type runOptions struct {
|
||||
}
|
||||
|
||||
func (options runOptions) apply(project *types.Project) error {
|
||||
if options.noDeps {
|
||||
err := project.ForServices([]string{options.Service}, types.IgnoreDependencies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
target, err := project.GetService(options.Service)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -93,13 +100,6 @@ func (options runOptions) apply(project *types.Project) error {
|
||||
}
|
||||
}
|
||||
|
||||
if options.noDeps {
|
||||
err := project.ForServices([]string{options.Service}, types.IgnoreDependencies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for i, s := range project.Services {
|
||||
if s.Name == options.Service {
|
||||
project.Services[i] = target
|
||||
|
||||
@@ -21,9 +21,8 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@@ -164,17 +163,31 @@ func runUp(ctx context.Context, streams api.Streams, backend api.Service, create
|
||||
consumer = formatter.NewLogConsumer(ctx, streams.Out(), streams.Err(), !upOptions.noColor, !upOptions.noPrefix, upOptions.timestamp)
|
||||
}
|
||||
|
||||
attachTo := services
|
||||
attachTo := utils.Set[string]{}
|
||||
if len(upOptions.attach) > 0 {
|
||||
attachTo = upOptions.attach
|
||||
attachTo.AddAll(upOptions.attach...)
|
||||
}
|
||||
if upOptions.attachDependencies {
|
||||
attachTo = project.ServiceNames()
|
||||
if err := project.WithServices(attachTo.Elements(), func(s types.ServiceConfig) error {
|
||||
if s.Attach == nil || *s.Attach {
|
||||
attachTo.Add(s.Name)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(attachTo) == 0 {
|
||||
attachTo = project.ServiceNames()
|
||||
if err := project.WithServices(services, func(s types.ServiceConfig) error {
|
||||
if s.Attach == nil || *s.Attach {
|
||||
attachTo.Add(s.Name)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
attachTo = utils.Remove(attachTo, upOptions.noAttach...)
|
||||
attachTo.RemoveAll(upOptions.noAttach...)
|
||||
|
||||
create := api.CreateOptions{
|
||||
Services: services,
|
||||
@@ -198,7 +211,7 @@ func runUp(ctx context.Context, streams api.Streams, backend api.Service, create
|
||||
Start: api.StartOptions{
|
||||
Project: project,
|
||||
Attach: consumer,
|
||||
AttachTo: attachTo,
|
||||
AttachTo: attachTo.Elements(),
|
||||
ExitCodeFrom: upOptions.exitCodeFrom,
|
||||
CascadeStop: upOptions.cascadeStop,
|
||||
Wait: upOptions.wait,
|
||||
|
||||
@@ -7,7 +7,6 @@ Define and run multi-container applications with Docker.
|
||||
|
||||
| Name | Description |
|
||||
|:--------------------------------|:------------------------------------------------------------------------|
|
||||
| [`alpha`](compose_alpha.md) | Experimental commands |
|
||||
| [`build`](compose_build.md) | Build or rebuild services |
|
||||
| [`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 |
|
||||
|
||||
@@ -5,12 +5,12 @@ Copy files/folders between a service container and the local filesystem
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------------|:------|:--------|:----------------------------------------------------------------------|
|
||||
| `-a`, `--archive` | | | Archive mode (copy all uid/gid information) |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `-L`, `--follow-link` | | | Always follow symbol link in SRC_PATH |
|
||||
| `--index` | `int` | `0` | Index of the container if there are multiple instances of a service . |
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------------|:------|:--------|:--------------------------------------------------------|
|
||||
| `-a`, `--archive` | | | Archive mode (copy all uid/gid information) |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `-L`, `--follow-link` | | | Always follow symbol link in SRC_PATH |
|
||||
| `--index` | `int` | `0` | index of the container if service has multiple replicas |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -5,16 +5,16 @@ Execute a command in a running container.
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------------|:--------------|:--------|:----------------------------------------------------------------------------------|
|
||||
| `-d`, `--detach` | | | Detached mode: Run command in the background. |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `-e`, `--env` | `stringArray` | | Set environment variables |
|
||||
| `--index` | `int` | `1` | index of the container if there are multiple instances of a service [default: 1]. |
|
||||
| `-T`, `--no-TTY` | | | Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY. |
|
||||
| `--privileged` | | | Give extended privileges to the process. |
|
||||
| `-u`, `--user` | `string` | | Run the command as this user. |
|
||||
| `-w`, `--workdir` | `string` | | Path to workdir directory for this command. |
|
||||
| Name | Type | Default | Description |
|
||||
|:------------------|:--------------|:--------|:---------------------------------------------------------------------------------|
|
||||
| `-d`, `--detach` | | | Detached mode: Run command in the background. |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `-e`, `--env` | `stringArray` | | Set environment variables |
|
||||
| `--index` | `int` | `0` | index of the container if service has multiple replicas |
|
||||
| `-T`, `--no-TTY` | | | Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY. |
|
||||
| `--privileged` | | | Give extended privileges to the process. |
|
||||
| `-u`, `--user` | `string` | | Run the command as this user. |
|
||||
| `-w`, `--workdir` | `string` | | Path to workdir directory for this command. |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -8,7 +8,7 @@ Print the public port for a port binding.
|
||||
| Name | Type | Default | Description |
|
||||
|:-------------|:---------|:--------|:--------------------------------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--index` | `int` | `1` | index of the container if service has multiple replicas |
|
||||
| `--index` | `int` | `0` | index of the container if service has multiple replicas |
|
||||
| `--protocol` | `string` | `tcp` | tcp or udp |
|
||||
|
||||
|
||||
|
||||
@@ -347,6 +347,7 @@ options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -21,6 +21,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: true
|
||||
kubernetes: false
|
||||
|
||||
@@ -69,6 +69,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: true
|
||||
kubernetes: false
|
||||
|
||||
@@ -29,6 +29,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: true
|
||||
kubernetes: false
|
||||
|
||||
@@ -159,6 +159,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -152,6 +152,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -10,7 +10,7 @@ options:
|
||||
- option: all
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Copy to all the containers of the service.
|
||||
description: copy to all the containers of the service.
|
||||
deprecated: true
|
||||
hidden: true
|
||||
experimental: false
|
||||
@@ -42,8 +42,7 @@ options:
|
||||
- option: index
|
||||
value_type: int
|
||||
default_value: "0"
|
||||
description: |
|
||||
Index of the container if there are multiple instances of a service .
|
||||
description: index of the container if service has multiple replicas
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
@@ -62,6 +61,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -90,6 +90,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -73,6 +73,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -46,6 +46,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -33,9 +33,8 @@ options:
|
||||
swarm: false
|
||||
- option: index
|
||||
value_type: int
|
||||
default_value: "1"
|
||||
description: |
|
||||
index of the container if there are multiple instances of a service [default: 1].
|
||||
default_value: "0"
|
||||
description: index of the container if service has multiple replicas
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
@@ -118,6 +117,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -38,6 +38,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -43,6 +43,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -91,6 +91,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -58,6 +58,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -17,6 +17,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -7,7 +7,7 @@ plink: docker_compose.yaml
|
||||
options:
|
||||
- option: index
|
||||
value_type: int
|
||||
default_value: "1"
|
||||
default_value: "0"
|
||||
description: index of the container if service has multiple replicas
|
||||
deprecated: false
|
||||
hidden: false
|
||||
@@ -37,6 +37,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -180,6 +180,7 @@ examples: |-
|
||||
The `docker compose ps` command currently only supports the `--filter status=<status>`
|
||||
option, but additional filter options may be added in the future.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -124,6 +124,7 @@ examples: |-
|
||||
`docker compose pull` will try to pull image for services with a build section. If pull fails, it will let
|
||||
user know this service image MUST be built. You can skip this by setting `--ignore-buildable` flag
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -66,6 +66,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -48,6 +48,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -76,6 +76,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -286,6 +286,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -16,6 +16,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -29,6 +29,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -23,6 +23,7 @@ examples: |-
|
||||
root 142353 142331 2 15:33 ? 00:00:00 ping localhost -c 5
|
||||
```
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -16,6 +16,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -285,6 +285,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -37,6 +37,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -27,6 +27,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
26
go.mod
26
go.mod
@@ -6,15 +6,15 @@ require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||
github.com/Microsoft/go-winio v0.6.1
|
||||
github.com/buger/goterm v1.0.4
|
||||
github.com/compose-spec/compose-go v1.15.1
|
||||
github.com/compose-spec/compose-go v1.17.0
|
||||
github.com/containerd/console v1.0.3
|
||||
github.com/containerd/containerd v1.7.2
|
||||
github.com/cucumber/godog v0.0.0-00010101000000-000000000000 // replaced; see replace for the actual version used
|
||||
github.com/distribution/distribution/v3 v3.0.0-20230601133803-97b1d649c493
|
||||
github.com/docker/buildx v0.11.0
|
||||
github.com/docker/cli v24.0.2+incompatible
|
||||
github.com/docker/cli-docs-tool v0.5.1
|
||||
github.com/docker/docker v24.0.2+incompatible
|
||||
github.com/docker/buildx v0.11.2
|
||||
github.com/docker/cli v24.0.4+incompatible
|
||||
github.com/docker/cli-docs-tool v0.6.0
|
||||
github.com/docker/docker v24.0.5-0.20230714235725-36e9e796c6fc+incompatible // v24.0.5-dev
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/fsnotify/fsevents v0.1.1
|
||||
@@ -24,12 +24,12 @@ require (
|
||||
github.com/jonboulle/clockwork v0.4.0
|
||||
github.com/mattn/go-shellwords v1.0.12
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/moby/buildkit v0.11.0-rc3.0.20230609092854-67a08623b95a
|
||||
github.com/moby/buildkit v0.12.1-0.20230717122532-faa0cc7da353 // v0.12 release branch
|
||||
github.com/moby/patternmatcher v0.5.0
|
||||
github.com/moby/term v0.5.0
|
||||
github.com/morikuni/aec v1.0.0
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.1.0-rc3
|
||||
github.com/opencontainers/image-spec v1.1.0-rc4
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.7.0
|
||||
@@ -44,9 +44,9 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.14.0
|
||||
go.uber.org/goleak v1.2.1
|
||||
golang.org/x/sync v0.3.0
|
||||
google.golang.org/grpc v1.56.0
|
||||
google.golang.org/grpc v1.56.2
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gotest.tools/v3 v3.4.0
|
||||
gotest.tools/v3 v3.5.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -71,7 +71,6 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cloudflare/cfssl v1.6.4 // indirect
|
||||
github.com/containerd/continuity v0.4.1 // indirect
|
||||
github.com/containerd/ttrpc v1.2.2 // indirect
|
||||
github.com/containerd/typeurl/v2 v2.1.1 // indirect
|
||||
github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect
|
||||
github.com/cucumber/messages-go/v16 v16.0.1 // indirect
|
||||
@@ -148,9 +147,9 @@ require (
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20230407161946-9e7a6df48576 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb // indirect
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
||||
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect
|
||||
github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531 // 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
|
||||
@@ -163,7 +162,8 @@ require (
|
||||
go.opentelemetry.io/otel/metric v0.37.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
||||
golang.org/x/crypto v0.7.0 // indirect
|
||||
golang.org/x/mod v0.9.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect
|
||||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/oauth2 v0.7.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
|
||||
54
go.sum
54
go.sum
@@ -131,8 +131,8 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
|
||||
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
|
||||
github.com/compose-spec/compose-go v1.15.1 h1:0yaEt6/66dLN0bNWYDTj0CDx626uCdQ9ipJVIJx8O8M=
|
||||
github.com/compose-spec/compose-go v1.15.1/go.mod h1:3yngGBGfls6FHGQsg4B1z6gz8ej9SOvmAJtxCwgbcnc=
|
||||
github.com/compose-spec/compose-go v1.17.0 h1:cvje90CU94dQyTnJoHJYjx9yE4Iggse1XmGcO3Qi5ts=
|
||||
github.com/compose-spec/compose-go v1.17.0/go.mod h1:zR2tP1+kZHi5vJz7PjpW6oMoDji/Js3GHjP+hfjf70Q=
|
||||
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
|
||||
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
|
||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
@@ -145,7 +145,6 @@ github.com/containerd/nydus-snapshotter v0.8.2 h1:7SOrMU2YmLzfbsr5J7liMZJlNi5WT6
|
||||
github.com/containerd/stargz-snapshotter v0.14.3 h1:OTUVZoPSPs8mGgmQUE1dqw3WX/3nrsmsurW7UPLWl1U=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k=
|
||||
github.com/containerd/ttrpc v1.2.2 h1:9vqZr0pxwOF5koz6N0N3kJ0zDHokrcPxIR/ZR2YFtOs=
|
||||
github.com/containerd/ttrpc v1.2.2/go.mod h1:sIT6l32Ph/H9cvnJsfXM5drIVzTr5A2flTf1G5tYZak=
|
||||
github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4=
|
||||
github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
@@ -167,17 +166,17 @@ github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zA
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/distribution/distribution/v3 v3.0.0-20230601133803-97b1d649c493 h1:fm5DpBD+A7o0+x9Nf+o9/4/qPGbfxLpr9qIPVuV8vQc=
|
||||
github.com/distribution/distribution/v3 v3.0.0-20230601133803-97b1d649c493/go.mod h1:+fqBJ4vPYo4Uu1ZE4d+bUtTLRXfdSL3NvCZIZ9GHv58=
|
||||
github.com/docker/buildx v0.11.0 h1:DNCOIYT/7J0sPBlU/ozEhFd4MtbnbFByn45yeTMHXVU=
|
||||
github.com/docker/buildx v0.11.0/go.mod h1:Yq7ZNjrwXKzW0uSFMk46dl5Gl903k5+bp6U4apsM5rs=
|
||||
github.com/docker/cli v24.0.2+incompatible h1:QdqR7znue1mtkXIJ+ruQMGQhpw2JzMJLRXp6zpzF6tM=
|
||||
github.com/docker/cli v24.0.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli-docs-tool v0.5.1 h1:jIk/cCZurZERhALPVKhqlNxTQGxn2kcI+56gE57PQXg=
|
||||
github.com/docker/cli-docs-tool v0.5.1/go.mod h1:zMjqTFCU361PRh8apiXzeAZ1Q/xupbIwTusYpzCXS/o=
|
||||
github.com/docker/buildx v0.11.2 h1:R3p9F0gnI4FwvQ0p40UwdX1T4ugap4UWxY3TFHoP4Ws=
|
||||
github.com/docker/buildx v0.11.2/go.mod h1:CWAABt10iIuGpleypA3103mplDfcGu0A2AvT03xfpTc=
|
||||
github.com/docker/cli v24.0.4+incompatible h1:Y3bYF9ekNTm2VFz5U/0BlMdJy73D+Y1iAAZ8l63Ydzw=
|
||||
github.com/docker/cli v24.0.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli-docs-tool v0.6.0 h1:Z9x10SaZgFaB6jHgz3OWooynhSa40CsWkpe5hEnG/qA=
|
||||
github.com/docker/cli-docs-tool v0.6.0/go.mod h1:zMjqTFCU361PRh8apiXzeAZ1Q/xupbIwTusYpzCXS/o=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
||||
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v24.0.2+incompatible h1:eATx+oLz9WdNVkQrr0qjQ8HvRJ4bOOxfzEo8R+dA3cg=
|
||||
github.com/docker/docker v24.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v24.0.5-0.20230714235725-36e9e796c6fc+incompatible h1:sdGvA1bxu/1J51gAs1XU0bZC+2WxncYnI210as3c6g8=
|
||||
github.com/docker/docker v24.0.5-0.20230714235725-36e9e796c6fc+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
|
||||
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
||||
@@ -465,8 +464,8 @@ github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WT
|
||||
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.11.0-rc3.0.20230609092854-67a08623b95a h1:1k3bAXwxC2N1FncWijq/43sLj2OVIZ11FT0APIXWhMg=
|
||||
github.com/moby/buildkit v0.11.0-rc3.0.20230609092854-67a08623b95a/go.mod h1:4sM7BBBqXOQ+vV6LrVAOAMhZI9cVNYV5RhZCl906a64=
|
||||
github.com/moby/buildkit v0.12.1-0.20230717122532-faa0cc7da353 h1:/ZIwqvOF3QKObJbjX96xVvAKtnWdw/AuEqysbbujaZA=
|
||||
github.com/moby/buildkit v0.12.1-0.20230717122532-faa0cc7da353/go.mod h1:+n9GmkxwBCjVz4u7wmiyh+oqvjIjQM+1zk3iJrWfdos=
|
||||
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
||||
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
||||
github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo=
|
||||
@@ -511,8 +510,8 @@ github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
|
||||
github.com/opencontainers/runc v1.1.7 h1:y2EZDS8sNng4Ksf0GUYNhKbTShZJPJg1FiXJNH/uoCk=
|
||||
github.com/opencontainers/runc v1.1.7/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50=
|
||||
github.com/opencontainers/runtime-spec v1.1.0-rc.2 h1:ucBtEms2tamYYW/SvGpvq9yUN0NEVL6oyLEwDcTSrk8=
|
||||
@@ -557,7 +556,6 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
|
||||
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
@@ -579,7 +577,6 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
@@ -626,12 +623,12 @@ github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4D
|
||||
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-20230407161946-9e7a6df48576 h1:fZXPQDVh5fm2x7pA0CH1TtH80tiZ0L7i834kZqZN8Pw=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20230407161946-9e7a6df48576/go.mod h1:q1CxMSzcAbjUkVGHoZeQUcCaALnaE4XdWk+zJcgMYFw=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb h1:uUe8rNyVXM8moActoBol6Xf6xX2GMr7SosR2EywMvGg=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb/go.mod h1:SxX/oNQ/ag6Vaoli547ipFK9J7BZn5JqJG0JE8lf8bA=
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk=
|
||||
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f h1:DLpt6B5oaaS8jyXHa9VA4rrZloBVPVXeCtrOsrFauxc=
|
||||
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc=
|
||||
github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531 h1:Y/M5lygoNPKwVNLMPXgVfsRT40CSFKXCxuU8LoHySjs=
|
||||
github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc=
|
||||
github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME=
|
||||
github.com/weppos/publicsuffix-go v0.15.1-0.20220329081811-9a40b608a236 h1:vMJBP3PQViZsF6cOINtvyMC8ptpLsyJ4EwyFnzuWNxc=
|
||||
github.com/weppos/publicsuffix-go v0.15.1-0.20220329081811-9a40b608a236/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE=
|
||||
@@ -716,6 +713,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
|
||||
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -741,8 +740,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/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.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
|
||||
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -863,7 +862,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
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.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
@@ -1039,8 +1037,8 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.56.0 h1:+y7Bs8rtMd07LeXmL3NxcTLn7mUkbKZqEpPhMNkwJEE=
|
||||
google.golang.org/grpc v1.56.0/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||
google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI=
|
||||
google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -1088,8 +1086,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
||||
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
||||
gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY=
|
||||
gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
107
internal/sync/docker_cp.go
Normal file
107
internal/sync/docker_cp.go
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
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 sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type ComposeClient interface {
|
||||
Exec(ctx context.Context, projectName string, options api.RunOptions) (int, error)
|
||||
|
||||
Copy(ctx context.Context, projectName string, options api.CopyOptions) error
|
||||
}
|
||||
|
||||
type DockerCopy struct {
|
||||
client ComposeClient
|
||||
|
||||
projectName string
|
||||
|
||||
infoWriter io.Writer
|
||||
}
|
||||
|
||||
var _ Syncer = &DockerCopy{}
|
||||
|
||||
func NewDockerCopy(projectName string, client ComposeClient, infoWriter io.Writer) *DockerCopy {
|
||||
return &DockerCopy{
|
||||
projectName: projectName,
|
||||
client: client,
|
||||
infoWriter: infoWriter,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DockerCopy) Sync(ctx context.Context, service types.ServiceConfig, paths []PathMapping) error {
|
||||
var errs []error
|
||||
for i := range paths {
|
||||
if err := d.sync(ctx, service, paths[i]); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (d *DockerCopy) sync(ctx context.Context, service types.ServiceConfig, pathMapping PathMapping) error {
|
||||
scale := 1
|
||||
if service.Deploy != nil && service.Deploy.Replicas != nil {
|
||||
scale = int(*service.Deploy.Replicas)
|
||||
}
|
||||
|
||||
if fi, statErr := os.Stat(pathMapping.HostPath); statErr == nil {
|
||||
if fi.IsDir() {
|
||||
for i := 1; i <= scale; i++ {
|
||||
_, err := d.client.Exec(ctx, d.projectName, api.RunOptions{
|
||||
Service: pathMapping.Service,
|
||||
Command: []string{"mkdir", "-p", pathMapping.ContainerPath},
|
||||
Index: i,
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Warnf("failed to create %q from %s: %v", pathMapping.ContainerPath, pathMapping.Service, err)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(d.infoWriter, "%s created\n", pathMapping.ContainerPath)
|
||||
} else {
|
||||
err := d.client.Copy(ctx, d.projectName, api.CopyOptions{
|
||||
Source: pathMapping.HostPath,
|
||||
Destination: fmt.Sprintf("%s:%s", pathMapping.Service, pathMapping.ContainerPath),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(d.infoWriter, "%s updated\n", pathMapping.ContainerPath)
|
||||
}
|
||||
} else if errors.Is(statErr, fs.ErrNotExist) {
|
||||
for i := 1; i <= scale; i++ {
|
||||
_, err := d.client.Exec(ctx, d.projectName, api.RunOptions{
|
||||
Service: pathMapping.Service,
|
||||
Command: []string{"rm", "-rf", pathMapping.ContainerPath},
|
||||
Index: i,
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Warnf("failed to delete %q from %s: %v", pathMapping.ContainerPath, pathMapping.Service, err)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(d.infoWriter, "%s deleted from service\n", pathMapping.ContainerPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
44
internal/sync/shared.go
Normal file
44
internal/sync/shared.go
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
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 sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
)
|
||||
|
||||
// PathMapping contains the Compose service and modified host system path.
|
||||
type PathMapping struct {
|
||||
// Service that the file event is for.
|
||||
Service string
|
||||
// HostPath that was created/modified/deleted outside the container.
|
||||
//
|
||||
// This is the path as seen from the user's perspective, e.g.
|
||||
// - C:\Users\moby\Documents\hello-world\main.go (file on Windows)
|
||||
// - /Users/moby/Documents/hello-world (directory on macOS)
|
||||
HostPath string
|
||||
// ContainerPath for the target file inside the container (only populated
|
||||
// for sync events, not rebuild).
|
||||
//
|
||||
// This is the path as used in Docker CLI commands, e.g.
|
||||
// - /workdir/main.go
|
||||
// - /workdir/subdir
|
||||
ContainerPath string
|
||||
}
|
||||
|
||||
type Syncer interface {
|
||||
Sync(ctx context.Context, service types.ServiceConfig, paths []PathMapping) error
|
||||
}
|
||||
152
internal/tracing/attributes.go
Normal file
152
internal/tracing/attributes.go
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// SpanOptions is a small helper type to make it easy to share the options helpers between
|
||||
// downstream functions that accept slices of trace.SpanStartOption and trace.EventOption.
|
||||
type SpanOptions []trace.SpanStartEventOption
|
||||
|
||||
func (s SpanOptions) SpanStartOptions() []trace.SpanStartOption {
|
||||
out := make([]trace.SpanStartOption, len(s))
|
||||
for i := range s {
|
||||
out[i] = s[i]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (s SpanOptions) EventOptions() []trace.EventOption {
|
||||
out := make([]trace.EventOption, len(s))
|
||||
for i := range s {
|
||||
out[i] = s[i]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ProjectOptions returns common attributes from a Compose project.
|
||||
//
|
||||
// For convenience, it's returned as a SpanOptions object to allow it to be
|
||||
// passed directly to the wrapping helper methods in this package such as
|
||||
// SpanWrapFunc.
|
||||
func ProjectOptions(proj *types.Project) SpanOptions {
|
||||
if proj == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
disabledServiceNames := make([]string, len(proj.DisabledServices))
|
||||
for i := range proj.DisabledServices {
|
||||
disabledServiceNames[i] = proj.DisabledServices[i].Name
|
||||
}
|
||||
|
||||
attrs := []attribute.KeyValue{
|
||||
attribute.String("project.name", proj.Name),
|
||||
attribute.String("project.dir", proj.WorkingDir),
|
||||
attribute.StringSlice("project.compose_files", proj.ComposeFiles),
|
||||
attribute.StringSlice("project.services.active", proj.ServiceNames()),
|
||||
attribute.StringSlice("project.services.disabled", disabledServiceNames),
|
||||
attribute.StringSlice("project.profiles", proj.Profiles),
|
||||
attribute.StringSlice("project.volumes", proj.VolumeNames()),
|
||||
attribute.StringSlice("project.networks", proj.NetworkNames()),
|
||||
attribute.StringSlice("project.secrets", proj.SecretNames()),
|
||||
attribute.StringSlice("project.configs", proj.ConfigNames()),
|
||||
attribute.StringSlice("project.extensions", keys(proj.Extensions)),
|
||||
}
|
||||
return []trace.SpanStartEventOption{
|
||||
trace.WithAttributes(attrs...),
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceOptions returns common attributes from a Compose service.
|
||||
//
|
||||
// For convenience, it's returned as a SpanOptions object to allow it to be
|
||||
// passed directly to the wrapping helper methods in this package such as
|
||||
// SpanWrapFunc.
|
||||
func ServiceOptions(service types.ServiceConfig) SpanOptions {
|
||||
attrs := []attribute.KeyValue{
|
||||
attribute.String("service.name", service.Name),
|
||||
attribute.String("service.image", service.Image),
|
||||
attribute.StringSlice("service.networks", keys(service.Networks)),
|
||||
}
|
||||
|
||||
configNames := make([]string, len(service.Configs))
|
||||
for i := range service.Configs {
|
||||
configNames[i] = service.Configs[i].Source
|
||||
}
|
||||
attrs = append(attrs, attribute.StringSlice("service.configs", configNames))
|
||||
|
||||
secretNames := make([]string, len(service.Secrets))
|
||||
for i := range service.Secrets {
|
||||
secretNames[i] = service.Secrets[i].Source
|
||||
}
|
||||
attrs = append(attrs, attribute.StringSlice("service.secrets", secretNames))
|
||||
|
||||
volNames := make([]string, len(service.Volumes))
|
||||
for i := range service.Volumes {
|
||||
volNames[i] = service.Volumes[i].Source
|
||||
}
|
||||
attrs = append(attrs, attribute.StringSlice("service.volumes", volNames))
|
||||
|
||||
return []trace.SpanStartEventOption{
|
||||
trace.WithAttributes(attrs...),
|
||||
}
|
||||
}
|
||||
|
||||
// ContainerOptions returns common attributes from a Moby container.
|
||||
//
|
||||
// For convenience, it's returned as a SpanOptions object to allow it to be
|
||||
// passed directly to the wrapping helper methods in this package such as
|
||||
// SpanWrapFunc.
|
||||
func ContainerOptions(container moby.Container) SpanOptions {
|
||||
attrs := []attribute.KeyValue{
|
||||
attribute.String("container.id", container.ID),
|
||||
attribute.String("container.image", container.Image),
|
||||
unixTimeAttr("container.created_at", container.Created),
|
||||
}
|
||||
|
||||
if len(container.Names) != 0 {
|
||||
attrs = append(attrs, attribute.String("container.name", strings.TrimPrefix(container.Names[0], "/")))
|
||||
}
|
||||
|
||||
return []trace.SpanStartEventOption{
|
||||
trace.WithAttributes(attrs...),
|
||||
}
|
||||
}
|
||||
|
||||
func keys[T any](m map[string]T) []string {
|
||||
out := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
out = append(out, k)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func timeAttr(key string, value time.Time) attribute.KeyValue {
|
||||
return attribute.String(key, value.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
func unixTimeAttr(key string, value int64) attribute.KeyValue {
|
||||
return timeAttr(key, time.Unix(value, 0).UTC())
|
||||
}
|
||||
91
internal/tracing/wrap.go
Normal file
91
internal/tracing/wrap.go
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.18.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// SpanWrapFunc wraps a function that takes a context with a trace.Span, marking the status as codes.Error if the
|
||||
// wrapped function returns an error.
|
||||
//
|
||||
// The context passed to the function is created from the span to ensure correct propagation.
|
||||
//
|
||||
// NOTE: This function is nearly identical to SpanWrapFuncForErrGroup, except the latter is designed specially for
|
||||
// convenience with errgroup.Group due to its prevalence throughout the codebase. The code is duplicated to avoid
|
||||
// adding even more levels of function wrapping/indirection.
|
||||
func SpanWrapFunc(spanName string, opts SpanOptions, fn func(ctx context.Context) error) func(context.Context) error {
|
||||
return func(ctx context.Context) error {
|
||||
ctx, span := Tracer.Start(ctx, spanName, opts.SpanStartOptions()...)
|
||||
defer span.End()
|
||||
|
||||
if err := fn(ctx); err != nil {
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
span.SetStatus(codes.Ok, "")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// SpanWrapFuncForErrGroup wraps a function that takes a context with a trace.Span, marking the status as codes.Error
|
||||
// if the wrapped function returns an error.
|
||||
//
|
||||
// The context passed to the function is created from the span to ensure correct propagation.
|
||||
//
|
||||
// NOTE: This function is nearly identical to SpanWrapFunc, except this function is designed specially for
|
||||
// convenience with errgroup.Group due to its prevalence throughout the codebase. The code is duplicated to avoid
|
||||
// adding even more levels of function wrapping/indirection.
|
||||
func SpanWrapFuncForErrGroup(ctx context.Context, spanName string, opts SpanOptions, fn func(ctx context.Context) error) func() error {
|
||||
return func() error {
|
||||
ctx, span := Tracer.Start(ctx, spanName, opts.SpanStartOptions()...)
|
||||
defer span.End()
|
||||
|
||||
if err := fn(ctx); err != nil {
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
span.SetStatus(codes.Ok, "")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// EventWrapFuncForErrGroup invokes a function and records an event, optionally including the returned
|
||||
// error as the "exception message" on the event.
|
||||
//
|
||||
// This is intended for lightweight usage to wrap errgroup.Group calls where a full span is not desired.
|
||||
func EventWrapFuncForErrGroup(ctx context.Context, eventName string, opts SpanOptions, fn func(ctx context.Context) error) func() error {
|
||||
return func() error {
|
||||
span := trace.SpanFromContext(ctx)
|
||||
eventOpts := opts.EventOptions()
|
||||
|
||||
err := fn(ctx)
|
||||
|
||||
if err != nil {
|
||||
eventOpts = append(eventOpts, trace.WithAttributes(semconv.ExceptionMessage(err.Error())))
|
||||
}
|
||||
span.AddEvent(eventName, eventOpts...)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -145,7 +145,6 @@ func (o BuildOptions) Apply(project *types.Project) error {
|
||||
if service.Build == nil {
|
||||
continue
|
||||
}
|
||||
service.Image = GetImageNameOrDefault(service, project.Name)
|
||||
if platform != "" {
|
||||
if len(service.Build.Platforms) > 0 && !utils.StringContains(service.Build.Platforms, platform) {
|
||||
return fmt.Errorf("service %q build.platforms does not support value set by DOCKER_DEFAULT_PLATFORM: %s", service.Name, platform)
|
||||
|
||||
@@ -70,7 +70,7 @@ type execDetails struct {
|
||||
}
|
||||
|
||||
// NewDryRunClient produces a DryRunClient
|
||||
func NewDryRunClient(apiClient client.APIClient, cli *command.DockerCli) (*DryRunClient, error) {
|
||||
func NewDryRunClient(apiClient client.APIClient, cli command.Cli) (*DryRunClient, error) {
|
||||
b, err := builder.New(cli, builder.WithSkippedValidation())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -217,7 +217,7 @@ func (s *ServiceProxy) List(ctx context.Context, options ListOptions) ([]Stack,
|
||||
return s.ListFn(ctx, options)
|
||||
}
|
||||
|
||||
// Convert implements Service interface
|
||||
// Config implements Service interface
|
||||
func (s *ServiceProxy) Config(ctx context.Context, project *types.Project, options ConfigOptions) ([]byte, error) {
|
||||
if s.ConfigFn == nil {
|
||||
return nil, ErrNotImplemented
|
||||
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/compose/v2/internal/tracing"
|
||||
|
||||
"github.com/docker/buildx/controller/pb"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
@@ -77,7 +79,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
return nil, err
|
||||
}
|
||||
|
||||
builtIDs := make([]string, len(project.Services))
|
||||
builtDigests := make([]string, len(project.Services))
|
||||
err = InDependencyOrder(ctx, project, func(ctx context.Context, name string) error {
|
||||
if len(options.Services) > 0 && !utils.Contains(options.Services, name) {
|
||||
return nil
|
||||
@@ -94,11 +96,11 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
} else {
|
||||
service.Build.Args = service.Build.Args.OverrideBy(args)
|
||||
}
|
||||
id, err := s.doBuildClassic(ctx, service, options)
|
||||
id, err := s.doBuildClassic(ctx, project.Name, service, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
builtIDs[idx] = id
|
||||
builtDigests[idx] = id
|
||||
|
||||
if options.Push {
|
||||
return s.push(ctx, project, api.PushOptions{})
|
||||
@@ -120,7 +122,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
builtIDs[idx] = digest
|
||||
builtDigests[idx] = digest
|
||||
|
||||
return nil
|
||||
}, func(traversal *graphTraversal) {
|
||||
@@ -137,9 +139,10 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
}
|
||||
|
||||
imageIDs := map[string]string{}
|
||||
for i, d := range builtIDs {
|
||||
if d != "" {
|
||||
imageIDs[project.Services[i].Image] = d
|
||||
for i, imageDigest := range builtDigests {
|
||||
if imageDigest != "" {
|
||||
imageRef := api.GetImageNameOrDefault(project.Services[i], project.Name)
|
||||
imageIDs[imageRef] = imageDigest
|
||||
}
|
||||
}
|
||||
return imageIDs, err
|
||||
@@ -169,7 +172,11 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.pullRequiredImages(ctx, project, images, quietPull)
|
||||
err = tracing.SpanWrapFunc("project/pull", tracing.ProjectOptions(project),
|
||||
func(ctx context.Context) error {
|
||||
return s.pullRequiredImages(ctx, project, images, quietPull)
|
||||
},
|
||||
)(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -185,16 +192,24 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
|
||||
}
|
||||
|
||||
if buildRequired {
|
||||
builtImages, err := s.build(ctx, project, api.BuildOptions{
|
||||
Progress: mode,
|
||||
})
|
||||
err = tracing.SpanWrapFunc("project/build", tracing.ProjectOptions(project),
|
||||
func(ctx context.Context) error {
|
||||
builtImages, err := s.build(ctx, project, api.BuildOptions{
|
||||
Progress: mode,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for name, digest := range builtImages {
|
||||
images[name] = digest
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for name, digest := range builtImages {
|
||||
images[name] = digest
|
||||
}
|
||||
}
|
||||
|
||||
// set digest as com.docker.compose.image label so we can detect outdated containers
|
||||
@@ -222,7 +237,8 @@ func (s *composeService) prepareProjectForBuild(project *types.Project, images m
|
||||
continue
|
||||
}
|
||||
|
||||
_, localImagePresent := images[service.Image]
|
||||
image := api.GetImageNameOrDefault(service, project.Name)
|
||||
_, localImagePresent := images[image]
|
||||
if localImagePresent && service.PullPolicy != types.PullPolicyBuild {
|
||||
service.Build = nil
|
||||
project.Services[i] = service
|
||||
@@ -292,8 +308,6 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
|
||||
}
|
||||
|
||||
func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, options api.BuildOptions) (build.Options, error) {
|
||||
tags := []string{service.Image}
|
||||
|
||||
buildArgs := flatten(service.Build.Args.Resolve(envResolver(project.Environment)))
|
||||
|
||||
for k, v := range storeutil.GetProxyConfig(s.dockerCli) {
|
||||
@@ -335,6 +349,7 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
|
||||
sessionConfig = append(sessionConfig, secretsProvider)
|
||||
}
|
||||
|
||||
tags := []string{api.GetImageNameOrDefault(service, project.Name)}
|
||||
if len(service.Build.Tags) > 0 {
|
||||
tags = append(tags, service.Build.Tags...)
|
||||
}
|
||||
@@ -345,18 +360,19 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
|
||||
|
||||
imageLabels := getImageBuildLabels(project, service)
|
||||
|
||||
push := options.Push && service.Image != ""
|
||||
exports := []bclient.ExportEntry{{
|
||||
Type: "docker",
|
||||
Attrs: map[string]string{
|
||||
"load": "true",
|
||||
"push": fmt.Sprint(options.Push),
|
||||
"push": fmt.Sprint(push),
|
||||
},
|
||||
}}
|
||||
if len(service.Build.Platforms) > 1 {
|
||||
exports = []bclient.ExportEntry{{
|
||||
Type: "image",
|
||||
Attrs: map[string]string{
|
||||
"push": fmt.Sprint(options.Push),
|
||||
"push": fmt.Sprint(push),
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ import (
|
||||
)
|
||||
|
||||
//nolint:gocyclo
|
||||
func (s *composeService) doBuildClassic(ctx context.Context, service types.ServiceConfig, options api.BuildOptions) (string, error) {
|
||||
func (s *composeService) doBuildClassic(ctx context.Context, projectName string, service types.ServiceConfig, options api.BuildOptions) (string, error) {
|
||||
var (
|
||||
buildCtx io.ReadCloser
|
||||
dockerfileCtx io.ReadCloser
|
||||
@@ -160,7 +160,8 @@ func (s *composeService) doBuildClassic(ctx context.Context, service types.Servi
|
||||
authConfigs[k] = registry.AuthConfig(auth)
|
||||
}
|
||||
buildOptions := imageBuildOptions(service.Build)
|
||||
buildOptions.Tags = append(buildOptions.Tags, service.Image)
|
||||
imageName := api.GetImageNameOrDefault(service, projectName)
|
||||
buildOptions.Tags = append(buildOptions.Tags, imageName)
|
||||
buildOptions.Dockerfile = relDockerfile
|
||||
buildOptions.AuthConfigs = authConfigs
|
||||
buildOptions.Memory = options.Memory
|
||||
|
||||
@@ -88,8 +88,11 @@ func (s *composeService) DryRunMode(ctx context.Context, dryRun bool) (context.C
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
err = cli.Initialize(flags.NewClientOptions(), command.WithInitializeClient(func(cli *command.DockerCli) (client.APIClient, error) {
|
||||
return api.NewDryRunClient(s.apiClient(), cli)
|
||||
|
||||
options := flags.NewClientOptions()
|
||||
options.Context = s.dockerCli.CurrentContext()
|
||||
err = cli.Initialize(options, command.WithInitializeClient(func(cli *command.DockerCli) (client.APIClient, error) {
|
||||
return api.NewDryRunClient(s.apiClient(), s.dockerCli)
|
||||
}))
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
@@ -200,6 +203,7 @@ func (s *composeService) projectFromName(containers Containers, projectName stri
|
||||
condition := ServiceConditionRunningOrHealthy
|
||||
// Let's restart the dependency by default if we don't have the info stored in the label
|
||||
restart := true
|
||||
required := true
|
||||
dependency := dcArr[0]
|
||||
|
||||
// backward compatibility
|
||||
@@ -209,7 +213,7 @@ func (s *composeService) projectFromName(containers Containers, projectName stri
|
||||
restart, _ = strconv.ParseBool(dcArr[2])
|
||||
}
|
||||
}
|
||||
service.DependsOn[dependency] = types.ServiceDependency{Condition: condition, Restart: restart}
|
||||
service.DependsOn[dependency] = types.ServiceDependency{Condition: condition, Restart: restart, Required: required}
|
||||
}
|
||||
}
|
||||
project.Services = append(project.Services, *service)
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
@@ -72,7 +73,9 @@ func getDefaultFilters(projectName string, oneOff oneOff, selectedServices ...st
|
||||
|
||||
func (s *composeService) getSpecifiedContainer(ctx context.Context, projectName string, oneOff oneOff, stopped bool, serviceName string, containerIndex int) (moby.Container, error) {
|
||||
defaultFilters := getDefaultFilters(projectName, oneOff, serviceName)
|
||||
defaultFilters = append(defaultFilters, containerNumberFilter(containerIndex))
|
||||
if containerIndex > 0 {
|
||||
defaultFilters = append(defaultFilters, containerNumberFilter(containerIndex))
|
||||
}
|
||||
containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
defaultFilters...,
|
||||
@@ -83,8 +86,16 @@ func (s *composeService) getSpecifiedContainer(ctx context.Context, projectName
|
||||
return moby.Container{}, err
|
||||
}
|
||||
if len(containers) < 1 {
|
||||
return moby.Container{}, fmt.Errorf("service %q is not running container #%d", serviceName, containerIndex)
|
||||
if containerIndex > 0 {
|
||||
return moby.Container{}, fmt.Errorf("service %q is not running container #%d", serviceName, containerIndex)
|
||||
}
|
||||
return moby.Container{}, fmt.Errorf("service %q is not running", serviceName)
|
||||
}
|
||||
sort.Slice(containers, func(i, j int) bool {
|
||||
x, _ := strconv.Atoi(containers[i].Labels[api.ContainerNumberLabel])
|
||||
y, _ := strconv.Atoi(containers[j].Labels[api.ContainerNumberLabel])
|
||||
return x < y
|
||||
})
|
||||
container := containers[0]
|
||||
return container, nil
|
||||
}
|
||||
|
||||
@@ -25,8 +25,12 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/docker/compose/v2/internal/tracing"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
containerType "github.com/docker/docker/api/types/container"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
@@ -93,17 +97,19 @@ func (c *convergence) apply(ctx context.Context, project *types.Project, options
|
||||
return err
|
||||
}
|
||||
|
||||
strategy := options.RecreateDependencies
|
||||
if utils.StringContains(options.Services, name) {
|
||||
strategy = options.Recreate
|
||||
}
|
||||
err = c.ensureService(ctx, project, service, strategy, options.Inherit, options.Timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tracing.SpanWrapFunc("service/apply", tracing.ServiceOptions(service), func(ctx context.Context) error {
|
||||
strategy := options.RecreateDependencies
|
||||
if utils.StringContains(options.Services, name) {
|
||||
strategy = options.Recreate
|
||||
}
|
||||
err = c.ensureService(ctx, project, service, strategy, options.Inherit, options.Timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.updateProject(project, name)
|
||||
return nil
|
||||
c.updateProject(project, name)
|
||||
return nil
|
||||
})(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -179,7 +185,8 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||
if i >= expected {
|
||||
// Scale Down
|
||||
container := container
|
||||
eg.Go(func() error {
|
||||
traceOpts := append(tracing.ServiceOptions(service), tracing.ContainerOptions(container)...)
|
||||
eg.Go(tracing.SpanWrapFuncForErrGroup(ctx, "service/scale/down", traceOpts, func(ctx context.Context) error {
|
||||
timeoutInSecond := utils.DurationSecondToInt(timeout)
|
||||
err := c.service.apiClient().ContainerStop(ctx, container.ID, containerType.StopOptions{
|
||||
Timeout: timeoutInSecond,
|
||||
@@ -188,7 +195,7 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||
return err
|
||||
}
|
||||
return c.service.apiClient().ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{})
|
||||
})
|
||||
}))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -198,11 +205,11 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||
}
|
||||
if mustRecreate {
|
||||
i, container := i, container
|
||||
eg.Go(func() error {
|
||||
eg.Go(tracing.SpanWrapFuncForErrGroup(ctx, "container/recreate", tracing.ContainerOptions(container), func(ctx context.Context) error {
|
||||
recreated, err := c.service.recreateContainer(ctx, project, service, container, inherit, timeout)
|
||||
updated[i] = recreated
|
||||
return err
|
||||
})
|
||||
}))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -218,9 +225,9 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||
w.Event(progress.CreatedEvent(name))
|
||||
default:
|
||||
container := container
|
||||
eg.Go(func() error {
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "service/start", tracing.ContainerOptions(container), func(ctx context.Context) error {
|
||||
return c.service.startContainer(ctx, container)
|
||||
})
|
||||
}))
|
||||
}
|
||||
updated[i] = container
|
||||
}
|
||||
@@ -231,7 +238,8 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||
number := next + i
|
||||
name := getContainerName(project.Name, service, number)
|
||||
i := i
|
||||
eg.Go(func() error {
|
||||
eventOpts := tracing.SpanOptions{trace.WithAttributes(attribute.String("container.name", name))}
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "service/scale/up", eventOpts, func(ctx context.Context) error {
|
||||
opts := createOptions{
|
||||
AutoRemove: false,
|
||||
AttachStdin: false,
|
||||
@@ -241,7 +249,7 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||
container, err := c.service.createContainer(ctx, project, service, name, number, opts)
|
||||
updated[actual+i] = container
|
||||
return err
|
||||
})
|
||||
}))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -286,9 +294,18 @@ func containerEvents(containers Containers, eventFunc func(string) progress.Even
|
||||
return events
|
||||
}
|
||||
|
||||
func containerSkippedEvents(containers Containers, eventFunc func(string, string) progress.Event, reason string) []progress.Event {
|
||||
events := []progress.Event{}
|
||||
for _, container := range containers {
|
||||
events = append(events, eventFunc(getContainerProgressName(container), reason))
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
// ServiceConditionRunningOrHealthy is a service condition on status running or healthy
|
||||
const ServiceConditionRunningOrHealthy = "running_or_healthy"
|
||||
|
||||
//nolint:gocyclo
|
||||
func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, dependencies types.DependsOnConfig, containers Containers) error {
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
w := progress.ContextWriter(ctx)
|
||||
@@ -312,6 +329,11 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
|
||||
case ServiceConditionRunningOrHealthy:
|
||||
healthy, err := s.isServiceHealthy(ctx, waitingFor, true)
|
||||
if err != nil {
|
||||
if !config.Required {
|
||||
w.Events(containerSkippedEvents(waitingFor, progress.SkippedEvent, fmt.Sprintf("optional dependency %q is not running or is unhealthy", dep)))
|
||||
logrus.Warnf("optional dependency %q is not running or is unhealthy: %s", dep, err.Error())
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if healthy {
|
||||
@@ -321,6 +343,11 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
|
||||
case types.ServiceConditionHealthy:
|
||||
healthy, err := s.isServiceHealthy(ctx, waitingFor, false)
|
||||
if err != nil {
|
||||
if !config.Required {
|
||||
w.Events(containerSkippedEvents(waitingFor, progress.SkippedEvent, fmt.Sprintf("optional dependency %q failed to start", dep)))
|
||||
logrus.Warnf("optional dependency %q failed to start: %s", dep, err.Error())
|
||||
return nil
|
||||
}
|
||||
w.Events(containerEvents(waitingFor, progress.ErrorEvent))
|
||||
return errors.Wrap(err, "dependency failed to start")
|
||||
}
|
||||
@@ -334,6 +361,12 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
|
||||
return err
|
||||
}
|
||||
if exited {
|
||||
logMessageSuffix := fmt.Sprintf("%q didn't complete successfully: exit %d", dep, code)
|
||||
if !config.Required {
|
||||
w.Events(containerSkippedEvents(waitingFor, progress.SkippedEvent, fmt.Sprintf("optional dependency %s", logMessageSuffix)))
|
||||
logrus.Warnf("optional dependency %s", logMessageSuffix)
|
||||
return nil
|
||||
}
|
||||
w.Events(containerEvents(waitingFor, progress.Exited))
|
||||
if code != 0 {
|
||||
return fmt.Errorf("service %q didn't complete successfully: exit %d", dep, code)
|
||||
@@ -549,13 +582,15 @@ func (s *composeService) createMobyContainer(ctx context.Context,
|
||||
// call via container.NetworkMode & network.NetworkingConfig
|
||||
// any remaining networks are connected one-by-one here after creation (but before start)
|
||||
serviceNetworks := service.NetworksByPriority()
|
||||
if len(serviceNetworks) > 1 {
|
||||
for _, networkKey := range serviceNetworks[1:] {
|
||||
mobyNetworkName := project.Networks[networkKey].Name
|
||||
epSettings := createEndpointSettings(project, service, number, networkKey, cfgs.Links, opts.UseNetworkAliases)
|
||||
if err := s.apiClient().NetworkConnect(ctx, mobyNetworkName, created.ID, epSettings); err != nil {
|
||||
return created, err
|
||||
}
|
||||
for _, networkKey := range serviceNetworks {
|
||||
mobyNetworkName := project.Networks[networkKey].Name
|
||||
if string(cfgs.Host.NetworkMode) == mobyNetworkName {
|
||||
// primary network already configured as part of ContainerCreate
|
||||
continue
|
||||
}
|
||||
epSettings := createEndpointSettings(project, service, number, networkKey, cfgs.Links, opts.UseNetworkAliases)
|
||||
if err := s.apiClient().NetworkConnect(ctx, mobyNetworkName, created.ID, epSettings); err != nil {
|
||||
return created, err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -236,8 +236,8 @@ func TestWaitDependencies(t *testing.T) {
|
||||
redisService := types.ServiceConfig{Name: "redis", Scale: 1}
|
||||
project := types.Project{Name: strings.ToLower(testProject), Services: []types.ServiceConfig{dbService, redisService}}
|
||||
dependencies := types.DependsOnConfig{
|
||||
"db": {Condition: types.ServiceConditionStarted},
|
||||
"redis": {Condition: types.ServiceConditionStarted},
|
||||
"db": {Condition: types.ServiceConditionStarted, Required: true},
|
||||
"redis": {Condition: types.ServiceConditionStarted, Required: true},
|
||||
}
|
||||
assert.NilError(t, tested.waitDependencies(context.Background(), &project, dependencies, nil))
|
||||
})
|
||||
|
||||
@@ -139,6 +139,7 @@ func prepareVolumes(p *types.Project) error {
|
||||
p.Services[i].DependsOn[service.Name].Condition == "" {
|
||||
p.Services[i].DependsOn[service.Name] = types.ServiceDependency{
|
||||
Condition: types.ServiceConditionStarted,
|
||||
Required: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -862,7 +863,7 @@ func buildContainerConfigMounts(p types.Project, s types.ServiceConfig) ([]mount
|
||||
target := config.Target
|
||||
if config.Target == "" {
|
||||
target = configsBaseDir + config.Source
|
||||
} else if !isUnixAbs(config.Target) {
|
||||
} else if !isAbsTarget(config.Target) {
|
||||
target = configsBaseDir + config.Target
|
||||
}
|
||||
|
||||
@@ -897,7 +898,7 @@ func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount
|
||||
target := secret.Target
|
||||
if secret.Target == "" {
|
||||
target = secretsDir + secret.Source
|
||||
} else if !isUnixAbs(secret.Target) {
|
||||
} else if !isAbsTarget(secret.Target) {
|
||||
target = secretsDir + secret.Target
|
||||
}
|
||||
|
||||
@@ -928,10 +929,24 @@ func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func isAbsTarget(p string) bool {
|
||||
return isUnixAbs(p) || isWindowsAbs(p)
|
||||
}
|
||||
|
||||
func isUnixAbs(p string) bool {
|
||||
return strings.HasPrefix(p, "/")
|
||||
}
|
||||
|
||||
func isWindowsAbs(p string) bool {
|
||||
if strings.HasPrefix(p, "\\\\") {
|
||||
return true
|
||||
}
|
||||
if len(p) > 2 && p[1] == ':' {
|
||||
return p[2] == '\\'
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.Mount, error) {
|
||||
source := volume.Source
|
||||
// on windows, filepath.IsAbs(source) is false for unix style abs path like /var/run/docker.sock.
|
||||
|
||||
@@ -124,7 +124,7 @@ func TestPrepareVolumes(t *testing.T) {
|
||||
Name: "aService",
|
||||
VolumesFrom: []string{"anotherService"},
|
||||
DependsOn: map[string]composetypes.ServiceDependency{
|
||||
"anotherService": {Condition: composetypes.ServiceConditionHealthy},
|
||||
"anotherService": {Condition: composetypes.ServiceConditionHealthy, Required: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -268,10 +268,15 @@ func NewGraph(project *types.Project, initialStatus ServiceStatus) (*Graph, erro
|
||||
graph.AddVertex(s.Name, s.Name, initialStatus)
|
||||
}
|
||||
|
||||
for _, s := range project.Services {
|
||||
for index, s := range project.Services {
|
||||
for _, name := range s.GetDependencies() {
|
||||
err := graph.AddEdge(s.Name, name)
|
||||
if err != nil {
|
||||
if !s.DependsOn[name].Required {
|
||||
delete(s.DependsOn, name)
|
||||
project.Services[index] = s
|
||||
continue
|
||||
}
|
||||
if api.IsNotFoundError(err) {
|
||||
ds, err := project.GetDisabledService(name)
|
||||
if err == nil {
|
||||
|
||||
@@ -24,25 +24,14 @@ import (
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
)
|
||||
|
||||
func (s *composeService) Port(ctx context.Context, projectName string, service string, port uint16, options api.PortOptions) (string, int, error) {
|
||||
projectName = strings.ToLower(projectName)
|
||||
list, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
projectFilter(projectName),
|
||||
serviceFilter(service),
|
||||
containerNumberFilter(options.Index),
|
||||
),
|
||||
})
|
||||
container, err := s.getSpecifiedContainer(ctx, projectName, oneOffInclude, false, service, options.Index)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return "", 0, fmt.Errorf("no container found for %s%s%d", service, api.Separator, options.Index)
|
||||
}
|
||||
container := list[0]
|
||||
for _, p := range container.Ports {
|
||||
if p.PrivatePort == port && p.Type == options.Protocol {
|
||||
return p.IP, int(p.PublicPort), nil
|
||||
|
||||
@@ -313,8 +313,15 @@ func isServiceImageToBuild(service types.ServiceConfig, services []types.Service
|
||||
return true
|
||||
}
|
||||
|
||||
for _, depService := range services {
|
||||
if depService.Image == service.Image && depService.Build != nil {
|
||||
if service.Image == "" {
|
||||
// N.B. this should be impossible as service must have either `build` or `image` (or both)
|
||||
return false
|
||||
}
|
||||
|
||||
// look through the other services to see if another has a build definition for the same
|
||||
// image name
|
||||
for _, svc := range services {
|
||||
if svc.Image == service.Image && svc.Build != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func (s *composeService) restart(ctx context.Context, projectName string, option
|
||||
}
|
||||
}
|
||||
|
||||
// ignore depends_on relations which are not impacted by restarting service
|
||||
// ignore depends_on relations which are not impacted by restarting service or not required
|
||||
for i, service := range project.Services {
|
||||
for name, r := range service.DependsOn {
|
||||
if !r.Restart {
|
||||
|
||||
@@ -18,6 +18,7 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -36,11 +37,6 @@ func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.
|
||||
return 0, err
|
||||
}
|
||||
|
||||
start := cmd.NewStartOptions()
|
||||
start.OpenStdin = !opts.Detach && opts.Interactive
|
||||
start.Attach = !opts.Detach
|
||||
start.Containers = []string{containerID}
|
||||
|
||||
// remove cancellable context signal handler so we can forward signals to container without compose to exit
|
||||
signal.Reset()
|
||||
|
||||
@@ -49,9 +45,14 @@ func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.
|
||||
go cmd.ForwardAllSignals(ctx, s.dockerCli, containerID, sigc)
|
||||
defer signal.Stop(sigc)
|
||||
|
||||
err = cmd.RunStart(s.dockerCli, &start)
|
||||
if sterr, ok := err.(cli.StatusError); ok {
|
||||
return sterr.StatusCode, nil
|
||||
err = cmd.RunStart(s.dockerCli, &cmd.StartOptions{
|
||||
OpenStdin: !opts.Detach && opts.Interactive,
|
||||
Attach: !opts.Detach,
|
||||
Containers: []string{containerID},
|
||||
})
|
||||
var stErr cli.StatusError
|
||||
if errors.As(err, &stErr) {
|
||||
return stErr.StatusCode, nil
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ func createTar(env string, config types.ServiceSecretConfig) (bytes.Buffer, erro
|
||||
value := []byte(env)
|
||||
b := bytes.Buffer{}
|
||||
tarWriter := tar.NewWriter(&b)
|
||||
mode := uint32(0o400)
|
||||
mode := uint32(0o444)
|
||||
if config.Mode != nil {
|
||||
mode = *config.Mode
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func createTar(env string, config types.ServiceSecretConfig) (bytes.Buffer, erro
|
||||
target := config.Target
|
||||
if config.Target == "" {
|
||||
target = "/run/secrets/" + config.Source
|
||||
} else if !isUnixAbs(config.Target) {
|
||||
} else if !isAbsTarget(config.Target) {
|
||||
target = "/run/secrets/" + config.Target
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ func (s *composeService) start(ctx context.Context, projectName string, options
|
||||
for _, s := range project.Services {
|
||||
depends[s.Name] = types.ServiceDependency{
|
||||
Condition: getDependencyCondition(s, project),
|
||||
Required: true,
|
||||
}
|
||||
}
|
||||
if options.WaitTimeout > 0 {
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/compose/v2/internal/tracing"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@@ -31,7 +33,7 @@ import (
|
||||
)
|
||||
|
||||
func (s *composeService) Up(ctx context.Context, project *types.Project, options api.UpOptions) error {
|
||||
err := progress.Run(ctx, func(ctx context.Context) error {
|
||||
err := progress.Run(ctx, tracing.SpanWrapFunc("project/up", tracing.ProjectOptions(project), func(ctx context.Context) error {
|
||||
err := s.create(ctx, project, options.Create)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -40,7 +42,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
|
||||
return s.start(ctx, project.Name, options.Start, nil)
|
||||
}
|
||||
return nil
|
||||
}, s.stdinfo())
|
||||
}), s.stdinfo())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func (s *composeService) Viz(_ context.Context, project *types.Project, opts api
|
||||
// dot is the perfect layout for this use case since graph is directed and hierarchical
|
||||
graphBuilder.WriteString(opts.Indentation + "layout=dot;\n")
|
||||
|
||||
addNodes(&graphBuilder, graph, &opts)
|
||||
addNodes(&graphBuilder, graph, project.Name, &opts)
|
||||
graphBuilder.WriteByte('\n')
|
||||
|
||||
addEdges(&graphBuilder, graph, &opts)
|
||||
@@ -63,7 +63,7 @@ func (s *composeService) Viz(_ context.Context, project *types.Project, opts api
|
||||
|
||||
// addNodes adds the corresponding graphviz representation of all the nodes in the given graph to the graphBuilder
|
||||
// returns the same graphBuilder
|
||||
func addNodes(graphBuilder *strings.Builder, graph vizGraph, opts *api.VizOptions) *strings.Builder {
|
||||
func addNodes(graphBuilder *strings.Builder, graph vizGraph, projectName string, opts *api.VizOptions) *strings.Builder {
|
||||
for serviceNode := range graph {
|
||||
// write:
|
||||
// "service name" [style="filled" label<<font point-size="15">service name</font>
|
||||
@@ -107,7 +107,7 @@ func addNodes(graphBuilder *strings.Builder, graph vizGraph, opts *api.VizOption
|
||||
if opts.IncludeImageName {
|
||||
graphBuilder.WriteString("<font point-size=\"10\">")
|
||||
graphBuilder.WriteString("<br/><br/><b>Image:</b><br/>")
|
||||
graphBuilder.WriteString(serviceNode.Image)
|
||||
graphBuilder.WriteString(api.GetImageNameOrDefault(*serviceNode, projectName))
|
||||
graphBuilder.WriteString("</font>")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
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
|
||||
@@ -17,13 +17,13 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/compose/v2/internal/sync"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/jonboulle/clockwork"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
@@ -54,11 +54,8 @@ type Trigger struct {
|
||||
|
||||
const quietPeriod = 2 * time.Second
|
||||
|
||||
// fileMapping contains the Compose service and modified host system path.
|
||||
//
|
||||
// For file sync, the container path is also included.
|
||||
// For rebuild, there is no container path, so it is always empty.
|
||||
type fileMapping struct {
|
||||
// fileEvent contains the Compose service and modified host system path.
|
||||
type fileEvent struct {
|
||||
// Service that the file event is for.
|
||||
Service string
|
||||
// HostPath that was created/modified/deleted outside the container.
|
||||
@@ -67,17 +64,11 @@ type fileMapping struct {
|
||||
// - C:\Users\moby\Documents\hello-world\main.go
|
||||
// - /Users/moby/Documents/hello-world/main.go
|
||||
HostPath string
|
||||
// ContainerPath for the target file inside the container (only populated
|
||||
// for sync events, not rebuild).
|
||||
//
|
||||
// This is the path as used in Docker CLI commands, e.g.
|
||||
// - /workdir/main.go
|
||||
ContainerPath string
|
||||
}
|
||||
|
||||
func (s *composeService) Watch(ctx context.Context, project *types.Project, services []string, _ api.WatchOptions) error { //nolint: gocyclo
|
||||
needRebuild := make(chan fileMapping)
|
||||
needSync := make(chan fileMapping)
|
||||
needRebuild := make(chan fileEvent)
|
||||
needSync := make(chan sync.PathMapping)
|
||||
|
||||
_, err := s.prepareProjectForBuild(project, nil)
|
||||
if err != nil {
|
||||
@@ -175,7 +166,7 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
func (s *composeService) watch(ctx context.Context, name string, watcher watch.Notify, triggers []Trigger, needSync chan fileMapping, needRebuild chan fileMapping) error {
|
||||
func (s *composeService) watch(ctx context.Context, name string, watcher watch.Notify, triggers []Trigger, needSync chan sync.PathMapping, needRebuild chan fileEvent) error {
|
||||
ignores := make([]watch.PathMatcher, len(triggers))
|
||||
for i, trigger := range triggers {
|
||||
ignore, err := watch.NewDockerPatternMatcher(trigger.Path, trigger.Ignore)
|
||||
@@ -209,11 +200,6 @@ WATCH:
|
||||
|
||||
fmt.Fprintf(s.stdinfo(), "change detected on %s\n", hostPath)
|
||||
|
||||
f := fileMapping{
|
||||
HostPath: hostPath,
|
||||
Service: name,
|
||||
}
|
||||
|
||||
switch trigger.Action {
|
||||
case WatchActionSync:
|
||||
logrus.Debugf("modified file %s triggered sync", hostPath)
|
||||
@@ -221,12 +207,18 @@ WATCH:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// always use Unix-style paths for inside the container
|
||||
f.ContainerPath = path.Join(trigger.Target, rel)
|
||||
needSync <- f
|
||||
needSync <- sync.PathMapping{
|
||||
Service: name,
|
||||
HostPath: hostPath,
|
||||
// always use Unix-style paths for inside the container
|
||||
ContainerPath: path.Join(trigger.Target, rel),
|
||||
}
|
||||
case WatchActionRebuild:
|
||||
logrus.Debugf("modified file %s requires image to be rebuilt", hostPath)
|
||||
needRebuild <- f
|
||||
needRebuild <- fileEvent{
|
||||
HostPath: hostPath,
|
||||
Service: name,
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("watch action %q is not supported", trigger)
|
||||
}
|
||||
@@ -304,57 +296,25 @@ func (s *composeService) makeRebuildFn(ctx context.Context, project *types.Proje
|
||||
}
|
||||
}
|
||||
|
||||
func (s *composeService) makeSyncFn(ctx context.Context, project *types.Project, needSync <-chan fileMapping) func() error {
|
||||
func (s *composeService) makeSyncFn(
|
||||
ctx context.Context,
|
||||
project *types.Project,
|
||||
needSync <-chan sync.PathMapping,
|
||||
) func() error {
|
||||
syncer := sync.NewDockerCopy(project.Name, s, s.stdinfo())
|
||||
|
||||
return func() error {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case opt := <-needSync:
|
||||
service, err := project.GetService(opt.Service)
|
||||
case pathMapping := <-needSync:
|
||||
service, err := project.GetService(pathMapping.Service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
scale := 1
|
||||
if service.Deploy != nil && service.Deploy.Replicas != nil {
|
||||
scale = int(*service.Deploy.Replicas)
|
||||
}
|
||||
|
||||
if fi, statErr := os.Stat(opt.HostPath); statErr == nil {
|
||||
if fi.IsDir() {
|
||||
for i := 1; i <= scale; i++ {
|
||||
_, err := s.Exec(ctx, project.Name, api.RunOptions{
|
||||
Service: opt.Service,
|
||||
Command: []string{"mkdir", "-p", opt.ContainerPath},
|
||||
Index: i,
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Warnf("failed to create %q from %s: %v", opt.ContainerPath, opt.Service, err)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(s.stdinfo(), "%s created\n", opt.ContainerPath)
|
||||
} else {
|
||||
err := s.Copy(ctx, project.Name, api.CopyOptions{
|
||||
Source: opt.HostPath,
|
||||
Destination: fmt.Sprintf("%s:%s", opt.Service, opt.ContainerPath),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(s.stdinfo(), "%s updated\n", opt.ContainerPath)
|
||||
}
|
||||
} else if errors.Is(statErr, fs.ErrNotExist) {
|
||||
for i := 1; i <= scale; i++ {
|
||||
_, err := s.Exec(ctx, project.Name, api.RunOptions{
|
||||
Service: opt.Service,
|
||||
Command: []string{"rm", "-rf", opt.ContainerPath},
|
||||
Index: i,
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Warnf("failed to delete %q from %s: %v", opt.ContainerPath, opt.Service, err)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(s.stdinfo(), "%s deleted from service\n", opt.ContainerPath)
|
||||
if err := syncer.Sync(ctx, service, []sync.PathMapping{pathMapping}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,7 +323,7 @@ func (s *composeService) makeSyncFn(ctx context.Context, project *types.Project,
|
||||
|
||||
type rebuildServices map[string]utils.Set[string]
|
||||
|
||||
func debounce(ctx context.Context, clock clockwork.Clock, delay time.Duration, input <-chan fileMapping, fn func(services rebuildServices)) {
|
||||
func debounce(ctx context.Context, clock clockwork.Clock, delay time.Duration, input <-chan fileEvent, fn func(services rebuildServices)) {
|
||||
services := make(rebuildServices)
|
||||
t := clock.NewTimer(delay)
|
||||
defer t.Stop()
|
||||
|
||||
@@ -19,6 +19,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/compose/v2/internal/sync"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/watch"
|
||||
"github.com/jonboulle/clockwork"
|
||||
@@ -27,7 +29,7 @@ import (
|
||||
)
|
||||
|
||||
func Test_debounce(t *testing.T) {
|
||||
ch := make(chan fileMapping)
|
||||
ch := make(chan fileEvent)
|
||||
var (
|
||||
ran int
|
||||
got []string
|
||||
@@ -47,7 +49,7 @@ func Test_debounce(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
for i := 0; i < 100; i++ {
|
||||
ch <- fileMapping{Service: "test"}
|
||||
ch <- fileEvent{Service: "test"}
|
||||
}
|
||||
assert.Equal(t, ran, 0)
|
||||
clock.Advance(quietPeriod)
|
||||
@@ -79,8 +81,8 @@ func (t testWatcher) Errors() chan error {
|
||||
}
|
||||
|
||||
func Test_sync(t *testing.T) {
|
||||
needSync := make(chan fileMapping)
|
||||
needRebuild := make(chan fileMapping)
|
||||
needSync := make(chan sync.PathMapping)
|
||||
needRebuild := make(chan fileEvent)
|
||||
ctx, cancelFunc := context.WithCancel(context.TODO())
|
||||
defer cancelFunc()
|
||||
|
||||
@@ -119,7 +121,7 @@ func Test_sync(t *testing.T) {
|
||||
watcher.Events() <- watch.NewFileEvent("/src/changed")
|
||||
select {
|
||||
case actual := <-needSync:
|
||||
assert.DeepEqual(t, fileMapping{Service: "test", HostPath: "/src/changed", ContainerPath: "/work/changed"}, actual)
|
||||
assert.DeepEqual(t, sync.PathMapping{Service: "test", HostPath: "/src/changed", ContainerPath: "/work/changed"}, actual)
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
t.Error("timeout")
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ func TestLocalComposeBuild(t *testing.T) {
|
||||
t.Run(env+" no rebuild when up again", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "up", "-d")
|
||||
|
||||
assert.Assert(t, !strings.Contains(res.Stdout(), "COPY static"), res.Stdout())
|
||||
assert.Assert(t, !strings.Contains(res.Stdout(), "COPY static"))
|
||||
})
|
||||
|
||||
t.Run(env+" rebuild when up --build", func(t *testing.T) {
|
||||
@@ -121,6 +121,11 @@ func TestLocalComposeBuild(t *testing.T) {
|
||||
res.Assert(t, icmd.Expected{Out: "COPY static2 /usr/share/nginx/html"})
|
||||
})
|
||||
|
||||
t.Run(env+" build --push ignored for unnamed images", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "--workdir", "fixtures/build-test", "build", "--push", "nginx")
|
||||
assert.Assert(t, !strings.Contains(res.Stdout(), "failed to push"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run(env+" cleanup build project", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "down")
|
||||
c.RunDockerCmd(t, "rmi", "build-test-nginx")
|
||||
|
||||
@@ -35,6 +35,12 @@ func TestLocalComposeExec(t *testing.T) {
|
||||
return ret
|
||||
}
|
||||
|
||||
cleanup := func() {
|
||||
c.RunDockerComposeCmd(t, cmdArgs("down", "--timeout=0")...)
|
||||
}
|
||||
cleanup()
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
c.RunDockerComposeCmd(t, cmdArgs("up", "-d")...)
|
||||
|
||||
t.Run("exec true", func(t *testing.T) {
|
||||
|
||||
@@ -136,4 +136,20 @@ func TestLocalComposeRun(t *testing.T) {
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/deps.yaml", "down", "--remove-orphans")
|
||||
})
|
||||
|
||||
t.Run("run without dependencies", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/deps.yaml", "run", "--no-deps", "service_a")
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "shared_dep"), res.Combined())
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "service_b"), res.Combined())
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/deps.yaml", "down", "--remove-orphans")
|
||||
})
|
||||
|
||||
t.Run("run with not required dependency", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/dependencies/deps-not-required.yaml", "run", "foo")
|
||||
assert.Assert(t, strings.Contains(res.Combined(), "foo"), res.Combined())
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "bar"), res.Combined())
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/dependencies/deps-not-required.yaml", "down", "--remove-orphans")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -281,8 +281,12 @@ func TestStopWithDependenciesAttached(t *testing.T) {
|
||||
const projectName = "compose-e2e-stop-with-deps"
|
||||
c := NewParallelCLI(t, WithEnv("COMMAND=echo hello"))
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/dependencies/compose.yaml", "-p", projectName, "up", "--attach-dependencies", "foo")
|
||||
res.Assert(t, icmd.Expected{Out: "exited with code 0"})
|
||||
})
|
||||
cleanup := func() {
|
||||
c.RunDockerComposeCmd(t, "-p", projectName, "down", "--remove-orphans", "--timeout=0")
|
||||
}
|
||||
cleanup()
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/dependencies/compose.yaml", "-p", projectName, "up", "--attach-dependencies", "foo")
|
||||
res.Assert(t, icmd.Expected{Out: "exited with code 0"})
|
||||
}
|
||||
|
||||
@@ -51,8 +51,18 @@ func TestUpExitCodeFrom(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
const projectName = "e2e-exit-code-from"
|
||||
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/start-fail/start-depends_on-long-lived.yaml", "--project-name", projectName, "up", "--exit-code-from=failure", "failure")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 42})
|
||||
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "--remove-orphans")
|
||||
}
|
||||
|
||||
func TestUpExitCodeFromContainerKilled(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
const projectName = "e2e-exit-code-from-kill"
|
||||
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/start-fail/start-depends_on-long-lived.yaml", "--project-name", projectName, "up", "--exit-code-from=test")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 137})
|
||||
res.Assert(t, icmd.Expected{ExitCode: 143})
|
||||
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "--remove-orphans")
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
services:
|
||||
base:
|
||||
image: base
|
||||
init: true
|
||||
build:
|
||||
context: .
|
||||
dockerfile: base.dockerfile
|
||||
service:
|
||||
init: true
|
||||
depends_on:
|
||||
- base
|
||||
build:
|
||||
|
||||
@@ -4,6 +4,7 @@ services:
|
||||
command: echo 'hello world'
|
||||
longrunning:
|
||||
image: alpine
|
||||
init: true
|
||||
depends_on:
|
||||
oneshot:
|
||||
condition: service_completed_successfully
|
||||
|
||||
11
pkg/e2e/fixtures/dependencies/deps-not-required.yaml
Normal file
11
pkg/e2e/fixtures/dependencies/deps-not-required.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
services:
|
||||
foo:
|
||||
image: bash
|
||||
command: echo "foo"
|
||||
depends_on:
|
||||
bar:
|
||||
required: false
|
||||
condition: service_healthy
|
||||
bar:
|
||||
image: nginx:alpine
|
||||
profiles: [not-required]
|
||||
@@ -3,6 +3,7 @@ services:
|
||||
my-service:
|
||||
image: alpine
|
||||
command: tail -f /dev/null
|
||||
init: true
|
||||
depends_on:
|
||||
nginx: {condition: service_healthy}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
services:
|
||||
foo:
|
||||
image: alpine
|
||||
init: true
|
||||
entrypoint: ["sleep", "600"]
|
||||
networks:
|
||||
default:
|
||||
@@ -9,4 +10,4 @@ networks:
|
||||
default:
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 10.1.0.0/16
|
||||
- subnet: 10.1.0.0/16
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
services:
|
||||
ping:
|
||||
image: alpine
|
||||
init: true
|
||||
command: ping localhost -c ${REPEAT:-1}
|
||||
hello:
|
||||
image: alpine
|
||||
|
||||
@@ -6,12 +6,14 @@ services:
|
||||
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
|
||||
db:
|
||||
image: gtardif/sentences-db
|
||||
init: true
|
||||
networks:
|
||||
- dbnet
|
||||
- closesnetworkname1
|
||||
- closesnetworkname2
|
||||
words:
|
||||
image: gtardif/sentences-api
|
||||
init: true
|
||||
ports:
|
||||
- "8080:8080"
|
||||
networks:
|
||||
@@ -19,6 +21,7 @@ services:
|
||||
- servicenet
|
||||
web:
|
||||
image: gtardif/sentences-web
|
||||
init: true
|
||||
ports:
|
||||
- "80:80"
|
||||
labels:
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
services:
|
||||
with-restart:
|
||||
image: alpine
|
||||
init: true
|
||||
command: tail -f /dev/null
|
||||
depends_on:
|
||||
nginx: {condition: service_healthy, restart: true}
|
||||
|
||||
no-restart:
|
||||
image: alpine
|
||||
init: true
|
||||
command: tail -f /dev/null
|
||||
depends_on:
|
||||
nginx: { condition: service_healthy }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
services:
|
||||
restart:
|
||||
image: alpine
|
||||
init: true
|
||||
command: ash -c "if [[ -f /tmp/restart.lock ]] ; then sleep infinity; else touch /tmp/restart.lock; fi"
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
services:
|
||||
db:
|
||||
image: gtardif/sentences-db
|
||||
init: true
|
||||
words:
|
||||
image: gtardif/sentences-api
|
||||
init: true
|
||||
ports:
|
||||
- "95:8080"
|
||||
web:
|
||||
image: gtardif/sentences-web
|
||||
init: true
|
||||
ports:
|
||||
- "90:80"
|
||||
labels:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
services:
|
||||
fail:
|
||||
image: alpine
|
||||
init: true
|
||||
command: sleep infinity
|
||||
healthcheck:
|
||||
test: "false"
|
||||
@@ -8,6 +9,7 @@ services:
|
||||
retries: 3
|
||||
depends:
|
||||
image: alpine
|
||||
init: true
|
||||
command: sleep infinity
|
||||
depends_on:
|
||||
fail:
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
services:
|
||||
safe:
|
||||
image: 'alpine'
|
||||
init: true
|
||||
command: ['/bin/sh', '-c', 'sleep infinity'] # never exiting
|
||||
failure:
|
||||
image: 'alpine'
|
||||
command: ['/bin/sh', '-c', 'sleep 2 ; echo "exiting" ; exit 42']
|
||||
init: true
|
||||
command: ['/bin/sh', '-c', 'sleep 1 ; echo "exiting with error" ; exit 42']
|
||||
test:
|
||||
image: 'alpine'
|
||||
init: true
|
||||
command: ['/bin/sh', '-c', 'sleep 99999 ; echo "tests are OK"'] # very long job
|
||||
depends_on: [safe]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
services:
|
||||
stderr:
|
||||
image: alpine
|
||||
init: true
|
||||
command: /bin/ash /log_to_stderr.sh
|
||||
volumes:
|
||||
- ./log_to_stderr.sh:/log_to_stderr.sh
|
||||
|
||||
34
pkg/e2e/fixtures/watch/compose.yaml
Normal file
34
pkg/e2e/fixtures/watch/compose.yaml
Normal file
@@ -0,0 +1,34 @@
|
||||
x-dev: &x-dev
|
||||
watch:
|
||||
- action: sync
|
||||
path: ./data
|
||||
target: /app/data
|
||||
ignore:
|
||||
- '*.foo'
|
||||
- ./ignored
|
||||
|
||||
services:
|
||||
alpine:
|
||||
build:
|
||||
dockerfile_inline: |-
|
||||
FROM alpine
|
||||
RUN mkdir -p /app/data
|
||||
init: true
|
||||
command: sleep infinity
|
||||
x-develop: *x-dev
|
||||
busybox:
|
||||
build:
|
||||
dockerfile_inline: |-
|
||||
FROM busybox
|
||||
RUN mkdir -p /app/data
|
||||
init: true
|
||||
command: sleep infinity
|
||||
x-develop: *x-dev
|
||||
debian:
|
||||
build:
|
||||
dockerfile_inline: |-
|
||||
FROM debian
|
||||
RUN mkdir -p /app/data
|
||||
init: true
|
||||
command: sleep infinity
|
||||
x-develop: *x-dev
|
||||
1
pkg/e2e/fixtures/watch/data/hello.txt
Normal file
1
pkg/e2e/fixtures/watch/data/hello.txt
Normal file
@@ -0,0 +1 @@
|
||||
hello world
|
||||
@@ -29,47 +29,34 @@ import (
|
||||
|
||||
func TestNetworks(t *testing.T) {
|
||||
// fixture is shared with TestNetworkModes and is not safe to run concurrently
|
||||
c := NewCLI(t)
|
||||
|
||||
const projectName = "network-e2e"
|
||||
c := NewCLI(t, WithEnv(
|
||||
"COMPOSE_PROJECT_NAME="+projectName,
|
||||
"COMPOSE_FILE=./fixtures/network-test/compose.yaml",
|
||||
))
|
||||
|
||||
t.Run("ensure we do not reuse previous networks", func(t *testing.T) {
|
||||
c.RunDockerOrExitError(t, "network", "rm", projectName+"-dbnet")
|
||||
c.RunDockerOrExitError(t, "network", "rm", "microservices")
|
||||
})
|
||||
c.RunDockerComposeCmd(t, "down", "-t0", "-v")
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/network-test/compose.yaml", "--project-name", projectName, "up",
|
||||
"-d")
|
||||
})
|
||||
c.RunDockerComposeCmd(t, "up", "-d")
|
||||
|
||||
t.Run("check running project", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-p", projectName, "ps")
|
||||
res.Assert(t, icmd.Expected{Out: `web`})
|
||||
res := c.RunDockerComposeCmd(t, "ps")
|
||||
res.Assert(t, icmd.Expected{Out: `web`})
|
||||
|
||||
endpoint := "http://localhost:80"
|
||||
output := HTTPGetWithRetry(t, endpoint+"/words/noun", http.StatusOK, 2*time.Second, 20*time.Second)
|
||||
assert.Assert(t, strings.Contains(output, `"word":`))
|
||||
endpoint := "http://localhost:80"
|
||||
output := HTTPGetWithRetry(t, endpoint+"/words/noun", http.StatusOK, 2*time.Second, 20*time.Second)
|
||||
assert.Assert(t, strings.Contains(output, `"word":`))
|
||||
|
||||
res = c.RunDockerCmd(t, "network", "ls")
|
||||
res.Assert(t, icmd.Expected{Out: projectName + "_dbnet"})
|
||||
res.Assert(t, icmd.Expected{Out: "microservices"})
|
||||
})
|
||||
res = c.RunDockerCmd(t, "network", "ls")
|
||||
res.Assert(t, icmd.Expected{Out: projectName + "_dbnet"})
|
||||
res.Assert(t, icmd.Expected{Out: "microservices"})
|
||||
|
||||
t.Run("port", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "--project-name", projectName, "port", "words", "8080")
|
||||
res.Assert(t, icmd.Expected{Out: `0.0.0.0:8080`})
|
||||
})
|
||||
res = c.RunDockerComposeCmd(t, "port", "words", "8080")
|
||||
res.Assert(t, icmd.Expected{Out: `0.0.0.0:8080`})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
_ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||
})
|
||||
|
||||
t.Run("check networks after down", func(t *testing.T) {
|
||||
res := c.RunDockerCmd(t, "network", "ls")
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), projectName), res.Combined())
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "microservices"), res.Combined())
|
||||
})
|
||||
c.RunDockerComposeCmd(t, "down", "-t0", "-v")
|
||||
res = c.RunDockerCmd(t, "network", "ls")
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), projectName), res.Combined())
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "microservices"), res.Combined())
|
||||
}
|
||||
|
||||
func TestNetworkAliases(t *testing.T) {
|
||||
|
||||
@@ -153,3 +153,16 @@ func TestScaleDoesntRecreate(t *testing.T) {
|
||||
assert.Check(t, !strings.Contains(res.Combined(), "Recreated"))
|
||||
|
||||
}
|
||||
|
||||
func TestUpWithDependencyNotRequired(t *testing.T) {
|
||||
c := NewCLI(t)
|
||||
const projectName = "compose-e2e-dependency-not-required"
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||
})
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/dependencies/deps-not-required.yaml", "--project-name", projectName,
|
||||
"--profile", "not-required", "up", "-d")
|
||||
assert.Assert(t, strings.Contains(res.Combined(), "foo"), res.Combined())
|
||||
assert.Assert(t, strings.Contains(res.Combined(), " optional dependency \"bar\" failed to start"), res.Combined())
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gotest.tools/v3/icmd"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
@@ -28,6 +30,12 @@ func TestWaitOnFaster(t *testing.T) {
|
||||
const projectName = "e2e-wait-faster"
|
||||
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/wait/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "faster")
|
||||
}
|
||||
@@ -36,6 +44,12 @@ func TestWaitOnSlower(t *testing.T) {
|
||||
const projectName = "e2e-wait-slower"
|
||||
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/wait/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "slower")
|
||||
}
|
||||
@@ -44,12 +58,27 @@ func TestWaitOnInfinity(t *testing.T) {
|
||||
const projectName = "e2e-wait-infinity"
|
||||
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/wait/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
|
||||
cmd := c.NewDockerComposeCmd(t, "--project-name", projectName, "wait", "infinity")
|
||||
r := icmd.StartCmd(cmd)
|
||||
assert.NilError(t, r.Error)
|
||||
t.Cleanup(func() {
|
||||
if r.Cmd.Process != nil {
|
||||
_ = r.Cmd.Process.Kill()
|
||||
}
|
||||
})
|
||||
|
||||
finished := make(chan struct{})
|
||||
ticker := time.NewTicker(7 * time.Second)
|
||||
go func() {
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "infinity")
|
||||
_ = r.Cmd.Wait()
|
||||
finished <- struct{}{}
|
||||
}()
|
||||
|
||||
@@ -64,6 +93,12 @@ func TestWaitAndDrop(t *testing.T) {
|
||||
const projectName = "e2e-wait-and-drop"
|
||||
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/wait/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "--down-project", "faster")
|
||||
|
||||
|
||||
189
pkg/e2e/watch_test.go
Normal file
189
pkg/e2e/watch_test.go
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/distribution/distribution/v3/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/icmd"
|
||||
"gotest.tools/v3/poll"
|
||||
)
|
||||
|
||||
func TestWatch(t *testing.T) {
|
||||
if runtime.GOOS == "darwin" {
|
||||
t.Skip("Test currently broken on macOS due to symlink issues (see compose-go#436)")
|
||||
}
|
||||
|
||||
services := []string{"alpine", "busybox", "debian"}
|
||||
for _, svcName := range services {
|
||||
t.Run(svcName, func(t *testing.T) {
|
||||
t.Helper()
|
||||
doTest(t, svcName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: these tests all share a single Compose file but are safe to run concurrently
|
||||
func doTest(t *testing.T, svcName string) {
|
||||
tmpdir := t.TempDir()
|
||||
dataDir := filepath.Join(tmpdir, "data")
|
||||
writeDataFile := func(name string, contents string) {
|
||||
t.Helper()
|
||||
dest := filepath.Join(dataDir, name)
|
||||
require.NoError(t, os.MkdirAll(filepath.Dir(dest), 0o700))
|
||||
t.Logf("writing %q to %q", contents, dest)
|
||||
require.NoError(t, os.WriteFile(dest, []byte(contents+"\n"), 0o600))
|
||||
}
|
||||
|
||||
composeFilePath := filepath.Join(tmpdir, "compose.yaml")
|
||||
CopyFile(t, filepath.Join("fixtures", "watch", "compose.yaml"), composeFilePath)
|
||||
|
||||
projName := "e2e-watch-" + svcName
|
||||
env := []string{
|
||||
"COMPOSE_FILE=" + composeFilePath,
|
||||
"COMPOSE_PROJECT_NAME=" + projName,
|
||||
}
|
||||
|
||||
cli := NewCLI(t, WithEnv(env...))
|
||||
|
||||
cleanup := func() {
|
||||
cli.RunDockerComposeCmd(t, "down", svcName, "--timeout=0", "--remove-orphans", "--volumes")
|
||||
}
|
||||
cleanup()
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
cli.RunDockerComposeCmd(t, "up", svcName, "--wait", "--build")
|
||||
|
||||
cmd := cli.NewDockerComposeCmd(t, "--verbose", "alpha", "watch", svcName)
|
||||
// stream output since watch runs in the background
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
r := icmd.StartCmd(cmd)
|
||||
require.NoError(t, r.Error)
|
||||
t.Cleanup(func() {
|
||||
// IMPORTANT: watch doesn't exit on its own, don't leak processes!
|
||||
if r.Cmd.Process != nil {
|
||||
_ = r.Cmd.Process.Kill()
|
||||
}
|
||||
})
|
||||
var testComplete atomic.Bool
|
||||
go func() {
|
||||
// if the process exits abnormally before the test is done, fail the test
|
||||
if err := r.Cmd.Wait(); err != nil && !testComplete.Load() {
|
||||
assert.Check(t, cmp.Nil(err))
|
||||
}
|
||||
}()
|
||||
|
||||
require.NoError(t, os.Mkdir(dataDir, 0o700))
|
||||
|
||||
checkFileContents := func(path string, contents string) poll.Check {
|
||||
return func(pollLog poll.LogT) poll.Result {
|
||||
if r.Cmd.ProcessState != nil {
|
||||
return poll.Error(fmt.Errorf("watch process exited early: %s", r.Cmd.ProcessState))
|
||||
}
|
||||
res := icmd.RunCmd(cli.NewDockerComposeCmd(t, "exec", svcName, "cat", path))
|
||||
if strings.Contains(res.Stdout(), contents) {
|
||||
return poll.Success()
|
||||
}
|
||||
return poll.Continue(res.Combined())
|
||||
}
|
||||
}
|
||||
|
||||
waitForFlush := func() {
|
||||
sentinelVal := uuid.Generate().String()
|
||||
writeDataFile("wait.txt", sentinelVal)
|
||||
poll.WaitOn(t, checkFileContents("/app/data/wait.txt", sentinelVal))
|
||||
}
|
||||
|
||||
t.Logf("Writing to a file until Compose watch is up and running")
|
||||
poll.WaitOn(t, func(t poll.LogT) poll.Result {
|
||||
writeDataFile("hello.txt", "hello world")
|
||||
return checkFileContents("/app/data/hello.txt", "hello world")(t)
|
||||
})
|
||||
|
||||
t.Logf("Modifying file contents")
|
||||
writeDataFile("hello.txt", "hello watch")
|
||||
poll.WaitOn(t, checkFileContents("/app/data/hello.txt", "hello watch"))
|
||||
|
||||
t.Logf("Deleting file")
|
||||
require.NoError(t, os.Remove(filepath.Join(dataDir, "hello.txt")))
|
||||
waitForFlush()
|
||||
cli.RunDockerComposeCmdNoCheck(t, "exec", svcName, "stat", "/app/data/hello.txt").
|
||||
Assert(t, icmd.Expected{
|
||||
ExitCode: 1,
|
||||
Err: "No such file or directory",
|
||||
},
|
||||
)
|
||||
|
||||
t.Logf("Writing to ignored paths")
|
||||
writeDataFile("data.foo", "ignored")
|
||||
writeDataFile(filepath.Join("ignored", "hello.txt"), "ignored")
|
||||
waitForFlush()
|
||||
cli.RunDockerComposeCmdNoCheck(t, "exec", svcName, "stat", "/app/data/data.foo").
|
||||
Assert(t, icmd.Expected{
|
||||
ExitCode: 1,
|
||||
Err: "No such file or directory",
|
||||
},
|
||||
)
|
||||
cli.RunDockerComposeCmdNoCheck(t, "exec", svcName, "stat", "/app/data/ignored").
|
||||
Assert(t, icmd.Expected{
|
||||
ExitCode: 1,
|
||||
Err: "No such file or directory",
|
||||
},
|
||||
)
|
||||
|
||||
t.Logf("Creating subdirectory")
|
||||
require.NoError(t, os.Mkdir(filepath.Join(dataDir, "subdir"), 0o700))
|
||||
waitForFlush()
|
||||
cli.RunDockerComposeCmd(t, "exec", svcName, "stat", "/app/data/subdir")
|
||||
|
||||
t.Logf("Writing to file in subdirectory")
|
||||
writeDataFile(filepath.Join("subdir", "file.txt"), "a")
|
||||
poll.WaitOn(t, checkFileContents("/app/data/subdir/file.txt", "a"))
|
||||
|
||||
t.Logf("Writing to file multiple times")
|
||||
writeDataFile(filepath.Join("subdir", "file.txt"), "x")
|
||||
writeDataFile(filepath.Join("subdir", "file.txt"), "y")
|
||||
writeDataFile(filepath.Join("subdir", "file.txt"), "z")
|
||||
poll.WaitOn(t, checkFileContents("/app/data/subdir/file.txt", "z"))
|
||||
writeDataFile(filepath.Join("subdir", "file.txt"), "z")
|
||||
writeDataFile(filepath.Join("subdir", "file.txt"), "y")
|
||||
writeDataFile(filepath.Join("subdir", "file.txt"), "x")
|
||||
poll.WaitOn(t, checkFileContents("/app/data/subdir/file.txt", "x"))
|
||||
|
||||
t.Logf("Deleting directory")
|
||||
require.NoError(t, os.RemoveAll(filepath.Join(dataDir, "subdir")))
|
||||
waitForFlush()
|
||||
cli.RunDockerComposeCmdNoCheck(t, "exec", svcName, "stat", "/app/data/subdir").
|
||||
Assert(t, icmd.Expected{
|
||||
ExitCode: 1,
|
||||
Err: "No such file or directory",
|
||||
},
|
||||
)
|
||||
|
||||
testComplete.Store(true)
|
||||
}
|
||||
@@ -47,3 +47,9 @@ func (s Set[T]) Elements() []T {
|
||||
}
|
||||
return elements
|
||||
}
|
||||
|
||||
func (s Set[T]) RemoveAll(elements ...T) {
|
||||
for _, e := range elements {
|
||||
s.Remove(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func Contains[T any](origin []T, element T) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// RemoveAll removes all elements from origin slice
|
||||
// Remove removes all elements from origin slice
|
||||
func Remove[T any](origin []T, elements ...T) []T {
|
||||
var filtered []T
|
||||
for _, v := range origin {
|
||||
|
||||
Reference in New Issue
Block a user