Documentation
¶
Index ¶
- Variables
- func BackupDeletable(ctx context.Context, js store.JobStore, backupID string) error
- func BackupInFlight(ctx context.Context, js store.JobStore, backupID string) (bool, error)
- func PublicParameters(m render.Meta, params map[string]any, secretVals map[string]bool) map[string]any
- func RestoreInFlight(ctx context.Context, js store.JobStore, backupID string) (bool, error)
- func SetDeployVerifyStableCount(n int)
- func SetDeployVerifyTimeout(d time.Duration)
- func SetVerifyStableCount(n int)
- func SetVerifyTimeout(d time.Duration)
- func ValidateTemplate(t store.Template) error
- type ApplyOptions
- type ApplyRequest
- type BackupRequest
- type BlobStore
- type BlobWriter
- type DeleteOptions
- type EvacuateRequest
- type EvacuationPlan
- type Freshness
- type HostInventory
- type HostStats
- type HostVolumeUsage
- type Manifest
- type MigrateRequest
- type Move
- type Observed
- type ObservedContainer
- type ObservedPod
- type ObservedPortMapping
- type ObservedVolume
- type PITRRestoreRequest
- type PairedInstanceRef
- type PlanIssue
- type PlannedMove
- type RenameRequest
- type RestoreRequest
- type Service
- func (s *Service) Apply(ctx context.Context, host string, req ApplyRequest, opts ApplyOptions) error
- func (s *Service) ApplyAndObserve(ctx context.Context, host string, req ApplyRequest, opts ApplyOptions) (Observed, error)
- func (s *Service) Backup(ctx context.Context, req BackupRequest, step func(step, detail string)) error
- func (s *Service) CheckBackupable(ctx context.Context, host, tmpl, slug string) error
- func (s *Service) CheckInstanceExists(ctx context.Context, host, tmpl, slug string) error
- func (s *Service) CheckMigratable(ctx context.Context, req MigrateRequest) error
- func (s *Service) CheckRenameable(ctx context.Context, host, tmpl, slug string, req RenameRequest) error
- func (s *Service) CheckRestorable(ctx context.Context, backupID string) (store.Backup, error)
- func (s *Service) CloneTemplate(ctx context.Context, srcID, newID string) (store.Template, error)
- func (s *Service) CopyVolume(ctx context.Context, fromHost, toHost, name string) (Manifest, error)
- func (s *Service) CreateTemplate(ctx context.Context, t store.Template) error
- func (s *Service) Delete(ctx context.Context, host, tmpl, slug string, opts DeleteOptions) error
- func (s *Service) DeleteBackup(ctx context.Context, id string) error
- func (s *Service) DeleteHostSecret(ctx context.Context, host, name string) error
- func (s *Service) DeleteTemplate(ctx context.Context, id string, force bool) error
- func (s *Service) DeleteVolume(ctx context.Context, host, name string, force bool) error
- func (s *Service) DropHostStats(host string)
- func (s *Service) EnableWarmInventory()
- func (s *Service) Get(ctx context.Context, host, tmpl, slug string) (Observed, error)
- func (s *Service) GetBackup(ctx context.Context, id string) (store.Backup, error)
- func (s *Service) GetTemplate(ctx context.Context, id string) (store.Template, error)
- func (s *Service) HostCounts(ctx context.Context, host string) (instances, containers int, err error)
- func (s *Service) HostLoad(ctx context.Context, host string) (podman.HostInfo, error)
- func (s *Service) HostSecrets(ctx context.Context, host string) ([]podman.Secret, error)
- func (s *Service) Hosts() []config.Host
- func (s *Service) InstanceCount(ctx context.Context, host string) (int, error)
- func (s *Service) InstanceSecretState(ctx context.Context, host, tmpl, slug string) (map[string]bool, error)
- func (s *Service) InstanceVolumes(ctx context.Context, host, tmpl, slug string) ([]podman.Volume, error)
- func (s *Service) InventorySnapshot() map[string]HostInventory
- func (s *Service) List(ctx context.Context, host, tmpl string) ([]Observed, error)
- func (s *Service) ListAllInstances(ctx context.Context, host string) ([]Observed, error)
- func (s *Service) ListAllInstancesWithMeta(ctx context.Context, host string) ([]Observed, Freshness, error)
- func (s *Service) ListBackups(ctx context.Context, host, tmpl, slug string, limit int) ([]store.Backup, error)
- func (s *Service) Logs(ctx context.Context, host, tmpl, slug, container string, ...) (<-chan podman.LogLine, error)
- func (s *Service) Migrate(ctx context.Context, req MigrateRequest, step func(step, detail string)) error
- func (s *Service) PITRRestore(ctx context.Context, req PITRRestoreRequest, step func(step, detail string)) error
- func (s *Service) Ping(ctx context.Context, host string) error
- func (s *Service) PlanEvacuation(ctx context.Context, req EvacuateRequest) (EvacuationPlan, error)
- func (s *Service) PortsInUse(ctx context.Context, host string) ([]podman.PortMapping, error)
- func (s *Service) PutHostSecret(ctx context.Context, host, name string, value []byte, persist bool) error
- func (s *Service) ReconcileBackup(ctx context.Context, req BackupRequest, step func(step, detail string)) (resolved, ok bool, message string, err error)
- func (s *Service) ReconcileMigrate(ctx context.Context, req MigrateRequest, step func(step, detail string)) (resolved, succeeded bool, message string, err error)
- func (s *Service) ReconcileSpecsOnHost(ctx context.Context, hostID string)
- func (s *Service) RefreshHost(ctx context.Context, host string) error
- func (s *Service) RefreshHostStats(ctx context.Context, host string) error
- func (s *Service) RefreshHostVolumeUsage(ctx context.Context, host string) error
- func (s *Service) Rename(ctx context.Context, host, tmpl, slug string, req RenameRequest, ...) error
- func (s *Service) ResolveEvacuation(ctx context.Context, req EvacuateRequest) ([]MigrateRequest, error)
- func (s *Service) Restart(ctx context.Context, host, tmpl, slug string) error
- func (s *Service) Restore(ctx context.Context, req RestoreRequest, step func(step, detail string)) error
- func (s *Service) RotateInstanceSecrets(ctx context.Context, host, tmpl, slug string, newSecrets map[string]string) error
- func (s *Service) SetBlobStore(bs BlobStore)
- func (s *Service) SetHosts(hosts []config.Host)
- func (s *Service) SetIngress(c ingress.Controller, network string)
- func (s *Service) SetInstanceCacheTTL(ttl time.Duration)
- func (s *Service) SetSidecarInjector(si SidecarInjector)
- func (s *Service) SetStore(st Store)
- func (s *Service) SetVerifyVolumes(v bool)
- func (s *Service) Start(ctx context.Context, host, tmpl, slug string) (Observed, error)
- func (s *Service) StatsSnapshot() map[string]HostStats
- func (s *Service) Stop(ctx context.Context, host, tmpl, slug string) error
- func (s *Service) StoredSpec(ctx context.Context, host, tmpl, slug string) (store.Spec, error)
- func (s *Service) Template(ctx context.Context, id string) (store.Template, error)
- func (s *Service) Templates(ctx context.Context) ([]store.Template, error)
- func (s *Service) UpdateInstanceParameters(ctx context.Context, host, tmpl, slug string, newParams map[string]any) error
- func (s *Service) UpdateTemplate(ctx context.Context, t store.Template) error
- func (s *Service) Upgrade(ctx context.Context, host string, req ApplyRequest, image string) error
- func (s *Service) UpgradeImage(ctx context.Context, host, tmpl, slug, image string) error
- func (s *Service) Version(ctx context.Context, host string) (string, error)
- func (s *Service) VolumeUsageSnapshot() map[string]HostVolumeUsage
- type SidecarInjector
- type Store
Constants ¶
This section is empty.
Variables ¶
var ( ErrUnknownHost = errors.New("unknown host") ErrUnknownTemplate = errors.New("unknown template") ErrInstanceNotFound = errors.New("instance not found") ErrInstanceExists = errors.New("instance already exists") ErrHostSecretMissing = errors.New("required host secret missing") ErrImagePull = errors.New("image pull failed") ErrHostDraining = errors.New("host is draining") ErrPortConflict = errors.New("required host port already in use") ErrSameHost = errors.New("source and destination host are the same") ErrStoreDisabled = errors.New("migrate requires the state store") ErrVolumeIntegrity = errors.New("volume copy failed integrity check") ErrBackupNotFound = errors.New("backup not found") ErrBackupNotRestorable = errors.New("backup is not restorable") ErrBackupBusy = errors.New("backup has a backup or restore in flight") ErrBackupsDisabled = errors.New("backups require a blob store (-backup-dir)") )
Sentinel errors mapped by the API layer to JSON error codes.
var ( ErrTemplateInUse = errors.New("template is in use by one or more instances") ErrTemplateExists = errors.New("template already exists") // ErrInvalidTemplate wraps validation failures (bad id, unparsable body, // unknown parameter type, ingress mismatch) so the API can map them to 400. ErrInvalidTemplate = errors.New("invalid template") )
Template-management sentinel errors, mapped by the API layer to JSON codes.
var ErrInvalidEvacuation = errors.New("invalid evacuation request")
ErrInvalidEvacuation means the request cannot be planned against the stored specs: an instance on the host has no destination in the map, a move names no instance, a slug is ambiguous across templates (slug-keyed form only), or a map/move entry names an unknown destination host. The API maps it to 400 invalid_request, giving every bad-map case one consistent status.
var (
ErrNewSlugSameAsOld = errors.New("new slug is the same as the old slug")
)
Functions ¶
func BackupDeletable ¶
BackupDeletable checks whether a backup is safe to delete: returns nil if neither a backup nor a restore job is active for it, ErrBackupBusy if one is. When js is nil (jobs disabled) the backup is always considered deletable. Callers must invoke this before Service.DeleteBackup.
func BackupInFlight ¶
BackupInFlight reports whether any active (queued/running/reconciling) backup job targets backupID. Used by the delete handler to refuse deleting a backup while it is still being written (ErrBackupBusy). Note: the gate is intentionally job-based, not row-state-based — a crashed daemon can leave a creating row with no live job, and that row must stay deletable.
func PublicParameters ¶ added in v1.0.26
func PublicParameters(m render.Meta, params map[string]any, secretVals map[string]bool) map[string]any
PublicParameters returns an instance's stored render parameters with anything that could carry secret material removed, for Observed.Parameters (#200).
Secrets are stored in Spec.Secrets (encrypted at rest) and reach the pod via secretKeyRef, so parameters are non-secret by construction. A template declaring a parameter `secret: true` is now rejected at validation time (render.ValidateParamDefs, #205), but a template stored before that check existed may still carry one. So the same two-pass redaction env_summary uses applies here:
- by NAME: parameters whose ParamDef sets Secret are dropped — a backstop for such pre-existing templates.
- by VALUE: string parameters whose value equals one of this instance's known secret values are dropped, catching a secret that was passed as a parameter without being declared one. An empty string never matches.
A dropped parameter is omitted entirely; there is no redaction marker. Returns nil (so the field is absent, not null) when nothing survives.
func RestoreInFlight ¶
RestoreInFlight reports whether any active (queued/running/reconciling) restore job targets backupID. Shared by the API and UI delete handlers to refuse deleting a backup mid-restore (ErrBackupBusy).
func SetDeployVerifyStableCount ¶ added in v1.0.1
func SetDeployVerifyStableCount(n int)
SetDeployVerifyStableCount overrides the number of consecutive ready polls needed for waitReady to succeed in the deploy path. No-op for n <= 0.
func SetDeployVerifyTimeout ¶
SetDeployVerifyTimeout configures the readiness wait applied after Apply and Start. No-op for d <= 0. Called at startup via -deploy-verify-timeout flag.
func SetVerifyStableCount ¶ added in v1.0.1
func SetVerifyStableCount(n int)
SetVerifyStableCount overrides the number of consecutive ready polls needed for waitReady to succeed in the migrate path. No-op for n <= 0. Called at startup via -migrate-verify-stable-count flag.
func SetVerifyTimeout ¶
SetVerifyTimeout overrides the maximum time waitRunning waits for the destination to become ready before the migrate fails (and rolls back). No-op for d <= 0. Called once at startup from the -migrate-verify-timeout flag.
func ValidateTemplate ¶
ValidateTemplate checks an authored or seed template before it is persisted:
- The template id must be a valid DNS-label-style name.
- A dry-run render of the body (with a dummy value for every declared parameter) must succeed — this catches template syntax errors and references to undeclared parameters (missingkey=error).
- If the template declares ingress, its container must be non-empty and its port in 1..65535 (render.ValidateIngress), AND the rendered pod must contain a container whose name matches Ingress.Container.
- No parameter may set `secret: true` (render.ValidateParamDefs) — parameter values are stored in plaintext, so authors must use secrets.per_instance.
Types ¶
type ApplyOptions ¶
type ApplyOptions struct {
Replace bool // if false and the pod exists, return ErrInstanceExists
SkipPull bool // if true, do not pre-pull container images (CI / local-only refs)
// AllowMissingSecrets relaxes the "every PerInstance secret must be present"
// validation rule for this Apply. It is used by the secret-rotation path:
// rotation overlays new values onto a stored spec and re-applies it, and must
// not be blocked just because a PerInstance secret was already unset in that
// spec (e.g. a template that gained a secret after the instance was deployed).
// The "unknown secret" check still applies. Deploys never set this.
AllowMissingSecrets bool
// RestoreIntent, when non-nil, requests a one-shot point-in-time restore for
// this Apply: it is handed to the SidecarInjector but is NOT persisted into
// the stored spec, so the reconcile path never replays it. Only the
// point-in-time restore trigger sets this; ordinary deploys leave it nil.
RestoreIntent *extension.RestoreIntent
}
ApplyOptions controls the side effects of Apply beyond the request body.
type ApplyRequest ¶
type ApplyRequest struct {
Template string `json:"template"`
Slug string `json:"slug"`
Parameters map[string]any `json:"parameters"`
Secrets map[string]string `json:"secrets"`
Domains []string `json:"domains,omitempty"`
}
ApplyRequest is the body of POST /instances and PUT /instances/{...}.
type BackupRequest ¶
type BackupRequest struct {
BackupID string `json:"backup_id"`
Host string `json:"host"`
Template string `json:"template"`
Slug string `json:"slug"`
}
BackupRequest is the backup job's args. BackupID is generated at enqueue time (store.NewBackupID) so POST can return it before the job runs.
type BlobWriter ¶
type BlobWriter = extension.BlobWriter
type DeleteOptions ¶
DeleteOptions controls cleanup beyond the pod itself.
type EvacuateRequest ¶
type EvacuateRequest struct {
FromHost string `json:"from_host"`
Map map[string]string `json:"map,omitempty"`
Moves []Move `json:"moves,omitempty"`
// Concurrency, if >0, overrides the server's default for how many child
// migrations run at once (clamped to [1,32] by the handler). Request-only:
// it does not affect the migrate plan, so ResolveEvacuation ignores it.
Concurrency int `json:"concurrency,omitempty"`
}
EvacuateRequest is the POST /evacuate body and the evacuate job's args.
Moves lists each instance's destination by (template, slug) — the composite identity used throughout the system. Map (slug -> destination host) is the legacy form kept for backward compatibility; it is rejected when a slug is ambiguous across templates on the source host.
At most one of {Map, Moves} should be set. If Moves is non-empty it wins; otherwise ResolveEvacuation falls back to Map.
type EvacuationPlan ¶
type EvacuationPlan struct {
FromHost string `json:"from_host"`
Moves []PlannedMove `json:"moves"`
}
EvacuationPlan is the result of PlanEvacuation: the resolved per-instance moves plus, for each, whether the destination would currently accept it.
type Freshness ¶ added in v1.0.20
Freshness describes how current a returned inventory snapshot is. HasData is false only on a cold, never-populated (or failed cold) host; Reachable is false when the most recent refresh failed but stale data is still being served.
type HostInventory ¶ added in v1.0.23
HostInventory is a point-in-time view of one host's cached inventory, as read by the metrics collector. It mirrors instEntry, which is unexported.
type HostStats ¶ added in v1.0.28
type HostStats struct {
Containers map[string]podman.ContainerStats
FetchedAt time.Time
}
HostStats is one host's most recent container resource sample, keyed by container name. Read by the metrics collector at scrape time.
type HostVolumeUsage ¶ added in v1.0.28
HostVolumeUsage is one host's most recent volume sizing, keyed by volume name.
type Manifest ¶
type Manifest map[string]fileInfo
Manifest fingerprints a volume's tar export, keyed by cleaned path.
type MigrateRequest ¶
type MigrateRequest struct {
FromHost string `json:"from_host"`
ToHost string `json:"to_host"`
Template string `json:"template"`
Slug string `json:"slug"`
Parameters map[string]any `json:"parameters"`
AlsoStop []PairedInstanceRef `json:"also_stop,omitempty"`
}
MigrateRequest is the POST /migrate body and the migrate job's args.
type Move ¶ added in v1.0.5
type Move struct {
Template string `json:"template"`
Slug string `json:"slug"`
ToHost string `json:"to_host"`
}
Move is one instance's destination in an evacuation request. Template+Slug together identify the instance uniquely, resolving the ambiguity when two templates share a slug on one host.
type Observed ¶
type Observed struct {
Template string `json:"template"`
Slug string `json:"slug"`
Ready bool `json:"ready"`
Pod ObservedPod `json:"pod"`
Containers []ObservedContainer `json:"containers"`
Volumes []ObservedVolume `json:"volumes,omitempty"`
EnvSummary map[string]string `json:"env_summary,omitempty"`
// Parameters is the instance's stored render parameters, as last applied
// (#200). Only the single-instance Get path populates it; list sweeps leave
// it nil so a host listing stays cheap and small. Absent (not null) when the
// instance has no stored spec, or when that spec is unreadable. Never
// carries secret material — see PublicParameters.
Parameters map[string]any `json:"parameters,omitempty"`
Warnings []string `json:"warnings,omitempty"`
}
Observed is the JSON shape returned for an instance.
func Normalize ¶
func Normalize(p podman.Pod, template, slug string, vols []podman.Volume, secretEnvs, secretVals map[string]bool) Observed
Normalize builds Observed from a Pod + the volumes the API thinks the instance owns. env_summary is redacted in two passes, because neither alone is sufficient:
- by NAME: names in secretEnvs (derived from the template's secretKeyRef blocks) plus a defensive substring check on SECRET. This sees only what the stored template declares.
- by VALUE: values in secretVals (the instance's known secret values, from the stored spec). A SidecarInjector adds containers and env after the template body was stored, so its secretKeyRef env is invisible to the name pass — this pass catches it however the value arrived (#198). An empty string never matches here, even if secretVals contains "" — a legitimately-empty env var must never be blanked, and callers building secretVals are expected to exclude "" too (defense in depth).
Either match omits the key from env_summary entirely; there is no redaction marker. Both sets may be nil.
type ObservedContainer ¶
type ObservedContainer struct {
Name string `json:"name"`
Image string `json:"image"`
ImageTag string `json:"image_tag,omitempty"`
Status string `json:"status"`
Health string `json:"health,omitempty"`
StartedAt time.Time `json:"started_at,omitempty"`
RestartCount int `json:"restart_count"`
Ports []ObservedPortMapping `json:"ports,omitempty"`
}
type ObservedPod ¶
type ObservedPortMapping ¶
type ObservedVolume ¶
type ObservedVolume struct {
Name string `json:"name"`
SizeBytes int64 `json:"size_bytes,omitempty"`
}
ObservedVolume names a volume the API believes belongs to the instance. What the name guarantees depends on the path that produced it (#209): only the single-instance Get path inspects volumes, so only there does an entry mean the volume exists on the host and carry a real SizeBytes. List sweeps derive the set from the template's declared volumes without any podman call, so an entry there means "declared, possibly not created yet" and SizeBytes is always 0 — sizes come from the separate volume-usage cache. Treat a swept entry as a name, not as proof of existence.
type PITRRestoreRequest ¶ added in v1.0.12
type PITRRestoreRequest struct {
Host string `json:"host"`
Template string `json:"template"`
Slug string `json:"slug"`
Timestamp string `json:"timestamp"`
Volumes []string `json:"volumes,omitempty"`
}
PITRRestoreRequest is the pitr-restore job's args: which instance to restore and to what point in time. Timestamp is the opaque selector handed to the injector (the Litestream injector interprets it as RFC3339); Volumes optionally narrows the restore to specific volumes (empty = all backup-marked volumes).
type PairedInstanceRef ¶ added in v1.0.1
PairedInstanceRef identifies an instance (on FromHost) that shares volumes with the instance being migrated. Stopping it before the volume copy prevents concurrent writes that would cause a verification mismatch.
type PlanIssue ¶
PlanIssue is a single reason a move is not clean: a blocking destination condition or an inconclusive (check_error) check.
type PlannedMove ¶
type PlannedMove struct {
Slug string `json:"slug"`
Template string `json:"template"`
ToHost string `json:"to_host"`
OK bool `json:"ok"` // true iff Issues is empty
Issues []PlanIssue `json:"issues"`
Provisions []string `json:"provisions"` // per-host secrets auto-provisioned on dest; informational, does not affect ok; [] not null
}
PlannedMove is one instance's planned move and its preflight verdict.
type RenameRequest ¶ added in v1.0.11
type RenameRequest struct {
NewSlug string `json:"new_slug"`
Domains []string `json:"domains,omitempty"`
// KeepOldStandby, when true, leaves the old pod stopped with volumes and
// secrets intact so it can be re-started as a standby.
KeepOldStandby bool `json:"keep_old_standby,omitempty"`
}
RenameRequest is the POST /rename body.
type RestoreRequest ¶
type RestoreRequest struct {
BackupID string `json:"backup_id"`
}
RestoreRequest is the restore job's args.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service orchestrates instance operations against podman hosts.
func (*Service) Apply ¶
func (s *Service) Apply(ctx context.Context, host string, req ApplyRequest, opts ApplyOptions) error
Apply creates or replaces an instance. If opts.Replace is false and the pod exists, returns ErrInstanceExists. Unless opts.SkipPull is set, every container image referenced in the rendered Pod spec is pulled before the manifest is played. Apply acquires the per-host lock (domain-carrying requests only, taken before the instance lock — a consistent order so the two never deadlock) and the per-instance lock, then runs applyLocked.
func (*Service) ApplyAndObserve ¶
func (s *Service) ApplyAndObserve(ctx context.Context, host string, req ApplyRequest, opts ApplyOptions) (Observed, error)
ApplyAndObserve creates or replaces an instance (via Apply), waits for container healthchecks to pass (up to deployVerifyTimeout), then returns the observed state. On readiness timeout the operation still succeeds but Observed.Warnings carries a human-readable message.
func (*Service) Backup ¶
func (s *Service) Backup(ctx context.Context, req BackupRequest, step func(step, detail string)) error
Backup snapshots every volume of an instance into the blob store: stop, export each volume (teed into the blob write and the manifest build in one pass), record metadata, restart. The instance is restarted even on failure; it is only restarted at all if it was running to begin with. step is a best-effort progress callback (may be nil).
func (*Service) CheckBackupable ¶
CheckBackupable runs the cheap synchronous validation the POST handler needs: known host, known template, stored spec present, blob store wired.
func (*Service) CheckInstanceExists ¶ added in v1.0.12
CheckInstanceExists validates the precondition for operating on a live instance — host known, template present, instance spec stored — and returns ErrUnknownHost / ErrUnknownTemplate / ErrInstanceNotFound otherwise.
Unlike CheckBackupable it does NOT require the tarball blob store: a point-in-time restore replays the Litestream S3 replica via the injected initContainer, which is independent of the -backup-dir/blob store. Gating PITR on the blob store would 501 a Litestream-only deployment that has no tarball backups configured.
func (*Service) CheckMigratable ¶
func (s *Service) CheckMigratable(ctx context.Context, req MigrateRequest) error
CheckMigratable runs the cheap synchronous validation the POST handler needs: distinct known hosts, known template, and an existing stored spec. No mutation.
func (*Service) CheckRenameable ¶ added in v1.0.11
func (s *Service) CheckRenameable(ctx context.Context, host, tmpl, slug string, req RenameRequest) error
CheckRenameable runs the cheap synchronous validation the handler needs: instance exists, new slug differs, template known, new slug not taken.
func (*Service) CheckRestorable ¶
CheckRestorable runs the synchronous validation the POST handler needs and returns the backup row: row exists and is complete, host known and not draining, instance (spec) still present. The drain check is upfront so a draining host can't fail the job after teardown.
func (*Service) CloneTemplate ¶
CloneTemplate copies srcID to a new template with id newID and Origin "user". ErrUnknownTemplate if src is absent; ErrTemplateExists if newID is taken.
func (*Service) CopyVolume ¶
CopyVolume streams a named volume's contents from one host to another through an in-process pipe — the data crosses the daemon's network (two connections) but never its disk. The destination volume must already exist. The source is only ever read, so a failed copy leaves it untouched (migrate relies on this).
CopyVolume builds and returns a Manifest from the source export stream so the caller can verify the destination against the exact same snapshot, without re-exporting the source (which would risk a false mismatch if the source is still settling — see #153).
func (*Service) CreateTemplate ¶
CreateTemplate validates t and persists it; ErrTemplateExists if the id already exists. Origin defaults to "user" when the caller leaves it blank.
func (*Service) Delete ¶
Delete removes the pod and optionally its volumes and per-instance secrets.
func (*Service) DeleteBackup ¶
DeleteBackup removes a backup's blobs, then its row — in that order, so a crash between the two leaves a harmless blob-less row rather than orphaned blobs. Callers must check BackupDeletable first.
func (*Service) DeleteHostSecret ¶
DeleteHostSecret removes a host secret from the host and from the store. Like PutHostSecret, the store write is a non-atomic tail: a store-delete failure surfaces after the host removal succeeded, but a retry skips the already-gone host secret and re-deletes the store row, so the divergence is self-healing.
func (*Service) DeleteTemplate ¶
DeleteTemplate removes a template. Unless force is set it is rejected with ErrTemplateInUse when any instance on any host references it.
func (*Service) DeleteVolume ¶
DeleteVolume removes a named volume on a host. Idempotent.
func (*Service) DropHostStats ¶ added in v1.0.28
DropHostStats discards the host's cached samples without sampling it. It is for the caller that decides NOT to call RefreshHostStats at all — e.g. the poller skipping a host whose inventory refresh just failed — which would otherwise leave the last-known sample in the cache forever, freezing the host's series instead of retiring them. Same contract as a failed refresh (see statsCache): absent beats frozen for cumulative counters.
No context and no I/O: it costs a tick nothing.
func (*Service) EnableWarmInventory ¶ added in v1.0.20
func (s *Service) EnableWarmInventory()
EnableWarmInventory switches the instance cache into warm mode so entries are served without expiry and kept fresh by the inventory poller. Call once at startup, before serving traffic, when the poller is enabled.
func (*Service) GetTemplate ¶
GetTemplate returns a stored template by id (ErrUnknownTemplate if absent).
func (*Service) HostCounts ¶
func (s *Service) HostCounts(ctx context.Context, host string) (instances, containers int, err error)
HostCounts returns the number of managed instances and the total number of their containers on a host, in a single ListAllInstances sweep.
func (*Service) HostSecrets ¶
HostSecrets lists secrets on a host.
func (*Service) InstanceCount ¶
InstanceCount returns the total number of podman-api-managed pods on a host across all known templates. Used by /hosts to surface drain decisions.
func (*Service) InstanceSecretState ¶
func (s *Service) InstanceSecretState(ctx context.Context, host, tmpl, slug string) (map[string]bool, error)
InstanceSecretState reports, per stored per-instance secret name, that a value is present — presence only, never the value (the secret model is write-only). Names a template declares but the instance never set are simply absent from the map. Returns ErrInstanceNotFound when no spec is stored, or the store's error (incl. store.ErrSpecCorrupt or store.ErrSecretsUndecryptable) when the spec cannot be read.
func (*Service) InstanceVolumes ¶
func (s *Service) InstanceVolumes(ctx context.Context, host, tmpl, slug string) ([]podman.Volume, error)
InstanceVolumes returns the named volumes the API believes belong to this instance. Volumes that don't exist on the host are omitted (no error).
func (*Service) InventorySnapshot ¶ added in v1.0.23
func (s *Service) InventorySnapshot() map[string]HostInventory
InventorySnapshot returns the warm cache's current contents for every host that has an entry, without triggering a fetch. It is meaningful only when the inventory poller is running — with the lazy cache, entries appear only after a read, so a snapshot would under-report.
func (*Service) ListAllInstances ¶
ListAllInstances returns every podman-api-managed pod on a host across all known templates. The result is the union of List(host, t) for each catalog template id, so a pod for a template the daemon doesn't know about is silently omitted.
Volumes on each result are declared names only — the sweep makes no podman call per volume, so an entry does not mean the volume exists and SizeBytes is always 0. See ObservedVolume; Get is the authority on existence and size.
func (*Service) ListAllInstancesWithMeta ¶ added in v1.0.20
func (s *Service) ListAllInstancesWithMeta(ctx context.Context, host string) ([]Observed, Freshness, error)
ListAllInstancesWithMeta is ListAllInstances plus freshness metadata (when the data was captured and whether the host was reachable on the last refresh), for UI callers that render a staleness cue.
func (*Service) ListBackups ¶
func (s *Service) ListBackups(ctx context.Context, host, tmpl, slug string, limit int) ([]store.Backup, error)
ListBackups returns an instance's backups, newest first.
func (*Service) Logs ¶
func (s *Service) Logs(ctx context.Context, host, tmpl, slug, container string, opts podman.LogOptions) (<-chan podman.LogLine, error)
Logs returns a channel of log lines from one container in an instance.
func (*Service) Migrate ¶
func (s *Service) Migrate(ctx context.Context, req MigrateRequest, step func(step, detail string)) error
Migrate moves an instance from one host to another: stop source, copy volumes, apply the spec on the destination, verify it is healthy, then reap the source. Failures before the destination is verified roll back. step is a best-effort progress callback (may be nil).
func (*Service) PITRRestore ¶ added in v1.0.12
func (s *Service) PITRRestore(ctx context.Context, req PITRRestoreRequest, step func(step, detail string)) error
PITRRestore performs a one-shot point-in-time restore: it recreates the instance's pod with a RestoreIntent handed to the SidecarInjector, then waits for the pod to come back up. The intent travels via ApplyOptions and is NOT persisted into the stored spec, so the reconcile path never replays the rollback — it fires exactly once.
Unlike volume (tarball) Restore, PITR keeps the volume in place: the injected initContainer overwrites the database inside it. The per-instance migrate lock serializes it against migrate and other restores.
func (*Service) PlanEvacuation ¶
func (s *Service) PlanEvacuation(ctx context.Context, req EvacuateRequest) (EvacuationPlan, error)
PlanEvacuation previews an evacuation without mutating anything or enqueuing a job. It defers to ResolveEvacuation for static map validation (returning the same sentinel errors the real POST /evacuate would), then runs the live destination preflight checks per resolved move, collecting every problem. A move with no issues would currently be accepted by the destination.
func (*Service) PortsInUse ¶
PortsInUse returns all currently-bound host ports on hostID.
func (*Service) PutHostSecret ¶
func (s *Service) PutHostSecret(ctx context.Context, host, name string, value []byte, persist bool) error
PutHostSecret creates-or-rotates a host secret on the host, then (when persist is true) records the value so a later migrate/evacuate can re-provision it on a destination. We "rotate" by removing then recreating, since podman secrets are immutable. Push happens before persist: we never store a value we failed to apply to the host. The store write is a non-atomic tail — if it fails the host already holds the new value while the store lags; the caller's retry re-rotates and re-persists idempotently, so the divergence is self-healing.
func (*Service) ReconcileBackup ¶
func (s *Service) ReconcileBackup(ctx context.Context, req BackupRequest, step func(step, detail string)) (resolved, ok bool, message string, err error)
ReconcileBackup drives a backup interrupted by a daemon restart to a terminal state: mark the row failed (CAS — a row that already completed means the job finished its work and only the terminal write was lost), delete any partial blobs, and restart the instance. Returns (ok=true) when the backup actually completed, (ok=false, message) when it was failed. resolved=false only when the host is unreachable and the restart attempt was inconclusive.
Unlike Backup, which only restarts if the instance was running before the snapshot began, ReconcileBackup always attempts to restart: post-crash the prior run-state is unknowable, so reconcile errs on the side of availability. A deliberately-stopped instance interrupted mid-backup may therefore come back running.
func (*Service) ReconcileMigrate ¶
func (s *Service) ReconcileMigrate(ctx context.Context, req MigrateRequest, step func(step, detail string)) (resolved, succeeded bool, message string, err error)
ReconcileMigrate drives a migrate that was interrupted by a daemon restart to a consistent state, inspecting the real host state rather than trusting any persisted progress. It returns:
resolved=false inconclusive (a host was unreachable) — caller retries later resolved=true, succeeded=true rolled forward (or the commit had finished) resolved=true, succeeded=false rolled back, or the dest is an orphan left in place
message is an operator-facing summary recorded in the job's error field for terminal failed outcomes; it is empty for success and for inconclusive results.
step is a best-effort progress callback (may be nil). It reuses the same primitives as Migrate (waitRunning/Start/Delete) and takes migrateLock so it cannot race a re-issued migrate of the same instance.
func (*Service) ReconcileSpecsOnHost ¶
ReconcileSpecsOnHost checks every stored instance spec on host against real pod state and re-converges any that are missing (not running). It is called once at daemon startup as a one-shot boot converge, so managed pods survive a host reboot. Errors are logged per-instance and never propagated to the HTTP layer — the method always returns nil (it tolerates any failure by logging and continuing so a partial host outage does not block the rest).
Concurrency: per-instance operations are serialized under the existing per-instance lock so this cannot race a concurrent Apply/Delete/Upgrade. No per-host lock is taken because boot converge re-creates only instances whose store row already exists and whose domains are already claimed — it creates no new cross-instance domain claims.
Limitations (by design):
- No image pull: images are expected to be cached from the original deploy.
- One-shot: called once on startup; no periodic drift-correction loop.
- Template-missing instances are skipped with a warning (not reaped).
- Secrets-undecryptable instances are skipped (wrong key file — operator must restart with the correct -spec-key-file).
func (*Service) RefreshHost ¶ added in v1.0.20
RefreshHost performs a live inventory sweep of host and stores it in the warm cache. On failure it marks the host's last-known-good entry unreachable (keeping the data) and returns the error for the caller to log. This is the only proactive cache populator; the background poller calls it per tick.
func (*Service) RefreshHostStats ¶ added in v1.0.28
RefreshHostStats samples every container's resource usage on host and stores it for the metrics collector. One podman call per host.
On error the host's samples are dropped, so its series go absent rather than freezing at their last value. The caller must NOT treat a failure here as the host being unreachable — reachability is the inventory refresh's to decide.
func (*Service) RefreshHostVolumeUsage ¶ added in v1.0.28
RefreshHostVolumeUsage sizes every volume on host via one `system df` call. Podman walks the whole store to answer, so this belongs on a slow cadence with its own timeout. On error the previous sizing is retained.
func (*Service) Rename ¶ added in v1.0.11
func (s *Service) Rename(ctx context.Context, host, tmpl, slug string, req RenameRequest, step func(step, detail string)) error
Rename renames an instance to a new slug on the same host: stop, copy volumes, deploy under the new slug, verify health, then reap or keep the old instance. step is a best-effort progress callback (may be nil).
Locking: the migrateLock on (tmpl, slug) serialises concurrent renames of the same source instance, but does NOT serialise against operations targeting the new slug — a concurrent Apply to the new slug is assumed not to happen (single-operator system). The rollback Delete(newSlug, ...) would nuke that racing Apply's data.
Post-commit window: once Apply succeeds and waitRunning passes, the commit steps (spec migration, ingress reconcile, old-instance reap) are all best-effort. A failure here returns an error but does not roll back — the new pod is healthy and serving, and the state is convergent (stale old spec row, possibly repeated ingress reconcile, etc.). This mirrors the post-commit window in applyLocked.
func (*Service) ResolveEvacuation ¶
func (s *Service) ResolveEvacuation(ctx context.Context, req EvacuateRequest) ([]MigrateRequest, error)
ResolveEvacuation validates the request against the specs stored on FromHost and returns the per-instance migrate plan, sorted by (template, slug) for determinism. It is pure read/validation (no mutation) and is called both synchronously by the POST handler (fast-fail, result discarded) and by the evacuate job handler at execution time (state may have drifted since enqueue).
func (*Service) Restore ¶
func (s *Service) Restore(ctx context.Context, req RestoreRequest, step func(step, detail string)) error
Restore replaces an instance's volumes in place from a backup: stop, tear down containers + volumes, recreate volumes from blobs, verify each against the stored manifest, re-apply the CURRENT spec, wait healthy. There is no rollback: a failure after teardown leaves the instance DOWN with volumes partially restored, but the spec row is preserved so the restore can be retried. The job error names the failed step. step is a best-effort progress callback (may be nil).
func (*Service) RotateInstanceSecrets ¶
func (s *Service) RotateInstanceSecrets(ctx context.Context, host, tmpl, slug string, newSecrets map[string]string) error
RotateInstanceSecrets overlays newSecrets onto the instance's stored per-instance secrets and re-applies (Replace=true), restarting the pod. Names absent from newSecrets keep their existing value — callers are write-only and never see current values. An empty newSecrets is rejected so a blank submit does not pointlessly restart the instance. Returns ErrInstanceNotFound when no spec is stored, or the store's error (incl. store.ErrSpecCorrupt or store.ErrSecretsUndecryptable) when the spec cannot be read.
The load (GetSpec) and re-apply (applyLocked) happen atomically under the per-instance lock: rotation is a read-modify-write of the stored secrets, so holding the lock across both halves keeps a concurrent rotation/upgrade of the same instance from reading the pre-commit spec and dropping this update. It takes only the instance lock (no host lock): rotation re-applies the instance's own already-persisted domains unchanged, which validateIngress excludes from its uniqueness check, so it can never create a new cross-instance domain claim and needs no per-host lock. (If a future edit let this method *change* domains, the missing host lock would become a real bug — the no-hostLock safety rests on domains being unchanged.) (#114)
func (*Service) SetBlobStore ¶
SetBlobStore wires the backup artifact store. Backups/restores are refused (ErrBackupsDisabled) until this is set; main always sets it.
func (*Service) SetHosts ¶
SetHosts atomically replaces the live host set. Used by main on SIGHUP to pick up edits to hosts/*.yaml (e.g. flipping drain) without restart.
func (*Service) SetIngress ¶
func (s *Service) SetIngress(c ingress.Controller, network string)
SetIngress enables ingress reconciliation. network is the shared podman network app pods join; passing a real controller marks ingress enabled so Apply will accept domains. Call with ingress.Disabled{} and "" to disable.
func (*Service) SetInstanceCacheTTL ¶ added in v1.0.17
SetInstanceCacheTTL replaces the per-host ListAllInstances cache with one of the given TTL. ttl == 0 disables caching (live passthrough). Wire from the server if the operator wants a non-default window; the default is 3s.
func (*Service) SetSidecarInjector ¶ added in v1.0.8
func (s *Service) SetSidecarInjector(si SidecarInjector)
SetSidecarInjector wires a sidecar injector that is called after template rendering but before the pod YAML is applied.
func (*Service) SetStore ¶
SetStore wires the template catalog + desired-state store. The store is mandatory — every template lookup and spec persist goes through it — so main must call this at startup, before the server begins accepting requests (tests pass a store.Memory). Unlike SetHosts it is NOT a concurrent hot-swap.
func (*Service) SetVerifyVolumes ¶
SetVerifyVolumes toggles post-copy volume integrity verification during migrate. Default true; set false (via -migrate-verify-volumes=false) to skip the extra source+dest re-export per volume.
func (*Service) Start ¶
Start starts a stopped instance and waits for container healthchecks to pass (up to deployVerifyTimeout). On readiness timeout the call still succeeds and Observed.Warnings carries a human-readable message.
func (*Service) StatsSnapshot ¶ added in v1.0.28
StatsSnapshot returns the cached container samples for every host. Never fetches: it runs on the scrape path.
func (*Service) StoredSpec ¶ added in v1.0.11
StoredSpec returns the persisted spec (parameters, secrets, domains) for an existing instance, so the UI edit form can pre-populate parameters and merge secrets before re-applying. The caller must NOT render or log the returned secrets — they are write-only merge inputs, intended to be overlaid with form values and re-persisted via ApplyAndObserve(Replace: true).
func (*Service) Template ¶
Template returns one catalog template by ID (read-only view), or store.ErrNotFound. A point lookup for callers that need a single template, avoiding Templates()' full-catalog list + scan.
func (*Service) Templates ¶
Templates returns the catalog's templates (read-only view). A store error is propagated so callers can surface it (e.g. an HTTP 500) rather than rendering an empty catalog as if it succeeded.
func (*Service) UpdateInstanceParameters ¶ added in v1.0.25
func (s *Service) UpdateInstanceParameters(ctx context.Context, host, tmpl, slug string, newParams map[string]any) error
UpdateInstanceParameters overlays newParams onto the instance's stored render parameters and re-applies (Replace=true), restarting the pod. Names absent from newParams keep their existing value; a parameter cannot be deleted this way (that still needs a full PUT). An empty newParams is rejected so a blank submit does not pointlessly restart the instance. Returns ErrInstanceNotFound when no spec is stored, or the store's error (incl. store.ErrSpecCorrupt or store.ErrSecretsUndecryptable) when the spec cannot be read.
The stored per-instance secrets are reused as-is and never need to be supplied by the caller — they are write-only plaintext the operator cannot read back, which is exactly why a parameter change had no API route before (pro#74). Like UpgradeImage it sets AllowMissingSecrets, so a template that gained a required per-instance secret after the instance was deployed does not block a parameter change to that already-running instance (the missing secret was already missing; the update never worsens the pod).
slug is NOT settable through newParams: if the stored spec already has a "slug" entry, it is pinned back to the canonical slug after the overlay below, the same way migrate.go and rename.go pin it before re-applying. Without this, a caller-supplied "slug" parameter (a declared template parameter, so it passes render.Validate) would render a manifest for a *different* pod while this method still holds and reports success under the original slug's lock — the wrong pod gets replaced (destructively, if it already exists) and the persisted spec for the original slug ends up describing a pod no later Start/Stop/Delete can find. The HTTP handler also rejects a "slug" key up front (defence in depth); this pin makes the method itself safe for any caller.
The pin is conditional (only fires when "slug" is already a key in merged) because nothing requires a template to declare "slug" as a parameter at all — a singleton template can hardcode metadata.name and never mention it. On such a template an unconditional write would inject an undeclared "slug" key and render.Validate would reject every PATCH with `unknown parameter "slug"`. A caller trying to *introduce* slug on a template that doesn't declare it is still rejected by render.Validate, which is the correct outcome — the safety property above is unaffected.
applyLocked discards the stored spec's InjectorSecrets and rebuilds the list by re-running the sidecar injector, then persists the fresh list — injector- declared secrets are re-derived on this path, not preserved, unlike the boot- converge path in spec_reconcile.go.
Replace: true means podman tears the pod down before creating the new one, and the spec is only persisted after a successful play; a parameter change that renders a manifest podman refuses to play returns an error with the pod gone and the stored spec still describing the pre-update state — recovery is boot converge or a manual re-apply.
The load (GetSpec) and re-apply (applyLocked) happen atomically under the per-instance lock: the overlay is a read-modify-write of the stored parameters, so holding the lock across both halves keeps a concurrent rotation/upgrade of the same instance from reading the pre-commit spec and dropping this update. It takes only the instance lock (no host lock): the update re-applies the instance's own already-persisted domains unchanged, which validateIngress excludes from its uniqueness check, so it can never create a new cross-instance domain claim and needs no per-host lock. Domains are not derivable from parameters — they come from ApplyRequest.Domains, which this method does not accept. (If a future edit let this method *change* domains, the missing host lock would become a real bug — the no-hostLock safety rests on domains being unchanged.) (#114)
func (*Service) UpdateTemplate ¶
UpdateTemplate validates t and upserts it. The template must already exist (ErrUnknownTemplate otherwise). The stored Origin is preserved so an edit cannot silently flip a "seed" template to "user".
func (*Service) Upgrade ¶
Upgrade replaces the pod with a new image. The pull happens inside Apply (which scans the rendered manifest and pulls every container image), so a bad image ref still fails fast — without a duplicate pre-pull here.
func (*Service) UpgradeImage ¶
UpgradeImage performs an image-only upgrade: it loads the instance's stored spec (parameters + secrets), overrides the "image" parameter, and re-applies with Replace. Existing secrets and parameters are reused as-is — the operator supplies only the new image; rotating a secret is a separate operation. Like RotateInstanceSecrets it sets AllowMissingSecrets, so a template that gained a required per-instance secret after the instance was deployed does not block an image upgrade of that already-running instance (the missing secret was already missing; the upgrade never worsens the pod). Returns ErrInstanceNotFound when no spec is stored for the instance.
The load (GetSpec) and re-apply (applyLocked) happen atomically under the per-instance lock: the image override is a read-modify-write of the stored parameters, so holding the lock across both halves keeps a concurrent rotation/upgrade of the same instance from reading the pre-commit spec and dropping this update. It takes only the instance lock (no host lock): the upgrade re-applies the instance's own already-persisted domains unchanged, which validateIngress excludes from its uniqueness check, so it can never create a new cross-instance domain claim and needs no per-host lock. (If a future edit let this method *change* domains, the missing host lock would become a real bug — the no-hostLock safety rests on domains being unchanged.) (#114)
func (*Service) VolumeUsageSnapshot ¶ added in v1.0.28
func (s *Service) VolumeUsageSnapshot() map[string]HostVolumeUsage
VolumeUsageSnapshot returns the cached volume sizing for every host. Never fetches: it runs on the scrape path.
type SidecarInjector ¶ added in v1.0.8
type SidecarInjector = extension.SidecarInjector
type Store ¶
type Store interface {
store.Store
store.TemplateStore
store.BackupStore
}
Store is the persistence surface the instance Service needs: the desired-state spec/host-secret store plus the template catalog. main wires a single store.DB, which satisfies this; tests pass a store.Memory. The Service always has a store — callers MUST SetStore before use.