mirror of
https://github.com/docker/compose.git
synced 2025-07-16 18:24:26 +02:00
introduce up --wait condition
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
9e6f51d262
commit
72e4519cbf
@ -50,6 +50,7 @@ type upOptions struct {
|
|||||||
noPrefix bool
|
noPrefix bool
|
||||||
attachDependencies bool
|
attachDependencies bool
|
||||||
attach []string
|
attach []string
|
||||||
|
wait bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts upOptions) apply(project *types.Project, services []string) error {
|
func (opts upOptions) apply(project *types.Project, services []string) error {
|
||||||
@ -100,22 +101,7 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
|||||||
Short: "Create and start containers",
|
Short: "Create and start containers",
|
||||||
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||||
create.timeChanged = cmd.Flags().Changed("timeout")
|
create.timeChanged = cmd.Flags().Changed("timeout")
|
||||||
if up.exitCodeFrom != "" {
|
return validateFlags(&up, &create)
|
||||||
up.cascadeStop = true
|
|
||||||
}
|
|
||||||
if create.Build && create.noBuild {
|
|
||||||
return fmt.Errorf("--build and --no-build are incompatible")
|
|
||||||
}
|
|
||||||
if up.Detach && (up.attachDependencies || up.cascadeStop || len(up.attach) > 0) {
|
|
||||||
return fmt.Errorf("--detach cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies")
|
|
||||||
}
|
|
||||||
if create.forceRecreate && create.noRecreate {
|
|
||||||
return fmt.Errorf("--force-recreate and --no-recreate are incompatible")
|
|
||||||
}
|
|
||||||
if create.recreateDeps && create.noRecreate {
|
|
||||||
return fmt.Errorf("--always-recreate-deps and --no-recreate are incompatible")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}),
|
}),
|
||||||
RunE: p.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
|
RunE: p.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
|
||||||
ignore := project.Environment["COMPOSE_IGNORE_ORPHANS"]
|
ignore := project.Environment["COMPOSE_IGNORE_ORPHANS"]
|
||||||
@ -148,10 +134,36 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
|||||||
flags.BoolVar(&up.attachDependencies, "attach-dependencies", false, "Attach to dependent containers.")
|
flags.BoolVar(&up.attachDependencies, "attach-dependencies", false, "Attach to dependent containers.")
|
||||||
flags.BoolVar(&create.quietPull, "quiet-pull", false, "Pull without printing progress information.")
|
flags.BoolVar(&create.quietPull, "quiet-pull", false, "Pull without printing progress information.")
|
||||||
flags.StringArrayVar(&up.attach, "attach", []string{}, "Attach to service output.")
|
flags.StringArrayVar(&up.attach, "attach", []string{}, "Attach to service output.")
|
||||||
|
flags.BoolVar(&up.wait, "wait", false, "Wait for services to be running|healthy. Implies detached mode.")
|
||||||
|
|
||||||
return upCmd
|
return upCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateFlags(up *upOptions, create *createOptions) error {
|
||||||
|
if up.exitCodeFrom != "" {
|
||||||
|
up.cascadeStop = true
|
||||||
|
}
|
||||||
|
if up.wait {
|
||||||
|
if up.attachDependencies || up.cascadeStop || len(up.attach) > 0 {
|
||||||
|
return fmt.Errorf("--wait cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies")
|
||||||
|
}
|
||||||
|
up.Detach = true
|
||||||
|
}
|
||||||
|
if create.Build && create.noBuild {
|
||||||
|
return fmt.Errorf("--build and --no-build are incompatible")
|
||||||
|
}
|
||||||
|
if up.Detach && (up.attachDependencies || up.cascadeStop || len(up.attach) > 0) {
|
||||||
|
return fmt.Errorf("--detach cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies")
|
||||||
|
}
|
||||||
|
if create.forceRecreate && create.noRecreate {
|
||||||
|
return fmt.Errorf("--force-recreate and --no-recreate are incompatible")
|
||||||
|
}
|
||||||
|
if create.recreateDeps && create.noRecreate {
|
||||||
|
return fmt.Errorf("--always-recreate-deps and --no-recreate are incompatible")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func runUp(ctx context.Context, backend api.Service, createOptions createOptions, upOptions upOptions, project *types.Project, services []string) error {
|
func runUp(ctx context.Context, backend api.Service, createOptions createOptions, upOptions upOptions, project *types.Project, services []string) error {
|
||||||
if len(project.Services) == 0 {
|
if len(project.Services) == 0 {
|
||||||
return fmt.Errorf("no service selected")
|
return fmt.Errorf("no service selected")
|
||||||
@ -199,6 +211,7 @@ func runUp(ctx context.Context, backend api.Service, createOptions createOptions
|
|||||||
AttachTo: attachTo,
|
AttachTo: attachTo,
|
||||||
ExitCodeFrom: upOptions.exitCodeFrom,
|
ExitCodeFrom: upOptions.exitCodeFrom,
|
||||||
CascadeStop: upOptions.cascadeStop,
|
CascadeStop: upOptions.cascadeStop,
|
||||||
|
Wait: upOptions.wait,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -124,6 +124,8 @@ type StartOptions struct {
|
|||||||
CascadeStop bool
|
CascadeStop bool
|
||||||
// ExitCodeFrom return exit code from specified service
|
// ExitCodeFrom return exit code from specified service
|
||||||
ExitCodeFrom string
|
ExitCodeFrom string
|
||||||
|
// Wait won't return until containers reached the running|healthy state
|
||||||
|
Wait bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestartOptions group options of the Restart API
|
// RestartOptions group options of the Restart API
|
||||||
|
@ -261,9 +261,11 @@ func getContainerProgressName(container moby.Container) string {
|
|||||||
return "Container " + getCanonicalContainerName(container)
|
return "Container " + getCanonicalContainerName(container)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
|
const ServiceConditionRuningOrHealthy = "running_or_healthy"
|
||||||
|
|
||||||
|
func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, dependencies types.DependsOnConfig) error {
|
||||||
eg, _ := errgroup.WithContext(ctx)
|
eg, _ := errgroup.WithContext(ctx)
|
||||||
for dep, config := range service.DependsOn {
|
for dep, config := range dependencies {
|
||||||
dep, config := dep, config
|
dep, config := dep, config
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
ticker := time.NewTicker(500 * time.Millisecond)
|
ticker := time.NewTicker(500 * time.Millisecond)
|
||||||
@ -271,8 +273,16 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
|
|||||||
for {
|
for {
|
||||||
<-ticker.C
|
<-ticker.C
|
||||||
switch config.Condition {
|
switch config.Condition {
|
||||||
|
case ServiceConditionRuningOrHealthy:
|
||||||
|
healthy, err := s.isServiceHealthy(ctx, project, dep, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if healthy {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
case types.ServiceConditionHealthy:
|
case types.ServiceConditionHealthy:
|
||||||
healthy, err := s.isServiceHealthy(ctx, project, dep)
|
healthy, err := s.isServiceHealthy(ctx, project, dep, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -502,7 +512,7 @@ func (s *composeService) connectContainerToNetwork(ctx context.Context, id strin
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Project, service string) (bool, error) {
|
func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Project, service string, fallbackRunning bool) (bool, error) {
|
||||||
containers, err := s.getContainers(ctx, project.Name, oneOffExclude, false, service)
|
containers, err := s.getContainers(ctx, project.Name, oneOffExclude, false, service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -516,6 +526,11 @@ func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Pr
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
if container.Config.Healthcheck == nil && fallbackRunning {
|
||||||
|
// Container does not define a health check, but we can fall back to "running" state
|
||||||
|
return container.State != nil && container.State.Status == "running", nil
|
||||||
|
}
|
||||||
|
|
||||||
if container.State == nil || container.State.Health == nil {
|
if container.State == nil || container.State.Health == nil {
|
||||||
return false, fmt.Errorf("container for service %q has no healthcheck configured", service)
|
return false, fmt.Errorf("container for service %q has no healthcheck configured", service)
|
||||||
}
|
}
|
||||||
@ -544,7 +559,7 @@ func (s *composeService) isServiceCompleted(ctx context.Context, project *types.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) startService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
|
func (s *composeService) startService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
|
||||||
err := s.waitDependencies(ctx, project, service)
|
err := s.waitDependencies(ctx, project, service.DependsOn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -493,6 +493,7 @@ func getDeployResources(s types.ServiceConfig) container.Resources {
|
|||||||
MemorySwap: int64(s.MemSwapLimit),
|
MemorySwap: int64(s.MemSwapLimit),
|
||||||
MemorySwappiness: swappiness,
|
MemorySwappiness: swappiness,
|
||||||
MemoryReservation: int64(s.MemReservation),
|
MemoryReservation: int64(s.MemReservation),
|
||||||
|
OomKillDisable: &s.OomKillDisable,
|
||||||
CPUCount: s.CPUCount,
|
CPUCount: s.CPUCount,
|
||||||
CPUPeriod: s.CPUPeriod,
|
CPUPeriod: s.CPUPeriod,
|
||||||
CPUQuota: s.CPUQuota,
|
CPUQuota: s.CPUQuota,
|
||||||
@ -503,6 +504,10 @@ func getDeployResources(s types.ServiceConfig) container.Resources {
|
|||||||
CpusetCpus: s.CPUSet,
|
CpusetCpus: s.CPUSet,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.PidsLimit != 0 {
|
||||||
|
resources.PidsLimit = &s.PidsLimit
|
||||||
|
}
|
||||||
|
|
||||||
setBlkio(s.BlkioConfig, &resources)
|
setBlkio(s.BlkioConfig, &resources)
|
||||||
|
|
||||||
if s.Deploy != nil {
|
if s.Deploy != nil {
|
||||||
|
@ -160,7 +160,7 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if !opts.NoDeps {
|
if !opts.NoDeps {
|
||||||
if err := s.waitDependencies(ctx, project, service); err != nil {
|
if err := s.waitDependencies(ctx, project, service.DependsOn); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,11 +58,26 @@ func (s *composeService) start(ctx context.Context, project *types.Project, opti
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.startService(ctx, project, service)
|
return s.startService(ctx, project, service)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if options.Wait {
|
||||||
|
depends := types.DependsOnConfig{}
|
||||||
|
for _, s := range project.Services {
|
||||||
|
depends[s.Name] = types.ServiceDependency{
|
||||||
|
Condition: ServiceConditionRuningOrHealthy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = s.waitDependencies(ctx, project, depends)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return eg.Wait()
|
return eg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user