deploy

package
v0.0.11 Latest Latest
Warning

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

Go to latest
Published: Sep 2, 2026 License: MIT Imports: 26 Imported by: 0

Documentation

Overview

Package deploy runs the production deploy pipeline over SSH: preflight, render, sync, build, before_deploy hooks, up, after_deploy hooks, health probe, and commit (the .pier/state.json write that records the active image tag for `pier rollback`). The package owns the typed error contract (ExitError, Kind) and the SSH client used for every remote command.

Index

Constants

View Source
const (
	ExitOK = 0
	// ExitGeneral is the fallback for errors that don't match a more
	// specific category below.
	ExitGeneral = 1
	// ExitPreflight is returned when SSH, port-probe, or
	// pier.toml validation fails before any image is built.
	ExitPreflight = 2
	// ExitBuild is returned when `docker compose build` fails on
	// the remote host.
	ExitBuild = 3
	// ExitUp is returned when `docker compose up -d` fails (or the
	// post-up health check fails) on the remote host.
	ExitUp = 4
	// ExitExecDown is returned by `pier shell` / `pier exec` when
	// the laravel.test container isn't running.
	ExitExecDown = 5
	// ExitPortInUse is returned by `pier dev` when the pre-flight
	// host-port probe finds one or more pier-owned host ports already
	// in use on 127.0.0.1. The user must edit [dev.ports] in pier.toml
	// to remap before retrying.
	ExitPortInUse = 6
	// ExitHooks is returned when a before_deploy or after_deploy
	// command fails on the remote host (a failed hook aborts the
	// deploy).
	ExitHooks = 7
	// ExitAborted is returned when the user aborts an interactive TUI
	// (q / Ctrl+C). 130 = 128 + SIGINT, the POSIX shell convention.
	ExitAborted = 130
)

Process exit codes used by every pier command. The CLI maps these to os.Exit in cmd/pier/main.go.

View Source
const ClockSyncThreshold = 60

ClockSyncThreshold is the max |remote - local| offset in seconds tolerated before pier force-sets the remote clock. Freshly-reset VMs boot with a stale RTC; apt rejects signed Release files whose dates fall outside the (wrong) guest clock, so even minutes of skew break provisioning.

Variables

View Source
var (
	// ErrNotBootstrapped wraps the deploy-side fail-fast: the server
	// is not provisioned and `pier bootstrap` must run first.
	ErrNotBootstrapped = errors.New("server not bootstrapped")
	// ErrAlreadyBootstrapped is returned by BootstrapEnv when the
	// probe passes; the CLI prints "already bootstrapped — skipping".
	ErrAlreadyBootstrapped = errors.New("already bootstrapped")
	// ErrSudoWrongPassword is returned by ValidateSudo when the sudo
	// password is rejected. The CLI re-prompts once.
	ErrSudoWrongPassword = errors.New("wrong sudo password")
	// ErrSudoNotSudoers is returned when the user has no sudo rights
	// at all; the CLI aborts with instructions.
	ErrSudoNotSudoers = errors.New("deploy user has no sudo rights")
)

Sentinel errors for the bootstrap flow. The CLI inspects these with errors.Is to decide whether to skip, re-prompt, or abort.

View Source
var (
	ErrBuild     = errors.New("build")
	ErrUp        = errors.New("up")
	ErrExecDown  = errors.New("container not running")
	ErrPortInUse = errors.New("port in use")
	ErrHooks     = errors.New("deploy hook")
	ErrAborted   = errors.New("aborted")
)

Sentinel errors. The CLI's PrintError inspects these to pick a category and a hint; tests use errors.Is to assert failure modes.

View Source
var ErrPreflight = errors.New("deploy: preflight failed")

ErrPreflight is the sentinel for every error returned by Dial (empty host, missing key, bad permissions, handshake failure). Callers use errors.Is to surface a KindConfig error with a clear message.

View Source
var ErrShellNoTTY = errors.New("remote shell requires a terminal")

not a terminal; an interactive remote shell would be unusable.

Functions

func AbortedError

func AbortedError() error

func BootstrapEnv

func BootstrapEnv(ctx context.Context, cfg SSHConfig, password string, opts BootstrapOpts) error

BootstrapEnv runs the full one-time provisioning flow for one server: probe (unless Force), sudo validation, clock sync, provision, deploy path creation (unless Path is empty), verify. Returns ErrAlreadyBootstrapped when the probe passes and Force is false.

func Build

func Build(ctx context.Context, r runner, dir, project, sha string, onLine func(string)) error

Build runs `docker compose --env-file .env.production -f docker-compose.prod.yml build --pull` on the remote host, streaming each output line to onLine. Used as stage 4 of the deploy pipeline. The --env-file is passed so ${...} interpolation in the compose file resolves instead of warning; --pull keeps the image fresh.

func BuildError

func BuildError(err error) error

func BuildLocalImage

func BuildLocalImage(ctx context.Context, dir, php, project, sha string, onLine func(string)) error

BuildLocalImage runs plain `docker build` on the local machine in dir (the project root), streaming stdout/stderr lines to onLine. Used as the build stage of the local_machine builder mode.

func ConfigError

func ConfigError(err error) error

func DockerError

func DockerError(err error) error

func EnsureClockSynced

func EnsureClockSynced(ctx context.Context, r stdinRunner, password string, onStdout, onStderr func(string)) error

EnsureClockSynced compares the remote clock to the local clock and, when they differ by more than ClockSyncThreshold seconds, force-sets the remote clock from the local one under sudo (`date -s @<epoch>`). Needs sudo only when a correction is required. On correction it re-reads the remote epoch, verifies the skew is now within ClockSyncThreshold (erroring out if the set did not apply), and emits one line via onStdout: `remote clock was Ns off; corrected to <RFC3339>`.

func EnsureDeployPath

func EnsureDeployPath(ctx context.Context, r cmdRunner, path string) error

EnsureDeployPath creates the deploy path as the deploy user without sudo, so the deploy path is writable for the file sync. Fails when the parent directory is not writable; the deploy preflight turns that into an actionable error.

func ExecDownError

func ExecDownError() error

func HealthURL

func HealthURL(cfg config.Config, env string) string

HealthURL returns the URL the health probe GETs for env. With a domain the probe targets https://<domain>:<port>/up (port resolved via laravelpkg.WebPort, default 443) with normal TLS verification — an end-to-end check that exercises the real Let's Encrypt certificate (the DNS preflight already verified the domain points at the deploy host). Without a domain it probes the deploy host IP with the resolved "laravel" port, so health checks pass before DNS or /etc/hosts entries exist.

func NetworkError

func NetworkError(err error) error

func NotBootstrappedError

func NotBootstrappedError(env string) error

NotBootstrappedError builds the deploy-side fail-fast error: the server was never provisioned, so `pier bootstrap <env>` must run first. Wrapped as a preflight error so the CLI exits with code 2.

func PortInUseError

func PortInUseError(ports []int) error

PortInUseError builds an error for a pre-flight port-probe collision. ports is the list of host ports that were already listening on 127.0.0.1 when `pier dev` started. The user edits [dev.ports] in pier.toml to remap and retries.

func PreflightError

func PreflightError(err error) error

PreflightError wraps err as a config-category ExitError with code ExitPreflight. Used for SSH handshake failures, missing pier.toml fields, and other pre-build problems.

func Probe

func Probe(ctx context.Context, cfg HealthConfig) error

Probe repeatedly GETs cfg.URL until it returns a 2xx, the Timeout elapses, or MaxAttempts is reached. The interval doubles each attempt, capped at 10 s. Used as stage 6 of the deploy pipeline; a failed probe triggers Rollback.

func ProbeBootstrap

func ProbeBootstrap(ctx context.Context, r stdinRunner) (bool, error)

ProbeBootstrap reports whether the deploy user can run docker without sudo: `command -v docker` and `docker info` must both succeed. A non-zero exit counts as "not bootstrapped" (false, nil); only session-level failures (connection resets, etc.) return a non-nil error.

func ProbeEnv

func ProbeEnv(ctx context.Context, cfg SSHConfig) (bool, error)

ProbeEnv dials cfg and runs the bootstrap probe. Convenience for the CLI's skip check before prompting for the password.

func Provision

func Provision(ctx context.Context, r stdinRunner, password, user string, onStdout, onStderr func(string)) error

Provision installs Docker Engine + the compose plugin with the official get.docker.com script and adds user to the docker group, both under sudo. Idempotent — safe to re-run with --force.

func ProvisionDeployPath

func ProvisionDeployPath(ctx context.Context, r stdinRunner, password, user, path string, onStdout, onStderr func(string)) error

ProvisionDeployPath creates the env's deploy path on the remote host under sudo and hands it to the deploy user: `mkdir -p <path> && chown <user>:<user> <path>`. Idempotent, so `--force` re-runs are safe. The user is quoted the same way Provision quotes it.

func RemoteBuildError

func RemoteBuildError(host string, err error) error

RemoteBuildError is BuildError stamped with the SSH host the build ran on, so the CLI can render a remote-aware hint.

func RemoteBuildImage

func RemoteBuildImage(ctx context.Context, r runner, dir, php, project, sha string, onLine func(string)) error

RemoteBuildImage runs plain `docker build` on a remote build server inside dir (the synced project root), streaming output lines to onLine. Used as the build stage of the build_server builder mode. dir, php, project, and sha are shell-quoted so a hostile value in pier.toml cannot inject commands into the remote shell (F2).

func RemoteCommandError

func RemoteCommandError(host string, err error) error

RemoteCommandError wraps a failed remote command. When err carries an *ssh.ExitError, pier's exit code mirrors the remote exit status; otherwise the failure is a session-level error and gets ExitGeneral. RemoteHost is stamped on both so the CLI renders the remote-aware hint.

func RemoteDockerError

func RemoteDockerError(host string, err error) error

RemoteDockerError is a general docker failure stamped with the SSH host, used by `pier status <env>` when a remote probe command fails.

func RemoteExec

func RemoteExec(ctx context.Context, cfg SSHConfig, dir string, args []string) error

RemoteExec runs a one-off command in the app service of the prod compose file on the remote host, streaming stdout/stderr to pier's own streams as they arrive. The remote exit status becomes pier's exit code via RemoteCommandError; a status-0 exit returns nil. Cancelling ctx aborts the in-flight session.

func RemoteHookError

func RemoteHookError(host string, err error) error

RemoteHookError is a failed before_deploy / after_deploy command stamped with the SSH host it ran on.

func RemoteShell

func RemoteShell(ctx context.Context, cfg SSHConfig, dir string) error

RemoteShell opens an interactive bash in the app service of the prod compose file on the remote host and returns when the remote shell exits. The remote exit status becomes pier's exit code. Cancelling ctx aborts the in-flight session.

func RemoteUpError

func RemoteUpError(host string, err error) error

RemoteUpError is UpError stamped with the SSH host the up ran on.

func ResolvedURL

func ResolvedURL(cfg config.Config, env string) string

ResolvedURL returns the public URL for the deployed env: scheme and port resolved from the env's domain and the "laravel" port (default 443 with a domain, 80 for the plain-HTTP default, or the per-env override from [deploy.<env>.ports.laravel]). The host is the env's domain when it resolves (DNS or /etc/hosts); otherwise it falls back to the deploy host IP, so the printed URL is usable before DNS entries point the domain at the server.

func Rollback

func Rollback(ctx context.Context, st stateStore, r runner, dir, project, target string) error

Rollback retags the previous deploy's image to the tag the prod compose file actually references and re-runs Up. target is that tag without the project prefix: "latest" in host_server mode (the compose keeps the build context and references <project>:latest) and "current" in the image modes (<project>:current). The "previous" image comes from .pier/state.json on the deploy host (written by Pipeline.commit at the end of every successful deploy). Returns an error if state.json is missing or if the previous image tag is empty (i.e. there is no prior deploy to roll back to). st may be nil, in which case local disk is used (unit tests); the pipeline always passes sftpStateStore.

func SSHError

func SSHError(err error) error

func SaveState

func SaveState(dir string, s *State) error

SaveState atomically writes s to .pier/state.json in dir. The .pier/ directory is created with 0755 if missing; the file is written to a sibling .tmp first and renamed into place so a crash mid-write can't corrupt the deploy record.

func SessionError

func SessionError(host string, err error) error

SessionError wraps an SSH session-level failure (session creation, pty or command start, stream setup) on host.

func Tag

func Tag(ctx context.Context, r runner, project, sha string) error

Tag retags the just-built <project>:latest image to <project>:<sha> (the immutable deploy record) and to <project>:current (the live alias that Rollback overwrites).

func TransferImage

func TransferImage(ctx context.Context, saver imageSaver, dst *Client, image string, onLine func(string)) (int64, error)

TransferImage streams image from saver into dst's docker daemon via `docker load` and returns the number of bytes streamed. Both sides run concurrently: the saver's output feeds an io.Pipe whose read side is the docker load session's stdin. A failure on either side aborts the transfer; the saver's error takes precedence so the reported cause matches where the image came from.

func Up

func Up(ctx context.Context, r runner, dir string) error

Up runs `docker compose --env-file .env.production -f docker-compose.prod.yml up -d --wait --wait-timeout 120 --remove-orphans` on the remote host and then reloads the webserver's caddy. Used as stage 5 of the deploy pipeline and by Rollback. The --env-file is required: the compose file interpolates ${DB_PASSWORD}/${APP_KEY} from it (without it compose warns and the app container gets blank secrets and 500s). The --wait makes compose return only once every service with a healthcheck (postgres, redis, the sidecars) is healthy — `up -d` alone returns as soon as containers start, so a fresh postgres volume still initializing would refuse connections from the very first after_deploy hook (`SQLSTATE[08006] Connection refused`), while the same hook succeeds minutes later in an interactive shell. --wait-timeout bounds that wait so a never- healthy service fails the deploy instead of hanging it. The reload is needed because the sync writes bind-mounted files in place (inode preserved), so a changed Caddyfile is visible to the webserver container, but caddy only reads config at start/reload and compose does not recreate a service whose spec is unchanged. Reloading unconditionally is harmless when nothing changed; the error is ignored because a freshly created container already loaded the current config. --remove-orphans stops and removes containers whose service is no longer in the compose file — exactly the sidecars the per-env render dropped — while named volumes (mysql_data, s3_data, ...) are preserved.

func UpError

func UpError(err error) error

func UserError

func UserError(err error) error

func ValidateSudo

func ValidateSudo(ctx context.Context, r stdinRunner, password string, onStdout, onStderr func(string)) error

ValidateSudo proves the password works by running `sudo -S -v`. Returns ErrSudoWrongPassword or ErrSudoNotSudoers on failure.

func VerifyBootstrap

func VerifyBootstrap(ctx context.Context, r stdinRunner, password, user string, onStdout, onStderr func(string)) error

VerifyBootstrap confirms the daemon runs, the compose plugin is present, and the user is a member of the docker group. Group membership only applies to new SSH connections, so the group file is checked directly (getent) instead of re-running docker.

Types

type BootstrapOpts

type BootstrapOpts struct {
	// Force skips the probe and re-provisions even when the server
	// is already bootstrapped.
	Force bool
	// User is the deploy user that gets docker group membership.
	User string
	// Path is the env's deploy directory ([deploy.<env>].path),
	// created with sudo and chowned to User. Empty means no path to
	// create.
	Path string
	// OnStdout/OnStderr stream each remote output line as it
	// arrives; may be nil.
	OnStdout func(string)
	OnStderr func(string)
}

BootstrapOpts is the parameter set for BootstrapEnv.

type Client

type Client struct {
	Config SSHConfig
	// contains filtered or unexported fields
}

Client is a thin wrapper around an *ssh.Client. It implements the internal runner interface (Run / RunStream) used by every other stage of the deploy pipeline.

func Dial

func Dial(ctx context.Context, cfg SSHConfig) (*Client, error)

Dial opens an SSH connection to cfg and returns a ready Client. When cfg.KnownHostsPath is set the remote host key is verified trust-on-first-use (F3); the CLI always sets it to ~/.ssh/known_hosts. Key auth is tried first; if the key file is missing or the server rejects every offered key (an auth-class failure), Dial falls back to the password from cfg.Password or cfg.PasswordPrompt and retries once. A password fallback is attempted only when a password source exists; otherwise the original handshake error is returned.

func (*Client) Close

func (c *Client) Close() error

Close shuts down the underlying SSH connection. Safe to call when the connection was never established.

func (*Client) InteractiveShell

func (c *Client) InteractiveShell(ctx context.Context, dir string) error

InteractiveShell runs the remote interactive bash session: local terminal size → pty request, raw-mode stdin with restore (also on error paths), SIGWINCH → WindowChange forwarding, and byte-copying of stdin/stdout/stderr through the session. Requires a TTY on local stdin. Cancelling ctx aborts the in-flight session.

func (*Client) ReadFile

func (c *Client) ReadFile(ctx context.Context, remotePath string) ([]byte, error)

ReadFile reads a remote file over SFTP on the already-open SSH connection. Returns (nil, nil) when the path does not exist.

func (*Client) Run

func (c *Client) Run(ctx context.Context, cmd string) ([]byte, []byte, error)

Run executes cmd on the remote host and returns the captured stdout, stderr, and the session error. Used by Tag, Up, and Rollback where the caller needs to inspect the output (or where streaming is unnecessary).

func (*Client) RunStdin

func (c *Client) RunStdin(ctx context.Context, cmd string, stdin string) ([]byte, []byte, error)

RunStdin executes cmd on the remote host with stdin piped from the given string. Used by bootstrap to feed the sudo password to `sudo -S` without it ever appearing in the command string (and thus in remote process listings or logs).

func (*Client) RunStream

func (c *Client) RunStream(ctx context.Context, cmd string, onLine func(string)) error

RunStream executes cmd on the remote host and invokes onLine for each line of stdout or stderr as it arrives. onLine may be invoked concurrently from two goroutines (the stdout and stderr readers), so callbacks must be safe for concurrent use. Used by Build, which needs to surface `docker compose build` progress — and, on failure, the compose validation errors that docker writes to stderr — in the deploy TUI in real time. When the remote command exits non-zero, the returned error carries the last runStreamTailSize streamed lines so the failure is diagnosable without re-running.

func (*Client) RunStreamStdin

func (c *Client) RunStreamStdin(ctx context.Context, cmd, stdin string, onStdout, onStderr func(string)) ([]byte, error)

RunStreamStdin executes cmd on the remote host with stdin piped from the given string, invoking onStdout/onStderr for each line as it arrives and returning the captured stderr. Used by bootstrap to stream provisioning output while feeding the sudo password to `sudo -S` without it ever appearing in the command string.

func (*Client) StreamIn

func (c *Client) StreamIn(ctx context.Context, cmd string, in io.Reader, onLine func(string)) error

StreamIn executes cmd on the remote host with stdin fed from in and invokes onLine for each stderr line as it arrives. The stdin path is binary-safe (no line scanning on the input side): it pipes `docker save` output into a remote `docker load`. stderr is line-streamed because docker load writes its progress and errors as lines. On a non-zero exit the returned error carries the last runStreamTailSize stderr lines.

func (*Client) StreamOut

func (c *Client) StreamOut(ctx context.Context, cmd string, out io.Writer, onLine func(string)) error

StreamOut executes cmd on the remote host piping its stdout into out and invoking onLine for each stderr line as it arrives. The stdout path is binary-safe (io.Copy, no line scanning): it streams `docker save` output from a build server into a sink. On a non-zero exit the returned error carries the last runStreamTailSize stderr lines.

func (*Client) SyncDir

func (c *Client) SyncDir(ctx context.Context, local, remote string, excludes []string) error

SyncDir copies the local directory tree to remote over SFTP, reusing the already-open SSH connection. Paths matched by excludes (see pathExcluded) are skipped; excluded directories are pruned. Remote parent directories are created on demand and file modes and modification times are preserved. ctx is honored between files; a single large file copy is not interruptible mid-stream.

func (*Client) WriteFile

func (c *Client) WriteFile(ctx context.Context, remotePath string, b []byte) error

WriteFile writes bytes to a remote file over SFTP, creating parent directories. The content lands in a sibling .tmp file first and is renamed into place, so a reader never observes a partial write.

type Event

type Event struct {
	Time    time.Time      `json:"time"`
	Phase   string         `json:"phase,omitempty"`
	Level   string         `json:"level,omitempty"`
	Message string         `json:"message"`
	Data    map[string]any `json:"data,omitempty"`
}

Event is a single structured log record. The CLI's stdLogger marshals this to JSON when --json is set; the TUI logger ignores it. Time is filled in by the receiver if zero.

type ExitError

type ExitError struct {
	Code int
	Kind Kind
	// RemoteHost is the SSH host a failed remote command ran on.
	// Empty means the failure was local.
	RemoteHost string
	Err        error
}

ExitError wraps a sentinel with a process exit code and a Kind (config / docker / ssh / network / user / unknown). The CLI's PrintError reads Kind to color the output and to look up a category-specific hint. RemoteHost, when non-empty, marks the error as a remote (SSH) command failure so PrintError renders a remote-aware hint instead of the local pier hint.

func (*ExitError) Error

func (e *ExitError) Error() string

func (*ExitError) Is

func (e *ExitError) Is(target error) bool

func (*ExitError) Unwrap

func (e *ExitError) Unwrap() error

type HealthConfig

type HealthConfig struct {
	URL         string
	Timeout     time.Duration
	Interval    time.Duration
	MaxAttempts int
}

HealthConfig drives Probe: the URL to GET, the overall Timeout, the Interval between attempts, and the MaxAttempts safety cap. The defaults come from DefaultHealthConfig.

func DefaultHealthConfig

func DefaultHealthConfig(cfg config.Config, env string) HealthConfig

DefaultHealthConfig returns a sensible default HealthConfig for a deploy env: GET to the deploy host's web endpoint (scheme and port resolved from the env's domain (see HealthURL)), 60 s total timeout, 2 s base interval, 30 attempts (interval doubles each attempt up to a 10 s cap).

type Kind

type Kind int

Kind categorizes an error so the CLI can pick a color and a hint when rendering. The values are stable; new categories append.

const (
	// KindUnknown is the zero value; the CLI renders these errors
	// without a [kind] label and with no hint.
	KindUnknown Kind = iota
	// KindConfig is for pier.toml parse / validation failures and
	// for pre-deploy checks that fail before any Docker work.
	KindConfig
	// KindDocker is for `docker compose` failures on the local or
	// remote host.
	KindDocker
	// KindSSH is for SSH handshake, auth, and session failures.
	KindSSH
	// KindNetwork is for DNS, registry, and pull-time connectivity
	// problems.
	KindNetwork
	// KindUser is for user-actionable problems (port collisions,
	// interactive aborts).
	KindUser
)

func (Kind) Hint

func (k Kind) Hint() string

Hint returns a short, kind-specific remediation hint rendered below the error chain by PrintError. Empty for KindUnknown and KindUser (the user already knows what they did).

func (Kind) String

func (k Kind) String() string

String returns the lower-case label used in the CLI's "error[kind]: ..." output.

type Logger

type Logger interface {
	Emit(Event)
	PhaseStart(name string)
	PhaseEnd(name string, err error)
	Log(level, format string, args ...any)
	JSON() bool
	Writer() io.Writer
}

Logger is the contract every deploy event sink implements. The stdLogger (CLI) emits plain lines or JSON; the TUI logger pushes events into the Bubble Tea channel. The Pipeline writes to whichever Logger the caller hands it.

type Pipeline

type Pipeline struct {
	Config    *config.Config
	Env       string
	DeployEnv config.DeployConfig
	Logger    Logger
	SSH       SSHConfig
	// BuildSSH is the SSH config of the dedicated build server, used
	// only when [deploy.<env>].builder is "build_server". Dialed in
	// preflight like the host connection.
	BuildSSH SSHConfig

	Health HealthConfig
	Now    func() time.Time
	// contains filtered or unexported fields
}

Pipeline is the top-level deploy driver. One Pipeline is constructed per `pier deploy <env>` invocation and Run is called exactly once.

func (*Pipeline) Run

func (p *Pipeline) Run(ctx context.Context) error

Run executes the full deploy pipeline: preflight, render, sync, build, before_deploy hooks, up, after_deploy hooks, health probe, commit. A failing before_deploy hook aborts the deploy (before the new release starts); a failing after_deploy hook, up, or health probe retags the previous image and re-deploys it (Rollback) before the error is returned. Now is set to time.Now if nil so tests can pin the timestamp written to state.json.

type SFTPStateStore

type SFTPStateStore struct {
	Client *Client
}

SFTPStateStore reads and writes .pier/state.json over SFTP on the deploy SSH connection, so the deploy record lives on the remote host where Rollback and `pier status <env>` can find it. Client is the already-dialed deploy connection.

func (SFTPStateStore) ReadState

func (s SFTPStateStore) ReadState(ctx context.Context, dir string) (*State, error)

func (SFTPStateStore) WriteState

func (s SFTPStateStore) WriteState(ctx context.Context, dir string, st *State) error

type SSHConfig

type SSHConfig struct {
	Host           string
	User           string
	KeyPath        string
	Port           int
	Password       string
	PasswordPrompt func() (string, error)
	KnownHostsPath string
}

SSHConfig is everything Dial needs to open a connection: target host, login user, path to the private key on the local filesystem, and the TCP port (0 means 22). When the server rejects the key (or the key file does not exist), Dial falls back to a password: Password wins, then PasswordPrompt (which may be nil to forbid prompting). KnownHostsPath names the OpenSSH known_hosts file to verify the remote host against; when it is set, the host key is verified trust-on-first-use (TOFU) — an unknown host is accepted and recorded, a known host whose key changed is rejected (MITM defense, F3). When empty, the host key is not verified.

type State

type State struct {
	Current    string `json:"current"`
	Previous   string `json:"previous"`
	DeployedAt string `json:"deployed_at"`
	DeployedBy string `json:"deployed_by"`
}

State is the parsed .pier/state.json from a remote project. The JSON tags are the wire format; do not rename without a migration.

func LoadState

func LoadState(dir string) (*State, error)

LoadState reads .pier/state.json from dir and decodes it. Returns (nil, nil) when the file does not exist (a fresh project, no deploys yet) so the caller can treat "no state" as a normal condition.

func (*State) HasPrevious

func (s *State) HasPrevious() bool

HasPrevious reports whether State carries a non-empty Previous image tag. Rollback short-circuits to "no previous deploy" when this is false.

type StatusReport

type StatusReport struct {
	Containers string // `docker compose -f docker-compose.prod.yml ps` output
	Disk       string // `df -h <path>` output
	DockerDisk string // `docker system df` output
	State      *State // parsed .pier/state.json; nil when the file is absent
	Healthy    bool   // single HTTP GET of health.URL returned 2xx
}

StatusReport is the result of a `pier status <env>` probe of a remote host: raw command output for containers and disk, the last deploy record, and a single HTTP health verdict.

func RemoteStatus

func RemoteStatus(ctx context.Context, de config.DeployConfig, health HealthConfig, r StatusRunner) (*StatusReport, error)

RemoteStatus probes the remote host behind r: container state, deploy-path and docker disk usage, the last deploy record, and one HTTP health check against health.URL. A missing state file is normal (a project with no deploys yet) and yields a nil State. A failed health probe sets Healthy=false instead of failing the probe; a failed probe command (compose, df, or docker system df) returns an error. For the state file, a failed `cat` (e.g. no deploys yet) yields a nil State; output that cannot be parsed returns an error.

type StatusRunner

type StatusRunner interface {
	Run(ctx context.Context, cmd string) ([]byte, []byte, error)
	Close() error
}

StatusRunner is the subset of *Client that RemoteStatus needs: Run to capture command output and Close to release the connection. The CLI dials a real client and passes it; tests inject a fake.

Jump to

Keyboard shortcuts

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