promote

package
v0.14.2-rc.0 Latest Latest
Warning

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

Go to latest
Published: Jul 11, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Index

Constants

View Source
const RollbackRefPrefix = "rollback/"

RollbackRefPrefix marks an environment whose state was set by a manual rollback rather than a hotfix integration branch. A rollback points an environment back at a prior known-good SHA and records the prior pointer in state.<env>.ref using this prefix, so the divergence guards treat the env as off-trunk (blocking forward promotion until it rejoins) without implying a hotfix integration branch, tags, or release drafts exist.

Variables

This section is empty.

Functions

func IsRollbackRef added in v0.2.0

func IsRollbackRef(ref string) bool

IsRollbackRef reports whether ref is a rollback-divergence ref (set by a manual rollback) rather than a hotfix integration ref. The rejoin cleanup uses this to skip integration-branch and hotfix-release deletion for an env that diverged via rollback, since no such objects were created.

func NewCommand

func NewCommand() *cobra.Command

NewCommand creates the promote parent command with subcommands.

func RollbackRef added in v0.12.0

func RollbackRef(component, env string) string

RollbackRef returns the divergence ref recorded on an environment rolled back under component. The default (empty) component yields rollback/<env>, byte-identical to the historical single-component form; a named component yields rollback/<component>/<env> so each component's rollback divergence occupies a disjoint namespace, mirroring the env/<component>/<env> integration branch namespacing a hotfix uses.

Types

type AncestorFunc added in v0.2.0

type AncestorFunc func(ancestor, descendant string) (bool, error)

AncestorFunc reports whether ancestor is contained in (is an ancestor of) descendant. It mirrors git.IsAncestor and exists so the divergence guards can be exercised deterministically in tests without a real object store.

type CleanReleasesRequest added in v0.2.0

type CleanReleasesRequest struct {
	Environment string
	BaseVersion string
	// SHA is the commit the environment pointed at while diverged (the hotfix
	// merge SHA). It is passed to the release lookup as a fallback so a hotfix
	// release whose tag was already deleted by a prior partial run can still be
	// resolved by its target_commitish and removed, rather than leaking.
	SHA string
	// Spec is the resolved tag grammar. It lets the hotfix-tag match parse a
	// custom pre-release token. A zero value is normalized to the historical
	// default by the cleaner, so callers with no custom grammar can omit it.
	Spec taggrammar.Spec
}

CleanReleasesRequest describes the hotfix release objects to remove when an environment rejoins trunk. BaseVersion is the version the environment held while diverged; its rc base identifies the hotfix tags (vX.Y.Z-rc.N.hotfix.M) and drafts that the RC-shaped cleanup deliberately cannot see.

type EnvPromotion

type EnvPromotion struct {
	Environment string `json:"environment"`
	SourceEnv   string `json:"source_env"`
	SHA         string `json:"sha"`
	Version     string `json:"version"`
	NeedsDeploy bool   `json:"needs_deploy"`
}

EnvPromotion describes what should happen for a single environment

type FinalizeOption added in v0.2.0

type FinalizeOption func(*Finalizer)

FinalizeOption customizes optional, additive Finalizer behavior. Required inputs stay positional on the constructor; cross-cutting concerns such as the divergence-end cleanup are threaded through options so existing callers and signatures are unaffected.

func WithClock added in v0.8.0

func WithClock(now func() time.Time) FinalizeOption

WithClock injects the wall clock used to stamp the audit timestamps written to state during finalization. The default is time.Now; supplying a fixed clock makes the emitted CommittedAt/DeployedAt/ReleasedOn values deterministic for tests and golden output. A nil argument is ignored so the default stands.

func WithComponent added in v0.12.0

func WithComponent(name string) FinalizeOption

WithComponent scopes finalization to the named declared component, so its promoted state is recorded under state.components.<name>.<env> (and, on a publish, latest_release.components.<name>) via the scoped serializer rather than the flat single-component state.<env> form. An empty name is a no-op, preserving the single-component path byte-identically. It is set by a per-component generated promote workflow passing --component.

func WithLifecycleCleaner added in v0.2.0

