docker

package
v1.0.0-beta.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 17, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package docker turns Docker container metadata into desired NPM resource state and streams container lifecycle events.

Index

Constants

View Source
const DefaultPrefix = "npm"

DefaultPrefix is the label namespace used when none is configured.

Variables

View Source
var DefaultPortPreference = fields.DefaultPortPreference

DefaultPortPreference is the order in which an exposed port is picked when a container exposes several of them.

Functions

func DedupeNetworks

func DedupeNetworks(names []string) []string

DedupeNetworks removes duplicates while keeping the first occurrence, so a preference list assembled from several sources stays readable in logs.

func EventFilters

func EventFilters() filters.Args

EventFilters returns the server-side filter set: only container lifecycle events that can change the desired configuration.

func IsEnabled

func IsEnabled(c Container, prefix string) bool

IsEnabled reports whether a container is synchronised.

func Managed

func Managed(c Container, opts ParseOptions) (bool, string)

Managed reports whether a container is synchronised, and why not when it is not.

With NPM_EXPOSED_BY_DEFAULT (the default) any container carrying at least one label of the namespace is managed and `npm.enable=false` opts out. With the flag turned off the historic behaviour applies: `npm.enable=true` is required.

func NewClient

func NewClient(host string) (*client.Client, error)

NewClient builds a Docker SDK client for the given host. Both "unix:///var/run/docker.sock" and "tcp://docker-socket-proxy:2375" work; the TCP form is the recommended, least-privilege setup.

func Scan

func Scan(containers []Container, opts ParseOptions) (Snapshot, Result)

Scan parses every container into the snapshot one reconcile run works from.

A container whose labels could not be parsed is recorded as *protected*: its resources are still in NPM, the tool simply cannot tell what they should look like right now. Deleting them because of a typo in one label would turn a warning into an outage.

Types

type APIClient

type APIClient interface {
	ContainerList(ctx context.Context, options container.ListOptions) ([]container.Summary, error)
	Events(ctx context.Context, options events.ListOptions) (<-chan events.Message, <-chan error)
	Ping(ctx context.Context) (types.Ping, error)
	Close() error
}

APIClient is the slice of the Docker SDK this package needs. Keeping it narrow makes the listener trivially mockable in tests.

type Container

type Container struct {
	ID       string
	Name     string
	Labels   map[string]string
	Networks []Network
	// Ports are the container's exposed and published ports, used to guess
	// the upstream port when no label names one.
	Ports []PortBinding
	// State is the Docker state ("running", "exited", ...).
	State string
}

Container is the subset of Docker container data the parser needs.

func (Container) ExposedPorts

func (c Container) ExposedPorts() []int

ExposedPorts returns the container's TCP ports, sorted, for error messages.

func (Container) GuessPort

func (c Container) GuessPort(preference []int) (int, bool)

GuessPort returns the container port to forward to when no label names one.

A container that exposes exactly one TCP port needs no configuration at all; with several ports the preference list decides, and when none of them is listed the caller is told to be explicit rather than being handed a random port.

func (Container) IPAddress

func (c Container) IPAddress(preferred []string, strict bool) string

IPAddress returns the container's IPv4 address, preferring the given networks (in order). Without strict mode the remaining networks are used as a fallback, in a stable alphabetical order; with strict mode an empty string is returned instead, because an address NPM cannot route to is worse than no proxy host at all.

func (Container) NetworkNames

func (c Container) NetworkNames() []string

NetworkNames returns the names of all networks the container is attached to.

func (Container) OnAnyNetwork

func (c Container) OnAnyNetwork(names []string) bool

OnAnyNetwork reports whether the container is attached to one of the given networks.

func (Container) PublishedPort

func (c Container) PublishedPort(private int) (int, bool)

PublishedPort returns the host port a container port is published on.

func (Container) ResolveHost

func (c Container) ResolveHost(opts ParseOptions) string

ResolveHost returns the upstream host for the container: the container's IP address when resolution is enabled and an address is available, otherwise the container name (Docker's embedded DNS resolves it inside a shared user-defined network).

Only IPv4 addresses are used: NPM writes the value straight into `proxy_pass`, where a bare IPv6 address would be invalid.

