Documentation
¶
Overview ¶
Package applier delivers apply requests: what GitOps Reverser sends the object that applies a GitTarget's folder back to its source cluster (its applier) when the branch worker has decided a commit is safe to apply. The worker decides WHEN; this package decides HOW, off the worker's queue, so a slow or unreachable applier never holds a branch.
Two kinds are served, behind one Client: a Flux Kustomization whose sourceRef is a GitRepository (a FluxTrigger's spec.kustomizationRef, flux.go), held suspended while idle and resumed for each apply, and an Argo CD Application with automated sync off (an ArgoCDTrigger's spec.applicationRef, argocd.go). Either way the applier applies only when GitOps Reverser asks. See docs/design/apply-trigger.md, "The Flux applier, suspended".
Each request starts one operation, which the dispatcher follows until the applier's own status (the handled tokens and Ready of a Kustomization, status.operationState of an Application) says it completed.
Index ¶
- Constants
- func BindingMismatch(trigger v1alpha3.ApplyTrigger, want Binding) string
- func DescribeCommit(commit string) string
- func HeldByAnother(ctx context.Context, reader client.Reader, trigger v1alpha3.ApplyTrigger) (v1alpha3.ApplyTrigger, error)
- func HeldByAnotherMessage(trigger, holder v1alpha3.ApplyTrigger) string
- func Holding(trigger v1alpha3.ApplyTrigger) bool
- func ListApplyTriggers(ctx context.Context, reader client.Reader, kind string, ...) ([]v1alpha3.ApplyTrigger, error)
- func Names(target *v1alpha3.GitTarget, trigger v1alpha3.ApplyTrigger) bool
- func NamingTargets(ctx context.Context, reader client.Reader, trigger v1alpha3.ApplyTrigger) ([]*v1alpha3.GitTarget, error)
- func TriggersOfApplier(ctx context.Context, reader client.Reader, trigger v1alpha3.ApplyTrigger) ([]v1alpha3.ApplyTrigger, error)
- type AccessCheck
- type ArgoCD
- type Ask
- type Binding
- type Client
- type Clients
- type Dispatcher
- type Flux
- func (f *Flux) Consumers(ctx context.Context, source types.NamespacedName) ([]fluxmeta.NamespacedObjectKindReference, error)
- func (f *Flux) Prefetch(ctx context.Context, ref, checked types.NamespacedName, commit string) error
- func (f *Flux) Progress(ctx context.Context, ref types.NamespacedName, s *Started) (Progress, error)
- func (f *Flux) Release(ctx context.Context, ref types.NamespacedName) (string, error)
- func (f *Flux) Request(ctx context.Context, ref types.NamespacedName, ask Ask) (Started, error)
- func (f *Flux) Suspend(ctx context.Context, ref types.NamespacedName) (string, error)
- func (f *Flux) Suspension(ctx context.Context, ref types.NamespacedName) (Suspension, error)
- func (f *Flux) Validate(ctx context.Context, ref types.NamespacedName) Verdict
- type Outcome
- type Progress
- type Reference
- type ReferenceState
- type References
- type Source
- type Started
- type StatusWriter
- func (w *StatusWriter) NeedLeaderElection() bool
- func (w *StatusWriter) PublishApplyTriggerStatus(trigger git.TriggerRef, status git.ApplyTriggerStatus)
- func (w *StatusWriter) RecordApplyOperation(ctx context.Context, ref git.TriggerRef, operation *v1alpha3.ApplyOperation) error
- func (w *StatusWriter) Start(ctx context.Context) error
- type Suspension
- type Verdict
Constants ¶
const ( // ReasonSucceeded is Configured=True: the applier exists, applies a source GitOps Reverser can // drive, and may be asked to apply. ReasonSucceeded = fluxmeta.SucceededReason // ReasonApplierNotFound is no such object in the applier's cluster, or its kind is not served // there. ReasonApplierNotFound = "ApplierNotFound" // ReasonUnsupportedSource is an applier whose source GitOps Reverser cannot drive: a // Kustomization that applies no GitRepository, or one pinned to anything but a branch, or an // Application with several sources. ReasonUnsupportedSource = "UnsupportedSource" // ReasonSourceMismatch is ApplyTriggerReady=False on a GitTarget whose trigger's applier applies // a different repository or branch than the GitTarget writes. ReasonSourceMismatch = "SourceMismatch" // ReasonAutomatedSyncEnabled is an Argo CD Application with automated sync on, which syncs on // its own and so is never asked to. ReasonAutomatedSyncEnabled = "AutomatedSyncEnabled" // ReasonForbidden is a ClusterProvider identity that may not read or write the applier. ReasonForbidden = "Forbidden" // ReasonCheckFailed is a check that could not complete: the applier's cluster did not answer. // Verdict.Err carries the cause; it says nothing about the applier itself. ReasonCheckFailed = fluxmeta.FailedReason )
Configured and ApplyTriggerReady reasons (docs/configuration.md, "Who applies this folder"). Succeeded and Failed are the generic reasons, aliased from Flux's meta package as the conditions guide asks.
const ( // EventReasonApplyRequested is a request that reached the applier. EventReasonApplyRequested = "ApplyRequested" // EventReasonApplyRequestFailed is the first failed attempt of a run of them. The branch worker // keeps the reason pending and asks again on a quiet branch; the Event is not repeated for its retries. EventReasonApplyRequestFailed = "ApplyRequestFailed" // EventReasonApplyFailed is a requested apply the applier ran and did not complete, the first of // a run of failed attempts. The branch worker asks again on a quiet branch. EventReasonApplyFailed = "ApplyFailed" // EventReasonApplyUnresolved is a requested apply that did not complete within // UnresolvedAfter. It is still followed, and its apply window stays open until it completes. EventReasonApplyUnresolved = "ApplyUnresolved" )
Event reasons recorded on a GitTarget.
const ArgoCDInitiator = "gitops-reverser"
ArgoCDInitiator is the username every sync GitOps Reverser starts carries in operation.initiatedBy, next to automated: true. It is what Argo CD's UI and history show as the sync's initiator.
const DefaultAttemptTimeout = 30 * time.Second
DefaultAttemptTimeout bounds one attempt up to its request: an applier's cluster that does not answer fails it, and the branch worker decides when to try again.
const DefaultOperationWait = 5 * time.Minute
DefaultOperationWait bounds how long an Argo CD attempt waits for an operation already running on the Application before it sets its own.
const DefaultPollInterval = 2 * time.Second
DefaultPollInterval is how often a requested apply's progress is read, until it is Unresolved.
const DefaultStatusRetryInterval = 5 * time.Second
DefaultStatusRetryInterval is how soon a trigger status write that failed is tried again.
const DefaultUnresolvedAfter = 5 * time.Minute
DefaultUnresolvedAfter is how long a requested apply may take before it is Unresolved. An apply that waits on health checks can take minutes. Past it, the operation is still followed, and its apply window stays open: the apply can still run, and closing the window would not stop it.
const DefaultUnresolvedPollMax = time.Minute
DefaultUnresolvedPollMax caps the interval an Unresolved operation is read at, which doubles from the poll interval.
const ReasonHeldByAnotherTrigger = "HeldByAnotherTrigger"
ReasonHeldByAnotherTrigger is Configured=False on a trigger whose applier another trigger of its kind holds: one in line that names the same object in the same ClusterProvider's cluster, and has an apply in flight, holds the Kustomization already, or was created first. The trigger takes the applier over once it no longer comes first.
const ReasonNotBound = "NotBound"
ReasonNotBound is Configured on a trigger whose naming GitTargets are none of them eligible: Unknown while one cannot be judged yet, False once each is refused for different reasons.
const ReasonNotReferenced = "NotReferenced"
ReasonNotReferenced is Configured=Unknown (and Ready=True) on a trigger no GitTarget names: it is asked nothing, so it does nothing.
const ReasonSuspendOwnedElsewhere = "SuspendOwnedElsewhere"
ReasonSuspendOwnedElsewhere is Configured=False on a FluxTrigger whose Kustomization's spec.suspend is applied by another field manager: whatever applies the Kustomization's manifest (a parent Kustomization, Helm, flux-operator) sets it, and would resume it on its next apply.
const SuspendFieldManager = "gitops-reverser"
SuspendFieldManager is the field manager GitOps Reverser holds a Kustomization's spec.suspend and its reconcile.fluxcd.io/requestedAt with. Every write of them is a server-side apply under it, so the Kustomization's managedFields say who holds it.
const TriggerConditionHeld = "Held"
TriggerConditionHeld is a FluxTrigger's Held condition: True (reason Suspended) once its Kustomization is suspended under GitOps Reverser's field manager and that suspend's token is handled, and True (reason Applying) while an operation of a branch worker has it resumed. The trigger's reconciler sets it.
Variables ¶
This section is empty.
Functions ¶
func BindingMismatch ¶
func BindingMismatch(trigger v1alpha3.ApplyTrigger, want Binding) string
BindingMismatch returns why the source a trigger last read is not want, or "" when it is. The GitTarget's ApplyTriggerReady condition reads it; Request checks the same against the applier itself before it writes.
func DescribeCommit ¶
DescribeCommit names a commit in an Event or a message: its short hash, or what an empty one means.
func HeldByAnother ¶
func HeldByAnother( ctx context.Context, reader client.Reader, trigger v1alpha3.ApplyTrigger, ) (v1alpha3.ApplyTrigger, error)
HeldByAnother is the trigger that holds trigger's applier when it is another, or nil when trigger holds it itself.
func HeldByAnotherMessage ¶
func HeldByAnotherMessage(trigger, holder v1alpha3.ApplyTrigger) string
HeldByAnotherMessage is the Configured message of a trigger whose applier holder holds.
func Holding ¶
func Holding(trigger v1alpha3.ApplyTrigger) bool
Holding reports whether trigger is a FluxTrigger that carries its finalizer: it holds its Kustomization, or hands it back or over.
func ListApplyTriggers ¶
func ListApplyTriggers( ctx context.Context, reader client.Reader, kind string, opts ...client.ListOption, ) ([]v1alpha3.ApplyTrigger, error)
ListApplyTriggers lists the triggers of kind, with opts (everywhere without a namespace option).
func Names ¶
func Names(target *v1alpha3.GitTarget, trigger v1alpha3.ApplyTrigger) bool
Names reports whether target's spec.applyTriggerRef names trigger.
func NamingTargets ¶
func NamingTargets( ctx context.Context, reader client.Reader, trigger v1alpha3.ApplyTrigger, ) ([]*v1alpha3.GitTarget, error)
NamingTargets lists the GitTargets that name trigger, in its namespace, and are not being deleted, by name.
func TriggersOfApplier ¶
func TriggersOfApplier( ctx context.Context, reader client.Reader, trigger v1alpha3.ApplyTrigger, ) ([]v1alpha3.ApplyTrigger, error)
TriggersOfApplier lists the triggers other than trigger that name its applier.
Types ¶
type AccessCheck ¶
type AccessCheck func(ctx context.Context, attrs authorizationv1.ResourceAttributes) (bool, error)
AccessCheck reports whether the ClusterProvider identity may perform attrs.
func SelfSubjectAccessCheck ¶
func SelfSubjectAccessCheck(client dynamic.Interface) AccessCheck
SelfSubjectAccessCheck answers an AccessCheck with a SelfSubjectAccessReview created through client, so it is answered for the identity client authenticates as. It goes through the dynamic client rather than a typed clientset because that client is the only one a remote cluster has.
type ArgoCD ¶
type ArgoCD struct {
// Now is the clock a request's token is taken from.
Now func() time.Time
// CanI is the access check Validate runs for update. NewArgoCD sets it to a
// SelfSubjectAccessReview through the same client.
CanI AccessCheck
// OperationWait bounds how long one request waits for an operation that is running to end
// before it sets its own; zero does not wait, and the request fails at once.
OperationWait time.Duration
// PollInterval is how often that wait reads the Application.
PollInterval time.Duration
// contains filtered or unexported fields
}
ArgoCD asks an Argo CD Application to sync, in one cluster.
func (*ArgoCD) Progress ¶
func (a *ArgoCD) Progress(ctx context.Context, ref types.NamespacedName, s *Started) (Progress, error)
Progress reports how far Argo CD is with the sync s asked for; the apply window closes once it completed. The operation state is this request's when its info carries the request's token. It is Running while its phase is Running (a failed sync waiting for Argo CD's own retry included, which is Running with a finishedAt) or Terminating, Succeeded once Succeeded, and Failed once Failed or Error. Until the controller starts it, the operation is still set with the token, and Requested. One Running or Terminating whose .operation somebody removed is Failed once Overdue: Argo CD processes it no further, and by then any controller pass in flight has written. An operation that is gone with no state of its own (somebody cleared it, or a later sync replaced its record) is Failed: nothing of it will run, and the worker decides again.
The token is nowhere on an Unconfirmed or Recovered operation whose update was never made, and on one whose update is still on its way. The controller moves an operation it starts into status.operationState whole, its info included (NewOperationState, controller/sync.go), and clears operation only once the state completed (setOperationState, controller/appcontroller.go), so the token is not lost on the way. Such an operation is settled (settleUnseen).
func (*ArgoCD) Request ¶
Request asks the Application ref to sync commit, the way Argo CD's automated sync would, with the revision pinned to commit and initiatedBy {username: gitops-reverser, automated: true}.
It first checks that the Application applies want and has automated sync off, and refuses with a permanent error when not: a sync of another branch would deploy that branch, and an Application that syncs on its own is not under GitOps Reverser's control. A sync somebody else started is never replaced: Request waits for it to end (OperationWait), and its update is guarded by the resourceVersion it read, so an operation set in between makes the update fail rather than be overwritten; it then reads again and waits for that one too.
current is asked before record and again after it, just before the update. When it reports false the decision no longer holds and nothing is set: errStale. record is called once before the first update, so a restart finds every sync that may have started; the update carries ask.Token whichever read it is made from.
An update that failed without saying whether it was made (a timeout, a lost connection, a server timeout or internal error) may have set the operation all the same, and Argo CD may be syncing: it returns an Unconfirmed operation rather than an error, which Progress follows by its token, so the apply window stays open until it is known to have run or never to run (settleUnseen).
An empty commit (the remote does not carry the branch) has nothing to pin, and asks nothing (OutcomeCurrent): Argo CD reports the missing branch itself on its next refresh, and the branch coming back is an observation that leaves a new request pending.
func (*ArgoCD) Validate ¶
Validate answers Configured for an ArgoCDTrigger's Application: that it exists in its cluster and its kind is served there, that it syncs a single source, that automated sync is off, and that this identity may update it, since a request is an update. Which repository and revision it syncs, it reports in the verdict's Source, for the GitTargets naming the trigger to check against what they write.
type Ask ¶
type Ask struct {
// Want is the repository and branch the applier must apply.
Want Binding
// Commit is the commit to apply; empty when the remote does not carry the branch.
Commit string
// Reason is why.
Reason v1alpha3.ApplyReason
// Token identifies the request in the applier's own status: the requestedAt a Flux
// Kustomization is resumed with, or the request entry in an Argo CD operation's info.
Token string
// Current reports whether the decision still holds; nil always does.
Current func() bool
// Record writes the operation on its trigger, and returns once it is written. It is called just
// before the write that starts the apply, so a restart finds every apply that may have started.
// Current is asked again after it, since it can wait on the status writer and the API server.
// Nil records nothing.
Record func(ctx context.Context) error
// Held reports, from a fresh read, whether the trigger holds its Flux Kustomization suspended
// (Held=True), and errs when it does not: a resume while the trigger's reconciler is still
// suspending it (an adoption, an external resume) would race that suspend. It is asked just
// before Record. Nil always holds; the Argo CD client never asks it.
Held func(ctx context.Context) error
}
Ask is what one Request asks of an applier.
type Binding ¶
Binding is the repository and branch an applier must apply from: the GitProvider's spec.url and the GitTarget's spec.branch. Request checks it before it writes anything, because a trigger's published source can be older than the applier it describes.
type Client ¶
type Client interface {
// Validate answers a trigger's Configured condition: the applier exists, applies a source GitOps
// Reverser can drive, and may be asked. It reports the source it read, whatever the verdict.
Validate(ctx context.Context, ref types.NamespacedName) Verdict
// Request starts one apply of ask.Commit, after checking the applier still applies ask.Want and
// asking ask.Current just before it writes. A permanent error is a configuration no retry can
// fix; errStale is a decision that no longer held.
Request(ctx context.Context, ref types.NamespacedName, ask Ask) (Started, error)
// Progress reports how far the applier is with the apply a Request started, and takes the
// operation's next step when it is the client's to take (a Flux Kustomization is suspended again
// after its apply). A read or write that fails is an error, and its Progress says nothing.
Progress(ctx context.Context, ref types.NamespacedName, s *Started) (Progress, error)
}
Client asks one kind of applier, in one cluster, to apply. The dispatcher (Dispatcher.client) and the trigger's Configured check (NewClient) pick one by the applier's kind, and use nothing else of it.
type Clients ¶
Clients resolves the dynamic client of the cluster an applier lives in, by ClusterProvider name (a trigger's spec.clusterProviderRef). In production it is (*watch.Manager).ApplierDynamicClient.
type Dispatcher ¶
type Dispatcher struct {
// AttemptTimeout bounds one attempt up to its request. An Argo CD attempt may wait out an
// operation already running for up to OperationWait more. Set before Start, as are the rest.
AttemptTimeout time.Duration
// OperationWait bounds an Argo CD attempt's wait for an operation already running.
OperationWait time.Duration
// UnresolvedAfter is how long a requested apply may take before it is reported Unresolved.
UnresolvedAfter time.Duration
// PollInterval is how often a requested apply's progress is read, and UnresolvedPollMax caps the
// interval an Unresolved one is read at.
PollInterval time.Duration
UnresolvedPollMax time.Duration
// Now is the clock request tokens are taken from.
Now func() time.Time
// APIReader reads a FluxTrigger uncached just before its Kustomization is resumed, so a suspend
// its reconciler is making is seen (Ask.Held). NewDispatcher sets it to the reader it was given.
APIReader client.Reader
// contains filtered or unexported fields
}
Dispatcher carries out the apply requests the branch workers decide. It implements git.ApplyRequester, and runs as a manager.Runnable beside the branch workers (it asks for leader election as they do, which keeps the two together should it ever be switched on).
It keeps no queue and makes no decision of its own. Each request is one attempt for one applier, carried out on its own goroutine and reported back to the worker that decided it: the request, then the operation it started as it moves, until it completes or the worker abandons it. The worker keeps the reason pending until an operation answers it, and decides every retry on a quiet branch. So nothing goes out that a worker did not check on a quiet branch, and nothing here can turn a delayed decision into an apply nobody checked.
What it does check is what can change between the worker's decision and the write. It reads each GitTarget again and asks only for those that are still the incarnation the worker decided for, still name a trigger that holds this applier on this branch, are still admitted by their ClusterProviders, and, for a Refusal, still consent. When no target still holds the request's reason it asks nothing at all, rather than ask for something weaker: a weaker request is the worker's to decide. Then the applier's client (Flux or Argo CD, by the applier's kind) checks the source binding before it writes.
func NewDispatcher ¶
func NewDispatcher( reader client.Reader, applierClients Clients, recorder record.EventRecorder, log logr.Logger, ) *Dispatcher
NewDispatcher returns a Dispatcher that reads GitTargets through reader (the control cluster), reaches appliers through applierClients, and records Events through recorder (nil records none).
func (*Dispatcher) Following ¶
func (d *Dispatcher) Following(trigger git.TriggerRef, token string) bool
Following reports whether an attempt of this dispatcher is carrying out or following the operation with token on trigger: from before it records the operation until the operation completed, or, abandoned, the Kustomization was suspended again. The FluxTrigger's reconciler asks it before it leaves a Kustomization resumed for an operation its trigger records: the dispatcher runs in the same process, on the leader, so an operation it does not follow is followed by nobody.
func (*Dispatcher) NeedLeaderElection ¶
func (d *Dispatcher) NeedLeaderElection() bool
NeedLeaderElection is true: the branch workers that decide requests run only on the leader.
func (*Dispatcher) PrefetchSource ¶
func (d *Dispatcher) PrefetchSource(req git.PrefetchRequest)
PrefetchSource asks the GitRepository a FluxTrigger's Kustomization applies to fetch now, ahead of the apply, and returns at once. It asks nothing when the trigger's status.source.prefetch is not true, when a FluxTrigger on the same source has an operation in flight (a resumed Kustomization applies whatever the artifact becomes), or when the artifact already holds the commit. It applies nothing, so it reports nothing.
func (*Dispatcher) RequestApply ¶
func (d *Dispatcher) RequestApply(req git.ApplyRequest)
RequestApply starts one attempt and returns at once; it is called on a branch worker's loop. The attempt reports on req.Report: once when it asked nothing, or as its operation moves until it completes. An operation the worker abandons, or one still out when the dispatcher stops, reports nothing more. Before Start, an attempt fails at once, except a recovered one, which waits for Start: the worker recovers once, and the operation it follows may have the Kustomization resumed.
type Flux ¶
type Flux struct {
// Now is the clock request tokens are taken from.
Now func() time.Time
// CanI is the access check Validate runs for patch. NewFlux sets it to a
// SelfSubjectAccessReview through the same client, so it is answered for the identity that
// will make the request.
CanI AccessCheck
// PollInterval is how often a request reads the GitRepository while it fetches.
PollInterval time.Duration
// contains filtered or unexported fields
}
Flux holds a Flux Kustomization suspended, and resumes it for each apply GitOps Reverser asks of it, in the applier's cluster. See "The Flux applier, suspended" in docs/design/apply-trigger.md: every fact about kustomize-controller this relies on is listed there, read in v1.9.6.
func (*Flux) Consumers ¶
func (f *Flux) Consumers( ctx context.Context, source types.NamespacedName, ) ([]fluxmeta.NamespacedObjectKindReference, error)
Consumers lists the objects in the cluster that apply the GitRepository source, sorted: Kustomizations whose spec.sourceRef names it, and HelmReleases whose chart is built from it, through spec.chart.spec.sourceRef or through a HelmChart spec.chartRef names. Any failure is an error, and then nothing is known: the caller must treat the source as shared.
A kind the cluster does not serve has no objects. For HelmReleases that holds only when no HelmChart is built from the source either: a release at a version not listed here would still apply through one, so then the consumers are unknown and that is an error.
func (*Flux) Prefetch ¶
func (f *Flux) Prefetch(ctx context.Context, ref, checked types.NamespacedName, commit string) error
Prefetch stamps checked, the GitRepository of the Kustomization ref whose consumers were checked, so it fetches now, unless its artifact already holds commit (errNotPrefetched). A Kustomization that applies another GitRepository now was repointed since its trigger published checked: that source's consumers were never checked, so nothing is stamped (errPrefetchHeld). It waits for nothing: the operation that applies the commit reads the artifact again before it resumes.
func (*Flux) Progress ¶
func (f *Flux) Progress(ctx context.Context, ref types.NamespacedName, s *Started) (Progress, error)
Progress reads where a resumed Kustomization's apply stands, and takes the operation's next step when it is due, so a Kustomization is never left resumed:
- Running until the reconcile that read the resume's token has ended. That reconcile marks the token handled on every return (finalizeStatus), whether it applied, failed, or waited on a dependency; and reconciles of one object never overlap. A token someone else stamped over ours stands in for it: the reconcile that handles it started after the resume.
- Then the outcome is read (verdict), from that reconcile's Ready and lastAppliedRevision, and the Kustomization is suspended again with a new token: Settling.
- Succeeded or Failed once that suspend is handled. A suspended reconcile handles it without applying, so by then every reconcile that started before the suspend has ended.
An Overdue operation (Unresolved) is suspended at once, without waiting for its token: the suspend does not stop an apply already running, but it stops whatever would follow, and its token still decides when the operation completes. A Kustomization found resumed after that suspend (by `flux resume`) is suspended again. A Recovered or Unconfirmed operation whose token is nowhere on the Kustomization was never resumed, or its token was stamped over (a suspend of its own the process stopped before reporting, `flux reconcile`): it is suspended, and judged once that suspend is handled on what ran since it was asked for. Nothing else is asked: an OutcomeCurrent request is finished.
func (*Flux) Release ¶
Release hands the Kustomization ref back to Flux: an empty apply under SuspendFieldManager drops spec.suspend and the requestedAt GitOps Reverser held, so the Kustomization is resumed and Flux owns its apply moments again. It returns the field manager that still keeps the Kustomization suspended, or "" once it is resumed.
An apply of the value a field holds shares the field with its owner, even forced, so a suspend that took over a `flux suspend` co-owns spec.suspend with that update, and the empty apply alone would leave the field set, suspended (measured in envtest: internal/controller/fluxtrigger_suspend_apiserver_test.go). Such an update entry is taken back first, with a forced spec.suspend: false, which a different value takes from every owner. A manager that applies spec.suspend keeps it: its manifest sets the field.
func (*Flux) Request ¶
Request starts one apply of commit by the Kustomization ref: it resumes the Kustomization with the request's token, and Progress follows it from there.
It first checks that the Kustomization applies want, and that no other field manager applies its spec.suspend, and refuses with a permanent error when either does not hold. Then it makes the GitRepository's artifact hold commit: when it does not, it stamps the GitRepository and waits for that fetch. An artifact that ends on another commit is errStale when the decision no longer holds, and a failure otherwise: the source cannot fetch the commit the worker checked.
A ForeignCommit or a CatchUp asks nothing (OutcomeCurrent) when the Kustomization is suspended and has applied commit: nothing applied since, and nothing will until it is resumed. A Refusal and a Repair always run the operation, because they ask for an apply of an unchanged commit.
Just before the resume it reads the Kustomization again, since the fetch can take a while: one that was replaced, applies another source now, or has its spec.suspend applied by another manager now is not resumed. It reads the artifact again and asks current: when either has moved, the decision no longer holds and nothing is resumed (errStale). Then record writes the operation on the trigger, so a restart finds it, current is asked again, since recording can wait, and only then is the Kustomization resumed: one server-side apply sets spec.suspend: false and reconcile.fluxcd.io/requestedAt to the token. That write is a new generation and a new requestedAt, so it enqueues a reconcile that reads both, which applies. It is made over the Kustomization as last read, and fails rather than land on one that changed since.
An empty commit (the remote does not carry the branch) has nothing to apply: the GitRepository is stamped, so Flux reports the missing branch itself, and nothing is resumed.
func (*Flux) Suspend ¶
Suspend suspends the Kustomization ref with a new token, and returns it. Once the Kustomization reports it handled, nothing that started before the suspend is still running.
func (*Flux) Suspension ¶
func (f *Flux) Suspension(ctx context.Context, ref types.NamespacedName) (Suspension, error)
Suspension reads how the Kustomization ref stands.
func (*Flux) Validate ¶
Validate answers Configured for a FluxTrigger's Kustomization: that it exists in its cluster and its kind is served there, that it applies a GitRepository that follows a branch, that no other field manager applies its spec.suspend, and that this identity may patch both objects, since a request stamps the one and suspends and resumes the other. Which repository and branch that is, it reports in the verdict's Source, for the GitTargets naming the trigger to check against what they write.
It reads; it never watches. A cluster without Flux installed answers ApplierNotFound from one failed GET and keeps no informer for kinds it does not serve.
type Outcome ¶
type Outcome string
Outcome is what one request did.
const OutcomeCurrent Outcome = "Current"
OutcomeCurrent asked for nothing: the applier needed no request (see each client's Request).
const OutcomeResumed Outcome = "Resumed"
OutcomeResumed resumed the Kustomization for one apply. OutcomeCurrent asks nothing: a CatchUp or ForeignCommit found it suspended, with the commit both in its artifact and applied.
const OutcomeSyncRequested Outcome = "SyncRequested"
OutcomeSyncRequested set the Application's operation to a sync of the commit.
type Progress ¶
type Progress struct {
// Outcome is git.ApplyRequested, git.ApplyRunning, git.ApplySettling, git.ApplySucceeded or
// git.ApplyFailed.
Outcome git.ApplyOutcome
// Message is why a Failed operation failed, or what a Succeeded one applied instead of the
// commit.
Message string
// SuspendToken is the requestedAt the suspend after a Flux apply carried, once written.
SuspendToken string
// AppliedRevision is the revision the applier reports it applied, once the operation completed.
AppliedRevision string
}
Progress is where the operation a Request started stands, as the applier's status shows it.
type Reference ¶
type Reference struct {
Target types.NamespacedName
State ReferenceState
// Reason is the refusal's condition reason (ReasonSourceMismatch,
// authz.ReasonNamespaceNotAuthorized), set only for a refused reference.
Reason string
// Message says why a reference is not eligible.
Message string
}
Reference is one GitTarget naming a trigger, judged.
type ReferenceState ¶
type ReferenceState int
ReferenceState is how one GitTarget naming a trigger stands with it.
const ( // ReferenceEligible names the trigger, is admitted, and writes what the applier fetches. ReferenceEligible ReferenceState = iota // ReferenceUndecided cannot be judged yet: its GitProvider or a ClusterProvider is missing, or // the applier's source has not been read. ReferenceUndecided // ReferenceRefused is not admitted, or writes another repository or branch. ReferenceRefused )
type References ¶
type References []Reference
References are the GitTargets naming one trigger, judged.
func JudgeReferences ¶
func JudgeReferences( ctx context.Context, reader client.Reader, trigger v1alpha3.ApplyTrigger, targets []*v1alpha3.GitTarget, source *Source, ) (References, error)
JudgeReferences judges each of targets, which name trigger, against the source its applier fetches, or with source nil, on admission alone: a reference that is admitted is then undecided. A read that fails with anything but NotFound is an error, so the caller keeps what it published.
func (References) Eligible ¶
func (refs References) Eligible() bool
Eligible reports whether at least one GitTarget naming the trigger is eligible.
func (References) Message ¶
func (refs References) Message() string
Message lists each GitTarget that is not eligible, and why.
func (References) Named ¶
func (refs References) Named() bool
Named reports whether any GitTarget names the trigger.
func (References) Reason ¶
func (refs References) Reason() string
Reason is the Configured reason of references that are not eligible: the refusal reason they share when every one is refused for the same one, and ReasonNotBound otherwise.
func (References) Refused ¶
func (refs References) Refused() bool
Refused reports whether every GitTarget naming the trigger is conclusively refused: there is at least one, and none is eligible or may yet be.
type Source ¶
type Source struct {
// GitRepository is the Kustomization's GitRepository (Flux only).
GitRepository types.NamespacedName
// URL is the repository.
URL string
// Revision is the branch a GitRepository follows, or an Application's targetRevision as
// written.
Revision string
}
Source is what an applier applies from: a GitRepository's spec.url and spec.ref.branch for a Kustomization, or an Application's spec.source repoURL and targetRevision.
type Started ¶
type Started struct {
// Outcome is what was asked, if anything.
Outcome Outcome
// Token identifies the request: the requestedAt a Flux Kustomization was resumed with, or the
// request entry in an Argo CD operation's info. Empty when nothing was asked.
Token string
// Commit is the commit asked for.
Commit string
// SuspendToken is the requestedAt a Flux Kustomization was suspended again with, once it was.
SuspendToken string
// Overdue is set by the dispatcher once the operation is Unresolved. A Flux Kustomization is
// then suspended at once, rather than once its apply was seen to end.
Overdue bool
// Recovered is an operation read back from the trigger's record rather than started by this
// process: the write that would start it (a Flux resume, an Argo CD update) may never have been
// made, and the record says nothing of that.
Recovered bool
// Unconfirmed is the error of the write that started the operation (a Flux resume, an Argo CD
// update), when it failed without saying whether it reached the API server (a timeout, a lost
// connection); nil for one that was answered. Such an operation is followed, because the apply
// may be running, as a Recovered one is: a Flux one is suspended again, and an Argo CD one is
// followed by its token until it completed or can no longer be set.
Unconfirmed error
// RequestedAt is when a Recovered operation was asked for, as its record says. A reconcile
// that ran since is the only evidence of its apply this process can read.
RequestedAt time.Time
// contains filtered or unexported fields
}
Started is the operation one Request started, may have started (Unconfirmed), or read back from the trigger's record (Recovered), for Progress to follow; with Outcome Current, nothing was asked.
type StatusWriter ¶
type StatusWriter struct {
// RetryInterval is how soon a failed write is tried again. Set before Start.
RetryInterval time.Duration
// contains filtered or unexported fields
}
StatusWriter writes what the branch workers publish on their apply triggers: status.operation and status.repairPending. It implements git.ApplyTriggerStatusWriter, and runs as a manager.Runnable beside the branch workers, on the leader as they are.
A publication never blocks the worker. It is held until written, and a later publication for the same trigger replaces one not yet written, so the status catches up with the worker's latest view rather than replaying each step. A write that fails is tried again after RetryInterval, unless a newer publication replaced it in between.
An operation an attempt starts reaches the trigger first through RecordApplyOperation, which the dispatcher calls just before the write that starts the apply, so the trigger never shows an operation that nothing could have applied. Writes are made one at a time, each of the latest publication, so a publication is never written over a later one.
The status may hold an operation somebody else recorded: an earlier process, or the trigger's reconciler for an apply started outside GitOps Reverser. A publication replaces status.operation only on a status that holds none, or holds the same operation (sameOperation) at an earlier point, so it never writes over a record that is not the one it carries. An operation the worker withdraws, because its attempt was recorded and then asked for no apply, is replaced by what the status held before it was recorded.
The rest of the status (the conditions, the source) belongs to the trigger's reconciler. Each write reads the trigger, sets these two fields and updates the status with the resourceVersion it read, so a status the reconciler wrote in between is never overwritten: the update conflicts and the write reads again.
func NewStatusWriter ¶
func NewStatusWriter(reader client.Reader, writer client.StatusClient, log logr.Logger) *StatusWriter
NewStatusWriter returns a StatusWriter that reads triggers through reader, uncached so a write does not lose its race to a lagging cache, and writes their status through writer.
func (*StatusWriter) NeedLeaderElection ¶
func (w *StatusWriter) NeedLeaderElection() bool
NeedLeaderElection is true: the branch workers that publish run only on the leader.
func (*StatusWriter) PublishApplyTriggerStatus ¶
func (w *StatusWriter) PublishApplyTriggerStatus(trigger git.TriggerRef, status git.ApplyTriggerStatus)
PublishApplyTriggerStatus holds status for trigger until it is written, replacing any not yet written, but not what that one withdrew. It does not block.
func (*StatusWriter) RecordApplyOperation ¶
func (w *StatusWriter) RecordApplyOperation( ctx context.Context, ref git.TriggerRef, operation *v1alpha3.ApplyOperation, ) error
RecordApplyOperation writes operation on the trigger now, and returns once it is written. It first writes what is held for the trigger, so the operation it replaces is the worker's latest view of it. It fails, so that nothing starts the apply, when the trigger is gone or being deleted, and when its status holds another operation that has not completed: an apply started outside GitOps Reverser, which the trigger's reconciler recorded, or one an earlier process recorded, which the worker follows before it asks for anything.
type Suspension ¶
type Suspension struct {
// Suspended is spec.suspend.
Suspended bool
// Held is spec.suspend held by GitOps Reverser's field manager.
Held bool
// Handled is the last requestedAt handled: no reconcile that read an earlier one is running.
Handled bool
// Applied is what the Kustomization's last apply reported, read for an apply GitOps Reverser
// did not ask for.
Applied Progress
}
Suspension is how a Kustomization stands with GitOps Reverser, for its FluxTrigger's reconciler.
type Verdict ¶
type Verdict struct {
// OK is Configured=True.
OK bool
// Reason is the condition reason.
Reason string
// Message names the object and what is wrong with it.
Message string
// Err is set only with ReasonCheckFailed: the read that failed. A caller may prefer to keep the
// verdict it already published over replacing a known answer with "could not tell".
Err error
// Source is what the applier applies, when Validate could read it.
Source *Source
}
Verdict is the answer Validate gives for a trigger's Configured condition.