Help us shape the future of Go! Take the 2026 Go Developer Survey

applier

package
v0.54.0 Latest Latest
Warning

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

Go to latest
Published: Oct 26, 2026 License: Apache-2.0 Imports: 32 Imported by: 0

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 FluxClaim's spec.kustomizationRef, flux.go), held suspended while idle and resumed for each apply, and an Argo CD Application held with automated sync off (an ArgoCDClaim'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

View Source
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 ApplierClaimReady=False on a GitTarget whose applier claim's applier applies
	// a different repository or branch than the GitTarget writes.
	ReasonSourceMismatch = "SourceMismatch"
	// ReasonArgoCDVersionNotSupported is an Application served by an Argo CD before 3.1, which has no
	// spec.syncPolicy.automated.enabled to hold automated sync off with. GitOps Reverser does not
	// support it: the applier claim writes nothing and is Stalled, so never Ready.
	ReasonArgoCDVersionNotSupported = "ArgoCDVersionNotSupported"
	// ReasonForbidden is a KubeProvider 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 ApplierClaimReady 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.

View Source
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 after a flush; 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 after a flush.
	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.

View Source
const ApplierClaimConditionInControl = "InControl"

ApplierClaimConditionInControl is an applier claim's InControl condition. For a FluxClaim: 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 applier claim's reconciler sets it.

View Source
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.

View Source
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.

View Source
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.

View Source
const DefaultPollInterval = 2 * time.Second

DefaultPollInterval is how often a requested apply's progress is read, until it is Unresolved.

View Source
const DefaultStatusRetryInterval = 5 * time.Second

DefaultStatusRetryInterval is how soon an applier claim status write that failed is tried again.

View Source
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.

View Source
const DefaultUnresolvedPollMax = time.Minute

DefaultUnresolvedPollMax caps the interval an Unresolved operation is read at, which doubles from the poll interval.

View Source
const ReasonAutomatedSyncOwnedElsewhere = "AutomatedSyncOwnedElsewhere"

ReasonAutomatedSyncOwnedElsewhere is Configured=False on an ArgoCDClaim whose Application has automated sync on, with spec.syncPolicy.automated.enabled applied by another field manager: whatever applies the Application's manifest (an app of apps, Flux, Helm) sets the switch on every apply, so it is that manifest's to change.

View Source
const ReasonHeldByAnotherClaim = "HeldByAnotherClaim"

ReasonHeldByAnotherClaim is Configured=False on an applier claim whose applier another applier claim of its kind holds: one in line that names the same object in the same KubeProvider's cluster, and has an apply in flight, holds the Kustomization already, or was created first. The applier claim takes the applier over once it no longer comes first.

View Source
const ReasonNotBound = "NotBound"

ReasonNotBound is Configured on an applier claim whose naming GitTargets are none of them eligible: Unknown while one cannot be judged yet, False once each is refused for different reasons.

View Source
const ReasonNotReferenced = "NotReferenced"

ReasonNotReferenced is Configured=Unknown (and Ready=True) on an applier claim no GitTarget names: it is asked nothing, so it does nothing.

View Source
const ReasonOwnedByApplicationSet = "OwnedByApplicationSet"

ReasonOwnedByApplicationSet is Configured=False on an ArgoCDClaim whose Application has automated sync on and was generated by an ApplicationSet. The ApplicationSet rewrites the Application's spec from its template on every reconcile, so the switch is the template's: the applier claim leaves it alone rather than fight the ApplicationSet over it.

View Source
const ReasonSuspendOwnedElsewhere = "SuspendOwnedElsewhere"

ReasonSuspendOwnedElsewhere is Configured=False on a FluxClaim 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.

View Source
const SuspendFieldManager = "gitops-reverser"

SuspendFieldManager is the field manager GitOps Reverser holds a Kustomization's spec.suspend and its reconcile.fluxcd.io/requestedAt with, and an Argo CD Application's spec.syncPolicy.automated.enabled. Every write of them is a server-side apply under it, so the applier's managedFields say who holds it.

Variables

This section is empty.

Functions

func BindingMismatch