func (Container) Running

func (c Container) Running() bool

Running reports whether the container is up.

"restarting" and "paused" count as running on purpose: a crash loop or a paused container is a temporary condition, and treating it as stopped would disable a host every few seconds.

type Event

type Event struct {
	ContainerID string
	Name        string
	Action      string
}

Event is a normalised container lifecycle event.

type InfoClient

type InfoClient interface {
	Info(ctx context.Context) (system.Info, error)
}

InfoClient is the optional part of the Docker API used to identify the daemon. A socket proxy may well refuse /info, which is why it is separate: the sync instance id then falls back to the host name.

type Listener

type Listener struct {
	// contains filtered or unexported fields
}

Listener reads container state and lifecycle events from the Docker API.

func NewListener

func NewListener(api APIClient, opts ParseOptions, log *slog.Logger) *Listener

NewListener wraps an APIClient. The ParseOptions carry the label prefix and the upstream-host resolution policy.

func (*Listener) Close

func (l *Listener) Close() error

Close releases the underlying Docker connection.

func (*Listener) Containers

func (l *Listener) Containers(ctx context.Context) ([]Container, error)

Containers returns all containers as parser input, including their network endpoints so the upstream IP can be resolved.

Stopped containers are included on purpose: "the container is gone" and "the container is stopped" are very different situations, and only the first one may ever remove a host (NPM_ON_STOP decides what the second one does).

func (*Listener) DaemonID

func (l *Listener) DaemonID(ctx context.Context) string

DaemonID returns the id of the Docker daemon, which is the natural default for the sync instance id: it is stable across restarts and recreates of this container, and different for every Docker host.

When the endpoint does not expose /info - a filtered socket proxy may well deny it - the answer is deliberately empty rather than something like the host name. Inside a container the host name is the short container id, so it changes with every recreate, and a sync instance whose identity changes would consider all of its own resources to belong to somebody else. No identity at all is the safe answer: it means "manage every resource carrying our marker", which is what a single-instance setup wants anyway.

func (*Listener) DiscoverOwnNetworks

func (l *Listener) DiscoverOwnNetworks(ctx context.Context) []string

DiscoverOwnNetworks returns the networks this process's own container is attached to. They are preferred when resolving upstream IPs, because a container reachable from here is reachable from NPM when both sit on the same network. Failures are not fatal: the caller falls back to the configured network or the first available address.

func (*Listener) NetworksOfContainer

func (l *Listener) NetworksOfContainer(ctx context.Context, name string) []string

NetworksOfContainer returns the networks a named container is attached to. It is how NPM_CONTAINER_NAME turns into an upstream network preference without the operator having to name the network as well.

func (*Listener) Options

func (l *Listener) Options() ParseOptions

Options returns the parse options in use.

func (*Listener) Ping

func (l *Listener) Ping(ctx context.Context) error

Ping verifies that the Docker endpoint is reachable.

func (*Listener) SelfID

func (l *Listener) SelfID() string

SelfID returns the container id this process runs in, if it is known.

func (*Listener) SetSelfID

func (l *Listener) SetSelfID(id string)

SetSelfID records the own container id so its events can be ignored. It is set automatically by DiscoverOwnNetworks.

func (*Listener) Snapshot

func (l *Listener) Snapshot(ctx context.Context) (Snapshot, error)

Snapshot returns the desired NPM resources of all containers, plus the containers whose labels could not be parsed. Invalid definitions are logged and skipped so one broken label set cannot stall the whole sync - and their containers are marked protected so the broken definition cannot delete anything either.

func (*Listener) StreamStatus

func (l *Listener) StreamStatus() StreamStatus

StreamStatus returns the health of the Docker event subscription.

func (*Listener) Summarize

func (l *Listener) Summarize(ctx context.Context) (Summary, error)

Summarize classifies the containers for the start-up overview.

func (*Listener) Targets

func (l *Listener) Targets(ctx context.Context) ([]*Target, error)

Targets returns only the desired resources. It is the convenience form used by tests and the validate subcommand.

func (*Listener) Watch

func (l *Listener) Watch(ctx context.Context, trigger chan<- Event) error

Watch streams relevant container events into trigger. It reconnects with exponential backoff until ctx is cancelled, then returns nil.

The filters are applied server-side so the daemon (or the socket proxy) only ever sends what this tool is allowed to see.

type Network

type Network struct {
	Name    string
	IPv4    string
	IPv6    string
	Aliases []string
}

Network is one network endpoint of a container.

type OnStop

type OnStop string

OnStop is the policy for a container that is stopped but still exists.

const (
	// OnStopDisable disables the resources of a stopped container (default).
	OnStopDisable OnStop = "disable"
	// OnStopKeep leaves them untouched and serving.
	OnStopKeep OnStop = "keep"
	// OnStopDelete removes them, as if the container had been destroyed.
	OnStopDelete OnStop = "delete"
)

The stop policies.

func ParseOnStop

func ParseOnStop(raw string) (OnStop, error)

ParseOnStop resolves the NPM_ON_STOP setting.

type ParseOptions

type ParseOptions struct {
	// Prefix is the label namespace, e.g. "npm".
	Prefix string
	// ResolveIP makes the parser default the upstream host to the container's
	// IP address instead of its name. Avoids 502s when NPM cannot resolve
	// Docker's internal DNS.
	ResolveIP bool
	// PreferNetworks lists network names to try first when resolving the IP,
	// typically the network NPM itself is attached to.
	PreferNetworks []string
	// StrictNetworks restricts IP resolution to PreferNetworks. Without it a
	// container that is not attached to any of them falls back to an address
	// from some other network - which NPM usually cannot reach, producing a
	// proxy host that resolves to a dead upstream. Set whenever the operator
	// named the network explicitly (NPM_NETWORK).
	StrictNetworks bool
	// Defaults are the effective field defaults (built-in, overridden by the
	// NPM_<KIND>_<FIELD> and NPM_DEFAULT_<FIELD> environment variables).
	Defaults *fields.Defaults
	// ExposedByDefault manages every container that carries at least one
	// label of the namespace, without requiring npm.enable=true
	// (NPM_EXPOSED_BY_DEFAULT, default true).
	ExposedByDefault bool
	// StrictLabels skips a resource whose label set contains an unknown
	// field instead of only warning about it.
	StrictLabels bool
	// PortPreference is the order in which an exposed port is picked when a
	// container exposes several (NPM_PORT_PREFERENCE).
	PortPreference []int
	// SelfID is this process's own container: it is never managed.
	SelfID string
	// Offline parses labels without a Docker daemon behind them, as the
	// `validate` subcommand does for a compose file: the container's address
	// and exposed ports are simply not knowable, and their absence is not an
	// error.
	Offline bool
}

ParseOptions controls how labels are turned into targets.

type PortBinding

type PortBinding struct {
	// Private is the container-internal port (what EXPOSE declares).
	Private int
	// Public is the host port, 0 when the port is not published.
	Public int
	// Type is "tcp" or "udp".
	Type string
}

PortBinding is one port of a container: the port inside the container and, when published, the port on the host.

type Result

type Result struct {
	Targets  []*Target
	Errors   []error
	Warnings []string
	// Skipped marks a container whose resource was dropped because of an
	// unknown label under STRICT_LABELS.
	Skipped bool
}

Result is the outcome of parsing one or many containers. Warnings are problems that do not invalidate a resource (an unknown label, a domain no certificate covers); Errors are definitions that had to be dropped.

func Parse

func Parse(c Container, opts ParseOptions) Result

Parse converts the labels of one container into its desired NPM resources. Containers that are not managed yield nothing. Invalid entries are reported individually so one broken definition cannot hide the valid ones.

func ParseAll

func ParseAll(containers []Container, opts ParseOptions) Result

ParseAll converts a list of containers, collecting per-container problems instead of aborting the whole reconcile run.

type Snapshot

type Snapshot struct {
	// Targets are the resources the labels ask for.
	Targets []*Target
	// Protected lists containers whose labels could not be parsed, by name
	// and by id. Their resources must not be deleted in this run: a typo in
	// one label must never take a production host down.
	Protected map[string]struct{}
	// Summary counts how the containers were classified.
	Summary Summary
	// Containers is how many containers Docker reported at all. Zero is
	// suspicious - a filtered socket proxy answering with an empty list looks
	// exactly like "every container is gone".
	Containers int
}

