release

package
v0.4.9 Latest Latest
Warning

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

Go to latest
Published: Apr 17, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package release orchestrates the gate-check → compose-up → record pipeline.

Index

Constants

View Source
const ParallelismDefault = 4

ParallelismDefault controls the service fan-out for gate checks.

Variables

This section is empty.

Functions

func AllPassed

func AllPassed(outcomes []GateOutcome) bool

AllPassed reports whether every outcome was both error-free and deployable.

func DefaultSnapshotDir

func DefaultSnapshotDir() string

ComposeFilesHelper makes "snapshot dir with env name in it" conveniently.

func PolicyFor added in v0.4.0

func PolicyFor(step FailedStep) bool

PolicyFor reports whether auto-rollback should run for a given failed step.

  • StepGate: no state changed upstream, broker view untouched → skip.
  • StepComposeUp / StepSmoke: possibly partial Compose-side state → rollback.
  • StepRecord: compose & smoke both succeeded; only broker bookkeeping is inconsistent. Auto-rolling back here would revert a running healthy deployment — operator-only territory. See ADR-0006.

func RunSmoke

func RunSmoke(ctx context.Context, cfg config.SmokeConfig, targetEnv string, progress io.Writer) error

RunSmoke executes the configured smoke command. Returns nil if the command is empty. TargetEnv is injected as TARGET_ENV unless the user already set it.

func WriteOverrideYAML added in v0.4.0

func WriteOverrideYAML(path string, images map[string]string) error

WriteOverrideYAML emits a minimal Compose override that pins services.<name>.image for each entry. The file is written 0600 under a 0750 directory.

Types

type AggregateGateChecker added in v0.4.6

type AggregateGateChecker interface {
	GateChecker
	CanIDeployMany(ctx context.Context, env string, selectors []broker.CanIDeploySelector) (*broker.CanIDeploySetResult, error)
}

AggregateGateChecker extends GateChecker with the multi-selector matrix query used for all_or_nothing environments. Pipelines may type-assert on this to decide whether to fan out or batch into one request. Declared as a separate interface so legacy test fakes keep compiling.

type BrokerClient

type BrokerClient = GateChecker

BrokerClient is kept as an alias of GateChecker for backwards compatibility with existing tests; prefer GateChecker in new code.

type ComposeDeployer

type ComposeDeployer interface {
	Pull(ctx context.Context, services []string, progress io.Writer) error
	Up(ctx context.Context, opts composeadapter.UpOptions, progress io.Writer) error
	PsJSON(ctx context.Context) ([]composeadapter.ContainerStatus, error)
	RenderConfigJSON(ctx context.Context) (*composeadapter.RenderedConfig, error)
}

ComposeDeployer is the subset of the Compose adapter used during deploy.

RenderConfigJSON is required so pre-deploy snapshots can capture the resolved {service → image} map, which the rollback flow uses to pin services back to their previous images.

type DeployBroker

type DeployBroker interface {
	AggregateGateChecker
	RecordDeployment(ctx context.Context, in broker.RecordDeploymentInput) error
	HasRelation(rel string) bool
	APICallCount() int
}

DeployBroker extends GateChecker with what the deploy pipeline needs.

type DeployDeps

type DeployDeps struct {
	Broker      DeployBroker
	Compose     ComposeDeployer
	Strategy    versioning.Strategy
	Logger      *slog.Logger
	UI          UI
	Progress    io.Writer
	Stderr      io.Writer
	SnapshotDir string
	// RollbackMode controls auto-rollback on compose-up / smoke failures.
	// Zero value defaults to RollbackOn (opt-out). See ADR-0006.
	RollbackMode RollbackMode
	// RollbackTimeout bounds the rollback's own `compose up --wait`. Defaults
	// to 2× Deploy.WaitTimeout, or 3 minutes if neither is set.
	RollbackTimeout time.Duration
	// ForceRecreate passes `--force-recreate` through to compose up. Debug
	// escape hatch for the fresh-build-same-tag case. See ADR 0011.
	ForceRecreate bool
}