func WithLifecycleCleaner(c LifecycleCleaner) FinalizeOption

WithLifecycleCleaner injects the divergence-end cleanup performed when a promotion rejoins a diverged environment to trunk. The default is a no-op, so promotions into non-diverged environments incur no cleanup behavior.

type Finalizer

type Finalizer struct {
	// contains filtered or unexported fields
}

Finalizer handles post-deployment state updates after a promotion completes. It updates the manifest state based on: - Deploy job results (from GitHub Actions) - Promotion result (from preflight output) - Release publishing status

func NewFinalizer

func NewFinalizer(configPath, targetEnv string, opts ...FinalizeOption) (*Finalizer, error)

NewFinalizer creates a new Finalizer instance. It loads the manifest from configPath and prepares for state updates. The manifest must have the ci: key at the top level.

Optional, additive behavior (such as the divergence-end lifecycle cleanup) is supplied through functional options so the required inputs stay positional.

func NewFinalizerWithKey

func NewFinalizerWithKey(configPath, targetEnv, manifestKey string, opts ...FinalizeOption) (*Finalizer, error)

NewFinalizerWithKey creates a Finalizer with a custom manifest key.

func (*Finalizer) CommitAndPush

func (f *Finalizer) CommitAndPush() error

CommitAndPush persists the manifest changes back to the trunk branch.

On real GitHub the write goes through the Contents REST API (via the gh CLI): API-created commits are signed by GitHub (shown as Verified) and, when made with a bypass-capable token, update the trunk even when a required status check protects the branch. In the act/gitea e2e environment there is no GitHub API, so the change is committed and pushed with plain git. The environment is detected exactly as the generated dispatch steps do, by GITHUB_SERVER_URL != https://github.com.

Note: We skip git pull because the workflow just checked out the repo, so it should already be at the latest state. The finalize job runs after all deploy jobs complete, and they don't modify the manifest.

func (*Finalizer) Run

func (f *Finalizer) Run() error

Run executes the finalization logic: 1. Updates environment state for all promoted environments 2. Updates per-deploy state for successful deploys only 3. Updates latest_release state if publishing a release 4. Writes the updated manifest back to disk

Note: This updates the in-memory state and writes to disk. Call this only when you want to persist changes.

func (*Finalizer) SetActor

func (f *Finalizer) SetActor(actor string)

SetActor sets the actor performing the finalization (for audit trail).

func (*Finalizer) SetDeployResult

func (f *Finalizer) SetDeployResult(name, result string)

SetDeployResult records the result of a deploy job. Valid results: "success", "failure", "skipped", "cancelled"

func (*Finalizer) SetHeadSHA added in v0.2.0

func (f *Finalizer) SetHeadSHA(sha string)

SetHeadSHA overrides the SHA written to state.<env>.sha during finalization. Call this when one or more callbacks declared auto_commits: true, meaning they pushed additional commits after the workflow started. Passing the post-callback HEAD ensures the recorded state matches what was actually built/deployed rather than the triggering commit.

When sha is empty (the default) the SHA from the promotion result is used unchanged, preserving existing behavior for runs without auto-committing callbacks.

func (*Finalizer) SetPromotionResult

func (f *Finalizer) SetPromotionResult(pr *PromotionResult)

SetPromotionResult sets the promotion result from preflight output. This contains information about which environments to update and release actions.

func (*Finalizer) WriteConfig

func (f *Finalizer) WriteConfig() error

WriteConfig writes the updated manifest back to disk. It rewrites only the mutable state subtree of the on-disk manifest, so any configuration this binary does not model is preserved rather than dropped on the round-trip.

type LifecycleCleaner added in v0.2.0

type LifecycleCleaner interface {
	// DeleteEnvBranch deletes the integration branch for env within component:
	// env/<component>/<env>, or env/<env> for the default empty component. The
	// component is recovered from the branch the rejoin is cleaning up so a
	// component's branch is deleted in its own namespace and a sibling's branch is
	// never cross-deleted.
	DeleteEnvBranch(component, env string) error
	// CleanHotfixReleases deletes the hotfix tags and release drafts for the
	// rejoining environment's prior base version.
	CleanHotfixReleases(req CleanReleasesRequest) error
}

