mirror of
https://github.com/docker/compose.git
synced 2026-02-13 03:59:29 +08:00
Compare commits
13 Commits
v2.19.0
...
multiplaye
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f06caeb844 | ||
|
|
49d1bc7524 | ||
|
|
827e864ed0 | ||
|
|
28301fb1a4 | ||
|
|
fa3e16c66b | ||
|
|
edd76bfd70 | ||
|
|
c496c23071 | ||
|
|
02284378bf | ||
|
|
10b290e682 | ||
|
|
3906a7a67c | ||
|
|
83671db3dd | ||
|
|
1a41678c58 | ||
|
|
035276e027 |
@@ -43,6 +43,7 @@ type buildOptions struct {
|
||||
noCache bool
|
||||
memory cliopts.MemBytes
|
||||
ssh string
|
||||
builder string
|
||||
}
|
||||
|
||||
func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) {
|
||||
@@ -54,6 +55,10 @@ func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions,
|
||||
return api.BuildOptions{}, err
|
||||
}
|
||||
}
|
||||
builderName := opts.builder
|
||||
if builderName == "" {
|
||||
builderName = os.Getenv("BUILDX_BUILDER")
|
||||
}
|
||||
|
||||
return api.BuildOptions{
|
||||
Pull: opts.pull,
|
||||
@@ -64,6 +69,7 @@ func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions,
|
||||
Quiet: opts.quiet,
|
||||
Services: services,
|
||||
SSHs: SSHKeys,
|
||||
Builder: builderName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -101,6 +107,7 @@ func buildCommand(p *ProjectOptions, progress *string, backend api.Service) *cob
|
||||
cmd.Flags().BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.")
|
||||
cmd.Flags().StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services.")
|
||||
cmd.Flags().StringVar(&opts.ssh, "ssh", "", "Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)")
|
||||
cmd.Flags().StringVar(&opts.builder, "builder", "", "Set builder to use.")
|
||||
cmd.Flags().Bool("parallel", true, "Build images in parallel. DEPRECATED")
|
||||
cmd.Flags().MarkHidden("parallel") //nolint:errcheck
|
||||
cmd.Flags().Bool("compress", true, "Compress the build context using gzip. DEPRECATED")
|
||||
|
||||
@@ -427,10 +427,12 @@ func RootCommand(streams command.Cli, backend api.Service) *cobra.Command { //no
|
||||
imagesCommand(&opts, streams, backend),
|
||||
versionCommand(streams),
|
||||
buildCommand(&opts, &progress, backend),
|
||||
publishCommand(&opts, backend),
|
||||
pushCommand(&opts, backend),
|
||||
pullCommand(&opts, backend),
|
||||
createCommand(&opts, backend),
|
||||
copyCommand(&opts, backend),
|
||||
waitCommand(&opts, backend),
|
||||
alphaCommand(&opts, backend),
|
||||
)
|
||||
|
||||
|
||||
55
cmd/compose/publish.go
Normal file
55
cmd/compose/publish.go
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
type publishOptions struct {
|
||||
*ProjectOptions
|
||||
composeOptions
|
||||
Repository string
|
||||
}
|
||||
|
||||
func publishCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := pushOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
publishCmd := &cobra.Command{
|
||||
Use: "publish [OPTIONS] [REPOSITORY]",
|
||||
Short: "Publish compose application",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPublish(ctx, backend, opts, args[0])
|
||||
}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
}
|
||||
return publishCmd
|
||||
}
|
||||
|
||||
func runPublish(ctx context.Context, backend api.Service, opts pushOptions, repository string) error {
|
||||
project, err := opts.ToProject(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return backend.Publish(ctx, project, repository)
|
||||
}
|
||||
@@ -31,6 +31,7 @@ type pushOptions struct {
|
||||
IncludeDeps bool
|
||||
Ignorefailures bool
|
||||
Quiet bool
|
||||
Repository string
|
||||
}
|
||||
|
||||
func pushCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
@@ -48,6 +49,7 @@ func pushCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
pushCmd.Flags().BoolVar(&opts.Ignorefailures, "ignore-push-failures", false, "Push what it can and ignores images with push failures")
|
||||
pushCmd.Flags().BoolVar(&opts.IncludeDeps, "include-deps", false, "Also push images of services declared as dependencies")
|
||||
pushCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Push without printing progress information")
|
||||
pushCmd.Flags().StringVarP(&opts.Repository, "repository", "r", "", "Also publish the compose application in repository")
|
||||
|
||||
return pushCmd
|
||||
}
|
||||
@@ -68,5 +70,6 @@ func runPush(ctx context.Context, backend api.Service, opts pushOptions, service
|
||||
return backend.Push(ctx, project, api.PushOptions{
|
||||
IgnoreFailures: opts.Ignorefailures,
|
||||
Quiet: opts.Quiet,
|
||||
Repository: opts.Repository,
|
||||
})
|
||||
}
|
||||
|
||||
72
cmd/compose/wait.go
Normal file
72
cmd/compose/wait.go
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
Copyright 2023 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type waitOptions struct {
|
||||
*ProjectOptions
|
||||
|
||||
services []string
|
||||
|
||||
downProject bool
|
||||
}
|
||||
|
||||
func waitCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := waitOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
|
||||
var statusCode int64
|
||||
var err error
|
||||
cmd := &cobra.Command{
|
||||
Use: "wait SERVICE [SERVICE...] [OPTIONS]",
|
||||
Short: "Block until the first service container stops",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
RunE: Adapt(func(ctx context.Context, services []string) error {
|
||||
opts.services = services
|
||||
statusCode, err = runWait(ctx, backend, &opts)
|
||||
return err
|
||||
}),
|
||||
PostRun: func(cmd *cobra.Command, args []string) {
|
||||
os.Exit(int(statusCode))
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&opts.downProject, "down-project", false, "Drops project when the first container stops")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runWait(ctx context.Context, backend api.Service, opts *waitOptions) (int64, error) {
|
||||
_, name, err := opts.projectOrName()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return backend.Wait(ctx, name, api.WaitOptions{
|
||||
Services: opts.services,
|
||||
DownProjectOnContainerExit: opts.downProject,
|
||||
})
|
||||
}
|
||||
@@ -33,6 +33,7 @@ Define and run multi-container applications with Docker.
|
||||
| [`unpause`](compose_unpause.md) | Unpause services |
|
||||
| [`up`](compose_up.md) | Create and start containers |
|
||||
| [`version`](compose_version.md) | Show the Docker Compose version information |
|
||||
| [`wait`](compose_wait.md) | Block until the first service container stops |
|
||||
|
||||
|
||||
### Options
|
||||
|
||||
@@ -8,6 +8,7 @@ Build or rebuild services
|
||||
| Name | Type | Default | Description |
|
||||
|:-----------------|:--------------|:--------|:------------------------------------------------------------------------------------------------------------|
|
||||
| `--build-arg` | `stringArray` | | Set build-time variables for services. |
|
||||
| `--builder` | `string` | | Set builder to use. |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `-m`, `--memory` | `bytes` | `0` | Set memory limit for the build container. Not supported by BuildKit. |
|
||||
| `--no-cache` | | | Do not use cache when building the image |
|
||||
|
||||
@@ -36,7 +36,7 @@ Run a one-off command on a service.
|
||||
|
||||
Runs a one-time command against a service.
|
||||
|
||||
the following command starts the `web` service and runs `bash` as its command:
|
||||
The following command starts the `web` service and runs `bash` as its command:
|
||||
|
||||
```console
|
||||
$ docker compose run web bash
|
||||
|
||||
15
docs/reference/compose_wait.md
Normal file
15
docs/reference/compose_wait.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# docker compose wait
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Block until the first service container stops
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-----------------|:-----|:--------|:---------------------------------------------|
|
||||
| `--down-project` | | | Drops project when the first container stops |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -171,6 +171,7 @@ cname:
|
||||
- docker compose unpause
|
||||
- docker compose up
|
||||
- docker compose version
|
||||
- docker compose wait
|
||||
clink:
|
||||
- docker_compose_build.yaml
|
||||
- docker_compose_config.yaml
|
||||
@@ -197,6 +198,7 @@ clink:
|
||||
- docker_compose_unpause.yaml
|
||||
- docker_compose_up.yaml
|
||||
- docker_compose_version.yaml
|
||||
- docker_compose_wait.yaml
|
||||
options:
|
||||
- option: ansi
|
||||
value_type: string
|
||||
|
||||
@@ -24,6 +24,15 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: builder
|
||||
value_type: string
|
||||
description: Set builder to use.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: compress
|
||||
value_type: bool
|
||||
default_value: "true"
|
||||
|
||||
@@ -3,7 +3,7 @@ short: Run a one-off command on a service.
|
||||
long: |-
|
||||
Runs a one-time command against a service.
|
||||
|
||||
the following command starts the `web` service and runs `bash` as its command:
|
||||
The following command starts the `web` service and runs `bash` as its command:
|
||||
|
||||
```console
|
||||
$ docker compose run web bash
|
||||
|
||||
34
docs/reference/docker_compose_wait.yaml
Normal file
34
docs/reference/docker_compose_wait.yaml
Normal file
@@ -0,0 +1,34 @@
|
||||
command: docker compose wait
|
||||
short: Block until the first service container stops
|
||||
long: Block until the first service container stops
|
||||
usage: docker compose wait SERVICE [SERVICE...] [OPTIONS]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
- option: down-project
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Drops project when the first container stops
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
|
||||
5
go.mod
5
go.mod
@@ -6,7 +6,7 @@ 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.0
|
||||
github.com/compose-spec/compose-go v1.15.1
|
||||
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
|
||||
@@ -29,7 +29,7 @@ require (
|
||||
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
|
||||
@@ -183,6 +183,7 @@ require (
|
||||
k8s.io/klog/v2 v2.90.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
|
||||
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect
|
||||
oras.land/oras-go/v2 v2.2.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
|
||||
8
go.sum
8
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.0 h1:rv3TTgbS3U4Y8sRTngrcxDmpbz+fq26wTqHculSCi6s=
|
||||
github.com/compose-spec/compose-go v1.15.0/go.mod h1:3yngGBGfls6FHGQsg4B1z6gz8ej9SOvmAJtxCwgbcnc=
|
||||
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/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=
|
||||
@@ -513,6 +513,8 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I
|
||||
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=
|
||||
@@ -1110,6 +1112,8 @@ k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+O
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4=
|
||||
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk=
|
||||
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
oras.land/oras-go/v2 v2.2.0 h1:E1fqITD56Eg5neZbxBtAdZVgDHD6wBabJo6xESTcQyo=
|
||||
oras.land/oras-go/v2 v2.2.0/go.mod h1:pXjn0+KfarspMHHNR3A56j3tgvr+mxArHuI8qVn59v8=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
||||
@@ -74,6 +74,8 @@ type Service interface {
|
||||
Events(ctx context.Context, projectName string, options EventsOptions) error
|
||||
// Port executes the equivalent to a `compose port`
|
||||
Port(ctx context.Context, projectName string, service string, port uint16, options PortOptions) (string, int, error)
|
||||
// Publish executes the equivalent to a `compose publish`
|
||||
Publish(ctx context.Context, project *types.Project, repository string) error
|
||||
// Images executes the equivalent of a `compose images`
|
||||
Images(ctx context.Context, projectName string, options ImagesOptions) ([]ImageSummary, error)
|
||||
// MaxConcurrency defines upper limit for concurrent operations against engine API
|
||||
@@ -84,6 +86,15 @@ type Service interface {
|
||||
Watch(ctx context.Context, project *types.Project, services []string, options WatchOptions) error
|
||||
// Viz generates a graphviz graph of the project services
|
||||
Viz(ctx context.Context, project *types.Project, options VizOptions) (string, error)
|
||||
// Wait blocks until at least one of the services' container exits
|
||||
Wait(ctx context.Context, projectName string, options WaitOptions) (int64, error)
|
||||
}
|
||||
|
||||
type WaitOptions struct {
|
||||
// Services passed in the command line to be waited
|
||||
Services []string
|
||||
// Executes a down when a container exits
|
||||
DownProjectOnContainerExit bool
|
||||
}
|
||||
|
||||
type VizOptions struct {
|
||||
@@ -121,6 +132,8 @@ type BuildOptions struct {
|
||||
SSHs []types.SSHKey
|
||||
// Memory limit for the build container
|
||||
Memory int64
|
||||
// Builder name passed in the command line
|
||||
Builder string
|
||||
}
|
||||
|
||||
// Apply mutates project according to build options
|
||||
@@ -249,6 +262,7 @@ type ConfigOptions struct {
|
||||
// PushOptions group options of the Push API
|
||||
type PushOptions struct {
|
||||
Quiet bool
|
||||
Repository string
|
||||
IgnoreFailures bool
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,8 @@ type ServiceProxy struct {
|
||||
MaxConcurrencyFn func(parallel int)
|
||||
DryRunModeFn func(ctx context.Context, dryRun bool) (context.Context, error)
|
||||
VizFn func(ctx context.Context, project *types.Project, options VizOptions) (string, error)
|
||||
WaitFn func(ctx context.Context, projectName string, options WaitOptions) (int64, error)
|
||||
PublishFn func(ctx context.Context, project *types.Project, repository string) error
|
||||
interceptors []Interceptor
|
||||
}
|
||||
|
||||
@@ -90,11 +92,13 @@ func (s *ServiceProxy) WithService(service Service) *ServiceProxy {
|
||||
s.TopFn = service.Top
|
||||
s.EventsFn = service.Events
|
||||
s.PortFn = service.Port
|
||||
s.PublishFn = service.Publish
|
||||
s.ImagesFn = service.Images
|
||||
s.WatchFn = service.Watch
|
||||
s.MaxConcurrencyFn = service.MaxConcurrency
|
||||
s.DryRunModeFn = service.DryRunMode
|
||||
s.VizFn = service.Viz
|
||||
s.WaitFn = service.Wait
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -309,6 +313,10 @@ func (s *ServiceProxy) Port(ctx context.Context, projectName string, service str
|
||||
return s.PortFn(ctx, projectName, service, port, options)
|
||||
}
|
||||
|
||||
func (s *ServiceProxy) Publish(ctx context.Context, project *types.Project, repository string) error {
|
||||
return s.PublishFn(ctx, project, repository)
|
||||
}
|
||||
|
||||
// Images implements Service interface
|
||||
func (s *ServiceProxy) Images(ctx context.Context, project string, options ImagesOptions) ([]ImageSummary, error) {
|
||||
if s.ImagesFn == nil {
|
||||
@@ -325,7 +333,7 @@ func (s *ServiceProxy) Watch(ctx context.Context, project *types.Project, servic
|
||||
return s.WatchFn(ctx, project, services, options)
|
||||
}
|
||||
|
||||
// Viz implements Viz interface
|
||||
// Viz implements Service interface
|
||||
func (s *ServiceProxy) Viz(ctx context.Context, project *types.Project, options VizOptions) (string, error) {
|
||||
if s.VizFn == nil {
|
||||
return "", ErrNotImplemented
|
||||
@@ -333,6 +341,14 @@ func (s *ServiceProxy) Viz(ctx context.Context, project *types.Project, options
|
||||
return s.VizFn(ctx, project, options)
|
||||
}
|
||||
|
||||
// Wait implements Service interface
|
||||
func (s *ServiceProxy) Wait(ctx context.Context, projectName string, options WaitOptions) (int64, error) {
|
||||
if s.WaitFn == nil {
|
||||
return 0, ErrNotImplemented
|
||||
}
|
||||
return s.WaitFn(ctx, projectName, options)
|
||||
}
|
||||
|
||||
func (s *ServiceProxy) MaxConcurrency(i int) {
|
||||
s.MaxConcurrencyFn(i)
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
}
|
||||
buildOptions.BuildArgs = mergeArgs(buildOptions.BuildArgs, flatten(args))
|
||||
|
||||
digest, err := s.doBuildBuildkit(ctx, service.Name, buildOptions, w)
|
||||
digest, err := s.doBuildBuildkit(ctx, service.Name, buildOptions, w, options.Builder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ import (
|
||||
"github.com/moby/buildkit/client"
|
||||
)
|
||||
|
||||
func (s *composeService) doBuildBuildkit(ctx context.Context, service string, opts build.Options, p *buildx.Printer) (string, error) {
|
||||
b, err := builder.New(s.dockerCli)
|
||||
func (s *composeService) doBuildBuildkit(ctx context.Context, service string, opts build.Options, p *buildx.Printer, builderName string) (string, error) {
|
||||
b, err := builder.New(s.dockerCli, builder.WithName(builderName))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ import (
|
||||
"github.com/containerd/containerd/platforms"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
containerType "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -233,7 +232,13 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||
name := getContainerName(project.Name, service, number)
|
||||
i := i
|
||||
eg.Go(func() error {
|
||||
container, err := c.service.createContainer(ctx, project, service, name, number, false, true, false)
|
||||
opts := createOptions{
|
||||
AutoRemove: false,
|
||||
AttachStdin: false,
|
||||
UseNetworkAliases: true,
|
||||
Labels: mergeLabels(service.Labels, service.CustomLabels),
|
||||
}
|
||||
container, err := c.service.createContainer(ctx, project, service, name, number, opts)
|
||||
updated[actual+i] = container
|
||||
return err
|
||||
})
|
||||
@@ -399,12 +404,11 @@ func getScale(config types.ServiceConfig) (int, error) {
|
||||
}
|
||||
|
||||
func (s *composeService) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig,
|
||||
name string, number int, autoRemove bool, useNetworkAliases bool, attachStdin bool) (container moby.Container, err error) {
|
||||
name string, number int, opts createOptions) (container moby.Container, err error) {
|
||||
w := progress.ContextWriter(ctx)
|
||||
eventName := "Container " + name
|
||||
w.Event(progress.CreatingEvent(eventName))
|
||||
container, err = s.createMobyContainer(ctx, project, service, name, number, nil,
|
||||
autoRemove, useNetworkAliases, attachStdin, w, mergeLabels(service.Labels, service.CustomLabels))
|
||||
container, err = s.createMobyContainer(ctx, project, service, name, number, nil, opts, w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -429,9 +433,13 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
|
||||
}
|
||||
name := getContainerName(project.Name, service, number)
|
||||
tmpName := fmt.Sprintf("%s_%s", replaced.ID[:12], name)
|
||||
created, err = s.createMobyContainer(ctx, project, service, tmpName, number, inherited,
|
||||
false, true, false, w,
|
||||
mergeLabels(service.Labels, service.CustomLabels).Add(api.ContainerReplaceLabel, replaced.ID))
|
||||
opts := createOptions{
|
||||
AutoRemove: false,
|
||||
AttachStdin: false,
|
||||
UseNetworkAliases: true,
|
||||
Labels: mergeLabels(service.Labels, service.CustomLabels).Add(api.ContainerReplaceLabel, replaced.ID),
|
||||
}
|
||||
created, err = s.createMobyContainer(ctx, project, service, tmpName, number, inherited, opts, w)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
@@ -484,19 +492,18 @@ func (s *composeService) startContainer(ctx context.Context, container moby.Cont
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *composeService) createMobyContainer(ctx context.Context, //nolint:gocyclo
|
||||
func (s *composeService) createMobyContainer(ctx context.Context,
|
||||
project *types.Project,
|
||||
service types.ServiceConfig,
|
||||
name string,
|
||||
number int,
|
||||
inherit *moby.Container,
|
||||
autoRemove, useNetworkAliases, attachStdin bool,
|
||||
opts createOptions,
|
||||
w progress.Writer,
|
||||
labels types.Labels,
|
||||
) (moby.Container, error) {
|
||||
var created moby.Container
|
||||
containerConfig, hostConfig, networkingConfig, err := s.getCreateOptions(ctx, project, service, number, inherit,
|
||||
autoRemove, attachStdin, labels)
|
||||
cfgs, err := s.getCreateConfigs(ctx, project, service, number, inherit, opts)
|
||||
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
@@ -514,18 +521,7 @@ func (s *composeService) createMobyContainer(ctx context.Context, //nolint:gocyc
|
||||
plat = &p
|
||||
}
|
||||
|
||||
links, err := s.getLinks(ctx, project.Name, service, number)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
if networkingConfig != nil {
|
||||
for k, s := range networkingConfig.EndpointsConfig {
|
||||
s.Links = links
|
||||
networkingConfig.EndpointsConfig[k] = s
|
||||
}
|
||||
}
|
||||
|
||||
response, err := s.apiClient().ContainerCreate(ctx, containerConfig, hostConfig, networkingConfig, plat, name)
|
||||
response, err := s.apiClient().ContainerCreate(ctx, cfgs.Container, cfgs.Host, cfgs.Network, plat, name)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
@@ -548,29 +544,19 @@ func (s *composeService) createMobyContainer(ctx context.Context, //nolint:gocyc
|
||||
Networks: inspectedContainer.NetworkSettings.Networks,
|
||||
},
|
||||
}
|
||||
for _, netName := range service.NetworksByPriority() {
|
||||
netwrk := project.Networks[netName]
|
||||
cfg := service.Networks[netName]
|
||||
aliases := []string{getContainerName(project.Name, service, number)}
|
||||
if useNetworkAliases {
|
||||
aliases = append(aliases, service.Name)
|
||||
if cfg != nil {
|
||||
aliases = append(aliases, cfg.Aliases...)
|
||||
}
|
||||
}
|
||||
if val, ok := created.NetworkSettings.Networks[netwrk.Name]; ok {
|
||||
if shortIDAliasExists(created.ID, val.Aliases...) {
|
||||
continue
|
||||
}
|
||||
err = s.apiClient().NetworkDisconnect(ctx, netwrk.Name, created.ID, false)
|
||||
if err != nil {
|
||||
|
||||
// the highest-priority network is the primary and is included in the ContainerCreate API
|
||||
// call via container.NetworkMode & network.NetworkingConfig
|
||||
// any remaining networks are connected one-by-one here after creation (but before start)
|
||||
serviceNetworks := service.NetworksByPriority()
|
||||
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
|
||||
}
|
||||
}
|
||||
err = s.connectContainerToNetwork(ctx, created.ID, netwrk.Name, cfg, links, aliases...)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
}
|
||||
|
||||
err = s.injectSecrets(ctx, project, service, created.ID)
|
||||
@@ -635,43 +621,6 @@ func (s *composeService) getLinks(ctx context.Context, projectName string, servi
|
||||
return links, nil
|
||||
}
|
||||
|
||||
func shortIDAliasExists(containerID string, aliases ...string) bool {
|
||||
for _, alias := range aliases {
|
||||
if alias == containerID[:12] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *composeService) connectContainerToNetwork(ctx context.Context, id string, netwrk string, cfg *types.ServiceNetworkConfig, links []string, aliases ...string) error {
|
||||
var (
|
||||
ipv4Address string
|
||||
ipv6Address string
|
||||
ipam *network.EndpointIPAMConfig
|
||||
)
|
||||
if cfg != nil {
|
||||
ipv4Address = cfg.Ipv4Address
|
||||
ipv6Address = cfg.Ipv6Address
|
||||
ipam = &network.EndpointIPAMConfig{
|
||||
IPv4Address: ipv4Address,
|
||||
IPv6Address: ipv6Address,
|
||||
LinkLocalIPs: cfg.LinkLocalIPs,
|
||||
}
|
||||
}
|
||||
err := s.apiClient().NetworkConnect(ctx, netwrk, id, &network.EndpointSettings{
|
||||
Aliases: aliases,
|
||||
IPAddress: ipv4Address,
|
||||
GlobalIPv6Address: ipv6Address,
|
||||
Links: links,
|
||||
IPAMConfig: ipam,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *composeService) isServiceHealthy(ctx context.Context, containers Containers, fallbackRunning bool) (bool, error) {
|
||||
for _, c := range containers {
|
||||
container, err := s.apiClient().ContainerInspect(ctx, c.ID)
|
||||
|
||||
@@ -48,6 +48,20 @@ import (
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
type createOptions struct {
|
||||
AutoRemove bool
|
||||
AttachStdin bool
|
||||
UseNetworkAliases bool
|
||||
Labels types.Labels
|
||||
}
|
||||
|
||||
type createConfigs struct {
|
||||
Container *container.Config
|
||||
Host *container.HostConfig
|
||||
Network *network.NetworkingConfig
|
||||
Links []string
|
||||
}
|
||||
|
||||
func (s *composeService) Create(ctx context.Context, project *types.Project, options api.CreateOptions) error {
|
||||
return progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
||||
return s.create(ctx, project, options)
|
||||
@@ -166,18 +180,16 @@ func (s *composeService) ensureProjectVolumes(ctx context.Context, project *type
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *composeService) getCreateOptions(ctx context.Context,
|
||||
func (s *composeService) getCreateConfigs(ctx context.Context,
|
||||
p *types.Project,
|
||||
service types.ServiceConfig,
|
||||
number int,
|
||||
inherit *moby.Container,
|
||||
autoRemove, attachStdin bool,
|
||||
labels types.Labels,
|
||||
) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
|
||||
|
||||
labels, err := s.prepareLabels(labels, service, number)
|
||||
opts createOptions,
|
||||
) (createConfigs, error) {
|
||||
labels, err := s.prepareLabels(opts.Labels, service, number)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return createConfigs{}, err
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -196,11 +208,6 @@ func (s *composeService) getCreateOptions(ctx context.Context,
|
||||
stdinOpen = service.StdinOpen
|
||||
)
|
||||
|
||||
binds, mounts, err := s.buildContainerVolumes(ctx, *p, service, inherit)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
proxyConfig := types.MappingWithEquals(s.configFile().ParseProxyConfig(s.apiClient().DaemonHost(), nil))
|
||||
env := proxyConfig.OverrideBy(service.Environment)
|
||||
|
||||
@@ -211,8 +218,8 @@ func (s *composeService) getCreateOptions(ctx context.Context,
|
||||
ExposedPorts: buildContainerPorts(service),
|
||||
Tty: tty,
|
||||
OpenStdin: stdinOpen,
|
||||
StdinOnce: attachStdin && stdinOpen,
|
||||
AttachStdin: attachStdin,
|
||||
StdinOnce: opts.AttachStdin && stdinOpen,
|
||||
AttachStdin: opts.AttachStdin,
|
||||
AttachStderr: true,
|
||||
AttachStdout: true,
|
||||
Cmd: runCmd,
|
||||
@@ -228,20 +235,7 @@ func (s *composeService) getCreateOptions(ctx context.Context,
|
||||
StopTimeout: ToSeconds(service.StopGracePeriod),
|
||||
}
|
||||
|
||||
portBindings := buildContainerPortBindingOptions(service)
|
||||
|
||||
resources := getDeployResources(service)
|
||||
|
||||
if service.NetworkMode == "" {
|
||||
service.NetworkMode = getDefaultNetworkMode(p, service)
|
||||
}
|
||||
|
||||
var networkConfig *network.NetworkingConfig
|
||||
for _, id := range service.NetworksByPriority() {
|
||||
networkConfig = s.createNetworkConfig(p, service, id)
|
||||
break
|
||||
}
|
||||
|
||||
// VOLUMES/MOUNTS/FILESYSTEMS
|
||||
tmpfs := map[string]string{}
|
||||
for _, t := range service.Tmpfs {
|
||||
if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
|
||||
@@ -250,7 +244,28 @@ func (s *composeService) getCreateOptions(ctx context.Context,
|
||||
tmpfs[arr[0]] = ""
|
||||
}
|
||||
}
|
||||
binds, mounts, err := s.buildContainerVolumes(ctx, *p, service, inherit)
|
||||
if err != nil {
|
||||
return createConfigs{}, err
|
||||
}
|
||||
var volumesFrom []string
|
||||
for _, v := range service.VolumesFrom {
|
||||
if !strings.HasPrefix(v, "container:") {
|
||||
return createConfigs{}, fmt.Errorf("invalid volume_from: %s", v)
|
||||
}
|
||||
volumesFrom = append(volumesFrom, v[len("container:"):])
|
||||
}
|
||||
|
||||
// NETWORKING
|
||||
links, err := s.getLinks(ctx, p.Name, service, number)
|
||||
if err != nil {
|
||||
return createConfigs{}, err
|
||||
}
|
||||
networkMode, networkingConfig := defaultNetworkSettings(p, service, number, links, opts.UseNetworkAliases)
|
||||
portBindings := buildContainerPortBindingOptions(service)
|
||||
|
||||
// MISC
|
||||
resources := getDeployResources(service)
|
||||
var logConfig container.LogConfig
|
||||
if service.Logging != nil {
|
||||
logConfig = container.LogConfig{
|
||||
@@ -258,31 +273,18 @@ func (s *composeService) getCreateOptions(ctx context.Context,
|
||||
Config: service.Logging.Options,
|
||||
}
|
||||
}
|
||||
|
||||
var volumesFrom []string
|
||||
for _, v := range service.VolumesFrom {
|
||||
if !strings.HasPrefix(v, "container:") {
|
||||
return nil, nil, nil, fmt.Errorf("invalid volume_from: %s", v)
|
||||
}
|
||||
volumesFrom = append(volumesFrom, v[len("container:"):])
|
||||
}
|
||||
|
||||
links, err := s.getLinks(ctx, p.Name, service, number)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
securityOpts, unconfined, err := parseSecurityOpts(p, service.SecurityOpt)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return createConfigs{}, err
|
||||
}
|
||||
|
||||
hostConfig := container.HostConfig{
|
||||
AutoRemove: autoRemove,
|
||||
AutoRemove: opts.AutoRemove,
|
||||
Binds: binds,
|
||||
Mounts: mounts,
|
||||
CapAdd: strslice.StrSlice(service.CapAdd),
|
||||
CapDrop: strslice.StrSlice(service.CapDrop),
|
||||
NetworkMode: container.NetworkMode(service.NetworkMode),
|
||||
NetworkMode: networkMode,
|
||||
Init: service.Init,
|
||||
IpcMode: container.IpcMode(service.Ipc),
|
||||
CgroupnsMode: container.CgroupnsMode(service.Cgroup),
|
||||
@@ -317,12 +319,28 @@ func (s *composeService) getCreateOptions(ctx context.Context,
|
||||
hostConfig.ReadonlyPaths = []string{}
|
||||
}
|
||||
|
||||
return &containerConfig, &hostConfig, networkConfig, nil
|
||||
cfgs := createConfigs{
|
||||
Container: &containerConfig,
|
||||
Host: &hostConfig,
|
||||
Network: networkingConfig,
|
||||
Links: links,
|
||||
}
|
||||
return cfgs, nil
|
||||
}
|
||||
|
||||
func (s *composeService) createNetworkConfig(p *types.Project, service types.ServiceConfig, networkID string) *network.NetworkingConfig {
|
||||
net := p.Networks[networkID]
|
||||
config := service.Networks[networkID]
|
||||
func getAliases(project *types.Project, service types.ServiceConfig, serviceIndex int, networkKey string, useNetworkAliases bool) []string {
|
||||
aliases := []string{getContainerName(project.Name, service, serviceIndex)}
|
||||
if useNetworkAliases {
|
||||
aliases = append(aliases, service.Name)
|
||||
if cfg := service.Networks[networkKey]; cfg != nil {
|
||||
aliases = append(aliases, cfg.Aliases...)
|
||||
}
|
||||
}
|
||||
return aliases
|
||||
}
|
||||
|
||||
func createEndpointSettings(p *types.Project, service types.ServiceConfig, serviceIndex int, networkKey string, links []string, useNetworkAliases bool) *network.EndpointSettings {
|
||||
config := service.Networks[networkKey]
|
||||
var ipam *network.EndpointIPAMConfig
|
||||
var (
|
||||
ipv4Address string
|
||||
@@ -337,15 +355,12 @@ func (s *composeService) createNetworkConfig(p *types.Project, service types.Ser
|
||||
LinkLocalIPs: config.LinkLocalIPs,
|
||||
}
|
||||
}
|
||||
return &network.NetworkingConfig{
|
||||
EndpointsConfig: map[string]*network.EndpointSettings{
|
||||
net.Name: {
|
||||
Aliases: getAliases(service, config),
|
||||
IPAddress: ipv4Address,
|
||||
IPv6Gateway: ipv6Address,
|
||||
IPAMConfig: ipam,
|
||||
},
|
||||
},
|
||||
return &network.EndpointSettings{
|
||||
Aliases: getAliases(p, service, serviceIndex, networkKey, useNetworkAliases),
|
||||
Links: links,
|
||||
IPAddress: ipv4Address,
|
||||
IPv6Gateway: ipv6Address,
|
||||
IPAMConfig: ipam,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,17 +419,39 @@ func (s *composeService) prepareLabels(labels types.Labels, service types.Servic
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
func getDefaultNetworkMode(project *types.Project, service types.ServiceConfig) string {
|
||||
// defaultNetworkSettings determines the container.NetworkMode and corresponding network.NetworkingConfig (nil if not applicable).
|
||||
func defaultNetworkSettings(
|
||||
project *types.Project,
|
||||
service types.ServiceConfig,
|
||||
serviceIndex int,
|
||||
links []string,
|
||||
useNetworkAliases bool,
|
||||
) (container.NetworkMode, *network.NetworkingConfig) {
|
||||
if service.NetworkMode != "" {
|
||||
return container.NetworkMode(service.NetworkMode), nil
|
||||
}
|
||||
|
||||
if len(project.Networks) == 0 {
|
||||
return "none"
|
||||
return "none", nil
|
||||
}
|
||||
|
||||
var networkKey string
|
||||
if len(service.Networks) > 0 {
|
||||
name := service.NetworksByPriority()[0]
|
||||
return project.Networks[name].Name
|
||||
networkKey = service.NetworksByPriority()[0]
|
||||
} else {
|
||||
networkKey = "default"
|
||||
}
|
||||
|
||||
return project.Networks["default"].Name
|
||||
mobyNetworkName := project.Networks[networkKey].Name
|
||||
epSettings := createEndpointSettings(project, service, serviceIndex, networkKey, links, useNetworkAliases)
|
||||
networkConfig := &network.NetworkingConfig{
|
||||
EndpointsConfig: map[string]*network.EndpointSettings{
|
||||
mobyNetworkName: epSettings,
|
||||
},
|
||||
}
|
||||
// From the Engine API docs:
|
||||
// > Supported standard values are: bridge, host, none, and container:<name|id>.
|
||||
// > Any other value is taken as a custom network's name to which this container should connect to.
|
||||
return container.NetworkMode(mobyNetworkName), networkConfig
|
||||
}
|
||||
|
||||
func getRestartPolicy(service types.ServiceConfig) container.RestartPolicy {
|
||||
@@ -1002,14 +1039,6 @@ func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
|
||||
}
|
||||
}
|
||||
|
||||
func getAliases(s types.ServiceConfig, c *types.ServiceNetworkConfig) []string {
|
||||
aliases := []string{s.Name}
|
||||
if c != nil {
|
||||
aliases = append(aliases, c.Aliases...)
|
||||
}
|
||||
return aliases
|
||||
}
|
||||
|
||||
func (s *composeService) ensureNetwork(ctx context.Context, n *types.NetworkConfig) error {
|
||||
if n.External.External {
|
||||
return s.resolveExternalNetwork(ctx, n)
|
||||
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert/cmp"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
|
||||
composetypes "github.com/compose-spec/compose-go/types"
|
||||
@@ -203,7 +205,7 @@ func TestBuildContainerMountOptions(t *testing.T) {
|
||||
assert.Equal(t, mounts[2].Target, "\\\\.\\pipe\\docker_engine")
|
||||
}
|
||||
|
||||
func TestGetDefaultNetworkMode(t *testing.T) {
|
||||
func TestDefaultNetworkSettings(t *testing.T) {
|
||||
t.Run("returns the network with the highest priority when service has multiple networks", func(t *testing.T) {
|
||||
service := composetypes.ServiceConfig{
|
||||
Name: "myService",
|
||||
@@ -231,7 +233,10 @@ func TestGetDefaultNetworkMode(t *testing.T) {
|
||||
}),
|
||||
}
|
||||
|
||||
assert.Equal(t, getDefaultNetworkMode(&project, service), "myProject_myNetwork2")
|
||||
networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true)
|
||||
assert.Equal(t, string(networkMode), "myProject_myNetwork2")
|
||||
assert.Check(t, cmp.Len(networkConfig.EndpointsConfig, 1))
|
||||
assert.Check(t, cmp.Contains(networkConfig.EndpointsConfig, "myProject_myNetwork2"))
|
||||
})
|
||||
|
||||
t.Run("returns default network when service has no networks", func(t *testing.T) {
|
||||
@@ -256,7 +261,10 @@ func TestGetDefaultNetworkMode(t *testing.T) {
|
||||
}),
|
||||
}
|
||||
|
||||
assert.Equal(t, getDefaultNetworkMode(&project, service), "myProject_default")
|
||||
networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true)
|
||||
assert.Equal(t, string(networkMode), "myProject_default")
|
||||
assert.Check(t, cmp.Len(networkConfig.EndpointsConfig, 1))
|
||||
assert.Check(t, cmp.Contains(networkConfig.EndpointsConfig, "myProject_default"))
|
||||
})
|
||||
|
||||
t.Run("returns none if project has no networks", func(t *testing.T) {
|
||||
@@ -270,6 +278,28 @@ func TestGetDefaultNetworkMode(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, getDefaultNetworkMode(&project, service), "none")
|
||||
networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true)
|
||||
assert.Equal(t, string(networkMode), "none")
|
||||
assert.Check(t, cmp.Nil(networkConfig))
|
||||
})
|
||||
|
||||
t.Run("returns defined network mode if explicitly set", func(t *testing.T) {
|
||||
service := composetypes.ServiceConfig{
|
||||
Name: "myService",
|
||||
NetworkMode: "host",
|
||||
}
|
||||
project := composetypes.Project{
|
||||
Name: "myProject",
|
||||
Services: []composetypes.ServiceConfig{service},
|
||||
Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{
|
||||
"default": {
|
||||
Name: "myProject_default",
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true)
|
||||
assert.Equal(t, string(networkMode), "host")
|
||||
assert.Check(t, cmp.Nil(networkConfig))
|
||||
})
|
||||
}
|
||||
|
||||
185
pkg/compose/publish.go
Normal file
185
pkg/compose/publish.go
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/distribution/distribution/v3/reference"
|
||||
client2 "github.com/docker/cli/cli/registry/client"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/opencontainers/image-spec/specs-go"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
func (s *composeService) Publish(ctx context.Context, project *types.Project, repository string) error {
|
||||
err := s.Push(ctx, project, api.PushOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
target, err := reference.ParseDockerRef(repository)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := s.dockerCli.RegistryClient(false)
|
||||
for i, service := range project.Services {
|
||||
ref, err := reference.ParseDockerRef(service.Image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
auth, err := encodedAuth(ref, s.configFile())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
inspect, err := s.apiClient().DistributionInspect(ctx, ref.String(), auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
canonical, err := reference.WithDigest(ref, inspect.Descriptor.Digest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
to, err := reference.WithDigest(target, inspect.Descriptor.Digest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = client.MountBlob(ctx, canonical, to)
|
||||
switch err.(type) {
|
||||
case client2.ErrBlobCreated:
|
||||
default:
|
||||
return err
|
||||
}
|
||||
service.Image = to.String()
|
||||
project.Services[i] = service
|
||||
}
|
||||
|
||||
err = s.publishComposeYaml(ctx, project, repository)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *composeService) publishComposeYaml(ctx context.Context, project *types.Project, repository string) error {
|
||||
ref, err := reference.ParseDockerRef(repository)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var manifests []v1.Descriptor
|
||||
|
||||
for _, composeFile := range project.ComposeFiles {
|
||||
stat, err := os.Stat(composeFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, "oras", "push", "--artifact-type", "application/vnd.docker.compose.yaml", ref.String(), composeFile)
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Stderr = s.stderr()
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out, err := io.ReadAll(stdout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var composeFileDigest string
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
if strings.HasPrefix(line, "Digest: ") {
|
||||
composeFileDigest = line[len("Digest: "):]
|
||||
}
|
||||
fmt.Fprintln(s.stdout(), line)
|
||||
}
|
||||
if composeFileDigest == "" {
|
||||
return fmt.Errorf("expected oras to display `Digest: xxx`")
|
||||
}
|
||||
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
manifests = append(manifests, v1.Descriptor{
|
||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
Digest: digest.Digest(composeFileDigest),
|
||||
Size: stat.Size(),
|
||||
ArtifactType: "application/vnd.docker.compose.yaml",
|
||||
})
|
||||
}
|
||||
|
||||
for _, service := range project.Services {
|
||||
dockerRef, err := reference.ParseDockerRef(service.Image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
manifests = append(manifests, v1.Descriptor{
|
||||
MediaType: v1.MediaTypeImageIndex,
|
||||
Digest: dockerRef.(reference.Digested).Digest(),
|
||||
Annotations: map[string]string{
|
||||
"com.docker.compose.service": service.Name,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
manifest := v1.Index{
|
||||
Versioned: specs.Versioned{
|
||||
SchemaVersion: 2,
|
||||
},
|
||||
MediaType: v1.MediaTypeImageIndex,
|
||||
Manifests: manifests,
|
||||
Annotations: map[string]string{
|
||||
"com.docker.compose": api.ComposeVersion,
|
||||
},
|
||||
}
|
||||
manifestContent, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
temp, err := os.CreateTemp(os.TempDir(), "compose")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(temp.Name(), manifestContent, 0o700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(temp.Name())
|
||||
|
||||
cmd := exec.CommandContext(ctx, "oras", "manifest", "push", ref.String(), temp.Name())
|
||||
cmd.Stdout = s.stdout()
|
||||
cmd.Stderr = s.stderr()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
@@ -26,14 +27,17 @@ import (
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/distribution/distribution/v3/reference"
|
||||
"github.com/docker/buildx/driver"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/opencontainers/go-digest"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"oras.land/oras-go/v2/content"
|
||||
"oras.land/oras-go/v2/registry/remote"
|
||||
)
|
||||
|
||||
func (s *composeService) Push(ctx context.Context, project *types.Project, options api.PushOptions) error {
|
||||
@@ -45,8 +49,8 @@ func (s *composeService) Push(ctx context.Context, project *types.Project, optio
|
||||
}, s.stdinfo(), "Pushing")
|
||||
}
|
||||
|
||||
func (s *composeService) push(ctx context.Context, project *types.Project, options api.PushOptions) error {
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
func (s *composeService) push(upctx context.Context, project *types.Project, options api.PushOptions) error {
|
||||
eg, ctx := errgroup.WithContext(upctx)
|
||||
eg.SetLimit(s.maxConcurrency)
|
||||
|
||||
info, err := s.apiClient().Info(ctx)
|
||||
@@ -79,7 +83,66 @@ func (s *composeService) push(ctx context.Context, project *types.Project, optio
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return eg.Wait()
|
||||
|
||||
err = eg.Wait()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx = upctx
|
||||
|
||||
if options.Repository != "" {
|
||||
repository, err := remote.NewRepository(options.Repository)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
yaml, err := project.MarshalYAML()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
manifests := []v1.Descriptor{
|
||||
{
|
||||
MediaType: "application/vnd.oci.artifact.manifest.v1+json",
|
||||
Digest: digest.FromBytes(yaml),
|
||||
Size: int64(len(yaml)),
|
||||
Data: yaml,
|
||||
ArtifactType: "application/vnd.docker.compose.yaml",
|
||||
},
|
||||
}
|
||||
for _, service := range project.Services {
|
||||
inspected, _, err := s.dockerCli.Client().ImageInspectWithRaw(ctx, service.Image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
manifests = append(manifests, v1.Descriptor{
|
||||
MediaType: v1.MediaTypeImageIndex,
|
||||
Digest: digest.Digest(inspected.RepoDigests[0]),
|
||||
Size: inspected.Size,
|
||||
Annotations: map[string]string{
|
||||
"com.docker.compose.service": service.Name,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
manifest := v1.Index{
|
||||
MediaType: v1.MediaTypeImageIndex,
|
||||
Manifests: manifests,
|
||||
Annotations: map[string]string{
|
||||
"com.docker.compose": api.ComposeVersion,
|
||||
},
|
||||
}
|
||||
manifestContent, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
manifestDescriptor := content.NewDescriptorFromBytes(v1.MediaTypeImageIndex, manifestContent)
|
||||
|
||||
err = repository.Push(ctx, manifestDescriptor, bytes.NewReader(manifestContent))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *composeService) pushServiceImage(ctx context.Context, service types.ServiceConfig, info moby.Info, configFile driver.Auth, w progress.Writer, quietPush bool) error {
|
||||
|
||||
@@ -99,8 +99,14 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
created, err := s.createContainer(ctx, project, service, service.ContainerName, 1,
|
||||
opts.AutoRemove, opts.UseNetworkAliases, opts.Interactive)
|
||||
createOpts := createOptions{
|
||||
AutoRemove: opts.AutoRemove,
|
||||
AttachStdin: opts.Interactive,
|
||||
UseNetworkAliases: opts.UseNetworkAliases,
|
||||
Labels: mergeLabels(service.Labels, service.CustomLabels),
|
||||
}
|
||||
|
||||
created, err := s.createContainer(ctx, project, service, service.ContainerName, 1, createOpts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
67
pkg/compose/wait.go
Normal file
67
pkg/compose/wait.go
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func (s *composeService) Wait(ctx context.Context, projectName string, options api.WaitOptions) (int64, error) {
|
||||
containers, err := s.getContainers(ctx, projectName, oneOffInclude, false, options.Services...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(containers) == 0 {
|
||||
return 0, fmt.Errorf("no containers for project %q", projectName)
|
||||
}
|
||||
|
||||
eg, waitCtx := errgroup.WithContext(ctx)
|
||||
var statusCode int64
|
||||
for _, c := range containers {
|
||||
c := c
|
||||
eg.Go(func() error {
|
||||
var err error
|
||||
resultC, errC := s.dockerCli.Client().ContainerWait(waitCtx, c.ID, "")
|
||||
|
||||
select {
|
||||
case result := <-resultC:
|
||||
fmt.Fprintf(s.dockerCli.Out(), "container %q exited with status code %d\n", c.ID, result.StatusCode)
|
||||
statusCode = result.StatusCode
|
||||
case err = <-errC:
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
err = eg.Wait()
|
||||
if err != nil {
|
||||
return 42, err // Ignore abort flag in case of error in wait
|
||||
}
|
||||
|
||||
if options.DownProjectOnContainerExit {
|
||||
return statusCode, s.Down(ctx, projectName, api.DownOptions{
|
||||
RemoveOrphans: true,
|
||||
})
|
||||
}
|
||||
|
||||
return statusCode, err
|
||||
}
|
||||
@@ -143,6 +143,10 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv
|
||||
|
||||
var paths []string
|
||||
for _, trigger := range config.Watch {
|
||||
if checkIfPathAlreadyBindMounted(trigger.Path, service.Volumes) {
|
||||
logrus.Warnf("path '%s' also declared by a bind mount volume, this path won't be monitored!\n", trigger.Path)
|
||||
continue
|
||||
}
|
||||
paths = append(paths, trigger.Path)
|
||||
}
|
||||
|
||||
@@ -383,3 +387,12 @@ func debounce(ctx context.Context, clock clockwork.Clock, delay time.Duration, i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkIfPathAlreadyBindMounted(watchPath string, volumes []types.ServiceVolumeConfig) bool {
|
||||
for _, volume := range volumes {
|
||||
if volume.Bind != nil && strings.HasPrefix(watchPath, volume.Source) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -423,3 +424,30 @@ func TestBuildPlatformsStandardErrors(t *testing.T) {
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestBuildBuilder(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
builderName := "build-with-builder"
|
||||
// declare builder
|
||||
result := c.RunDockerCmd(t, "buildx", "create", "--name", builderName, "--use", "--bootstrap")
|
||||
assert.NilError(t, result.Error)
|
||||
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test/", "down")
|
||||
_ = c.RunDockerCmd(t, "buildx", "rm", "-f", builderName)
|
||||
})
|
||||
|
||||
t.Run("use specific builder to run build command", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "--project-directory", "fixtures/build-test", "build", "--builder", builderName)
|
||||
assert.NilError(t, res.Error, res.Stderr())
|
||||
})
|
||||
|
||||
t.Run("error when using specific builder to run build command", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "--project-directory", "fixtures/build-test", "build", "--builder", "unknown-builder")
|
||||
res.Assert(t, icmd.Expected{
|
||||
ExitCode: 1,
|
||||
Err: fmt.Sprintf(`no builder %q found`, "unknown-builder"),
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -277,7 +277,7 @@ networks:
|
||||
})
|
||||
}
|
||||
|
||||
func TestStopWithDependeciesAttached(t *testing.T) {
|
||||
func TestStopWithDependenciesAttached(t *testing.T) {
|
||||
const projectName = "compose-e2e-stop-with-deps"
|
||||
c := NewParallelCLI(t, WithEnv("COMMAND=echo hello"))
|
||||
|
||||
|
||||
11
pkg/e2e/fixtures/wait/compose.yaml
Normal file
11
pkg/e2e/fixtures/wait/compose.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
services:
|
||||
faster:
|
||||
image: alpine
|
||||
command: sleep 2
|
||||
slower:
|
||||
image: alpine
|
||||
command: sleep 5
|
||||
infinity:
|
||||
image: alpine
|
||||
command: sleep infinity
|
||||
|
||||
72
pkg/e2e/wait_test.go
Normal file
72
pkg/e2e/wait_test.go
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
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 e2e
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestWaitOnFaster(t *testing.T) {
|
||||
const projectName = "e2e-wait-faster"
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "faster")
|
||||
}
|
||||
|
||||
func TestWaitOnSlower(t *testing.T) {
|
||||
const projectName = "e2e-wait-slower"
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "slower")
|
||||
}
|
||||
|
||||
func TestWaitOnInfinity(t *testing.T) {
|
||||
const projectName = "e2e-wait-infinity"
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
|
||||
finished := make(chan struct{})
|
||||
ticker := time.NewTicker(7 * time.Second)
|
||||
go func() {
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "infinity")
|
||||
finished <- struct{}{}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-finished:
|
||||
t.Fatal("wait infinity should not finish")
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitAndDrop(t *testing.T) {
|
||||
const projectName = "e2e-wait-and-drop"
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "--down-project", "faster")
|
||||
|
||||
res := c.RunDockerCmd(t, "ps", "--all")
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), projectName), res.Combined())
|
||||
}
|
||||
@@ -423,6 +423,21 @@ func (mr *MockServiceMockRecorder) Viz(ctx, project, options interface{}) *gomoc
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Viz", reflect.TypeOf((*MockService)(nil).Viz), ctx, project, options)
|
||||
}
|
||||
|
||||
// Wait mocks base method.
|
||||
func (m *MockService) Wait(ctx context.Context, projectName string, options api.WaitOptions) (int64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Wait", ctx, projectName, options)
|
||||
ret0, _ := ret[0].(int64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Wait indicates an expected call of Wait.
|
||||
func (mr *MockServiceMockRecorder) Wait(ctx, projectName, options interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Wait", reflect.TypeOf((*MockService)(nil).Wait), ctx, projectName, options)
|
||||
}
|
||||
|
||||
// Watch mocks base method.
|
||||
func (m *MockService) Watch(ctx context.Context, project *types.Project, services []string, options api.WatchOptions) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -68,7 +68,7 @@ type Notify interface {
|
||||
// - Watch /src/repo, but ignore /src/repo/.git
|
||||
// - Watch /src/repo, but ignore everything in /src/repo/bazel-bin except /src/repo/bazel-bin/app-binary
|
||||
//
|
||||
// The PathMatcher inteface helps us manage these ignores.
|
||||
// The PathMatcher interface helps us manage these ignores.
|
||||
type PathMatcher interface {
|
||||
Matches(file string) (bool, error)
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ func dedupePathsForRecursiveWatcher(paths []string) []string {
|
||||
}
|
||||
|
||||
if IsChild(current, existing) {
|
||||
// Mark the element empty fo removal.
|
||||
// Mark the element empty for removal.
|
||||
result[i] = ""
|
||||
hasRemovals = true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user