| 468 | } |
| 469 | |
| 470 | func (s *composeService) isServiceHealthy(ctx context.Context, containers Containers, fallbackRunning bool) (bool, error) { |
| 471 | for _, c := range containers { |
| 472 | res, err := s.apiClient().ContainerInspect(ctx, c.ID, client.ContainerInspectOptions{}) |
| 473 | if err != nil { |
| 474 | return false, err |
| 475 | } |
| 476 | ctr := res.Container |
| 477 | name := ctr.Name[1:] |
| 478 | |
| 479 | if ctr.State.Status == container.StateExited { |
| 480 | return false, fmt.Errorf("container %s exited (%d)", name, ctr.State.ExitCode) |
| 481 | } |
| 482 | |
| 483 | noHealthcheck := ctr.Config.Healthcheck == nil || (len(ctr.Config.Healthcheck.Test) > 0 && ctr.Config.Healthcheck.Test[0] == "NONE") |
| 484 | if noHealthcheck && fallbackRunning { |
| 485 | // Container does not define a health check, but we can fall back to "running" state |
| 486 | return ctr.State != nil && ctr.State.Status == container.StateRunning, nil |
| 487 | } |
| 488 | |
| 489 | if ctr.State == nil || ctr.State.Health == nil { |
| 490 | return false, fmt.Errorf("container %s has no healthcheck configured", name) |
| 491 | } |
| 492 | switch ctr.State.Health.Status { |
| 493 | case container.Healthy: |
| 494 | // Continue by checking the next container. |
| 495 | case container.Unhealthy: |
| 496 | return false, fmt.Errorf("container %s is unhealthy", name) |
| 497 | case container.Starting: |
| 498 | return false, nil |
| 499 | default: |
| 500 | return false, fmt.Errorf("container %s had unexpected health status %q", name, ctr.State.Health.Status) |
| 501 | } |
| 502 | } |
| 503 | return true, nil |
| 504 | } |
| 505 | |
| 506 | func (s *composeService) isServiceCompleted(ctx context.Context, containers Containers) (bool, int, error) { |
| 507 | for _, c := range containers { |