LifecycleCleaner performs the side effects of ending a divergence: deleting the per-environment integration branch and removing the hotfix tags and release objects minted for that base. It is a small interface with a no-op default so a normal promotion into a non-diverged environment is never forced to provide one; the production implementation is wired only when finalize runs in a repository with GitHub context.

type Option added in v0.2.0

type Option func(*guardConfig)

Option customizes optional, additive behavior on a Promoter or Preflighter. Required inputs stay positional on the constructor; cross-cutting concerns (such as the git-ancestry checker) are threaded through options so new capability never changes an existing signature.

func WithAncestorFunc added in v0.2.0

func WithAncestorFunc(fn AncestorFunc) Option

WithAncestorFunc overrides the git-ancestry checker used by the promotion divergence guards. The default is git.IsAncestor; tests inject a stub so the patch-containment rule can be exercised without a populated repository.

type PreflightResult

type PreflightResult struct {
	// Mode and configuration
	Mode              PromotionMode `json:"mode"`
	Target            string        `json:"target,omitempty"`              // For cascade mode
	Force             bool          `json:"force,omitempty"`               // For default mode
	RollbackOnFailure bool          `json:"rollback_on_failure,omitempty"` // Revert successful deploys if any fails

	// Source/target info
	SourceEnv     string `json:"source_env"`
	TargetEnv     string `json:"target_env"`
	SourceSHA     string `json:"source_sha"`
	SourceVersion string `json:"source_version"`
	// SourceImageDigest is an immutable artifact identifier (e.g. a Docker image
	// content digest) for the source env, threaded alongside the mutable
	// SourceVersion so operators can pin deploys to immutable content. It is
	// sourced from the source env's build state: cascade treats artifact_id as an
	// opaque immutable id, so this is only a real content digest if the operator's
	// build populates artifact_id with one. When the source env has multiple
	// builds, the first build (by sorted build name) with a non-empty artifact_id
	// is used; when none has one, this stays empty and the deploy input is omitted.
	SourceImageDigest string `json:"source_image_digest,omitempty"`

	// Rollback SHA (target env's current SHA before promotion - what we revert to on failure)
	RollbackSHA string `json:"rollback_sha,omitempty"`

	// Changelog comparison point (SHA of first target env before promotion)
	ChangelogBaseSHA string `json:"changelog_base_sha"`

	// Environments to update
	EnvsToUpdate []string `json:"envs_to_update"`
	SkippedEnvs  []string `json:"skipped_envs"`

	// Deploy decisions
	DeploysToRun         []string `json:"deploys_to_run"`          // Local deploys to run
	ExternalDeploysToRun []string `json:"external_deploys_to_run"` // External deploys to run

	// Release info
	IsPrereleaseEnv bool   `json:"is_prerelease_env"`
	IsFinalEnv      bool   `json:"is_final_env"`
	IsCascade       bool   `json:"is_cascade"`
	ReleaseAction   string `json:"release_action"`

	// Full-pass prod deployment (separate from cascade)
	HasProdDeployment bool   `json:"has_prod_deployment"`
	ProdSHA           string `json:"prod_sha"`
	ProdVersion       string `json:"prod_version"`

	// Breaking changes
	HasBreaking       bool   `json:"has_breaking"`
	CanProceed        bool   `json:"can_proceed"`
	BreakingBlockedAt string `json:"breaking_blocked_at,omitempty"` // The transition that was blocked

	// Warnings carries non-fatal advisories surfaced during preflight, for
	// example a forced override of the diverged-env patch-containment guard.
	Warnings []string `json:"warnings,omitempty"`

	// Full promotion result for downstream use
	PromotionResult *PromotionResult `json:"promotion_result"`
}

PreflightResult contains all outputs needed by the workflow

type Preflighter

type Preflighter struct {
	// contains filtered or unexported fields
}

Preflighter handles preflight validation and planning

func NewPreflighter

func NewPreflighter(opts PreflighterOptions, options ...Option) *Preflighter

