Documentation
¶
Overview ¶
Package reconcile is the pull loop: for each application, fetch the repository, render it, plan against the swarm, and — when the sync policy says so — apply.
Each application runs on its own schedule in its own goroutine. One application whose repository is unreachable must not stall the others, and a shared queue would make exactly that happen.
The set of applications is mutable while the loop runs: Add, Remove and Replace start, stop and retune per-application loops under the reconciler's lock. This is what lets the app set itself be reconciled from git (issue #47); the loop that drives those operations from a diff is a separate concern.
Index ¶
- Constants
- Variables
- type Builder
- type Engine
- type Fetcher
- type Options
- type Reconciler
- func (r *Reconciler) Add(spec application.Spec) error
- func (r *Reconciler) Diffs(app string) ([]application.ReleaseDiff, error)
- func (r *Reconciler) History(ctx context.Context, app string) (application.History, error)
- func (r *Reconciler) Remove(name string) error
- func (r *Reconciler) Replace(spec application.Spec) error
- func (r *Reconciler) Run(ctx context.Context) error
- func (r *Reconciler) SetRegistryAuth(app string, resolver regauth.Resolver)
- func (r *Reconciler) Sync(ctx context.Context, app string) error
- func (r *Reconciler) SyncNow(ctx context.Context, app string) error
- func (r *Reconciler) View(app string) (application.View, bool)
- func (r *Reconciler) Views() []application.View
Constants ¶
const ( // DefaultInterval matches ArgoCD's. Every tick costs a git fetch, a full // render of every release, and a read of the swarm's release records, so // the default is deliberately not aggressive; an application that needs to // be quicker sets its own. DefaultInterval = 3 * time.Minute )
Variables ¶
var ErrNotPlanned = errors.New("no plan yet")
ErrNotPlanned is returned when an application has not been reconciled yet, so there is nothing to diff.
Functions ¶
This section is empty.
Types ¶
type Builder ¶
type Builder interface {
Build(ctx context.Context, app string, spec application.Source, co git.Checkout) (*source.Built, error)
}
Builder turns a working tree into a plan's inputs. *source.Builder implements it.
type Engine ¶
type Engine interface {
PlanApply(ctx context.Context, rf *charts.ReleaseFile, src charts.ChartSource, opts charts.PlanOptions) (*charts.Plan, error)
Apply(ctx context.Context, plan *charts.Plan, opts charts.InstallOptions) ([]charts.ApplyResult, error)
History(ctx context.Context, release string) ([]charts.Release, error)
// Uninstall removes a release's stack and its recorded revisions, keeping
// its volumes unless asked. Used only by prune, so an application whose
// sync policy does not enable it never reaches this.
Uninstall(ctx context.Context, release string, purgeVolumes bool) (*charts.UninstallResult, error)
}
Engine is the part of the chart engine this loop uses. *charts.Engine implements it.
type Fetcher ¶
type Fetcher interface {
Fetch(ctx context.Context, app string, src application.Source) (git.Checkout, error)
}
Fetcher brings an application's repository to a revision. *git.Sourcer implements it.
type Options ¶
type Options struct {
Fetcher Fetcher
Builder Builder
Swarms swarms.Registry
NewEngine func(charts.Backend) Engine
Interval time.Duration
Log *slog.Logger
Now func() time.Time
// RegistryAuth resolves an application's image-pull credential, keyed by
// application name. An application absent from the map deploys public images
// only. Built at startup by regauth.Load, which is where a missing or
// unparseable secret becomes a startup error.
RegistryAuth map[string]regauth.Resolver
// ForbiddenSecretMounts names the controller's own mounted secrets, which no
// reconciled stack may mount. Controller-wide; built at startup from the
// controller's /run/secrets. Empty disables the check.
ForbiddenSecretMounts map[string]struct{}
// ControllerID is the identity half of the owner stamp this reconciler
// writes, so that a second controller on the same swarm does not read these
// releases as its own. Empty is application.DefaultControllerID.
ControllerID string
}
Options configures a Reconciler. Everything has a working default except the fetcher and the builder, which have no sensible one.
type Reconciler ¶
type Reconciler struct {
// contains filtered or unexported fields
}
Reconciler runs the loop and holds what it last observed.
func New ¶
func New(apps []application.Spec, o Options) *Reconciler
New returns a Reconciler for the given applications.
func (*Reconciler) Add ¶
func (r *Reconciler) Add(spec application.Spec) error
Add starts reconciling a new application. It returns an error if one of that name is already present: the caller that drives the set from a git diff (issue #52) tells add from replace itself and does not rely on this being an upsert.
func (*Reconciler) Diffs ¶
func (r *Reconciler) Diffs(app string) ([]application.ReleaseDiff, error)
Diffs returns the manifest changes the last plan found. It does not re-render: what it reports is what the status reports, which is the point.
func (*Reconciler) History ¶
func (r *Reconciler) History(ctx context.Context, app string) (application.History, error)
History returns the recorded revisions of every release the application declares, newest first.
It reads the swarm rather than the status cache: history is the one thing that survives a controller restart with no database of its own, because the engine keeps one Docker Config per revision in Raft. Serving it from memory would make it disappear on exactly the restart it is most useful after.
func (*Reconciler) Remove ¶
func (r *Reconciler) Remove(name string) error
Remove stops reconciling an application and drops what was observed of it. It cancels the application's loop and waits for that goroutine to return before reporting done, so a removed application is provably no longer reconciling — a caller replacing the whole set can rely on that. The deployed stack is left running and becomes unmanaged (D-e); pruning it is separate (issue #54).
func (*Reconciler) Replace ¶
func (r *Reconciler) Replace(spec application.Spec) error
Replace swaps the spec a running application reconciles against, keyed by name, keeping its recorded status and its loop: the next tick reads the new spec, so a healthy loop is retuned rather than restarted. A change of name is not a replace — it is a Remove of the old and an Add of the new, which the caller does, because the two report as different applications.
func (*Reconciler) Run ¶
func (r *Reconciler) Run(ctx context.Context) error
Run reconciles until ctx is cancelled.
It starts a loop for every application in the set and then blocks, so that applications added while it runs are supervised under the same context and a removed one's loop is cancelled with it. A destination the swarm registry cannot resolve is not fatal here: it fails its own application on the next tick, surfaced on that application's status, rather than stopping the loop that observes every other one.
func (*Reconciler) SetRegistryAuth ¶
func (r *Reconciler) SetRegistryAuth(app string, resolver regauth.Resolver)
SetRegistryAuth installs the image-pull credential an application's images are pulled with, replacing whatever it had. A nil resolver removes it, which is what an application that has just dropped its registryAuth needs — otherwise the credential it no longer declares would keep being sent.
It exists because the set is mutable: regauth.Load resolves the applications declared at startup, and an application that joins later brings a credential that map has never seen. The caller driving the set resolves it and puts it here before the application is added, so the first reconcile already has it.
func (*Reconciler) Sync ¶
func (r *Reconciler) Sync(ctx context.Context, app string) error
Sync reconciles one application now, applying if its policy allows.
func (*Reconciler) SyncNow ¶
func (r *Reconciler) SyncNow(ctx context.Context, app string) error
SyncNow reconciles one application now and applies whatever the plan contains, whether or not the policy is automated. It is what the API's sync action calls: a manual policy means "do not deploy on a schedule", not "never deploy".
func (*Reconciler) View ¶
func (r *Reconciler) View(app string) (application.View, bool)
View returns one application's spec and last observed status.
func (*Reconciler) Views ¶
func (r *Reconciler) Views() []application.View
Views returns every application, in the order they were declared or added.