Snapshot is the desired state of one reconcile run, plus what the parser learned about the containers it read.

func (Snapshot) IsProtected

func (s Snapshot) IsProtected(name, id string) bool

IsProtected reports whether a container is shielded from orphan deletion.

type StreamStatus

type StreamStatus struct {
	// Connected reports whether the event stream is currently subscribed.
	Connected bool
	// Since is when the current state began.
	Since time.Time
	// LastError is why the stream dropped, if it did.
	LastError string
}

StreamStatus describes the health of the Docker event subscription.

func (StreamStatus) Down

func (s StreamStatus) Down(now time.Time) time.Duration

Down reports how long the stream has been disconnected, 0 while it is up.

type Summary

type Summary struct {
	Managed   int
	OptedOut  int
	Unlabeled int
	// Stopped counts managed containers that are not running.
	Stopped int
}

Summary counts how the containers of one run were classified.

func Classify

func Classify(containers []Container, opts ParseOptions) Summary

Classify counts managed, opted out and unlabelled containers for the start-up overview.

type Target

type Target struct {
	Kind          npm.Kind
	Index         int
	ContainerID   string
	ContainerName string
	// Running mirrors the container state. A resource whose container is
	// stopped is never reconfigured, only enabled or disabled (NPM_ON_STOP).
	Running bool
	// ExplicitPlus lists the NPMplus-only fields the labels actually set, so
	// a warning against upstream NPM only appears when the user asked for
	// something that flavour cannot do.
	ExplicitPlus []string

	// Shared across proxy, redirect and 404 hosts.
	DomainNames []string
	// Certificate is the unresolved certificate wish; the reconcile loop
	// turns it into an id once it knows which certificates exist.
	Certificate certs.Spec
	// SSLForced is nil for "auto": forced as soon as a certificate is
	// attached.
	SSLForced      *bool
	HTTP2Support   bool
	HSTSEnabled    bool
	HSTSSubdomains bool
	HTTP3Support   bool
	BlockExploits  bool
	AdvancedConfig string
	Enabled        bool

	// Proxy hosts.
	ForwardScheme       string
	ForwardHost         string
	ForwardPort         int
	Websockets          bool
	Caching             bool
	TrustForwardedProto bool
	AccessListIDs       []int
	AccessListNames     []string
	AccessListType      string
	Locations           []npm.Location

	// Proxy hosts, NPMplus only.
	NoIndex             bool
	CrowdsecAppsec      bool
	RequestBuffering    bool
	ResponseBuffering   bool
	UpstreamCompression bool
	FancyIndex          bool
	XFrameOptions       string
	AuthRequest         string
	AuthRequestUpstream string
	LocationConfig      string

	// Redirection hosts.
	ForwardDomainName string
	ForwardHTTPCode   int
	PreservePath      bool

	// Streams.
	IncomingPort   int
	ForwardingHost string
	ForwardingPort int
	TCPForwarding  bool
	UDPForwarding  bool
	ProxyProtocol  int
	ProxyTLS       bool
	Description    string

	// Let's Encrypt (all kinds that carry a certificate).
	LetsEncryptEmail   string
	LetsEncryptAgree   bool
	DNSChallenge       bool
	DNSProvider        string
	DNSCredentials     string
	PropagationSeconds int
}

Target is one desired NPM resource derived from a container. A single container can produce many targets through indexed labels (npm.proxy.domains, npm.proxy.1.domains, npm.1.stream.incoming_port, ...).

func (*Target) Complete

func (t *Target) Complete() bool

Complete reports whether the target carries everything a create or update needs. It is false only for a stopped container, whose address and ports Docker no longer reports: such a resource is left exactly as it is and only enabled or disabled.

func (*Target) Describe

func (t *Target) Describe() string

Describe returns a log friendly identifier such as "web#0 proxy app.example.com".

func (*Target) Domains

func (t *Target) Domains() []string

Domains returns the domain names this target serves; streams have none.

func (*Target) Key

func (t *Target) Key() string

Key is the identity of the target within its kind: the alphabetically first domain name, or the incoming port for streams.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL