337 lines
11 KiB
Go
337 lines
11 KiB
Go
package ports
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// PullPolicy enumerates the supported image pull policies. The value
|
|
// set mirrors `config.ImagePullPolicy`; the runtime/wiring layer
|
|
// translates between the two so the docker adapter does not import
|
|
// `internal/config` and the port package stays free of configuration
|
|
// concerns.
|
|
type PullPolicy string
|
|
|
|
// Supported pull policies, frozen by `rtmanager/README.md §Configuration`.
|
|
const (
|
|
// PullPolicyIfMissing pulls the image only when it is absent from
|
|
// the local Docker daemon.
|
|
PullPolicyIfMissing PullPolicy = "if_missing"
|
|
|
|
// PullPolicyAlways pulls the image on every start.
|
|
PullPolicyAlways PullPolicy = "always"
|
|
|
|
// PullPolicyNever skips the pull and fails the start when the image
|
|
// is absent.
|
|
PullPolicyNever PullPolicy = "never"
|
|
)
|
|
|
|
// IsKnown reports whether policy belongs to the frozen pull-policy
|
|
// vocabulary.
|
|
func (policy PullPolicy) IsKnown() bool {
|
|
switch policy {
|
|
case PullPolicyIfMissing, PullPolicyAlways, PullPolicyNever:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
//go:generate go run go.uber.org/mock/mockgen -destination=../adapters/docker/mocks/mock_dockerclient.go -package=mocks galaxy/rtmanager/internal/ports DockerClient
|
|
|
|
// DockerClient is the narrow Docker port Runtime Manager uses. The
|
|
// production adapter wraps `github.com/docker/docker/client`; service
|
|
// tests use a generated mock. The surface intentionally exposes only
|
|
// the operations RTM needs; `docker logs` and stream attach are out
|
|
// of scope for v1.
|
|
type DockerClient interface {
|
|
// EnsureNetwork verifies the configured Docker network is present
|
|
// on the daemon. It returns ErrNetworkMissing when the network does
|
|
// not exist; RTM never creates networks itself.
|
|
EnsureNetwork(ctx context.Context, name string) error
|
|
|
|
// PullImage pulls ref according to policy. It returns nil on
|
|
// success and a wrapped Docker error otherwise. Implementations
|
|
// honour PullPolicyNever by skipping the pull and returning nil
|
|
// when the image is already present, or returning ErrImageNotFound
|
|
// otherwise.
|
|
PullImage(ctx context.Context, ref string, policy PullPolicy) error
|
|
|
|
// InspectImage returns image metadata for ref. It returns
|
|
// ErrImageNotFound when no such image exists locally.
|
|
InspectImage(ctx context.Context, ref string) (ImageInspect, error)
|
|
|
|
// InspectContainer returns container metadata for containerID. It
|
|
// returns ErrContainerNotFound when no such container exists.
|
|
InspectContainer(ctx context.Context, containerID string) (ContainerInspect, error)
|
|
|
|
// Run creates and starts one container according to spec. The
|
|
// returned RunResult carries the assigned container id, the stable
|
|
// engine endpoint, and the wall-clock observed by the daemon.
|
|
Run(ctx context.Context, spec RunSpec) (RunResult, error)
|
|
|
|
// Stop sends SIGTERM to the container followed by SIGKILL after
|
|
// timeout. It returns nil when the container exited cleanly and
|
|
// ErrContainerNotFound when it is already gone.
|
|
Stop(ctx context.Context, containerID string, timeout time.Duration) error
|
|
|
|
// Remove removes the container. It returns nil when the container
|
|
// no longer exists (idempotent removal).
|
|
Remove(ctx context.Context, containerID string) error
|
|
|
|
// List returns container summaries that match filter. Implementations
|
|
// translate ListFilter into the appropriate Docker filters argument.
|
|
List(ctx context.Context, filter ListFilter) ([]ContainerSummary, error)
|
|
|
|
// EventsListen subscribes to the Docker events stream and returns
|
|
// the decoded event channel together with an asynchronous error
|
|
// channel. The caller cancels ctx to terminate the subscription.
|
|
// Implementations close events when the subscription terminates.
|
|
EventsListen(ctx context.Context) (events <-chan DockerEvent, errs <-chan error, err error)
|
|
}
|
|
|
|
// RunSpec stores the request shape used by DockerClient.Run.
|
|
type RunSpec struct {
|
|
// Name stores the container name (typically `galaxy-game-{game_id}`).
|
|
Name string
|
|
|
|
// Image stores the image reference resolved by the producer.
|
|
Image string
|
|
|
|
// Hostname stores the container hostname assigned for the embedded
|
|
// Docker DNS to resolve from other containers on the network.
|
|
Hostname string
|
|
|
|
// Network stores the user-defined Docker network the container
|
|
// attaches to.
|
|
Network string
|
|
|
|
// Env stores the environment variables forwarded to the container
|
|
// (e.g. GAME_STATE_PATH, STORAGE_PATH).
|
|
Env map[string]string
|
|
|
|
// Cmd overrides the entrypoint arguments for the container. Production
|
|
// callers leave it nil so the engine image's own CMD runs; tests use
|
|
// it to drive a tiny container that does not embed RTM-specific
|
|
// behaviour. Empty Cmd means "use image default", which mirrors the
|
|
// Docker SDK contract.
|
|
Cmd []string
|
|
|
|
// Labels stores the labels applied to the container so the
|
|
// reconciler and the events listener can identify it.
|
|
Labels map[string]string
|
|
|
|
// BindMounts stores the host-to-container bind mounts. RTM uses
|
|
// exactly one mount in v1 (the per-game state directory).
|
|
BindMounts []BindMount
|
|
|
|
// LogDriver stores the Docker logging driver name.
|
|
LogDriver string
|
|
|
|
// LogOpts stores the logging-driver options as key=value pairs.
|
|
LogOpts map[string]string
|
|
|
|
// CPUQuota stores the `--cpus` value applied as a resource limit.
|
|
CPUQuota float64
|
|
|
|
// Memory stores the `--memory` value (e.g. `512m`) applied as a
|
|
// resource limit.
|
|
Memory string
|
|
|
|
// PIDsLimit stores the `--pids-limit` value.
|
|
PIDsLimit int
|
|
}
|
|
|
|
// BindMount stores one host-to-container bind mount.
|
|
type BindMount struct {
|
|
// HostPath stores the absolute host path bound into the container.
|
|
HostPath string
|
|
|
|
// MountPath stores the absolute in-container path the host
|
|
// directory is mounted at.
|
|
MountPath string
|
|
|
|
// ReadOnly mounts the host path read-only when true.
|
|
ReadOnly bool
|
|
}
|
|
|
|
// RunResult stores the response shape returned by DockerClient.Run.
|
|
type RunResult struct {
|
|
// ContainerID identifies the created container.
|
|
ContainerID string
|
|
|
|
// EngineEndpoint stores the stable URL Game Master uses to reach
|
|
// the engine container.
|
|
EngineEndpoint string
|
|
|
|
// StartedAt stores the wall-clock the daemon observed for the
|
|
// start event.
|
|
StartedAt time.Time
|
|
}
|
|
|
|
// ImageInspect stores the subset of `docker image inspect` fields RTM
|
|
// reads. Only Labels are required at start time (resource limits live
|
|
// there); other fields may be populated when convenient for diagnostics.
|
|
type ImageInspect struct {
|
|
// Ref stores the image reference the inspection was scoped to.
|
|
Ref string
|
|
|
|
// Labels stores the image-level labels (e.g.
|
|
// `com.galaxy.cpu_quota`).
|
|
Labels map[string]string
|
|
}
|
|
|
|
// ContainerInspect stores the subset of `docker inspect` fields RTM
|
|
// reads from a running or exited container.
|
|
type ContainerInspect struct {
|
|
// ID identifies the container.
|
|
ID string
|
|
|
|
// ImageRef stores the image reference the container was started
|
|
// from.
|
|
ImageRef string
|
|
|
|
// Hostname stores the container hostname.
|
|
Hostname string
|
|
|
|
// Labels stores the container labels assigned at create time.
|
|
Labels map[string]string
|
|
|
|
// Status stores the verbatim Docker `State.Status` value (e.g.
|
|
// `running`, `exited`).
|
|
Status string
|
|
|
|
// Health stores the verbatim Docker `State.Health.Status` value
|
|
// (e.g. `healthy`, `unhealthy`). Empty when the image declares no
|
|
// HEALTHCHECK.
|
|
Health string
|
|
|
|
// RestartCount stores the Docker `RestartCount` observed at
|
|
// inspection time.
|
|
RestartCount int
|
|
|
|
// StartedAt stores the daemon-observed start wall-clock.
|
|
StartedAt time.Time
|
|
|
|
// FinishedAt stores the daemon-observed exit wall-clock. Zero when
|
|
// the container is still running.
|
|
FinishedAt time.Time
|
|
|
|
// ExitCode stores the exit code reported by the daemon. Zero when
|
|
// the container is still running.
|
|
ExitCode int
|
|
|
|
// OOMKilled reports whether the container was killed by the OOM
|
|
// killer.
|
|
OOMKilled bool
|
|
}
|
|
|
|
// ContainerSummary stores the subset of `docker ps` fields RTM reads.
|
|
type ContainerSummary struct {
|
|
// ID identifies the container.
|
|
ID string
|
|
|
|
// ImageRef stores the image reference.
|
|
ImageRef string
|
|
|
|
// Hostname stores the container hostname.
|
|
Hostname string
|
|
|
|
// Labels stores the container labels assigned at create time.
|
|
Labels map[string]string
|
|
|
|
// Status stores the verbatim Docker `State.Status` value.
|
|
Status string
|
|
|
|
// StartedAt stores the daemon-observed start wall-clock.
|
|
StartedAt time.Time
|
|
}
|
|
|
|
// ListFilter stores the criteria used by DockerClient.List.
|
|
type ListFilter struct {
|
|
// Labels stores label key=value pairs that must all be present on
|
|
// the container. Empty matches every container.
|
|
Labels map[string]string
|
|
}
|
|
|
|
// DockerEvent stores one decoded entry from the Docker events stream.
|
|
// RTM only consumes container-scoped events.
|
|
type DockerEvent struct {
|
|
// Action stores the Docker event action verbatim (e.g. `start`,
|
|
// `die`, `oom`, `destroy`).
|
|
Action string
|
|
|
|
// ContainerID identifies the container the event refers to.
|
|
ContainerID string
|
|
|
|
// Labels stores the container labels carried by the event
|
|
// attributes when present.
|
|
Labels map[string]string
|
|
|
|
// ExitCode stores the exit code attribute when applicable (e.g.
|
|
// `die` events). Zero when the action does not carry one.
|
|
ExitCode int
|
|
|
|
// OccurredAt stores the daemon-observed event wall-clock.
|
|
OccurredAt time.Time
|
|
}
|
|
|
|
// String returns policy as its stored enum value. Convenient for use in
|
|
// log fields and error messages.
|
|
func (policy PullPolicy) String() string {
|
|
return string(policy)
|
|
}
|
|
|
|
// ErrNetworkMissing reports that the configured Docker network is not
|
|
// present on the daemon.
|
|
var ErrNetworkMissing = errors.New("docker network missing")
|
|
|
|
// ErrImageNotFound reports that an image reference does not resolve to
|
|
// a local Docker image.
|
|
var ErrImageNotFound = errors.New("docker image not found")
|
|
|
|
// ErrContainerNotFound reports that a container id does not resolve to
|
|
// a Docker container.
|
|
var ErrContainerNotFound = errors.New("docker container not found")
|
|
|
|
// Validate reports whether spec carries the structural invariants
|
|
// required by DockerClient.Run. Adapters use it as the first defence
|
|
// against malformed specs originating in service code.
|
|
func (spec RunSpec) Validate() error {
|
|
if spec.Name == "" {
|
|
return fmt.Errorf("run spec: name must not be empty")
|
|
}
|
|
if spec.Image == "" {
|
|
return fmt.Errorf("run spec: image must not be empty")
|
|
}
|
|
if spec.Hostname == "" {
|
|
return fmt.Errorf("run spec: hostname must not be empty")
|
|
}
|
|
if spec.Network == "" {
|
|
return fmt.Errorf("run spec: network must not be empty")
|
|
}
|
|
if spec.LogDriver == "" {
|
|
return fmt.Errorf("run spec: log driver must not be empty")
|
|
}
|
|
if spec.CPUQuota <= 0 {
|
|
return fmt.Errorf("run spec: cpu quota must be positive")
|
|
}
|
|
if spec.Memory == "" {
|
|
return fmt.Errorf("run spec: memory must not be empty")
|
|
}
|
|
if spec.PIDsLimit <= 0 {
|
|
return fmt.Errorf("run spec: pids limit must be positive")
|
|
}
|
|
for index, mount := range spec.BindMounts {
|
|
if mount.HostPath == "" {
|
|
return fmt.Errorf("run spec: bind mounts[%d]: host path must not be empty", index)
|
|
}
|
|
if mount.MountPath == "" {
|
|
return fmt.Errorf("run spec: bind mounts[%d]: mount path must not be empty", index)
|
|
}
|
|
}
|
|
return nil
|
|
}
|