instance

package
v1.0.19 Latest Latest
Warning

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

Go to latest
Published: Jul 23, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
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.

View Source
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.

View Source
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.

View Source
var (
	ErrNewSlugSameAsOld = errors.New("new slug is the same as the old slug")
)

Functions

func BackupDeletable

func BackupDeletable(ctx context.Context, js store.JobStore, backupID string) error

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

func BackupInFlight(ctx context.Context, js store.JobStore, backupID string) (bool, error)

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 RestoreInFlight

func RestoreInFlight(ctx context.Context, js store.JobStore, backupID string) (bool, error)

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

func SetDeployVerifyTimeout(d time.Duration)

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

func SetVerifyTimeout(d time.Duration)

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

func ValidateTemplate(t store.Template) error

ValidateTemplate checks an authored or seed template before it is persisted:

  1. The template id must be a valid DNS-label-style name.
  2. 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).
  3. 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.

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 BlobStore

type BlobStore = extension.BlobStore

type BlobWriter

type BlobWriter = extension.BlobWriter

type DeleteOptions

type DeleteOptions struct {
	PruneVolumes bool
	PruneSecrets bool
}

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 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"`
	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 map[string]bool) Observed

Normalize builds Observed from a Pod + the volumes the API thinks the instance owns. Env vars whose names appear in secretEnvs (the set derived from the template's secretKeyRef blocks) are dropped from env_summary so secret material never returns to the CMS. A defensive substring check on SECRET also catches anything not anchored to a known template.

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 ObservedPod struct {
	ID      string    `json:"id,omitempty"`
	Status  string    `json:"status"`
	Created time.Time `json:"created,omitempty"`
}

type ObservedPortMapping

type ObservedPortMapping struct {
	HostIP        string `json:"host_ip,omitempty"`
	HostPort      int    `json:"host_port"`
	ContainerPort int    `json:"container_port"`
	Protocol      string `json:"protocol,omitempty"`
}

type ObservedVolume

type ObservedVolume struct {
	Name      string `json:"name"`
	SizeBytes int64  `json:"size_bytes,omitempty"`
}

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

type PairedInstanceRef struct {
	Template string `json:"template"`
	Slug     string `json:"slug"`
}

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

type PlanIssue struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

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 NewService

func NewService(client podman.Client, hosts []config.Host) *Service

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

func (s *Service) CheckBackupable(ctx context.Context, host, tmpl, slug string) error

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

func (s *Service) CheckInstanceExists(ctx context.Context, host, tmpl, slug string) error

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

func (s *Service) CheckRestorable(ctx context.Context, backupID string) (store.Backup, error)

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

func (s *Service) CloneTemplate(ctx context.Context, srcID, newID string) (store.Template, error)

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

func (s *Service) CopyVolume(ctx context.Context, fromHost, toHost, name string) (Manifest, error)

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

func (s *Service) CreateTemplate(ctx context.Context, t store.Template) error

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

func (s *Service) Delete(ctx context.Context, host, tmpl, slug string, opts DeleteOptions) error

Delete removes the pod and optionally its volumes and per-instance secrets.

func (*Service) DeleteBackup

func (s *Service) DeleteBackup(ctx context.Context, id string) error

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

func (s *Service) DeleteHostSecret(ctx context.Context, host, name string) error

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

func (s *Service) DeleteTemplate(ctx context.Context, id string, force bool) error

DeleteTemplate removes a template. Unless force is set it is rejected with ErrTemplateInUse when any instance on any host references it.

func (*Service) DeleteVolume

func (s *Service) DeleteVolume(ctx context.Context, host, name string, force bool) error

DeleteVolume removes a named volume on a host. Idempotent.

func (*Service) Get

func (s *Service) Get(ctx context.Context, host, tmpl, slug string) (Observed, error)

Get returns the observed shape for an instance.

func (*Service) GetBackup

func (s *Service) GetBackup(ctx context.Context, id string) (store.Backup, error)

GetBackup returns one backup row, mapping absence to ErrBackupNotFound.

func (*Service) GetTemplate

func (s *Service) GetTemplate(ctx context.Context, id string) (store.Template, error)

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) HostLoad

func (s *Service) HostLoad(ctx context.Context, host string) (podman.HostInfo, error)

HostLoad returns a point-in-time resource snapshot for a host.

func (*Service) HostSecrets

func (s *Service) HostSecrets(ctx context.Context, host string) ([]podman.Secret, error)

HostSecrets lists secrets on a host.

func (*Service) Hosts

func (s *Service) Hosts() []config.Host

Hosts returns the configured hosts (read-only view for the API).

func (*Service) InstanceCount

func (s *Service) InstanceCount(ctx context.Context, host string) (int, error)

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) List

func (s *Service) List(ctx context.Context, host, tmpl string) ([]Observed, error)

List returns all instances of a given template on a host.

func (*Service) ListAllInstances

func (s *Service) ListAllInstances(ctx context.Context, host string) ([]Observed, error)

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.

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) Ping

func (s *Service) Ping(ctx context.Context, host string) error

Ping checks reachability of a host.

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

func (s *Service) PortsInUse(ctx context.Context, host string) ([]podman.PortMapping, error)

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

func (s *Service) ReconcileSpecsOnHost(ctx context.Context, hostID string)

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) 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) Restart

func (s *Service) Restart(ctx context.Context, host, tmpl, slug string) error

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

func (s *Service) SetBlobStore(bs BlobStore)

SetBlobStore wires the backup artifact store. Backups/restores are refused (ErrBackupsDisabled) until this is set; main always sets it.

func (*Service) SetHosts

func (s *Service) SetHosts(hosts []config.Host)

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

func (s *Service) SetInstanceCacheTTL(ttl time.Duration)

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

func (s *Service) SetStore(st Store)

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

func (s *Service) SetVerifyVolumes(v bool)

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

func (s *Service) Start(ctx context.Context, host, tmpl, slug string) (Observed, error)

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) Stop

func (s *Service) Stop(ctx context.Context, host, tmpl, slug string) error

func (*Service) StoredSpec added in v1.0.11

func (s *Service) StoredSpec(ctx context.Context, host, tmpl, slug string) (store.Spec, error)

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

func (s *Service) Template(ctx context.Context, id string) (store.Template, error)

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

func (s *Service) Templates(ctx context.Context) ([]store.Template, error)

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) UpdateTemplate

func (s *Service) UpdateTemplate(ctx context.Context, t store.Template) error

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

func (s *Service) Upgrade(ctx context.Context, host string, req ApplyRequest, image string) error

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

func (s *Service) UpgradeImage(ctx context.Context, host, tmpl, slug, image string) error

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) Version

func (s *Service) Version(ctx context.Context, host string) (string, error)

Version returns the podman version string for a host.

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.

Jump to

Keyboard shortcuts

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