reconcile

package
v0.2.21 Latest Latest
Warning

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

Go to latest
Published: Aug 14, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package reconcile drives a non-clobbering, idempotent sync of a desired set of Units into a ConfigHub Space. It is the engine behind the installer's "plan"/"update" and cub's "variant upload" refresh: given a desired Unit set (each with a stable merge-external-source identity), it computes adds/updates/deletes against the live Space and applies them so that operator post-install edits survive (3-way merge on update, empty-merge instead of delete on removal).

The engine never scans directories, parses manifests, or knows about AppConfig — the caller supplies the desired set already expanded, with a body (Path or Content) and a source name per Unit. All ConfigHub side effects go through a CubRunner, so callers can fork the cub binary or invoke the CLI in-process.

This engine was lifted from the installer's internal/diff. The installer still carries its own copy; migrating it onto this package is tracked by confighub issue #4806.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DefaultChangeSetSlug

func DefaultChangeSetSlug(t time.Time) string

DefaultChangeSetSlug returns the conventional slug for a reconcile ChangeSet at the given time. Stable input → stable output (no nondeterminism); callers wanting the live timestamp pass time.Now().

Format: reconcile-YYYYMMDD-HHMMSS (UTC). Avoids RFC3339's colons and timezone offsets that ConfigHub slug rules disallow.

func RestoreCommand

func RestoreCommand(space, slug string, updatedSlugs []string) string

RestoreCommand returns the verbatim shell command an operator can run to revert the updates a ChangeSet captured. updatedSlugs scopes the restore to exactly the Units modified in the ChangeSet, avoiding partial-failure noise when other Units share the ownership label but were not part of it. Uses --patch because bulk restore requires it.

Types

type ApplyOptions

type ApplyOptions struct {
	// Yes permits emptying Units that dropped out of the desired set.
	// Required when the plan contains deletes.
	Yes bool
	// ChangeSetSlug is the slug used when opening a per-Space ChangeSet for
	// updates. Defaults to DefaultChangeSetSlug(time.Now()) chosen by the
	// caller — pass a non-empty, retry-stable slug for reproducibility.
	ChangeSetSlug string
	// ChangeSetDescription is the description on the opened ChangeSet.
	// Defaults to a generic line derived from the Space's DisplayName.
	ChangeSetDescription string
	// Stdout/Stderr override the I/O streams; nil uses os.Stdout/os.Stderr.
	Stdout io.Writer
	Stderr io.Writer
	// PostSpaceHook runs after each Space's changes. Nil = no-op.
	PostSpaceHook PostSpaceHook
}

ApplyOptions tunes Apply. Zero value is the safe default: interactive refusal to empty Units without Yes, os.Stdout/os.Stderr, no hook.

type ApplyResult

type ApplyResult struct {
	Created          int
	Updated          int
	Deleted          int
	ChangeSetsOpened []ChangeSetRef
}

ApplyResult reports what Apply did.

type BinaryRunner

type BinaryRunner struct {
	Bin string
}

BinaryRunner is a CubRunner that forks the cub binary. Bin defaults to "cub" resolved from $PATH. This is the runner the installer uses; a CLI that embeds the engine can instead supply an in-process runner.

func (BinaryRunner) Run

func (r BinaryRunner) Run(ctx context.Context, args []string, stdout, stderr io.Writer) error

Run executes cub with args, streaming to stdout/stderr (nil discards).

type ChangeSetRef

type ChangeSetRef struct {
	Space        string
	Slug         string
	Component    string
	UpdatedSlugs []string
}

ChangeSetRef names one opened ChangeSet so the caller can render the revert command. UpdatedSlugs is the exact set of Units modified inside it.

type CubRunner

type CubRunner interface {
	Run(ctx context.Context, args []string, stdout, stderr io.Writer) error
}

CubRunner executes the cub CLI. args excludes the leading "cub". Either writer may be nil (discard). Implementations either fork a cub process (BinaryRunner) or dispatch to the CLI's commands in-process.

type DesiredUnit

type DesiredUnit struct {
	// Slug is the Unit slug — its stable identity in the Space.
	Slug string
	// SourceName is the merge-external-source identity passed to cub on
	// create/update. The server picks the 3-way-merge base by source type
	// per Unit, so this value only labels the change, but keeping it stable
	// across runs (typically the source file's basename) keeps the audit
	// trail coherent. Defaults to baseName(Path), or Slug when Path is empty.
	SourceName string
	// Path is a file on disk holding the Unit body. Used when Content is nil.
	Path string
	// Content, when non-nil, is the Unit body; the engine stages it to a
	// temp file for cub. Takes precedence over Path — set it for bodies that
	// don't already exist as files (e.g. AppConfig raw env content).
	Content []byte
	// Toolchain, when set, is forwarded to `cub unit create --toolchain`.
	// Empty lets cub infer it. Ignored on update (cub uses the existing Unit).
	Toolchain string
	// Bodyless marks a Unit that should be recognized as present — kept out
	// of delete candidates — but never added or updated by this engine. Used
	// for Units whose body is maintained elsewhere, e.g. an AppConfig
	// placeholder populated by an Upsert link.
	Bodyless bool
}