func BindingMismatch(claim v1alpha3.ApplierClaim, want Binding) string

BindingMismatch returns why the source an applier claim last read is not want, or "" when it is. The GitTarget's ApplierClaimReady condition reads it; Request checks the same against the applier itself before it writes.

func ClaimsOfApplier added in v0.54.0

func ClaimsOfApplier(
	ctx context.Context, reader client.Reader, claim v1alpha3.ApplierClaim,
) ([]v1alpha3.ApplierClaim, error)

ClaimsOfApplier lists the applier claims other than claim that name its applier.

func DescribeCommit

func DescribeCommit(commit string) string

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, claim v1alpha3.ApplierClaim,
) (v1alpha3.ApplierClaim, error)

HeldByAnother is the applier claim that holds claim's applier when it is another, or nil when claim holds it itself.

func HeldByAnotherMessage

func HeldByAnotherMessage(claim, holder v1alpha3.ApplierClaim) string

HeldByAnotherMessage is the Configured message of an applier claim whose applier holder holds.

func Holding

func Holding(claim v1alpha3.ApplierClaim) bool

Holding reports whether claim carries its kind's finalizer: it holds its applier (a Kustomization suspended, an Application's automated sync off), or hands it back or over.

func ListApplierClaims added in v0.54.0

func ListApplierClaims(
	ctx context.Context, reader client.Reader, kind string, opts ...client.ListOption,
) ([]v1alpha3.ApplierClaim, error)

ListApplierClaims lists the applier claims of kind, with opts (everywhere without a namespace option).

func Names

func Names(target *v1alpha3.GitTarget, claim v1alpha3.ApplierClaim) bool

Names reports whether target's spec.applierClaimRef names claim.

func NamingTargets

func NamingTargets(
	ctx context.Context, reader client.Reader, claim v1alpha3.ApplierClaim,
) ([]*v1alpha3.GitTarget, error)

NamingTargets lists the GitTargets that name claim, in its namespace, and are not being deleted, by name.

Types

type AccessCheck

type AccessCheck func(ctx context.Context, attrs authorizationv1.ResourceAttributes) (bool, error)

AccessCheck reports whether the KubeProvider 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 NewArgoCD

func NewArgoCD(client dynamic.Interface) *ArgoCD

NewArgoCD returns an Argo CD client over a cluster's dynamic client.

func (*ArgoCD) AutomatedSync added in v0.54.0

func (a *ArgoCD) AutomatedSync(ctx context.Context, ref types.NamespacedName) (AutomatedSync, error)

AutomatedSync reads how the Application ref's automated sync stands.

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) ReleaseAutomatedSync added in v0.54.0

func (a *ArgoCD) ReleaseAutomatedSync(ctx context.Context, ref types.NamespacedName, wasOn *bool) (Released, error)

ReleaseAutomatedSync hands the Application ref's automated sync switch back. An empty apply under SuspendFieldManager gives the switch up, and removes it when nobody else owns it, which leaves the Application as its own spec says: on with an automated block, manual without one. That is what the hand-back does when it leaves the Application as it should be:

  • wasOn, when set, is whether automated sync was on before the applier claim held it (the applier claim's status.previousSuspendState), and the hand-back puts that back.
  • An ApplicationSet that generates the Application by then, or a field manager that applies its switch, owns the switch: the hand-back leaves the value they set. An ApplicationSet writes the switch with an update, which takes no share of a value that does not change, so giving it up would switch automated sync on against its template.

When giving the switch up would leave anything else (a server-side dry run of the empty apply says), the switch is set to what it should be and kept under GitOps Reverser's field manager, where whoever owns it now changes it as any field. An Application that is gone is a NotFound error: an apply would create one.

func (*ArgoCD) Request

func (a *ArgoCD) Request(ctx context.Context, ref types.NamespacedName, ask Ask) (Started, error)

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).

The worker never asks for an empty commit: when the remote does not carry the branch there is nothing to pin, and Argo CD reports the missing branch itself on its next refresh.

func (*ArgoCD) SwitchOff added in v0.54.0

func (a *ArgoCD) SwitchOff(ctx context.Context, ref types.NamespacedName) (bool, error)

