Documentation
¶
Overview ¶
Package imageswitch moves an existing non-pgvector Postgres cluster to the pgvector image of the same major version without touching the old volume (EPIC P7-ADOPT D6, P7-ADOPT-22).
Order (nothing is switched before every check has passed):
preflight -> quiesce (services stopped, databases read-only) -> dump (one exported snapshot per database) -> restore into a NEW volume inside a one-shot server (no initdb.d, no network) -> verify the restore -> re-check the old cluster has not changed since the dump -> switch env, compose and lock -> start.
Purpose: `nself db image switch --to pgvector`. The command layer (cmd/commands/db_image*.go) supplies the three seams below; this package never imports reconcile or cobra. Inputs: Config and Deps. Outputs: a Report; coded errors E509-E512. Constraints: the old data volume is never removed, renamed or mounted read-write by this package; the database password reaches a container only as PGPASSWORD through docker.RunSpec.EnvPass; state and reports hold no secret.
Index ¶
- Constants
- func Asset(name string) (string, error)
- func StatePath(projectDir string) string
- type Applier
- type Check
- type Config
- type ContainerInfo
- type Deps
- type DockerRuntime
- func (DockerRuntime) ExecSQL(ctx context.Context, container, user, db, sql string) (string, error)
- func (DockerRuntime) Inspect(ctx context.Context, name string) (ContainerInfo, error)
- func (DockerRuntime) RemoveContainer(ctx context.Context, name string) error
- func (DockerRuntime) RunOneShot(ctx context.Context, spec docker.RunSpec) (string, string, error)
- func (DockerRuntime) VolumeCreate(ctx context.Context, name string, labels map[string]string) error
- func (DockerRuntime) VolumeExists(ctx context.Context, name string) (bool, error)
- func (DockerRuntime) VolumeLabels(ctx context.Context, name string) (map[string]string, bool, error)
- func (DockerRuntime) VolumeRemove(ctx context.Context, name string) error
- type Phase
- type Report
- type RunOptions
- type Runtime
- type Stack
- type State
- type Switch
Constants ¶
const ( EnvImage = "POSTGRES_IMAGE" EnvDataVolume = "POSTGRES_DATA_VOLUME" )
Env keys the switch writes; the first two are also what rollback restores.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Applier ¶
type Applier interface {
// Plan returns what Apply would change, as JSON-ready data; nothing is written.
Plan(ctx context.Context, env map[string]string) (any, error)
// Apply writes the lines, regenerates, and undoes the lines if it fails.
Apply(ctx context.Context, env map[string]string) error
}
Applier writes env lines and regenerates compose and lock through the build plan path (reconcile). A value of "" means the key is set empty (unset).
type Check ¶
type Check struct {
Name string `json:"name"`
OK bool `json:"ok"`
Detail string `json:"detail,omitempty"`
}
Check is one preflight result.
type Config ¶
type Config struct {
ProjectDir string
ProjectName string
// Service is the compose service name of postgres ("postgres").
Service string
// Container is the postgres container name (<project>_postgres).
Container string
// User is POSTGRES_USER; Password is POSTGRES_PASSWORD (EnvPass only).
User string
Password string
// TargetFor returns the pgvector image of the given Postgres major, or an
// error when there is none.
TargetFor func(major int) (string, error)
// PrevEnv holds the current raw values of EnvImage and EnvDataVolume in the
// project (empty when unset); rollback writes them back.
PrevEnv map[string]string
}
Config describes the project the switch runs in.
type ContainerInfo ¶
type ContainerInfo struct {
Image string
Running bool
DataVolume string // name of the volume mounted at /var/lib/postgresql/data
Networks []string
ComposeProject string // label com.docker.compose.project
}
ContainerInfo is what the switch reads from `docker inspect`.
type Deps ¶
type Deps struct {
Runtime Runtime
Stack Stack
Applier Applier
// Out receives one progress line per step; nil discards.
Out io.Writer
// Now is the clock; nil uses time.Now.
Now func() time.Time
// FreeBytes reports the free bytes under a host path; nil uses statfs.
FreeBytes func(path string) (uint64, error)
}
Deps are the effects the switch makes. Tests replace every one.
type DockerRuntime ¶
type DockerRuntime struct{}
DockerRuntime is the Runtime backed by the docker CLI.
func (DockerRuntime) ExecSQL ¶
ExecSQL runs psql in the container over the local socket (trust inside the container).
func (DockerRuntime) Inspect ¶
func (DockerRuntime) Inspect(ctx context.Context, name string) (ContainerInfo, error)
Inspect reads the postgres container.
func (DockerRuntime) RemoveContainer ¶
func (DockerRuntime) RemoveContainer(ctx context.Context, name string) error
RemoveContainer force-removes a container.
func (DockerRuntime) RunOneShot ¶
RunOneShot runs a throwaway container.
func (DockerRuntime) VolumeCreate ¶
VolumeCreate creates a labelled volume.
func (DockerRuntime) VolumeExists ¶
VolumeExists reports whether a volume exists.
func (DockerRuntime) VolumeLabels ¶
func (DockerRuntime) VolumeLabels(ctx context.Context, name string) (map[string]string, bool, error)
VolumeLabels reads a volume's labels.
func (DockerRuntime) VolumeRemove ¶
func (DockerRuntime) VolumeRemove(ctx context.Context, name string) error
VolumeRemove removes a volume.
type Phase ¶
type Phase string
Phase is how far a switch has got. It is written to state.json after every step.
const ( // PhaseStarted: preflight passed and the state exists; nothing was stopped. PhaseStarted Phase = "started" // PhaseQuiesced: services stopped, databases read-only, other sessions gone. PhaseQuiesced Phase = "quiesced" // PhaseDumped: the dump and the old-cluster capture are in the stage. PhaseDumped Phase = "dumped" // PhaseRestored: the new volume holds the restore and was captured. PhaseRestored Phase = "restored" // PhaseVerified: the restore equals the dump-time capture. PhaseVerified Phase = "verified" // PhaseRechecked: the old cluster still equals the dump-time capture. PhaseRechecked Phase = "rechecked" // PhaseSwitching: postgres is being stopped and env, compose and lock written. PhaseSwitching Phase = "switching" // PhaseSwitched: env, compose and lock name the new volume; the stack is not yet started. PhaseSwitched Phase = "switched" // PhaseDone: the stack runs on the new volume. PhaseDone Phase = "done" // PhaseRolledBack: the project is back on the old volume. PhaseRolledBack Phase = "rolled-back" )
type Report ¶
type Report struct {
// Status: planned, switched, already-pgvector, rolled-back.
Status string `json:"status"`
Phase Phase `json:"phase,omitempty"`
OldImage string `json:"old_image,omitempty"`
NewImage string `json:"new_image,omitempty"`
OldVolume string `json:"old_volume,omitempty"`
NewVolume string `json:"new_volume,omitempty"`
// Preflight lists every check; a failed one stops the switch with E509.
Preflight []Check `json:"preflight,omitempty"`
// Steps names what a run does, in order.
Steps []string `json:"steps,omitempty"`
// Stage is the directory the dump is written to (mode 0700); Dumps names the dump
// files in it, one per database. Both are for --plan; the stage is removed at the end.
Stage string `json:"stage,omitempty"`
Dumps []string `json:"dumps,omitempty"`
Databases int `json:"databases,omitempty"`
Tables int `json:"tables,omitempty"`
Sequences int `json:"sequences,omitempty"`
DataBytes int64 `json:"data_bytes,omitempty"`
// DowntimeSeconds estimates the window from stopping services to a healthy stack.
DowntimeSeconds int `json:"downtime_seconds,omitempty"`
// Tolerated lists the bootstrap statements the restore skipped, each logged.
Tolerated []string `json:"tolerated,omitempty"`
// Build is the compose and lock change the build plan path would make.
Build any `json:"build,omitempty"`
NextSteps []string `json:"next_steps,omitempty"`
Warnings []string `json:"warnings,omitempty"`
}
Report is the command's data (`--json` envelope data).
type RunOptions ¶
type RunOptions struct {
// Resume continues an incomplete switch from its state.
Resume bool
}
RunOptions are the flags of a run.
type Runtime ¶
type Runtime interface {
// Inspect returns the container, or an error when it does not exist.
Inspect(ctx context.Context, name string) (ContainerInfo, error)
// ExecSQL runs sql in the running container with psql over the local socket.
ExecSQL(ctx context.Context, container, user, db, sql string) (string, error)
// RunOneShot runs a throwaway container to completion.
RunOneShot(ctx context.Context, spec docker.RunSpec) (stdout, stderr string, err error)
// RemoveContainer force-removes a container; absent is success.
RemoveContainer(ctx context.Context, name string) error
// VolumeLabels returns a volume's labels; exists is false when it is absent.
VolumeLabels(ctx context.Context, name string) (labels map[string]string, exists bool, err error)
// VolumeCreate, VolumeExists and VolumeRemove manage named volumes.
VolumeCreate(ctx context.Context, name string, labels map[string]string) error
VolumeExists(ctx context.Context, name string) (bool, error)
VolumeRemove(ctx context.Context, name string) error
}
Runtime is docker for the switch: inspect, one-shots, volumes.
type Stack ¶
type Stack interface {
// Running lists the service names that are running now.
Running(ctx context.Context) ([]string, error)
// Stop stops the services; Up starts them (none: every service).
Stop(ctx context.Context, services ...string) error
Up(ctx context.Context, services ...string) error
// WaitHealthy returns when the service reports healthy, or an error on timeout.
WaitHealthy(ctx context.Context, service string) error
}
Stack is the compose lifecycle of the project.
type State ¶
type State struct {
Version int `json:"version"`
RunID string `json:"run_id"`
Phase Phase `json:"phase"`
Container string `json:"container"`
Network string `json:"network"`
ComposeProject string `json:"compose_project"`
BootUser string `json:"boot_user"`
Major int `json:"major"`
OldImage string `json:"old_image"`
NewImage string `json:"new_image"`
OldVolume string `json:"old_volume"`
NewVolume string `json:"new_volume"`
Databases []string `json:"databases"`
// ReadOnlySet is true once ALTER DATABASE ... default_transaction_read_only
// was set on the old cluster by this switch.
ReadOnlySet bool `json:"read_only_set"`
// StoppedServices are the services this switch stopped; abort and rollback restart them.
StoppedServices []string `json:"stopped_services"`
// NewVolumeCreated is true once this switch created the new volume (it may remove it).
NewVolumeCreated bool `json:"new_volume_created"`
// PrevEnv is the raw value of each env key the switch writes, before the switch.
PrevEnv map[string]string `json:"prev_env"`
Tolerated []string `json:"tolerated,omitempty"`
Tables int `json:"tables,omitempty"`
Sequences int `json:"sequences,omitempty"`
DataBytes int64 `json:"data_bytes,omitempty"`
StartedAt time.Time `json:"started_at"`
UpdatedAt time.Time `json:"updated_at"`
}
State is the persisted progress of one switch.
func Incomplete ¶
Incomplete returns the state when a switch started in the project and has not finished. It is cheap: one stat when there is none. An unreadable state counts as incomplete (the guard must fail closed) and is returned as a State with an empty phase.
type Switch ¶
type Switch struct {
// contains filtered or unexported fields
}
Switch runs one image switch.
func (*Switch) Plan ¶
Plan reports what a run would do and changes nothing. The report is returned with the E509 error when a check failed, so the caller can show the checks.