DesiredUnit is one Unit the caller wants present in the Space.

type Engine

type Engine struct {
	Cub CubRunner
}

Engine reconciles desired Unit sets against ConfigHub via Cub.

func New

func New(runner CubRunner) *Engine

New returns an Engine backed by runner.

func (*Engine) Apply

func (e *Engine) Apply(ctx context.Context, plan Plan, opts ApplyOptions) (ApplyResult, error)

Apply executes the plan. Updates run inside a per-Space ChangeSet (adds and deletes are not ChangeSet-revertable). An empty plan is a no-op.

func (*Engine) Compute

func (e *Engine) Compute(ctx context.Context, sources []SpaceSource) (Plan, error)

Compute produces a Plan by querying ConfigHub for the current owned Unit set (per SpaceSource.ListWhere) and diffing it against the desired set. ConfigHub state is the single source of truth: an add is a desired Unit absent from the Space, an update is a desired Unit whose dry-run merge reports non-bookkeeping changes, a delete is an owned Unit absent from the desired set (excluding Ignore and Bodyless Units). Compute mutates nothing.

type Plan

type Plan struct {
	Spaces []SpacePlan
}

Plan is the diff between the desired Unit sets and live ConfigHub state, broken down per Space.

func (Plan) Counts

func (p Plan) Counts() (adds, updates, deletes int)

Counts returns the totals across all Spaces in the plan.

func (Plan) HasChanges

func (p Plan) HasChanges() bool

HasChanges reports whether the plan would create, update, or delete anything.

type PostSpaceHook

type PostSpaceHook func(ctx context.Context, sp SpacePlan) error

PostSpaceHook runs after a Space's Unit changes (adds, updates, deletes) have been applied. Callers use it to reconcile links, refresh bookkeeping records, or any per-Space follow-up. Returning an error fails Apply.

type SlugDiff

type SlugDiff struct {
	Slug       string
	SourceName string
	Path       string
	Content    []byte
	Toolchain  string
	// DiffText is the cub -o mutations diff (ANSI-stripped, bookkeeping
	// filtered). Set only for Updates.
	DiffText string
}

SlugDiff is one entry in Adds/Updates/Deletes. For Deletes only Slug is set.

type SpacePlan

type SpacePlan struct {
	Space          string
	DisplayName    string
	DisplayVersion string
	// Adds exist in Desired but not in the live Space.
	Adds []SlugDiff
	// Updates exist in both and cub's dry-run reports non-bookkeeping changes.
	Updates []SlugDiff
	// Deletes are owned Units absent from Desired (Ignore/Bodyless excluded).
	Deletes []SlugDiff

	Target            string
	CreateLabels      []string
	CreateAnnotations []string
	UpdateAnnotations []string
}

SpacePlan is the slice of a Plan in one Space, carrying forward the stamping config from its SpaceSource so Apply is self-contained.

type SpaceSource

type SpaceSource struct {
	// Space is the target Space slug.
	Space string
	// ListWhere scopes which existing Units this reconcile owns and may
	// therefore delete: the engine lists current Units matching this cub
	// where-clause (e.g. "Labels.Package='web'"). Empty means every Unit in
	// the Space is owned.
	ListWhere string
	// Ignore lists owned Unit slugs to never treat as delete candidates even
	// when absent from Desired (e.g. a bookkeeping record Unit).
	Ignore []string
	// Desired is the target Unit set.
	Desired []DesiredUnit
	// Target is the target ref forwarded to `cub unit create --target` for
	// adds. Empty means no target binding. Existing Units keep their binding.
	Target string
	// CreateLabels / CreateAnnotations are key=value pairs stamped on adds
	// only — never re-applied on update, which would clobber operator edits.
	CreateLabels      []string
	CreateAnnotations []string
	// UpdateAnnotations are key=value pairs re-stamped on every update via a
	// separate --patch (engine-owned bookkeeping the caller wants kept in
	// sync, e.g. a version marker). Empty skips the extra patch.
	UpdateAnnotations []string
	// DisplayName / DisplayVersion are presentation only, echoed in Apply's
	// per-Space banner and the default ChangeSet description.
	DisplayName    string
	DisplayVersion string
}

SpaceSource is the desired state for one Space: the Unit set plus the ownership scoping and stamping the engine applies. Component/variant semantics live in the caller; the engine only reconciles Units.

Jump to

Keyboard shortcuts

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