DeployDeps collects the collaborators Deploy needs. The CLI layer builds these from config; tests can substitute fakes.

type DeployReport

type DeployReport struct {
	Plan             *Plan
	Outcomes         []GateOutcome
	Pre              *Snapshot
	Post             *Snapshot
	PreSnapshotFile  string
	PostSnapshotFile string
	FailedAtStep     FailedStep
	FailedCause      error
	Rollback         *RollbackReport
	StartedAt        time.Time
	FinishedAt       time.Time
}

DeployReport captures what happened, successful or not. On failure the caller uses FailedAtStep to decide the exit code and whether to emit a rollback hint.

func Deploy

func Deploy(ctx context.Context, cfg *config.Config, envName, onlyService string, dryRun bool, deps DeployDeps) (*DeployReport, error)

Deploy runs the full lock-held pipeline. The caller MUST hold an environment lock for the duration of this call. See internal/lock.

Ordering is strict:

(1) pre-snapshot
(2) gate check
(3) compose up
(4) smoke (optional)
(5) record-deployment         ← always last
(6) post-snapshot

record-deployment is never called before step 5. A failure in (1)-(4) leaves the broker's view unchanged, which is what ADR 0004 requires.

type FailedStep

type FailedStep string

FailedStep is the broad category of what went wrong during deploy.

const (
	StepGate      FailedStep = "gate"
	StepComposeUp FailedStep = "compose-up"
	StepSmoke     FailedStep = "smoke"
	StepRecord    FailedStep = "record-deployment"
)

type GateChecker

type GateChecker interface {
	CanIDeploy(ctx context.Context, in broker.CanIDeployInput) (*broker.CanIDeployResult, error)
}

GateChecker is what gate-check logic needs from a Pact Broker client. Defined here (not in broker/) so release/ depends only on behaviour, per ISP and the "accept interfaces" idiom.

type GateOutcome

type GateOutcome struct {
	Service     string
	Pacticipant string
	Release     versioning.Release
	Deployable  bool
	Reason      string
	BrokerURL   string
	VerifyURL   string
	Err         error
}

GateOutcome is the per-service result of a can-i-deploy check.

func CheckService

func CheckService(ctx context.Context, client GateChecker, svc, pacticipant, envName string, rel versioning.Release) GateOutcome

CheckService runs one gate check.

func GateAll

func GateAll(ctx context.Context, client GateChecker, plan *Plan) []GateOutcome

GateAll runs gate checks for every service in plan.

Two shapes are supported:

  • Per-service fan-out (default): each service is checked independently against the broker. This is correct when deployments are independent and matches the behaviour of the legacy can-i-deploy endpoint.
  • Aggregate matrix query (all_or_nothing, 2+ services): the candidate set is sent in a single matrix request so the broker evaluates the services together. This is the fix for monolithic rollouts where every candidate is deployed with the same version: per-service queries falsely assume "everything else stays on current prod" and gate the rollout on verifications that are already known to pass within the candidate set.

Single-service plans always use the per-service path even with AllOrNothing=true, because the aggregate has no advantage for one selector and it keeps `--service foo` usable for probing.

The outcome slice is returned in plan.Services order regardless of completion order so output is deterministic.

type Plan

type Plan struct {
	Env      string
	Services []string                         // order matters (output stability)
	Releases map[string]versioning.Release    // service -> release
	Mapping  map[string]config.ServiceMapping // service -> pacticipant mapping

	// AllOrNothing mirrors the environment's all_or_nothing setting. When
	// true, GateAll routes the check through a single matrix query so the
	// broker evaluates the candidate set together. False keeps the
	// per-service fan-out.
	AllOrNothing bool
}

Plan is the resolved versions for every service being gated.

func BuildPlan