NewPreflighter creates a new Preflighter instance. Optional behavior (such as the git-ancestry checker used by the divergence guards) is supplied through functional options so the required inputs stay positional.

func (*Preflighter) Run

func (p *Preflighter) Run() (*PreflightResult, error)

Run executes the preflight checks and returns the result

func (*Preflighter) SetDeployCheck

func (p *Preflighter) SetDeployCheck(name string, include bool)

SetDeployCheck sets whether a deploy should be included

type PreflighterOptions

type PreflighterOptions struct {
	Config            *config.CICDFile
	Mode              PromotionMode
	Target            string // For cascade mode (e.g., "dev-to-prod")
	Force             bool   // For default mode
	BaseDir           string
	DeploysFilter     []string // Specific deploys to include (empty = all)
	RollbackOnFailure bool     // Revert successful deploys if any fails
	AllowDowngrade    bool     // Permit promoting an older version onto an env
}

PreflighterOptions configures the Preflighter

type Promoter

type Promoter struct {
	// contains filtered or unexported fields
}

Promoter handles promotion logic

func NewPromoter

func NewPromoter(opts PromoterOptions, options ...Option) (*Promoter, error)

NewPromoter creates a new Promoter. Optional behavior (such as the git-ancestry checker used by the divergence guards) is supplied through functional options so the required inputs stay positional.

func (*Promoter) CommitAndPush

func (p *Promoter) CommitAndPush(message string) error

CommitAndPush commits the state change and pushes to remote. It delegates to the shared git rebase-retry helper so promote and hotfix finalize write manifest state identically.

func (*Promoter) Promote

func (p *Promoter) Promote(mode PromotionMode, target string) (*PromotionResult, error)

Promote executes a promotion and returns the result mode: "default" for sequential single-step, "cascade" for atomic multi-step target: for cascade mode, the target (e.g., "dev-to-prod")

type PromoterOptions

type PromoterOptions struct {
	ConfigPath string
	DryRun     bool
	Actor      string
	Force      bool // For default mode: continue on failure
	// Component scopes persisted state to the named declared component. Empty
	// selects the single-component path, byte-identical to today.
	Component string
}

PromoterOptions configures the Promoter

type PromotionMode

type PromotionMode string

PromotionMode defines how the promotion operates

const (
	// ModeDefault promotes each environment to its immediate next (sequential)
	// Supports force flag to continue on failure
	ModeDefault PromotionMode = "default"

	// ModeCascade promotes source through all intermediate envs to target (atomic)
	// Bails entirely on any failure - no partial state updates
	ModeCascade PromotionMode = "cascade"
)

type PromotionResult

type PromotionResult struct {
	Success        bool           `json:"success"`
	Mode           PromotionMode  `json:"mode"`
	Target         string         `json:"target,omitempty"`       // Target for cascade mode (e.g., "dev-to-prod")
	Force          bool           `json:"force,omitempty"`        // Force flag for default mode
	IsCascade      bool           `json:"is_cascade"`             // True for cascade mode
	Promotions     []EnvPromotion `json:"promotions"`             // Successful promotions
	FailedEnvs     []string       `json:"failed_envs,omitempty"`  // Envs that failed (default+force)
	SkippedEnvs    []string       `json:"skipped_envs,omitempty"` // Envs skipped due to no-op
	FinalEnv       string         `json:"final_env,omitempty"`
	ReleaseAction  string         `json:"release_action,omitempty"` // "prerelease", "publish", or ""
	ReleaseData    *ReleaseData   `json:"release_data,omitempty"`
	ProdDeployment *EnvPromotion  `json:"prod_deployment,omitempty"`
	Error          string         `json:"error,omitempty"`
}

PromotionResult is the output of a promotion operation

func (*PromotionResult) ToJSON

func (r *PromotionResult) ToJSON() string

ToJSON returns the result as JSON

type ReleaseData

type ReleaseData struct {
	SHA        string `json:"sha"`
	RCVersion  string `json:"rc_version"`  // e.g., v1.0.0-rc.0
	SemVersion string `json:"sem_version"` // e.g., v1.0.0
}

ReleaseData contains info for release management

Jump to

Keyboard shortcuts

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