SwitchOff holds the Application ref's automated sync off: a server-side apply of spec.syncPolicy.automated.enabled: false under SuspendFieldManager. It is forced: a click in Argo CD's UI, or a `kubectl edit`, took the switch with an update, and it returns to GitOps Reverser. It touches nothing else of automated, so prune and selfHeal stay as they are, and prune keeps applying to the syncs GitOps Reverser starts. It first checks that the API keeps the switch (switchKept), and when it does not, reports false and writes nothing: on Argo CD before 3.1 the write would switch a manual Application's automated sync ON.

func (*ArgoCD) Validate

func (a *ArgoCD) Validate(ctx context.Context, ref types.NamespacedName) Verdict

Validate answers Configured for an ArgoCDClaim's Application: that it exists in its cluster and its kind is served there, that it syncs a single source, that its automated sync is off or the applier claim may switch it off, and that this identity may update it (a request is an update) and, when the switch is the applier claim's to hold, patch it (the hold is a server-side apply). Which repository and revision it syncs, it reports in the verdict's Source, for the GitTargets naming the applier claim 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 applier claim, 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
	// InControl reports, from a fresh read, whether the applier claim is in control of its Flux
	// Kustomization (InControl=True), and errs when it is not: a resume while the applier claim's
	// reconciler is still suspending it (an adoption, an external resume) would race that suspend. It
	// is asked just before Record. Nil always is; the Argo CD client never asks it.
	InControl func(ctx context.Context) error
}

Ask is what one Request asks of an applier.

type AutomatedSync added in v0.54.0

type AutomatedSync struct {
	// On is automated sync on, by Argo CD's own rule.
	On bool
	// Held is the switch held by GitOps Reverser's field manager.
	Held bool
	// Owner names who else owns the switch (an ApplicationSet, or a field manager that applies it),
	// or is "" when the switch is GitOps Reverser's to hold.
	Owner string
}

AutomatedSync is how an Application's automated sync stands with GitOps Reverser, for its ArgoCDClaim's reconciler.

type Binding

type Binding struct {
	RepositoryURL string
	Branch        string
}

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 an applier claim's published source can be older than the applier it describes.

type Client