func BuildPlan(ctx context.Context, cfg *config.Config, envName, onlyService string, strat versioning.Strategy) (*Plan, error)

BuildPlan resolves versions and service-to-pacticipant mapping from config.

type RollbackDeps added in v0.4.0

type RollbackDeps struct {
	Compose     ComposeDeployer
	Logger      *slog.Logger
	UI          UI
	Progress    io.Writer
	SnapshotDir string // root under which snapshots & rollback artefacts are written
	WaitTimeout time.Duration
}

RollbackDeps bundles what ExecuteRollback needs. Mirrors DeployDeps in spirit: the CLI constructs concrete implementations; tests supply fakes.

type RollbackHint

type RollbackHint struct {
	Env             string
	FailedAt        FailedStep
	Cause           error
	PreSnapshot     *Snapshot
	PreSnapshotFile string
	PostSnapshot    *Snapshot
	Rollback        *RollbackReport
}

func (RollbackHint) Write

func (h RollbackHint) Write(w io.Writer)

Write emits the human-readable hint to w.

type RollbackMode added in v0.4.0

type RollbackMode string

RollbackMode controls whether the deploy pipeline auto-executes a rollback on compose-up or smoke failure.

const (
	// RollbackOn is the default: on applicable failures, restore the
	// pre-deploy images via an override file and re-run `compose up`.
	RollbackOn RollbackMode = "on"
	// RollbackOff preserves the pre-v0.4 behaviour: hint-only, operator
	// drives the recovery by hand.
	RollbackOff RollbackMode = "off"
	// RollbackDryRun prints the plan to stderr but never invokes the
	// compose adapter.
	RollbackDryRun RollbackMode = "dry-run"
)

func ParseRollbackMode added in v0.4.0

func ParseRollbackMode(s string) (RollbackMode, error)

ParseRollbackMode accepts the string form used by the CLI flag and returns the canonical value, or an error for anything unrecognised.

func ResolveRollbackMode added in v0.4.0

func ResolveRollbackMode(m RollbackMode) RollbackMode

ResolveRollbackMode normalises the zero value to RollbackOn so callers that omit the field get the documented default behaviour.

type RollbackPlan added in v0.4.0

type RollbackPlan struct {
	Env              string            `json:"env"`
	FromSnapshotFile string            `json:"from_snapshot_file,omitempty"`
	Images           map[string]string `json:"images"`   // service → previous image ref
	Services         []string          `json:"services"` // sorted; stable output
}

RollbackPlan is the concrete action the executor will take: services to re-apply and the image reference each must land on.

func BuildRollbackPlan added in v0.4.0

func BuildRollbackPlan(pre *Snapshot, current map[string]string) (*RollbackPlan, bool, string)

BuildRollbackPlan compares pre-snapshot images against the currently-rendered compose config. Returns (plan, true) when rollback is meaningful, or (nil, false, reason) when it should be skipped.

current may be nil when the caller could not render the current config; in that case we fall back to "restore every service recorded in pre".

type RollbackReport added in v0.4.0

type RollbackReport struct {
	Mode             RollbackMode  `json:"mode"`
	Attempted        bool          `json:"attempted"`
	Succeeded        bool          `json:"succeeded"`
	Skipped          bool          `json:"skipped,omitempty"`
	SkipReason       string        `json:"skip_reason,omitempty"`
	Plan             *RollbackPlan `json:"plan,omitempty"`
	OverrideFile     string        `json:"override_file,omitempty"`
	PostSnapshotFile string        `json:"post_snapshot_file,omitempty"`
	StartedAt        time.Time     `json:"started_at"`
	FinishedAt       time.Time     `json:"finished_at"`
	Duration         time.Duration `json:"duration"`
	Err              string        `json:"error,omitempty"`
	ReportFile       string        `json:"report_file,omitempty"`
}

RollbackReport captures the outcome. Written to .c2quay/rollbacks/<ts>.json and embedded in RollbackHint for operator-visible output.

func ExecuteRollback added in v0.4.0

func ExecuteRollback(ctx context.Context, deps RollbackDeps, plan *RollbackPlan, mode RollbackMode) (*RollbackReport, error)

ExecuteRollback drives the compensating action for a failed deploy.

Flow:

  1. Write an override YAML pinning each affected service to its previous image.
  2. Run `docker compose up -d --wait` with the override layered on top of the project's base files. We reuse the same adapter code path as deploy, so the docker/compose#10596 --wait workaround still applies.
  3. Capture a post-rollback snapshot for the audit trail.
  4. Serialise the RollbackReport next to the snapshots.

Non-fatal errors (post-snapshot capture, report write) are logged and flagged in the report but do not propagate: rollback's job is to restore the compose plane, not to guarantee perfect audit.

type ServiceDelta

type ServiceDelta struct {
	Service string
	Before  versioning.Release
	After   versioning.Release
	Changed bool
}

ServiceDelta describes how a single service's release changed between two snapshots.

func Diff

func Diff(pre, post *Snapshot) []ServiceDelta

Diff compares two snapshots by release identity.

type Snapshot

type Snapshot struct {
	CapturedAt time.Time                        `json:"captured_at"`
	Env        string                           `json:"env"`
	Releases   map[string]versioning.Release    `json:"releases,omitempty"`
	Containers []composeadapter.ContainerStatus `json:"containers"`
	// Images maps service name → resolved image reference at capture time.
	// Populated from `docker compose config --format json`. May be empty if
	// the render call fails; callers treat empty Images as "rollback not
	// possible from this snapshot".
	Images map[string]string `json:"images,omitempty"`
}

Snapshot captures the state of a compose project at a point in time. Written before and after a deploy so we can diff, generate rollback hints, and execute auto-rollback when something fails.

func CaptureSnapshot

func CaptureSnapshot(ctx context.Context, adapter snapshotSource, env string, releases map[string]versioning.Release) (*Snapshot, error)

func LoadSnapshot added in v0.4.0

func LoadSnapshot(path string) (*Snapshot, error)

LoadSnapshot reads a snapshot JSON file from disk. Used by the standalone `c2quay rollback --from-snapshot` command.

func (*Snapshot) Write

func (s *Snapshot) Write(dir, suffix string) (string, error)

Write serializes the snapshot as JSON Lines-friendly single JSON object into dir. The filename contains the timestamp and suffix.

type UI

type UI interface {
	Step(label, detail string)
	Ok(label, detail string)
	Fail(label, detail string)
	Warn(label, detail string)
}

UI is the progressive output surface used by orchestrators. output.Writer satisfies it, but release/ does not import output/ directly so tests can supply a trivial fake.

type VerifyDeps

type VerifyDeps struct {
	Broker   GateChecker
	Strategy versioning.Strategy
}

VerifyDeps bundles everything the verify orchestrator needs. The caller (CLI layer) constructs the concrete implementations and passes them in.

type VerifyReport

type VerifyReport struct {
	Plan     *Plan
	Outcomes []GateOutcome
}

VerifyReport is the output of a verify run. The CLI formats it as text or JSON.

func Verify

func Verify(ctx context.Context, cfg *config.Config, envName, onlyService string, deps VerifyDeps) (*VerifyReport, error)

Verify resolves the plan and asks the broker about every service. It does not print anything or return non-nil error on gate failure: the caller inspects the report. Operator-level errors (plan build, broker unreachable) do return err.

func (*VerifyReport) AllPassed

func (r *VerifyReport) AllPassed() bool

AllPassed is true when every outcome is deployable and error-free.

func (*VerifyReport) FirstError

func (r *VerifyReport) FirstError() error

FirstError returns the first non-nil outcome error, or nil.

Jump to

Keyboard shortcuts

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