Documentation
¶
Overview ¶
Package gate decides which Bundles may cross a Gate, and drives the crossing.
Index ¶
- Constants
- func Allowed(windows []v1alpha1.Window, t time.Time) (bool, string)
- func Eligible(candidates []Candidate) []*v1alpha1.Bundle
- func NewPassage(gate *v1alpha1.Gate, bundle *v1alpha1.Bundle, actor string) *v1alpha1.Passage
- func NextAuto(gateName string, candidates []Candidate, current *v1alpha1.Bundle) *v1alpha1.Bundle
- type Candidate
- type Code
- type Reconciler
- type Verifier
Constants ¶
const ( // LabelGate associates a Passage with the Gate being crossed. LabelGate = "hecate.dev/gate" // LabelBundle associates a Passage with the Bundle being moved. LabelBundle = "hecate.dev/bundle" // ReconcileInterval is how often a Gate re-assesses health and eligibility. // // ponytail: a flat interval rather than scheduling a wake-up at the next // window opening. One reconcile per Gate per minute costs nothing, and the // precise version needs its own cron evaluation to decide when to wake. // Revisit if someone runs thousands of Gates. ReconcileInterval = time.Minute // HistoryLimit caps GateStatus.History. An unbounded list inside a status // object is rewritten on every reconcile and grows for ever; the durable // record lives on the Bundle and in the evidence store. See D13. HistoryLimit = 10 // BlockedLimit caps BundleStatus.Blocked, for the same reason HistoryLimit // caps the Gate's history: a list inside a status subresource that only // grows will eventually make the object exceed etcd's size limit, at which // point nothing can be recorded against it at all. // // It reached 733KB and 2,065 entries in twenty minutes once, all of them // the same DNS failure (#121). The retry loop that produced them is fixed, // but the cap stays: an unbounded list is a defect on its own, and this one // is on the object a promotion cannot proceed without. BlockedLimit = 10 )
const ActorController = passage.ActorController
ActorController is what a crossing the controller started records as having asked for it.
Defined in pkg/passage because the steps have to recognise it as well — an automatic crossing has no human to record as the deployer — and pkg/passage is the package both sides can see.
const DefaultRetain int32 = 20
DefaultRetain is how many finished Passages a Gate keeps when it does not say.
Higher than the Beacon's, deliberately. A Beacon emits a Bundle whenever an artifact appears, so its objects are numerous and individually cheap; a Gate produces one Passage per crossing attempt, and each is the record of how something got into an environment. Fewer objects, worth more each.
const ReasonInvalidSteps = "InvalidSteps"
ReasonInvalidSteps marks a Gate whose Passage template will not run.
Variables ¶
This section is empty.
Functions ¶
func Allowed ¶
Allowed reports whether a Passage may start at t, and why not if it may not.
Semantics:
- No windows: always allowed.
- Any deny window open: refused. Deny beats allow, because a change freeze that an allow window can override is not a change freeze.
- Allow windows present: at least one must be open.
A refusal is not a failure. Eligible Bundles queue and cross when the window opens, which is the entire point of having windows.
func Eligible ¶
Eligible returns just the Bundles that may cross, newest first.
Order matters: callers that pick one want the newest, and callers that display the list want the same order the operator would expect.
func NewPassage ¶
NewPassage builds the Passage that crosses a Bundle through a Gate.
Exported so that a crossing requested by a human is constructed identically to one the controller starts: the labels other components select on, the copied steps and vars, and the generated name are all part of the contract, and a second construction elsewhere would drift from this one silently.
Steps and vars are copied rather than referenced, so editing a Gate does not retroactively change what an in-flight or completed Passage did.
func NextAuto ¶
NextAuto returns the Bundle an automatic Gate should cross next, or nil.
Automatic crossings only ever move **forward**: the newest eligible Bundle, and only if it is newer than whatever currently occupies the Gate. Without that rule a Gate with two eligible Bundles would cross them alternately for ever, since neither is "current" once the other arrives.
Rolling back is therefore deliberately not automatic. An older Bundle stays eligible and a human can cross it by creating a Passage directly; a controller that can roll back on its own is a controller that will.
Types ¶
type Candidate ¶
type Candidate struct {
Bundle *v1alpha1.Bundle
// Eligible reports whether this Bundle may cross now.
Eligible bool
// Reason explains an ineligible verdict, in words a human can act on.
// Empty when Eligible.
Reason string
// Code is the same verdict as a stable identifier, so a caller can branch
// without reading English. The approval queue needs to know which Bundles
// are waiting on a human specifically, and matching on the prose would make
// rewording a message a breaking change.
Code Code
}
Candidate is one Bundle judged against a Gate.
func Evaluate ¶
Evaluate judges every Bundle against a Gate's admission rules.
It returns a verdict for every Bundle the Gate could conceivably admit — including the ineligible ones with their reason — because "why is nothing crossing?" is the question operators actually ask, and a filtered list cannot answer it.
Bundles from a Beacon this Gate does not admit at all are omitted entirely: they are not this Gate's business.
type Code ¶
type Code string
Code is a machine-readable reason a Bundle may not cross.
const ( // CodeAlreadyCurrent means it is already in this Gate. Not a rejection. CodeAlreadyCurrent Code = "AlreadyCurrent" // CodeUpstreamNotCleared means an upstream Gate has not passed it. CodeUpstreamNotCleared Code = "UpstreamNotCleared" // CodeAwaitingApproval means a human has not approved it for this Gate. CodeAwaitingApproval Code = "AwaitingApproval" )
type Reconciler ¶
type Reconciler struct {
client.Client
// Health assesses the Gate's watches. May be nil, in which case health is
// reported as NotApplicable rather than silently omitted.
Health *health.Registry
// Steps validates a Gate's step list. May be nil, in which case a Gate is
// not checked — which is worse than checking it, and better than a
// controller that refuses to start because nobody wired a registry.
Steps *passage.Registry
Recorder events.EventRecorder
// Verifiers is the verifier registry, injectable for tests. Nil uses the
// built-in set.
Verifiers map[string]Verifier
// Now is the clock, injectable for tests.
Now func() time.Time
}
Reconciler drives one Gate: assess health, record crossings, decide what is eligible, and start automatic Passages.
func (*Reconciler) SetupWithManager ¶
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error
SetupWithManager registers the controller.