mirror of https://github.com/docker/compose.git
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
|
||||
attachDependencies bool
|
||||
attach []string
|
||||
wait bool
|
||||
}
|
||||
|
||||
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",
|
||||
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||
create.timeChanged = cmd.Flags().Changed("timeout")
|
||||
if up.exitCodeFrom != "" {
|
||||
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
|
||||
return validateFlags(&up, &create)
|
||||
}),
|
||||
RunE: p.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
|
||||
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(&create.quietPull, "quiet-pull", false, "Pull without printing progress information.")
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
if len(project.Services) == 0 {
|
||||
return fmt.Errorf("no service selected")
|
||||
|
@ -199,6 +211,7 @@ func runUp(ctx context.Context, backend api.Service, createOptions createOptions
|
|||
AttachTo: attachTo,
|
||||
ExitCodeFrom: upOptions.exitCodeFrom,
|
||||
CascadeStop: upOptions.cascadeStop,
|
||||
Wait: upOptions.wait,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -124,6 +124,8 @@ type StartOptions struct {
|
|||
CascadeStop bool
|
||||
// ExitCodeFrom return exit code from specified service
|
||||
ExitCodeFrom string
|
||||
// Wait won't return until containers reached the running|healthy state
|
||||
Wait bool
|
||||
}
|
||||
|
||||
// RestartOptions group options of the Restart API
|
||||
|
|
|
@ -261,9 +261,11 @@ func getContainerProgressName(container moby.Container) string {
|
|||
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)
|
||||
for dep, config := range service.DependsOn {
|
||||
for dep, config := range dependencies {
|
||||
dep, config := dep, config
|
||||
eg.Go(func() error {
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
|
@ -271,8 +273,16 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
|
|||
for {
|
||||
<-ticker.C
|
||||
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:
|
||||
healthy, err := s.isServiceHealthy(ctx, project, dep)
|
||||
healthy, err := s.isServiceHealthy(ctx, project, dep, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -502,7 +512,7 @@ func (s *composeService) connectContainerToNetwork(ctx context.Context, id strin
|
|||
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)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
@ -516,6 +526,11 @@ func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Pr
|
|||
if err != nil {
|
||||
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 {
|
||||
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 {
|
||||
err := s.waitDependencies(ctx, project, service)
|
||||
err := s.waitDependencies(ctx, project, service.DependsOn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -493,6 +493,7 @@ func getDeployResources(s types.ServiceConfig) container.Resources {
|
|||
MemorySwap: int64(s.MemSwapLimit),
|
||||
MemorySwappiness: swappiness,
|
||||
MemoryReservation: int64(s.MemReservation),
|
||||
OomKillDisable: &s.OomKillDisable,
|
||||
CPUCount: s.CPUCount,
|
||||
CPUPeriod: s.CPUPeriod,
|
||||
CPUQuota: s.CPUQuota,
|
||||
|
@ -503,6 +504,10 @@ func getDeployResources(s types.ServiceConfig) container.Resources {
|
|||
CpusetCpus: s.CPUSet,
|
||||
}
|
||||
|
||||
if s.PidsLimit != 0 {
|
||||
resources.PidsLimit = &s.PidsLimit
|
||||
}
|
||||
|
||||
setBlkio(s.BlkioConfig, &resources)
|
||||
|
||||
if s.Deploy != nil {
|
||||
|
|
|
@ -160,7 +160,7 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
|
|||
return "", err
|
||||
}
|
||||
if !opts.NoDeps {
|
||||
if err := s.waitDependencies(ctx, project, service); err != nil {
|
||||
if err := s.waitDependencies(ctx, project, service.DependsOn); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,11 +58,26 @@ func (s *composeService) start(ctx context.Context, project *types.Project, opti
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.startService(ctx, project, service)
|
||||
})
|
||||
if err != nil {
|
||||
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()
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue