Documentation
¶
Overview ¶
Package takeover moves a server's ports 80 and 443 from the proxy of the platform it is migrating off (Coolify's Traefik, a Caddy or nginx container) to this control plane's own ingress, with a recorded way back. It only acts through the Docker Engine API wrappers, the settings store and the existing restart: there is no host shell and no file writer here by construction.
Index ¶
- Constants
- Variables
- func TraefikHosts(labels map[string]string) []string
- type Action
- type BootDecision
- type Check
- type Config
- type Containers
- type DBStore
- func (s DBStore) Create(ctx context.Context, r Run) error
- func (s DBStore) Get(ctx context.Context, id string) (Run, error)
- func (s DBStore) List(ctx context.Context, limit int) ([]Run, error)
- func (s DBStore) ListByState(ctx context.Context, states ...string) ([]Run, error)
- func (s DBStore) Save(ctx context.Context, r Run) error
- type Deps
- type DomainCheck
- type DomainFact
- type DomainRow
- type Facts
- type Fix
- type Holder
- type LabelRouter
- type Plan
- type PlanStep
- type Previous
- type Proxy
- type Rows
- type Run
- type Runner
- func (r *Runner) Get(ctx context.Context, id string) (Run, error)
- func (r *Runner) List(ctx context.Context, limit int) ([]Run, error)
- func (r *Runner) Resume(ctx context.Context)
- func (r *Runner) Rollback(ctx context.Context, id, reason, actor string) (Run, error)
- func (r *Runner) Start(ctx context.Context, req StartRequest) (run Run, created bool, err error)
- type Settings
- type StartRequest
- type Step
- type Store
- type TLSSettings
- type Verifier
Constants ¶
const ( BootNone = "none" BootApply = "apply" BootRollback = "rollback" )
Boot actions.
const ( DefaultTargetHTTP = ":80" DefaultTargetHTTPS = ":443" // DefaultWindow is how long a takeover may stay unverified before it rolls back by itself. DefaultWindow = 5 * time.Minute // PlanPath is the API path a re-check fix points at. PlanPath = "/api/v1/migration/takeover/plan" )
Default listen addresses a takeover moves the ingress to.
const ( StatePreparing = "preparing" StateApplying = "applying" StateVerifying = "verifying" StateDone = "done" StateRollingBack = "rolling_back" StateRolledBack = "rolled_back" StateFailed = "failed" )
Run states.
const ( StatusPass = "pass" StatusWarn = "warn" StatusBlock = "block" )
Check statuses.
const ( VerdictReady = "ready" VerdictWarn = "warnings" VerdictBlocked = "blocked" // VerdictDone means this server already listens on the target ports. VerdictDone = "done" )
Plan verdicts.
const ( StepPending = "pending" StepRunning = "running" StepDone = "done" StepFailed = "failed" StepSkipped = "skipped" )
Step states.
const ( StepStopProxy = "stop_old_proxy" StepTLS = "tls_settings" StepListen = "listen_addresses" StepRestart = "restart" StepVerify = "verify" StepRollback = "rollback_record" StepRestore = "rollback" )
Step ids, in plan order.
const ( CheckPorts = "ports" CheckHealth = "apps_healthy" CheckDNS = "dns" CheckCerts = "certificates" CheckACME = "acme" CheckConflict = "duplicate_domain" CheckUnmanaged = "unmanaged_domains" CheckResources = "resources" CheckRestart = "restart" CheckDocker = "docker" CheckSnippet = "saved_not_enforced" CheckPlatform = "platform_restarts_proxy" CheckRestartPol = "restart_policy" CheckSocket = "socket_activation" )
Check ids.
const ( ActionRequest = "request" ActionLink = "link" ActionCopy = "copy" )
Fix action kinds.
Variables ¶
var ( ErrBlocked = errors.New("takeover: the preflight has blocking checks") ErrNotRollbackable = errors.New("takeover: nothing to roll back") ErrNotFound = errors.New("takeover: run not found") )
Errors a caller maps to a status.
Functions ¶
func TraefikHosts ¶
TraefikHosts returns the hostnames a container's Traefik router labels claim, sorted and without duplicates. Regex and wildcard rules are skipped.
Types ¶
type Action ¶
type Action struct {
Kind string `json:"kind"`
Label string `json:"label"`
API string `json:"api,omitempty"`
Value string `json:"value,omitempty"`
}
Action is the one primary thing a fix offers.
type BootDecision ¶
BootDecision is what the process does with a stored run before it binds.
func DecideBoot ¶
func DecideBoot(run Run) BootDecision
DecideBoot maps a stored run to a boot action. A run only gets one boot with the new addresses before verification ends it: a second start while it is still unverified means the first one failed, so the old addresses return.
func PrepareBoot ¶
PrepareBoot loads the latest unfinished or finished-active run, records the decision (a boot counted, or the rollback intent) and returns it. A nil store or no run is BootNone.
type Check ¶
type Check struct {
ID string `json:"id"`
Title string `json:"title"`
Status string `json:"status"`
Detail string `json:"detail,omitempty"`
Items []string `json:"items,omitempty"`
Fix *Fix `json:"fix,omitempty"`
}
Check is one preflight item.
type Config ¶
type Config struct {
Window time.Duration
VerifyInterval time.Duration
StartAttempts int
StartWait time.Duration
Now func() time.Time
}
Config bounds a run. Zero values use the defaults.
type Containers ¶
type Containers interface {
Stop(ctx context.Context, name string) error
Start(ctx context.Context, name string) error
}
Containers starts and stops the old proxy through the Docker Engine API.
type DBStore ¶
type DBStore struct{ DB Rows }
DBStore persists runs in the control plane database.
func (DBStore) ListByState ¶
ListByState implements Store.
type Deps ¶
type Deps struct {
Containers Containers
Settings Settings
Verifier Verifier
// Domains lists what verification must cover.
Domains func(ctx context.Context) ([]string, error)
// Restart asks the process to restart itself, false when it cannot.
Restart func() bool
// Audit records one entry; nil records nothing.
Audit func(ctx context.Context, action, detail string)
}
Deps is everything a Runner acts through.
type DomainCheck ¶
type DomainCheck struct {
Domain string `json:"domain"`
OK bool `json:"ok"`
Status int `json:"status,omitempty"`
Delivered bool `json:"delivered"`
Detail string `json:"detail,omitempty"`
}
DomainCheck is the outcome of verifying one domain over real HTTPS.
type DomainFact ¶
type DomainFact struct {
Domain string
App string
Healthy bool
HealthDetail string
ResolvesTo []string
DNSErr string
CustomCert bool
}
DomainFact is what the preflight learned about one domain.
type DomainRow ¶
type DomainRow struct {
Domain string `json:"domain"`
App string `json:"app"`
Healthy bool `json:"healthy"`
Detail string `json:"detail,omitempty"`
DNS string `json:"dns"`
Cert string `json:"certificate"`
}
DomainRow is one domain the takeover has to keep serving.
type Facts ¶
type Facts struct {
Holders []Holder
HostListeners []int
// HostProcess names the process per host listener port when known.
HostProcess map[int]string
CurrentHTTP string
CurrentHTTPS string
TargetHTTP string
TargetHTTPS string
Domains []DomainFact
ServerIPs []string
LabelRouters []LabelRouter
OldDomains []string
ACMEEnabled bool
DiskFreeGB float64
DiskKnown bool
MemoryMB int
MinDiskGB float64
MinMemoryMB int
RestartSupported bool
DockerAvailable bool
AcceptUnmanaged bool
// SocketActivation is true when systemd holds the ingress sockets.
SocketActivation bool
Window time.Duration
Now time.Time
}
Facts is everything BuildPlan decides from. The caller gathers it, so the rules stay pure and table-testable.
type LabelRouter ¶
LabelRouter is a Traefik label on a container that claims a domain.
type Plan ¶
type Plan struct {
Verdict string `json:"verdict"`
Checks []Check `json:"checks"`
Steps []PlanStep `json:"steps"`
Domains []DomainRow `json:"domains"`
// Rollback says what the undo button restores.
Rollback []string `json:"rollback"`
WindowSeconds int `json:"window_seconds"`
TargetHTTP string `json:"target_http"`
TargetHTTPS string `json:"target_https"`
CurrentHTTP string `json:"current_http"`
CurrentHTTPS string `json:"current_https"`
ProxyKind string `json:"proxy_kind,omitempty"`
ProxyNames []string `json:"proxy_containers"`
CheckedAt string `json:"checked_at"`
}
Plan is the preflight result plus the exact steps a run would take.
type PlanStep ¶
type PlanStep struct {
ID string `json:"id"`
Title string `json:"title"`
Detail string `json:"detail"`
// Reversible is false only for steps rollback cannot undo on its own.
Reversible bool `json:"reversible"`
}
PlanStep is one thing a run does, in order.
type Previous ¶
type Previous struct {
HTTPAddr string `json:"http_addr"`
HTTPSAddr string `json:"https_addr"`
TLSTerminatedUpstream bool `json:"tls_terminated_upstream"`
PublicHTTPSPort int `json:"public_https_port"`
SettingsChanged bool `json:"settings_changed"`
Proxies []Proxy `json:"proxies"`
}
Previous is everything rollback restores. It holds no secrets.
type Proxy ¶
type Proxy struct {
Name string `json:"name"`
Image string `json:"image"`
WasRunning bool `json:"was_running"`
Stopped bool `json:"stopped"`
Restored bool `json:"restored"`
}
Proxy is one container the run stopped, so rollback starts exactly it.
type Rows ¶
type Rows interface {
CreatePortTakeover(ctx context.Context, r store.PortTakeover) error
SavePortTakeover(ctx context.Context, r store.PortTakeover) error
GetPortTakeover(ctx context.Context, id string) (store.PortTakeover, error)
ListPortTakeovers(ctx context.Context, limit int) ([]store.PortTakeover, error)
ListPortTakeoversByState(ctx context.Context, states ...string) ([]store.PortTakeover, error)
}
Rows is the control plane database surface DBStore needs; *store.DB provides it.
type Run ¶
type Run struct {
ID string `json:"id"`
State string `json:"state"`
Actor string `json:"actor,omitempty"`
Previous Previous `json:"previous"`
TargetHTTP string `json:"target_http"`
TargetHTTPS string `json:"target_https"`
Steps []Step `json:"steps"`
Verify []DomainCheck `json:"verify"`
Error string `json:"error,omitempty"`
Boots int `json:"boots"`
WindowSeconds int `json:"window_seconds"`
DeadlineAt time.Time `json:"deadline_at,omitzero"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
FinishedAt time.Time `json:"finished_at,omitzero"`
}
Run is one takeover and, when it fails or is undone, its rollback.
func (Run) Active ¶
Active reports whether the run's addresses are the ones this process must listen on at start.
func (Run) Rollbackable ¶
Rollbackable reports whether the undo button still does something.
type Runner ¶
type Runner struct {
Store Store
Deps Deps
Cfg Config
Log *slog.Logger
// contains filtered or unexported fields
}
Runner drives takeovers and their rollbacks.
func (*Runner) Resume ¶
Resume continues whatever a restart interrupted. Call it once after the router is built; it returns when the work is finished or ctx ends.
func (*Runner) Rollback ¶
Rollback undoes a takeover that is applying, verifying or done: it records the intent and restarts so the old addresses apply; Resume then starts the old proxy. Repeating it on a run already rolling back or rolled back is a no-op.
type Settings ¶
type Settings interface {
GetTLS(ctx context.Context) (TLSSettings, error)
SetTLS(ctx context.Context, s TLSSettings) error
}
Settings reads and writes those settings.
type StartRequest ¶
StartRequest is what a run needs from its plan.
type Step ¶
type Step struct {
ID string `json:"id"`
Title string `json:"title"`
State string `json:"state"`
Detail string `json:"detail,omitempty"`
StartedAt time.Time `json:"started_at,omitzero"`
FinishedAt time.Time `json:"finished_at,omitzero"`
}
Step is one timed entry of a run.
type Store ¶
type Store interface {
Create(ctx context.Context, r Run) error
Get(ctx context.Context, id string) (Run, error)
Save(ctx context.Context, r Run) error
List(ctx context.Context, limit int) ([]Run, error)
ListByState(ctx context.Context, states ...string) ([]Run, error)
}
Store persists runs.
type TLSSettings ¶
TLSSettings is the part of the ingress settings a takeover flips.