mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
146 lines
4.3 KiB
Go
146 lines
4.3 KiB
Go
package api
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/docker/docker/api/types"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
LabelManaged = "uncloud.managed"
|
|
LabelServiceID = "uncloud.service.id"
|
|
LabelServiceName = "uncloud.service.name"
|
|
LabelServiceMode = "uncloud.service.mode"
|
|
LabelServicePorts = "uncloud.service.ports"
|
|
|
|
StateCreated = "created"
|
|
StateDead = "dead"
|
|
StateExited = "exited"
|
|
StatePaused = "paused"
|
|
StateRemoving = "removing"
|
|
StateRestarting = "restarting"
|
|
StateRunning = "running"
|
|
)
|
|
|
|
type Container struct {
|
|
types.Container
|
|
}
|
|
|
|
func (c *Container) Name() string {
|
|
return c.Names[0][1:] // Remove leading slash.
|
|
}
|
|
|
|
// ServiceID returns the ID of the service this container belongs to.
|
|
func (c *Container) ServiceID() string {
|
|
return c.Labels[LabelServiceID]
|
|
}
|
|
|
|
// ServiceName returns the name of the service this container belongs to.
|
|
func (c *Container) ServiceName() string {
|
|
return c.Labels[LabelServiceName]
|
|
}
|
|
|
|
// ServiceMode returns the replication mode of the service this container belongs to.
|
|
func (c *Container) ServiceMode() string {
|
|
return c.Labels[LabelServiceMode]
|
|
}
|
|
|
|
// ServicePorts returns the ports this container publishes as part of its service.
|
|
func (c *Container) ServicePorts() ([]PortSpec, error) {
|
|
encoded, ok := c.Labels[LabelServicePorts]
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
if strings.TrimSpace(encoded) == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
publishPorts := strings.Split(encoded, ",")
|
|
ports := make([]PortSpec, len(publishPorts))
|
|
for i, p := range publishPorts {
|
|
port, err := ParsePortSpec(strings.TrimSpace(p))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ports[i] = port
|
|
}
|
|
|
|
return ports, nil
|
|
}
|
|
|
|
func (c *Container) ServiceSpec() ServiceSpec {
|
|
// TODO: migrate api.Container type to use ContainerJSON to make it possible to construct
|
|
// a ServiceSpec from a Container.
|
|
return ServiceSpec{}
|
|
}
|
|
|
|
// runningStatusRegex matches the status string of a running container.
|
|
// - "Up 3 minutes (healthy)" -> groups: ["Up 3 minutes (healthy)", "healthy"]
|
|
// - "Up 5 seconds" -> groups: ["Up 5 seconds", ""]
|
|
// - "Up 2 hours (unhealthy)" -> groups: ["Up 2 hours (unhealthy)", "unhealthy"]
|
|
// - "Up 1 minute (health: starting)" -> groups: ["Up 1 minute (health: starting)", "health: starting"]
|
|
// - "Restarting (0) 5 seconds ago" -> no match
|
|
// See https://github.com/moby/moby/blob/c130ce1f5d1e38b98a97044a39557de43bc0d58f/container/state.go#L77-L90
|
|
// for more details on how the status string for a running container is formatted.
|
|
var runningStatusRegex = regexp.MustCompile(`^Up [^(]+(?:\(([^)]+)\))?$`)
|
|
|
|
// Healthy determines if the container is running and healthy based on its status string.
|
|
// A running container with no health check configured is considered healthy.
|
|
func (c *Container) Healthy() bool {
|
|
if c.State != StateRunning {
|
|
return false
|
|
}
|
|
|
|
matches := runningStatusRegex.FindStringSubmatch(c.Status)
|
|
// Not "Up" or invalid format.
|
|
if matches == nil {
|
|
return false
|
|
}
|
|
|
|
// If there's no health status (no health check configured so no parentheses), container is considered healthy.
|
|
if matches[1] == "" {
|
|
return true
|
|
}
|
|
|
|
// If the health status in parentheses is "healthy", the container is considered healthy.
|
|
return matches[1] == types.Healthy
|
|
}
|
|
|
|
// Stopped determines if the container is stopped and doesn't try to restart.
|
|
func (c *Container) Stopped() bool {
|
|
return c.State == StateCreated || c.State == StateDead || c.State == StateExited
|
|
}
|
|
|
|
// ConflictingServicePorts returns a list of service ports that conflict with the given ports.
|
|
func (c *Container) ConflictingServicePorts(ports []PortSpec) ([]PortSpec, error) {
|
|
svcPorts, err := c.ServicePorts()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get service ports: %w", err)
|
|
}
|
|
|
|
var conflicting []PortSpec
|
|
for _, p := range ports {
|
|
if p.Mode != PortModeHost {
|
|
continue
|
|
}
|
|
|
|
// Two host ports conflict if they have the same published port number and protocol, and either:
|
|
// * At least one host IP is not set (meaning it uses all interfaces)
|
|
// * Both host IPs are identical
|
|
for _, svcPort := range svcPorts {
|
|
if svcPort.Mode != PortModeHost ||
|
|
svcPort.PublishedPort != p.PublishedPort ||
|
|
svcPort.Protocol != p.Protocol {
|
|
continue
|
|
}
|
|
|
|
if !svcPort.HostIP.IsValid() || !p.HostIP.IsValid() || svcPort.HostIP.Compare(p.HostIP) == 0 {
|
|
conflicting = append(conflicting, p)
|
|
}
|
|
}
|
|
}
|
|
|
|
return conflicting, nil
|
|
}
|