type Client interface {
	// Validate answers an applier claim'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 applier claim's Configured check (NewClient) pick one by the applier's kind, and use nothing else of it.

func NewClient

func NewClient(kind string, client dynamic.Interface) Client

NewClient returns the Client for an applier of kind (v1alpha3.ApplierKind*) over the dynamic client of the cluster it lives in.

type Clients

type Clients func(ctx context.Context, clusterID string) (dynamic.Interface, error)

Clients resolves the dynamic client of the cluster an applier lives in, by KubeProvider name (an applier claim's spec.kubeProviderRef). 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 FluxClaim uncached just before its Kustomization is resumed, so a suspend
	// its reconciler is making is seen (Ask.InControl). 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 after a flush, 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 an applier claim that holds this applier on this branch, are still admitted by their KubeProviders, 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(claim git.ApplierClaimRef, token string) bool

Following reports whether an attempt of this dispatcher is carrying out or following the operation with token on claim: from before it records the operation until the operation completed, or, abandoned, the Kustomization was suspended again. The FluxClaim's reconciler asks it before it leaves a Kustomization resumed for an operation its applier claim 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) 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.

func (*Dispatcher) Start

func (d *Dispatcher) Start(ctx context.Context) error

Start runs until ctx ends, and then waits for the attempts still running, which end with it. It first starts the recovered attempts asked for before it.

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 NewFlux

func NewFlux(client dynamic.Interface) *Flux

NewFlux returns a Flux client over the dynamic client of the applier's cluster.

func (*Flux) AppliedBy added in v0.54.0

AppliedBy 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 what applies the source is unknown and that is an error.

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.

func (*Flux) Release

func (f *Flux) Release(ctx context.Context, ref types.NamespacedName, wasSuspended bool) (string, error)

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. One that was suspended before GitOps Reverser held it (wasSuspended, the applier claim's status.previousSuspendState) is handed back suspended instead: an apply of spec.suspend: true alone gives the requestedAt up and keeps the suspend, under GitOps Reverser's field manager until somebody resumes it, and SuspendFieldManager is returned.

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/fluxclaim_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

func (f *Flux) Request(ctx context.Context, ref types.NamespacedName, ask Ask) (Started, error)

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.

Every reason runs the operation. The branch worker decides what needs no apply: it follows every operation, keeps the revision each applier last applied, and sends no converge of that commit (docs/design/applier-echoes.md, S2). An adopt re-applies the same commit on purpose: that is what stamps Flux's owner labels on an object the API created.

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 applier claim, 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.

The worker never asks for an empty commit: when the remote does not carry the branch there is nothing to apply, and the GitRepository reports the missing branch itself.

func (*Flux) Suspend

func (f *Flux) Suspend(ctx context.Context, ref types.NamespacedName) (string, error)

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

func (f *Flux) Validate(ctx context.Context, ref types.NamespacedName) Verdict

Validate answers Configured for a FluxClaim'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 applier claim 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 OutcomeResumed Outcome = "Resumed"

OutcomeResumed resumed the Kustomization for one apply.

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 an applier claim, judged.

type ReferenceState

type ReferenceState int

ReferenceState is how one GitTarget naming an applier claim stands with it.

const (
	// ReferenceEligible names the applier claim, is admitted, and writes what the applier fetches.
	ReferenceEligible ReferenceState = iota
	// ReferenceUndecided cannot be judged yet: its GitProvider or a KubeProvider 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 applier claim, judged.

func JudgeReferences

func JudgeReferences(
	ctx context.Context, reader client.Reader, claim v1alpha3.ApplierClaim, targets []*v1alpha3.GitTarget,
	source *Source,
) (References, error)

JudgeReferences judges each of targets, which name claim, 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 applier claim 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 applier claim.

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 applier claim is conclusively refused: there is at least one, and none is eligible or may yet be.

type Released added in v0.54.0

type Released struct {
	// On is automated sync on once handed back.
	On bool
	// Kept says why GitOps Reverser's field manager kept the switch's value instead of giving it up,
	// or is "" when it gave it up.
	Kept string
}

Released is how an Application's automated sync switch was handed back.

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 applier claim'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 applier claim's record (Recovered), for Progress to follow.

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 applier claims: status.operation and status.repairPending. It implements git.ApplierClaimStatusWriter, 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 applier claim 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 applier claim first through RecordApplyOperation, which the dispatcher calls just before the write that starts the apply, so the applier claim 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 applier claim'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 applier claim's reconciler. Each write reads the applier claim, 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 applier claims 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) PublishApplierClaimStatus added in v0.54.0

func (w *StatusWriter) PublishApplierClaimStatus(claim git.ApplierClaimRef, status git.ApplierClaimStatus)

PublishApplierClaimStatus holds status for claim 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.ApplierClaimRef, operation *v1alpha3.ApplyOperation,
) error

RecordApplyOperation writes operation on the applier claim now, and returns once it is written. It first writes what is held for the applier claim, so the operation it replaces is the worker's latest view of it. It fails, so that nothing starts the apply, when the applier claim is gone or being deleted, and when its status holds another operation that has not completed: an apply started outside GitOps Reverser, which the applier claim's reconciler recorded, or one an earlier process recorded, which the worker follows before it asks for anything.

func (*StatusWriter) Start

func (w *StatusWriter) Start(ctx context.Context) error

Start writes what is published until ctx ends.

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 FluxClaim'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 an applier claim's Configured condition.

Directories

Path Synopsis
Package argosim is the API server's field ownership of an Argo CD Application's automated sync switch, spec.syncPolicy.automated.enabled, for tests of code that holds it.
Package argosim is the API server's field ownership of an Argo CD Application's automated sync switch, spec.syncPolicy.automated.enabled, for tests of code that holds it.
Package fluxsim is a fake source cluster running kustomize-controller and source-controller, for tests of code that drives Flux.
Package fluxsim is a fake source cluster running kustomize-controller and source-controller, for tests of code that drives Flux.

Jump to

Keyboard shortcuts

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