mirror of https://github.com/docker/compose.git
Implement service_healthy dependency condition
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
adb62e9080
commit
251c52664a
|
@ -389,10 +389,10 @@ func getContainerCreateOptions(p *types.Project, s types.ServiceConfig, number i
|
|||
MacAddress: s.MacAddress,
|
||||
Labels: labels,
|
||||
StopSignal: s.StopSignal,
|
||||
// Env: s.Environment, FIXME conversion
|
||||
// Healthcheck: s.HealthCheck, FIXME conversion
|
||||
Env: toMobyEnv(s.Environment),
|
||||
Healthcheck: toMobyHealthCheck(s.HealthCheck),
|
||||
// Volumes: // FIXME unclear to me the overlap with HostConfig.Mounts
|
||||
// StopTimeout: s.StopGracePeriod FIXME conversion
|
||||
StopTimeout: toSeconds(s.StopGracePeriod),
|
||||
}
|
||||
|
||||
mountOptions := buildContainerMountOptions(p, s, inherit)
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
|
@ -39,6 +40,8 @@ const (
|
|||
)
|
||||
|
||||
func (s *local) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
|
||||
s.waitDependencies(ctx, project, service)
|
||||
|
||||
actual, err := s.containerService.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
filters.Arg("label", fmt.Sprintf("%s=%s", projectLabel, project.Name)),
|
||||
|
@ -108,6 +111,28 @@ func (s *local) ensureService(ctx context.Context, project *types.Project, servi
|
|||
return eg.Wait()
|
||||
}
|
||||
|
||||
func (s *local) waitDependencies(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
for dep, config := range service.DependsOn {
|
||||
switch config.Condition {
|
||||
case "service_healthy":
|
||||
eg.Go(func() error {
|
||||
for range time.Tick(500 * time.Millisecond) {
|
||||
healthy, err := s.isServiceHealthy(ctx, project, dep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if healthy {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
func nextContainerNumber(containers []moby.Container) (int, error) {
|
||||
max := 0
|
||||
for _, c := range containers {
|
||||
|
@ -257,3 +282,33 @@ func (s *local) connectContainerToNetwork(ctx context.Context, id string, servic
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *local) isServiceHealthy(ctx context.Context, project *types.Project, service string) (bool, error) {
|
||||
containers, err := s.containerService.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
filters.Arg("label", fmt.Sprintf("%s=%s", projectLabel, project.Name)),
|
||||
filters.Arg("label", fmt.Sprintf("%s=%s", serviceLabel, service)),
|
||||
),
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, c := range containers {
|
||||
container, err := s.containerService.apiClient.ContainerInspect(ctx, c.ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if container.State == nil || container.State.Health == nil {
|
||||
return false, fmt.Errorf("container for service %q has no healthcheck configured", service)
|
||||
}
|
||||
switch container.State.Health.Status {
|
||||
case "starting":
|
||||
return false, nil
|
||||
case "unhealthy":
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
|
||||
}
|
||||
|
|
|
@ -23,7 +23,9 @@ import (
|
|||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
compose "github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/go-connections/nat"
|
||||
|
@ -93,6 +95,57 @@ func toPorts(ports []types.Port) []containers.Port {
|
|||
return result
|
||||
}
|
||||
|
||||
func toMobyEnv(environment compose.MappingWithEquals) []string {
|
||||
var env []string
|
||||
for k, v := range environment {
|
||||
if v == nil {
|
||||
env = append(env, k)
|
||||
} else {
|
||||
env = append(env, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
func toMobyHealthCheck(check *compose.HealthCheckConfig) *container.HealthConfig {
|
||||
if check == nil {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
interval time.Duration
|
||||
timeout time.Duration
|
||||
period time.Duration
|
||||
retries int
|
||||
)
|
||||
if check.Interval != nil {
|
||||
interval = time.Duration(*check.Interval)
|
||||
}
|
||||
if check.Timeout != nil {
|
||||
timeout = time.Duration(*check.Timeout)
|
||||
}
|
||||
if check.StartPeriod != nil {
|
||||
period = time.Duration(*check.StartPeriod)
|
||||
}
|
||||
if check.Retries != nil {
|
||||
retries = int(*check.Retries)
|
||||
}
|
||||
return &container.HealthConfig{
|
||||
Test: check.Test,
|
||||
Interval: interval,
|
||||
Timeout: timeout,
|
||||
StartPeriod: period,
|
||||
Retries: retries,
|
||||
}
|
||||
}
|
||||
|
||||
func toSeconds(d *compose.Duration) *int {
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
s := int(time.Duration(*d).Seconds())
|
||||
return &s
|
||||
}
|
||||
|
||||
func fromPorts(ports []containers.Port) (map[nat.Port]struct{}, map[nat.Port][]nat.PortBinding, error) {
|
||||
var (
|
||||
exposedPorts = make(map[nat.Port]struct{}, len(ports))
|
||||
|
|
Loading…
Reference in New Issue