Documentation
¶
Overview ¶
Package deploy is part of the Redoubt control plane. See CLAUDE.md for its role.
Index ¶
- Constants
- Variables
- func CanTransition(from, to State) bool
- func IsActive(s State) bool
- func IsTerminal(s State) bool
- type Actor
- type AppStatus
- type AppView
- type Broker
- type CreateAppRequest
- type DeployRequest
- type DeploymentView
- type ErrIllegalTransition
- type GitSourceResult
- type GitSourceView
- type LogLine
- type Logs
- type Pipeline
- type Poller
- type ReleaseView
- type RollbackRequest
- type SecretMeta
- type Service
- func (s *Service) CreateApp(ctx context.Context, actor Actor, req CreateAppRequest) (AppView, error)
- func (s *Service) DeleteApp(ctx context.Context, actor Actor, name string) error
- func (s *Service) DeleteSecret(ctx context.Context, actor Actor, appName, name string) error
- func (s *Service) Deploy(ctx context.Context, actor Actor, appName string, req DeployRequest) (DeploymentView, error)
- func (s *Service) DeploymentLogs(ctx context.Context, actor Actor, id string, after, limit int64) ([]LogLine, error)
- func (s *Service) GetApp(ctx context.Context, actor Actor, name string) (AppView, error)
- func (s *Service) GetDeployment(ctx context.Context, actor Actor, id string) (DeploymentView, error)
- func (s *Service) GetRelease(ctx context.Context, actor Actor, id string) (ReleaseView, error)
- func (s *Service) ListApps(ctx context.Context, actor Actor) ([]AppView, error)
- func (s *Service) ListDeployments(ctx context.Context, actor Actor, appName string, limit int64) ([]DeploymentView, error)
- func (s *Service) ListReleases(ctx context.Context, actor Actor, appName string, limit int64) ([]ReleaseView, error)
- func (s *Service) ListSecrets(ctx context.Context, actor Actor, appName string) ([]SecretMeta, error)
- func (s *Service) Rollback(ctx context.Context, actor Actor, appName string, req RollbackRequest) (DeploymentView, error)
- func (s *Service) SBOMPath(ctx context.Context, actor Actor, releaseID, format string) (string, error)
- func (s *Service) SetGitSource(ctx context.Context, actor Actor, appName string, req SetGitSourceRequest) (GitSourceResult, error)
- func (s *Service) SetScanPolicy(ctx context.Context, actor Actor, appName, policy string) (AppView, error)
- func (s *Service) SetSecret(ctx context.Context, actor Actor, appName, name, value string) (SecretMeta, error)
- func (s *Service) WebhookLookup(ctx context.Context, appName string) ([]byte, string, webhooks.Provider, error)
- func (s *Service) WebhookTrigger(ctx context.Context, appName string, ev webhooks.Event) error
- type SetGitSourceRequest
- type State
- type Static
- type StaticRequest
- type StaticResult
- type Worker
Constants ¶
const ( StreamBuild = "build" StreamRun = "run" StreamSystem = "system" )
Log streams.
const ( PermAppsManage = "apps.manage" PermAppsDeploy = "apps.deploy" PermAppsView = "apps.view" PermSecretsWrite = "secrets.write" PermLogsView = "logs.view" )
Permissions checked by this service (mirrors internal/auth permissions by name so that the auth package stays the single source of truth; the strings must match auth.Perm* values).
const DefaultKeepReleases = 3
DefaultKeepReleases is how many non-active releases keep their images for rollback.
Variables ¶
var ErrForbidden = errors.New("forbidden")
ErrForbidden is returned when the actor lacks a permission (HTTP 403).
var ErrInvalid = errors.New("invalid request")
ErrInvalid wraps request validation failures (HTTP 400).
var ErrNotFound = errors.New("not found")
ErrNotFound is returned for unknown apps/deployments (HTTP 404).
Functions ¶
func CanTransition ¶
CanTransition reports whether from -> to is legal.
func IsTerminal ¶
IsTerminal reports whether no further transitions are possible.
Types ¶
type Actor ¶
type Actor struct {
ID string
Email string
Role string
// Can reports whether the actor holds a permission; nil means "bootstrap: allow all".
Can func(perm string) bool
}
Actor identifies who performs an operation (from the session/API token/bootstrap).
type AppStatus ¶
type AppStatus struct {
App string `json:"app"`
Release string `json:"release"`
ContainerID string `json:"container_id"`
ContainerName string `json:"container_name"`
Image string `json:"image"`
State string `json:"state"`
Hardened bool `json:"hardened"`
Problems []string `json:"problems,omitempty"`
}
AppStatus summarises one managed container for listings and the self-audit.
type AppView ¶
type AppView struct {
ID string `json:"id"`
Name string `json:"name"`
Hostname string `json:"hostname"`
URL string `json:"url,omitempty"`
Port int64 `json:"port"`
MemoryBytes int64 `json:"memory_bytes"`
NanoCPUs int64 `json:"nano_cpus"`
PidsLimit int64 `json:"pids_limit"`
Overrides docker.Overrides `json:"overrides"`
ScanPolicy string `json:"scan_policy"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
GitSource *GitSourceView `json:"git_source,omitempty"`
}
AppView is the API representation of an app (never includes secret values).
type Broker ¶
type Broker struct {
// contains filtered or unexported fields
}
Broker fans live log lines out to SSE subscribers per deployment.
type CreateAppRequest ¶
type CreateAppRequest struct {
Name string `json:"name"`
Hostname string `json:"hostname"`
Port int `json:"port"`
MemoryBytes int64 `json:"memory_bytes"`
NanoCPUs int64 `json:"nano_cpus"`
PidsLimit int64 `json:"pids_limit"`
Overrides docker.Overrides `json:"overrides"`
// ScanPolicy is off | warn | block (default warn).
ScanPolicy string `json:"scan_policy,omitempty"`
}
CreateAppRequest describes a new app.
type DeployRequest ¶
type DeployRequest struct {
Trigger string `json:"trigger"` // manual | webhook | poll | rollback
CommitSHA string `json:"commit_sha,omitempty"`
CommitMessage string `json:"commit_message,omitempty"`
// ImageRef deploys a pre-built image for apps without a git source.
ImageRef string `json:"image_ref,omitempty"`
}
DeployRequest triggers a deployment.
type DeploymentView ¶
type DeploymentView struct {
ID string `json:"id"`
App string `json:"app"`
Trigger string `json:"trigger"`
Actor string `json:"actor"`
CommitSHA string `json:"commit_sha"`
CommitMessage string `json:"commit_message"`
ImageRef string `json:"image_ref"`
State string `json:"state"`
Error string `json:"error,omitempty"`
ContainerName string `json:"container_name,omitempty"`
ReleaseID string `json:"release_id,omitempty"`
CreatedAt string `json:"created_at"`
StartedAt string `json:"started_at,omitempty"`
FinishedAt string `json:"finished_at,omitempty"`
}
DeploymentView is the API representation of a deployment.
type ErrIllegalTransition ¶
type ErrIllegalTransition struct{ From, To State }
ErrIllegalTransition is returned for transitions outside the graph.
func (ErrIllegalTransition) Error ¶
func (e ErrIllegalTransition) Error() string
type GitSourceResult ¶
type GitSourceResult struct {
GitSourceView
WebhookSecret string `json:"webhook_secret"`
}
GitSourceResult is returned once: it contains the webhook secret in plaintext, which is shown to the operator exactly one time (write-only thereafter).
type GitSourceView ¶
type GitSourceView struct {
Provider string `json:"provider"`
RepoURL string `json:"repo_url"`
Branch string `json:"branch"`
BaseDir string `json:"base_dir"`
Dockerfile string `json:"dockerfile"`
DeployKeyPublic string `json:"deploy_key_public"`
WebhookURL string `json:"webhook_url"`
PollInterval int64 `json:"poll_interval_seconds"`
LastSeenCommit string `json:"last_seen_commit"`
}
GitSourceView is the API representation of a git source (secrets omitted).
type LogLine ¶
type LogLine struct {
Seq int64 `json:"seq"`
TS string `json:"ts"`
Stream string `json:"stream"`
Line string `json:"line"`
}
LogLine is one persisted, already-redacted line of build/deploy output.
type Logs ¶
type Logs struct {
Store *store.Store
Broker *Broker
// Redact is applied to every line before persistence (nil = identity).
Redact func(string) string
// MaxLines caps persisted lines per deployment (oldest trimmed). Default 5000.
MaxLines int64
Logger *slog.Logger
Now func() time.Time
// contains filtered or unexported fields
}
Logs persists deployment output (size-capped) and publishes it live. Callers MUST wrap writers with the secrets redactor before handing them to build/run steps; Logs itself applies an additional Redact func when set (defense in depth).
type Pipeline ¶
type Pipeline struct {
Store *store.Store
Docker *docker.Client
Builder build.Builder
Routes *proxy.Writer
Keeper *secrets.Keeper
// Redactor is the process-wide redactor: every secret value the pipeline decrypts is
// registered here before it can appear anywhere (golden rule 3).
Redactor *secrets.Redactor
Logs *Logs
Audit audit.Sink
Cfg config.Config
Logger *slog.Logger
Now func() time.Time
// TestContainers labels containers redoubt.test=1 (test-suite only).
TestContainers bool
HealthTimeout time.Duration
CloneMaxBytes int64
// AllowLocalRepos permits file:// / path repositories (dev mode and tests only).
AllowLocalRepos bool
// KeepReleases is how many non-active release images are retained for rollback (default 3).
KeepReleases int
// Scanner runs the Trivy/Syft gate before a container starts (nil = scanning disabled).
Scanner scan.Scanner
}
Pipeline executes deployments: clone → build → hardened run → health-gated cutover → route. It is the River worker for queue.DeployArgs and the only code path that runs app containers.
type Poller ¶
type Poller struct {
Service *Service
Logger *slog.Logger
// Tick is the scheduler resolution (default 30s); each source is checked when its own
// interval has elapsed since its last check.
Tick time.Duration
// Resolve is overridable for tests; defaults to git.ResolveBranchHead.
Resolve func(ctx context.Context, src git.Source) (string, error)
// contains filtered or unexported fields
}
Poller is the webhook fallback for firewalled hosts: for every git source with a poll interval it periodically resolves the branch head (a pure-Go ls-remote, no clone) and triggers a deployment when it changes. It shares the Service's deploy path, so polling and webhooks are audited and queued identically.
type ReleaseView ¶
type ReleaseView struct {
ID string `json:"id"`
App string `json:"app"`
DeploymentID string `json:"deployment_id"`
ImageRef string `json:"image_ref"`
ImageDigest string `json:"image_digest,omitempty"`
Active bool `json:"active"`
SBOMPath string `json:"sbom_path,omitempty"`
ScanSummary string `json:"scan_summary,omitempty"`
CreatedAt string `json:"created_at"`
}
ReleaseView is the API representation of a release (a deployable image reference).
type RollbackRequest ¶
type RollbackRequest struct {
ReleaseID string `json:"release_id,omitempty"`
}
RollbackRequest selects the release to return to; empty means "the one before the active release".
type SecretMeta ¶
type SecretMeta struct {
Name string `json:"name"`
Version int64 `json:"version"`
UpdatedAt string `json:"updated_at"`
}
SecretMeta is what the API returns about a secret: never the value.
type Service ¶
type Service struct {
Store *store.Store
Queue *queue.Client
Keeper *secrets.Keeper
Redactor *secrets.Redactor
Routes *proxy.Writer
Docker *docker.Client
Logs *Logs
Audit audit.Sink
Cfg config.Config
Logger *slog.Logger
Now func() time.Time
// AllowLocalRepos permits file:// repositories (dev/tests).
AllowLocalRepos bool
// PublicBaseURL is used to render webhook URLs (e.g. https://redoubt.example.com).
PublicBaseURL string
}
Service is the application service layer shared by the JSON API, the HTMX UI and the CLI. It owns app records, git sources, write-only secrets and deployment triggers. RBAC checks live here (golden rule 6): every method takes the acting principal.
func (*Service) CreateApp ¶
func (s *Service) CreateApp(ctx context.Context, actor Actor, req CreateAppRequest) (AppView, error)
CreateApp registers a new app.
func (*Service) DeleteSecret ¶
DeleteSecret removes a secret.
func (*Service) Deploy ¶
func (s *Service) Deploy(ctx context.Context, actor Actor, appName string, req DeployRequest) (DeploymentView, error)
Deploy creates a deployment row and enqueues the job in the same transaction.
func (*Service) DeploymentLogs ¶
func (s *Service) DeploymentLogs(ctx context.Context, actor Actor, id string, after, limit int64) ([]LogLine, error)
DeploymentLogs returns persisted (redacted) log lines after seq.
func (*Service) GetDeployment ¶
func (s *Service) GetDeployment(ctx context.Context, actor Actor, id string) (DeploymentView, error)
GetDeployment returns one deployment (scoped to its app name for authorization clarity).
func (*Service) GetRelease ¶
GetRelease returns one release by id.
func (*Service) ListDeployments ¶
func (s *Service) ListDeployments(ctx context.Context, actor Actor, appName string, limit int64) ([]DeploymentView, error)
ListDeployments lists an app's deployments, newest first.
func (*Service) ListReleases ¶
func (s *Service) ListReleases(ctx context.Context, actor Actor, appName string, limit int64) ([]ReleaseView, error)
ListReleases returns an app's releases, newest first.
func (*Service) ListSecrets ¶
func (s *Service) ListSecrets(ctx context.Context, actor Actor, appName string) ([]SecretMeta, error)
ListSecrets returns names and metadata only.
func (*Service) Rollback ¶
func (s *Service) Rollback(ctx context.Context, actor Actor, appName string, req RollbackRequest) (DeploymentView, error)
Rollback re-deploys a previous release's image through the full health-gated deploy path (no clone, no build). Deployer and above.
func (*Service) SBOMPath ¶
func (s *Service) SBOMPath(ctx context.Context, actor Actor, releaseID, format string) (string, error)
SBOMPath returns the on-disk SBOM for a release in the given format. The path is derived from the release's deployment id and a fixed file name, then verified to lie inside <DataDir>/sboms — no user-controlled path component is ever used.
func (*Service) SetGitSource ¶
func (s *Service) SetGitSource(ctx context.Context, actor Actor, appName string, req SetGitSourceRequest) (GitSourceResult, error)
SetGitSource creates/replaces the app's git source, generating a webhook secret and an ed25519 deploy key (private half age-encrypted at rest).
func (*Service) SetScanPolicy ¶
func (s *Service) SetScanPolicy(ctx context.Context, actor Actor, appName, policy string) (AppView, error)
SetScanPolicy changes an app's vulnerability scan policy (off | warn | block).
func (*Service) SetSecret ¶
func (s *Service) SetSecret(ctx context.Context, actor Actor, appName, name, value string) (SecretMeta, error)
SetSecret creates or rotates a secret. The value is never returned or logged.
func (*Service) WebhookLookup ¶
func (s *Service) WebhookLookup(ctx context.Context, appName string) ([]byte, string, webhooks.Provider, error)
WebhookLookup implements webhooks.Handler.Lookup: returns the decrypted secret, branch and provider. Not-found conditions (invalid name, unknown app, app without a git source) are wrapped with webhooks.ErrUnknownApp so the handler answers 404 rather than treating them as a 500.
type SetGitSourceRequest ¶
type SetGitSourceRequest struct {
Provider string `json:"provider"`
RepoURL string `json:"repo_url"`
Branch string `json:"branch"`
BaseDir string `json:"base_dir"`
Dockerfile string `json:"dockerfile"`
PollInterval int64 `json:"poll_interval_seconds"`
// Token is an optional HTTPS access token (write-only; stored as the reserved secret
// REDOUBT_GIT_TOKEN, never returned).
Token string `json:"token,omitempty"`
}
SetGitSourceRequest connects a repository to an app.
type State ¶
type State string
State is a deployment's lifecycle state. Every transition is persisted and audited.
const ( StateQueued State = "queued" StateCloning State = "cloning" StateBuilding State = "building" StateStarting State = "starting" StateRouting State = "routing" StateHealthy State = "healthy" StateFailed State = "failed" StateSuperseded State = "superseded" )
Deployment states (also the CHECK constraint in migrations/00001_init.sql).
type Static ¶
type Static struct {
Docker *docker.Client
Routes *proxy.Writer
Audit audit.Sink
Cfg config.Config
Logger *slog.Logger
// Now is overridable for tests.
Now func() time.Time
// TestContainers marks every container with redoubt.test=1 (set only by the test-suite).
TestContainers bool
// HealthTimeout bounds how long Deploy waits for the container to become healthy.
HealthTimeout time.Duration
}
Static deploys pre-built images as hardened, routed app containers. It is the Phase 0 deploy path and the tail end of the Phase 1 git → build → run pipeline (which produces an image and then calls Deploy).
func (*Static) Deploy ¶
func (s *Static) Deploy(ctx context.Context, actor string, req StaticRequest) (StaticResult, error)
Deploy pulls (if needed), runs the image as a hardened container on its per-app network, attaches Traefik, writes the route, and retires the previous release.
type StaticRequest ¶
type StaticRequest struct {
App string `json:"app"`
Image string `json:"image"`
Host string `json:"host"`
Port int `json:"port"`
// Overrides are the audited escape hatches (see docker.Overrides).
Overrides docker.Overrides `json:"overrides"`
}
StaticRequest describes one deployment.
type StaticResult ¶
type StaticResult struct {
App string `json:"app"`
Release string `json:"release"`
ContainerID string `json:"container_id"`
ContainerName string `json:"container_name"`
Network string `json:"network"`
Host string `json:"host"`
URL string `json:"url"`
Warnings []docker.Warning `json:"warnings,omitempty"`
}
StaticResult is what Deploy returns.
type Worker ¶
type Worker struct {
river.WorkerDefaults[queue.DeployArgs]
P *Pipeline
}
Worker adapts Pipeline to River.