diff --git a/README.md b/README.md
index e89e02b58..da566595a 100644
--- a/README.md
+++ b/README.md
@@ -62,7 +62,7 @@ several source clusters, repositories, branches, and folders.
- **Creates Git commits.** The output is Git commits that reflect the state of your cluster.
Deploying is Flux's and Argo CD's job: run one alongside to bring changes from Git back into the
cluster. Only a folder that both GitOps Reverser writes and Flux or Argo CD applies needs more: an
- apply trigger, so GitOps Reverser decides when it is applied. See
+ applier claim, so GitOps Reverser decides when it is applied. See
[bi-directional usage](docs/bi-directional.md).
- **API-first, not API-only.** Most changes are assumed to arrive through the API, so that is what
publishing is tuned for. Your changes end up in your remote Git repo within seconds. Other
@@ -168,7 +168,7 @@ which source files changed.
The operator pushes directly to the configured branch. Flux or Argo CD deploying other folders is
unaffected. If one of them deploys the folder you write, read the
-[bi-directional guide](docs/bi-directional.md) first. Without an apply trigger, live edits can be
+[bi-directional guide](docs/bi-directional.md) first. Without an applier claim, live edits can be
reverted by Flux or Argo CD, and [replaying captured state](docs/api-first-publication.md) can
overwrite concurrent Git edits to the same object. Decide per folder which side is authoritative.
diff --git a/api/v1alpha3/applytrigger_types.go b/api/v1alpha3/applierclaim_types.go
similarity index 76%
rename from api/v1alpha3/applytrigger_types.go
rename to api/v1alpha3/applierclaim_types.go
index 55687459f..07261f26e 100644
--- a/api/v1alpha3/applytrigger_types.go
+++ b/api/v1alpha3/applierclaim_types.go
@@ -9,41 +9,41 @@ import (
"k8s.io/apimachinery/pkg/types"
)
-// What every apply trigger kind shares: the reference a GitTarget names one by, why an apply is
-// asked for, and the operation a trigger's status records. The kinds themselves are in
-// fluxtrigger_types.go and argocdtrigger_types.go. docs/definitions.md fixes the words: the
+// What every applier claim kind shares: the reference a GitTarget names one by, why an apply is
+// asked for, and the operation an applier claim's status records. The kinds themselves are in
+// fluxclaim_types.go and argocdclaim_types.go. docs/definitions.md fixes the words: the
// APPLIER is the Flux Kustomization or Argo CD Application, and the TRIGGER is this operator's
// resource that holds it.
-// The apply trigger kinds GitTarget.spec.applyTriggerRef can name.
+// The applier claim kinds GitTarget.spec.applierClaimRef can name.
const (
- // ApplyTriggerKindFlux is a FluxTrigger, which holds a Flux Kustomization.
- ApplyTriggerKindFlux = "FluxTrigger"
- // ApplyTriggerKindArgoCD is an ArgoCDTrigger, which holds an Argo CD Application.
- ApplyTriggerKindArgoCD = "ArgoCDTrigger"
+ // ApplierClaimKindFlux is a FluxClaim, which holds a Flux Kustomization.
+ ApplierClaimKindFlux = "FluxClaim"
+ // ApplierClaimKindArgoCD is an ArgoCDClaim, which holds an Argo CD Application.
+ ApplierClaimKindArgoCD = "ArgoCDClaim"
)
-// The kinds of applier a trigger holds.
+// The kinds of applier an applier claim holds.
const (
- // ApplierKindKustomization is a Flux Kustomization (FluxTrigger.spec.kustomizationRef).
+ // ApplierKindKustomization is a Flux Kustomization (FluxClaim.spec.kustomizationRef).
ApplierKindKustomization = "Kustomization"
- // ApplierKindApplication is an Argo CD Application (ArgoCDTrigger.spec.applicationRef).
+ // ApplierKindApplication is an Argo CD Application (ArgoCDClaim.spec.applicationRef).
ApplierKindApplication = "Application"
)
-// Why a kind and a name, and no namespace: the kind keeps each trigger its own schema, the way
-// Flux's sourceRef names a kind, and the namespace is the GitTarget's own because the trigger's
-// KubeProvider admits the namespace it is in. A GitTarget that could name a
-// trigger elsewhere would drive an applier that its own namespace was never admitted to.
+// Why a kind and a name, and no namespace: the kind keeps each applier claim its own schema, the way
+// Flux's sourceRef names a kind, and the namespace is the GitTarget's own because the applier claim's
+// KubeProvider admits the namespace it is in. A GitTarget that could name an
+// applier claim elsewhere would drive an applier that its own namespace was never admitted to.
-// ApplyTriggerReference names an apply trigger in the GitTarget's own namespace.
-type ApplyTriggerReference struct {
- // Kind is the trigger's kind: FluxTrigger or ArgoCDTrigger.
+// ApplierClaimReference names an applier claim in the GitTarget's own namespace.
+type ApplierClaimReference struct {
+ // Kind is the applier claim's kind: FluxClaim or ArgoCDClaim.
// +required
- // +kubebuilder:validation:Enum=FluxTrigger;ArgoCDTrigger
+ // +kubebuilder:validation:Enum=FluxClaim;ArgoCDClaim
Kind string `json:"kind"`
- // Name is the trigger's name.
+ // Name is the applier claim's name.
// +required
// +kubebuilder:validation:MinLength=1
Name string `json:"name"`
@@ -63,7 +63,7 @@ const (
ApplyReasonRepair ApplyReason = "Repair"
// ApplyReasonCatchUp adopts what the API created in the applier's folders, so the applier owns
// and can later prune it, and converges the applier once at startup and when a GitTarget first
- // names its trigger.
+ // names its applier claim.
ApplyReasonCatchUp ApplyReason = "CatchUp"
)
@@ -108,7 +108,7 @@ func (p ApplyOperationPhase) Completed() bool {
return p == ApplyOperationSucceeded || p == ApplyOperationFailed
}
-// ApplyOperation is an apply GitOps Reverser started on the trigger's applier: the one in flight,
+// ApplyOperation is an apply GitOps Reverser started on the applier claim's applier: the one in flight,
// or the last one, once it completed. The branch worker writes it on every transition.
type ApplyOperation struct {
// Phase is where the operation stands. Requested, Running, Settling and Unresolved are in
@@ -117,7 +117,7 @@ type ApplyOperation struct {
// +required
Phase ApplyOperationPhase `json:"phase"`
- // Reason is why the apply was asked for. With several GitTargets on the trigger, it is the
+ // Reason is why the apply was asked for. With several GitTargets on the applier claim, it is the
// strongest of their reasons.
// +optional
Reason ApplyReason `json:"reason,omitempty"`
@@ -156,26 +156,26 @@ type ApplyOperation struct {
Message string `json:"message,omitempty"`
}
-// ApplyTrigger is what every trigger kind answers, so the controllers, the branch workers and the
-// dispatcher handle a FluxTrigger and an ArgoCDTrigger alike.
+// ApplierClaim is what every applier claim kind answers, so the controllers, the branch workers and the
+// dispatcher handle a FluxClaim and an ArgoCDClaim alike.
// +kubebuilder:object:generate=false
-type ApplyTrigger interface {
+type ApplierClaim interface {
metav1.Object
runtime.Object
- // TriggerKind is the trigger's kind (ApplyTriggerKind*).
- TriggerKind() string
+ // ClaimKind is the applier claim's kind (ApplierClaimKind*).
+ ClaimKind() string
// KubeProviderKey is the KubeProvider whose cluster holds the applier.
KubeProviderKey() types.NamespacedName
- // ApplierKind is the kind of applier the trigger holds (ApplierKind*).
+ // ApplierKind is the kind of applier the applier claim holds (ApplierKind*).
ApplierKind() string
- // Applier is the object the trigger holds, its namespace defaulted to the trigger's own.
+ // Applier is the object the applier claim holds, its namespace defaulted to the applier claim's own.
Applier() types.NamespacedName
- // SourceBinding is the repository and branch the applier applies, as the trigger last read
+ // SourceBinding is the repository and branch the applier applies, as the applier claim last read
// them, and false when it has not read them.
SourceBinding() (repositoryURL, branch string, known bool)
// SharedSource is the source object the applier reads, which other appliers may read too, and
- // false when the applier has none it shares this way or the trigger has not read it. Appliers
+ // false when the applier has none it shares this way or the applier claim has not read it. Appliers
// sharing one have one operation at a time.
SharedSource() (source types.NamespacedName, known bool)
// Operation is status.operation, and SetOperation replaces it and status.repairPending.
@@ -190,13 +190,13 @@ type ApplyTrigger interface {
SetObservedGeneration(generation int64)
}
-// NewApplyTrigger returns an empty trigger of kind, or nil for a kind this release does not know.
-func NewApplyTrigger(kind string) ApplyTrigger {
+// NewApplierClaim returns an empty applier claim of kind, or nil for a kind this release does not know.
+func NewApplierClaim(kind string) ApplierClaim {
switch kind {
- case ApplyTriggerKindFlux:
- return &FluxTrigger{}
- case ApplyTriggerKindArgoCD:
- return &ArgoCDTrigger{}
+ case ApplierClaimKindFlux:
+ return &FluxClaim{}
+ case ApplierClaimKindArgoCD:
+ return &ArgoCDClaim{}
default:
return nil
}
diff --git a/api/v1alpha3/argocdtrigger_types.go b/api/v1alpha3/argocdclaim_types.go
similarity index 70%
rename from api/v1alpha3/argocdtrigger_types.go
rename to api/v1alpha3/argocdclaim_types.go
index 5d81e0e55..7fad93119 100644
--- a/api/v1alpha3/argocdtrigger_types.go
+++ b/api/v1alpha3/argocdclaim_types.go
@@ -10,30 +10,30 @@ import (
"k8s.io/apimachinery/pkg/types"
)
-// ArgoCDTriggerFinalizer is the finalizer an ArgoCDTrigger carries while it holds its
+// ArgoCDClaimFinalizer is the finalizer an ArgoCDClaim carries while it holds its
// Application's automated sync off: from before it first switches it off until it has handed the
-// switch back or over. Deleting the trigger, or the last eligible GitTarget naming it, hands the
-// switch back as status.previousSuspendState recorded it. A trigger that never held the
+// switch back or over. Deleting the applier claim, or the last eligible GitTarget naming it, hands the
+// switch back as status.previousSuspendState recorded it. An applier claim that never held the
// switch (one whose Application an ApplicationSet generated, or whose switch another field manager
// applies) has none.
-const ArgoCDTriggerFinalizer = "configbutler.ai/argocd-trigger"
+const ArgoCDClaimFinalizer = "configbutler.ai/argocd-claim"
// spec.clusterProviderRef was replaced by spec.kubeProviderRef, with the cluster-scoped kind it
// named. It stays in the schema only to be refused by name; the rule sits on spec so CRD validation
// ratcheting cannot wave a stored value through on the next edit.
-// ArgoCDTriggerSpec names the Argo CD Application the trigger holds, and the cluster it is in.
+// ArgoCDClaimSpec names the Argo CD Application the applier claim holds, and the cluster it is in.
// +kubebuilder:validation:XValidation:rule="!has(self.clusterProviderRef)",message="spec.clusterProviderRef was replaced by spec.kubeProviderRef, which names a namespaced KubeProvider"
-type ArgoCDTriggerSpec struct {
- // Immutable for the reason FluxTriggerSpec's references are: the trigger holds what it names.
+type ArgoCDClaimSpec struct {
+ // Immutable for the reason FluxClaimSpec's references are: the applier claim holds what it names.
// KubeProviderRef names the KubeProvider whose cluster holds the Application, such as a
// management cluster running Argo CD for the source cluster. Namespace omitted means this
- // trigger's own namespace. The provider must admit this trigger's namespace: its own, or one its
+ // applier claim's own namespace. The provider must admit this applier claim's namespace: its own, or one its
// spec.accessFrom admits. Its identity needs get, update and patch on the Application. Immutable.
// +required
// +kubebuilder:validation:XValidation:rule="self.name != ''",message="spec.kubeProviderRef.name must not be empty"
- // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.kubeProviderRef is immutable; delete and recreate the ArgoCDTrigger to hold an Application elsewhere"
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.kubeProviderRef is immutable; delete and recreate the ArgoCDClaim to hold an Application elsewhere"
KubeProviderRef meta.NamespacedObjectReference `json:"kubeProviderRef"`
// RemovedClusterProviderRef is the replaced spelling of kubeProviderRef. It is never read;
@@ -42,39 +42,39 @@ type ArgoCDTriggerSpec struct {
RemovedClusterProviderRef *meta.LocalObjectReference `json:"clusterProviderRef,omitempty"`
// ApplicationRef is the Argo CD Application that applies the folders of the GitTargets naming
- // this trigger. It must have a single spec.source whose targetRevision names a branch. The
- // trigger holds its automated sync off: it applies spec.syncPolicy.automated.enabled: false,
+ // this applier claim. It must have a single spec.source whose targetRevision names a branch. The
+ // applier claim holds its automated sync off: it applies spec.syncPolicy.automated.enabled: false,
// which keeps automated.prune for the syncs GitOps Reverser starts, switches it off again when
// somebody switches it on, and gives it up when it lets go. An Application an ApplicationSet
// generated, or whose switch another field manager applies, must have automated sync off
- // already: the switch is theirs, and the trigger only checks it. GitOps Reverser starts each
+ // already: the switch is theirs, and the applier claim only checks it. GitOps Reverser starts each
// sync by setting the Application's operation, as Argo CD's automated sync does, pinned to the
// commit it checked, and never replaces an operation that is running. Its namespace defaults to
- // the trigger's namespace. Immutable.
+ // the applier claim's namespace. Immutable.
// +required
- // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.applicationRef is immutable; delete and recreate the ArgoCDTrigger to hold another Application"
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.applicationRef is immutable; delete and recreate the ArgoCDClaim to hold another Application"
// +kubebuilder:validation:XValidation:rule="self.name != ''",message="spec.applicationRef.name must not be empty"
ApplicationRef meta.NamespacedObjectReference `json:"applicationRef"`
}
-// ArgoCDTriggerStatus is what the trigger knows about its Application, and the sync it started.
-type ArgoCDTriggerStatus struct {
+// ArgoCDClaimStatus is what the applier claim knows about its Application, and the sync it started.
+type ArgoCDClaimStatus struct {
// ObservedGeneration is the latest generation observed by the controller.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
- // Conditions are Configured (an eligible GitTarget names the trigger; the Application exists,
- // syncs a single source, has automated sync off or a switch the trigger may hold, may be
- // written, and no other ArgoCDTrigger holds it), Held (AutomatedSyncOff: automated sync is off,
+ // Conditions are Configured (an eligible GitTarget names the applier claim; the Application exists,
+ // syncs a single source, has automated sync off or a switch the applier claim may hold, may be
+ // written, and no other ArgoCDClaim holds it), Held (AutomatedSyncOff: automated sync is off,
// held by GitOps Reverser or kept off by the switch's owner; Releasing: being handed back;
- // NotReferenced; NotConfigured), and the kstatus Ready/Reconciling/Stalled trio. A trigger acts only while an
+ // NotReferenced; NotConfigured), and the kstatus Ready/Reconciling/Stalled trio. An applier claim acts only while an
// eligible GitTarget references it: one in its namespace that names it, is admitted by its
- // KubeProviders, and writes the repository and branch the Application syncs. A trigger no
+ // KubeProviders, and writes the repository and branch the Application syncs. An applier claim no
// GitTarget names reads nothing in the Application's cluster and is Configured=Unknown,
// Ready=True, both NotReferenced; one named only by GitTargets that are not eligible is
- // Configured NotBound or a refusal. Of the ArgoCDTriggers in line for one Application in one
+ // Configured NotBound or a refusal. Of the ArgoCDClaims in line for one Application in one
// cluster, one holds it: the one holding it already, then the one created first. The others are
- // Configured=False (HeldByAnotherTrigger) until it lets go, and the next in line takes it over.
+ // Configured=False (HeldByAnotherClaim) until it lets go, and the next in line takes it over.
// +optional
// +listType=map
// +listMapKey=type
@@ -83,10 +83,10 @@ type ArgoCDTriggerStatus struct {
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
// Source is the Application's spec.source, as last read, published while an eligible GitTarget
- // references the trigger. A GitTarget naming this trigger must write the repository it syncs, on
+ // references the applier claim. A GitTarget naming this applier claim must write the repository it syncs, on
// the branch its targetRevision names.
// +optional
- Source *ArgoCDTriggerSource `json:"source,omitempty"`
+ Source *ArgoCDClaimSource `json:"source,omitempty"`
// Operation is the sync GitOps Reverser started on the Application: the one in flight, or the
// last one. Absent until the first.
@@ -99,7 +99,7 @@ type ArgoCDTriggerStatus struct {
// +optional
RepairPending bool `json:"repairPending,omitempty"`
- // PreviousSuspendState is how the Application stood before the trigger first switched its
+ // PreviousSuspendState is how the Application stood before the applier claim first switched its
// automated sync off, which handing it back restores: NotSuspended (automated sync was on) or
// Suspended (it was off). Recorded with the first switch-off, and cleared once the Application is
// handed back. Absent, a hand-back gives the switch up and leaves what the Application's own spec
@@ -109,8 +109,8 @@ type ArgoCDTriggerStatus struct {
PreviousSuspendState SuspendState `json:"previousSuspendState,omitempty"`
}
-// ArgoCDTriggerSource is the source an Application syncs.
-type ArgoCDTriggerSource struct {
+// ArgoCDClaimSource is the source an Application syncs.
+type ArgoCDClaimSource struct {
// RepoURL is the Application's spec.source.repoURL.
// +optional
RepoURL string `json:"repoURL,omitempty"`
@@ -135,55 +135,55 @@ type ArgoCDTriggerSource struct {
// +kubebuilder:printcolumn:name="Message",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].message`,priority=1
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
-// ArgoCDTrigger holds an Argo CD Application's automated sync off for GitOps Reverser, which starts
+// ArgoCDClaim holds an Argo CD Application's automated sync off for GitOps Reverser, which starts
// every sync it runs, each after publishing the edits pending on the branch: after a commit
// GitOps Reverser did not push, after a refused edit, after a reverted edit, and to adopt what the
-// API created or converge the Application (CatchUp). GitTargets name it with spec.applyTriggerRef.
-type ArgoCDTrigger struct {
+// API created or converge the Application (CatchUp). GitTargets name it with spec.applierClaimRef.
+type ArgoCDClaim struct {
metav1.TypeMeta `json:",inline"`
// metadata is a standard object metadata
// +optional
metav1.ObjectMeta `json:"metadata,omitempty,omitzero"`
- // spec defines the Application the trigger holds
+ // spec defines the Application the applier claim holds
// +required
- Spec ArgoCDTriggerSpec `json:"spec"`
+ Spec ArgoCDClaimSpec `json:"spec"`
- // status defines the observed state of ArgoCDTrigger
+ // status defines the observed state of ArgoCDClaim
// +optional
- Status ArgoCDTriggerStatus `json:"status,omitempty,omitzero"`
+ Status ArgoCDClaimStatus `json:"status,omitempty,omitzero"`
}
// +kubebuilder:object:root=true
-// ArgoCDTriggerList contains a list of ArgoCDTrigger.
-type ArgoCDTriggerList struct {
+// ArgoCDClaimList contains a list of ArgoCDClaim.
+type ArgoCDClaimList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
- Items []ArgoCDTrigger `json:"items"`
+ Items []ArgoCDClaim `json:"items"`
}
func init() {
- SchemeBuilder.Register(&ArgoCDTrigger{}, &ArgoCDTriggerList{})
+ SchemeBuilder.Register(&ArgoCDClaim{}, &ArgoCDClaimList{})
}
-var _ ApplyTrigger = (*ArgoCDTrigger)(nil)
+var _ ApplierClaim = (*ArgoCDClaim)(nil)
-// TriggerKind is ArgoCDTrigger.
-func (t *ArgoCDTrigger) TriggerKind() string { return ApplyTriggerKindArgoCD }
+// ClaimKind is ArgoCDClaim.
+func (t *ArgoCDClaim) ClaimKind() string { return ApplierClaimKindArgoCD }
-// KubeProviderKey is spec.kubeProviderRef, its namespace defaulted to the trigger's own.
-func (t *ArgoCDTrigger) KubeProviderKey() types.NamespacedName {
+// KubeProviderKey is spec.kubeProviderRef, its namespace defaulted to the applier claim's own.
+func (t *ArgoCDClaim) KubeProviderKey() types.NamespacedName {
return kubeProviderKey(t.Spec.KubeProviderRef, t.Namespace)
}
// ApplierKind is Application.
-func (t *ArgoCDTrigger) ApplierKind() string { return ApplierKindApplication }
+func (t *ArgoCDClaim) ApplierKind() string { return ApplierKindApplication }
-// Applier is spec.applicationRef, its namespace defaulted to the trigger's own.
-func (t *ArgoCDTrigger) Applier() types.NamespacedName {
+// Applier is spec.applicationRef, its namespace defaulted to the applier claim's own.
+func (t *ArgoCDClaim) Applier() types.NamespacedName {
return types.NamespacedName{
Namespace: defaultedNamespace(t.Spec.ApplicationRef.Namespace, t.Namespace),
Name: t.Spec.ApplicationRef.Name,
@@ -192,7 +192,7 @@ func (t *ArgoCDTrigger) Applier() types.NamespacedName {
// SourceBinding is status.source's repoURL, and the branch its targetRevision names ("HEAD" for an
// empty one, which follows no branch GitOps Reverser writes).
-func (t *ArgoCDTrigger) SourceBinding() (string, string, bool) {
+func (t *ArgoCDClaim) SourceBinding() (string, string, bool) {
if t.Status.Source == nil {
return "", "", false
}
@@ -205,25 +205,25 @@ func (t *ArgoCDTrigger) SourceBinding() (string, string, bool) {
// SharedSource is none: every sync pins its own revision, so Applications reading one repository
// do not disturb each other's operations.
-func (t *ArgoCDTrigger) SharedSource() (types.NamespacedName, bool) {
+func (t *ArgoCDClaim) SharedSource() (types.NamespacedName, bool) {
return types.NamespacedName{}, false
}
// Operation is status.operation.
-func (t *ArgoCDTrigger) Operation() *ApplyOperation { return t.Status.Operation }
+func (t *ArgoCDClaim) Operation() *ApplyOperation { return t.Status.Operation }
// RepairPending is status.repairPending.
-func (t *ArgoCDTrigger) RepairPending() bool { return t.Status.RepairPending }
+func (t *ArgoCDClaim) RepairPending() bool { return t.Status.RepairPending }
// SetOperation replaces status.operation and status.repairPending.
-func (t *ArgoCDTrigger) SetOperation(operation *ApplyOperation, repairPending bool) {
+func (t *ArgoCDClaim) SetOperation(operation *ApplyOperation, repairPending bool) {
t.Status.Operation, t.Status.RepairPending = operation, repairPending
}
// PreviousSuspendState is status.previousSuspendState.
-func (t *ArgoCDTrigger) PreviousSuspendState() SuspendState { return t.Status.PreviousSuspendState }
+func (t *ArgoCDClaim) PreviousSuspendState() SuspendState { return t.Status.PreviousSuspendState }
// SetPreviousSuspendState replaces status.previousSuspendState.
-func (t *ArgoCDTrigger) SetPreviousSuspendState(state SuspendState) {
+func (t *ArgoCDClaim) SetPreviousSuspendState(state SuspendState) {
t.Status.PreviousSuspendState = state
}
diff --git a/api/v1alpha3/examples_test.go b/api/v1alpha3/examples_test.go
index fc02f38a9..d1a3c23f7 100644
--- a/api/v1alpha3/examples_test.go
+++ b/api/v1alpha3/examples_test.go
@@ -139,10 +139,10 @@ func newExampleObject(kind string) any {
return &ClusterWatchRule{}
case "CommitRequest":
return &CommitRequest{}
- case ApplyTriggerKindFlux:
- return &FluxTrigger{}
- case ApplyTriggerKindArgoCD:
- return &ArgoCDTrigger{}
+ case ApplierClaimKindFlux:
+ return &FluxClaim{}
+ case ApplierClaimKindArgoCD:
+ return &ArgoCDClaim{}
}
return nil
}
diff --git a/api/v1alpha3/fluxtrigger_types.go b/api/v1alpha3/fluxclaim_types.go
similarity index 70%
rename from api/v1alpha3/fluxtrigger_types.go
rename to api/v1alpha3/fluxclaim_types.go
index e2ee6310f..783e1d950 100644
--- a/api/v1alpha3/fluxtrigger_types.go
+++ b/api/v1alpha3/fluxclaim_types.go
@@ -8,33 +8,33 @@ import (
"k8s.io/apimachinery/pkg/types"
)
-// FluxTriggerFinalizer is the finalizer a FluxTrigger carries while it holds its Kustomization
+// FluxClaimFinalizer is the finalizer a FluxClaim carries while it holds its Kustomization
// suspended: from before its first adoption write until it has handed the Kustomization back or
-// over. Deleting the trigger, or the last eligible GitTarget naming it, suspends the Kustomization
+// over. Deleting the applier claim, or the last eligible GitTarget naming it, suspends the Kustomization
// once more, waits until nothing started before that suspend is running, and then hands it back to
// Flux as status.previousSuspendState recorded it: spec.suspend is released, so the Kustomization is resumed,
-// unless it was suspended before the trigger held it, and stays so. A trigger that never held its
+// unless it was suspended before the applier claim held it, and stays so. An applier claim that never held its
// Kustomization has none.
-const FluxTriggerFinalizer = "configbutler.ai/flux-trigger"
+const FluxClaimFinalizer = "configbutler.ai/flux-claim"
// spec.clusterProviderRef was replaced by spec.kubeProviderRef, with the cluster-scoped kind it
// named. It stays in the schema only to be refused by name; the rule sits on spec so CRD validation
// ratcheting cannot wave a stored value through on the next edit.
-// FluxTriggerSpec names the Flux Kustomization the trigger holds, and the cluster it is in.
+// FluxClaimSpec names the Flux Kustomization the applier claim holds, and the cluster it is in.
// +kubebuilder:validation:XValidation:rule="!has(self.clusterProviderRef)",message="spec.clusterProviderRef was replaced by spec.kubeProviderRef, which names a namespaced KubeProvider"
-type FluxTriggerSpec struct {
- // Why immutable: the trigger holds the Kustomization it names (it suspends it while idle and
+type FluxClaimSpec struct {
+ // Why immutable: the applier claim holds the Kustomization it names (it suspends it while idle and
// hands it back when deleted), so pointing it elsewhere would leave the first one held by
- // nothing. Delete and recreate the trigger instead; its finalizer hands the old one back.
+ // nothing. Delete and recreate the applier claim instead; its finalizer hands the old one back.
// KubeProviderRef names the KubeProvider whose cluster holds the Kustomization, such as a
// management cluster running Flux for the source cluster. Namespace omitted means this
- // trigger's own namespace. The provider must admit this trigger's namespace: its own, or one its
+ // applier claim's own namespace. The provider must admit this applier claim's namespace: its own, or one its
// spec.accessFrom admits. Its identity needs get and patch on the Kustomization and its GitRepository. Immutable.
// +required
// +kubebuilder:validation:XValidation:rule="self.name != ''",message="spec.kubeProviderRef.name must not be empty"
- // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.kubeProviderRef is immutable; delete and recreate the FluxTrigger to hold a Kustomization elsewhere"
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.kubeProviderRef is immutable; delete and recreate the FluxClaim to hold a Kustomization elsewhere"
KubeProviderRef meta.NamespacedObjectReference `json:"kubeProviderRef"`
// RemovedClusterProviderRef is the replaced spelling of kubeProviderRef. It is never read;
@@ -43,33 +43,33 @@ type FluxTriggerSpec struct {
RemovedClusterProviderRef *meta.LocalObjectReference `json:"clusterProviderRef,omitempty"`
// KustomizationRef is the Flux Kustomization that applies the folders of the GitTargets naming
- // this trigger. Its spec.sourceRef must be a GitRepository that follows a branch. Its namespace
- // defaults to the trigger's namespace. Immutable.
+ // this applier claim. Its spec.sourceRef must be a GitRepository that follows a branch. Its namespace
+ // defaults to the applier claim's namespace. Immutable.
// +required
- // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.kustomizationRef is immutable; delete and recreate the FluxTrigger to hold another Kustomization"
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.kustomizationRef is immutable; delete and recreate the FluxClaim to hold another Kustomization"
// +kubebuilder:validation:XValidation:rule="self.name != ''",message="spec.kustomizationRef.name must not be empty"
KustomizationRef meta.NamespacedObjectReference `json:"kustomizationRef"`
}
-// FluxTriggerStatus is what the trigger knows about its Kustomization, and the apply it started.
-type FluxTriggerStatus struct {
+// FluxClaimStatus is what the applier claim knows about its Kustomization, and the apply it started.
+type FluxClaimStatus struct {
// ObservedGeneration is the latest generation observed by the controller.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
- // Conditions are Configured (an eligible GitTarget names the trigger; the Kustomization exists,
- // applies a GitRepository that follows a branch, may be patched, and no other FluxTrigger holds
+ // Conditions are Configured (an eligible GitTarget names the applier claim; the Kustomization exists,
+ // applies a GitRepository that follows a branch, may be patched, and no other FluxClaim holds
// it), Held (Suspended: suspended under GitOps Reverser with the last suspend handled; Applying:
// resumed for an apply GitOps Reverser started; Suspending: a suspend is not handled yet;
// Releasing: being handed back; NotReferenced; NotConfigured), and the kstatus
- // Ready/Reconciling/Stalled trio. A trigger acts only while an eligible GitTarget references it:
+ // Ready/Reconciling/Stalled trio. An applier claim acts only while an eligible GitTarget references it:
// one in its namespace that names it, is admitted by its KubeProviders, and writes the
- // repository and branch the GitRepository fetches. A trigger no GitTarget names reads nothing
+ // repository and branch the GitRepository fetches. An applier claim no GitTarget names reads nothing
// in the Kustomization's cluster and is Configured=Unknown, Ready=True, both NotReferenced; one
// named only by GitTargets that are not eligible is Configured NotBound or a refusal. Of the
- // FluxTriggers in line for one Kustomization in one cluster, one holds it: the one with an apply
+ // FluxClaims in line for one Kustomization in one cluster, one holds it: the one with an apply
// in flight, then the one holding it already, then the one created first. The others are
- // Configured=False (HeldByAnotherTrigger) until it lets go, and the next in line takes it over.
+ // Configured=False (HeldByAnotherClaim) until it lets go, and the next in line takes it over.
// +optional
// +listType=map
// +listMapKey=type
@@ -78,10 +78,10 @@ type FluxTriggerStatus struct {
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
// Source is the GitRepository the Kustomization applies, as last read, published while an
- // eligible GitTarget references the trigger. A GitTarget naming this trigger must write the
+ // eligible GitTarget references the applier claim. A GitTarget naming this applier claim must write the
// repository and branch it fetches.
// +optional
- Source *FluxTriggerSource `json:"source,omitempty"`
+ Source *FluxClaimSource `json:"source,omitempty"`
// Operation is the apply GitOps Reverser started on the Kustomization: the one in flight, or
// the last one. Absent until the first.
@@ -94,7 +94,7 @@ type FluxTriggerStatus struct {
// +optional
RepairPending bool `json:"repairPending,omitempty"`
- // PreviousSuspendState is how the Kustomization stood before the trigger first suspended it,
+ // PreviousSuspendState is how the Kustomization stood before the applier claim first suspended it,
// which handing it back restores: one that was Suspended stays suspended, one that was
// NotSuspended is resumed. Recorded with the first suspend, and cleared once the Kustomization is
// handed back. Absent, a hand-back resumes the Kustomization.
@@ -102,8 +102,8 @@ type FluxTriggerStatus struct {
PreviousSuspendState SuspendState `json:"previousSuspendState,omitempty"`
}
-// FluxTriggerSource is the GitRepository a Kustomization applies.
-type FluxTriggerSource struct {
+// FluxClaimSource is the GitRepository a Kustomization applies.
+type FluxClaimSource struct {
// GitRepository is the Kustomization's spec.sourceRef, its namespace defaulted.
// +required
GitRepository meta.NamespacedObjectReference `json:"gitRepository"`
@@ -117,11 +117,11 @@ type FluxTriggerSource struct {
Branch string `json:"branch,omitempty"`
// Why information only (decision 2 of 2026-10-17 in docs/design/apply-trigger.md): one
- // GitRepository for several Kustomizations is a normal setup, and a consumer no trigger names
+ // GitRepository for several Kustomizations is a normal setup, and a consumer no applier claim names
// applies a folder that has no promise of protection anyway.
// OtherConsumers are the other objects in the cluster that apply this GitRepository and that no
- // FluxTrigger holds: Kustomizations, and HelmReleases whose chart comes from it. Each applies a
+ // FluxClaim holds: Kustomizations, and HelmReleases whose chart comes from it. Each applies a
// new artifact the moment it arrives. Information only: it never affects Ready.
// +optional
// +listType=atomic
@@ -142,55 +142,55 @@ type FluxTriggerSource struct {
// +kubebuilder:printcolumn:name="Message",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].message`,priority=1
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
-// FluxTrigger holds a Flux Kustomization for GitOps Reverser, which starts every apply it makes,
+// FluxClaim holds a Flux Kustomization for GitOps Reverser, which starts every apply it makes,
// each after publishing the edits pending on the branch: after a commit GitOps Reverser did not
// push, after a refused edit, after a reverted edit, and to adopt what the API created or converge
-// the Kustomization (CatchUp). GitTargets name it with spec.applyTriggerRef.
-type FluxTrigger struct {
+// the Kustomization (CatchUp). GitTargets name it with spec.applierClaimRef.
+type FluxClaim struct {
metav1.TypeMeta `json:",inline"`
// metadata is a standard object metadata
// +optional
metav1.ObjectMeta `json:"metadata,omitempty,omitzero"`
- // spec defines the Kustomization the trigger holds
+ // spec defines the Kustomization the applier claim holds
// +required
- Spec FluxTriggerSpec `json:"spec"`
+ Spec FluxClaimSpec `json:"spec"`
- // status defines the observed state of FluxTrigger
+ // status defines the observed state of FluxClaim
// +optional
- Status FluxTriggerStatus `json:"status,omitempty,omitzero"`
+ Status FluxClaimStatus `json:"status,omitempty,omitzero"`
}
// +kubebuilder:object:root=true
-// FluxTriggerList contains a list of FluxTrigger.
-type FluxTriggerList struct {
+// FluxClaimList contains a list of FluxClaim.
+type FluxClaimList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
- Items []FluxTrigger `json:"items"`
+ Items []FluxClaim `json:"items"`
}
func init() {
- SchemeBuilder.Register(&FluxTrigger{}, &FluxTriggerList{})
+ SchemeBuilder.Register(&FluxClaim{}, &FluxClaimList{})
}
-var _ ApplyTrigger = (*FluxTrigger)(nil)
+var _ ApplierClaim = (*FluxClaim)(nil)
-// TriggerKind is FluxTrigger.
-func (t *FluxTrigger) TriggerKind() string { return ApplyTriggerKindFlux }
+// ClaimKind is FluxClaim.
+func (t *FluxClaim) ClaimKind() string { return ApplierClaimKindFlux }
-// KubeProviderKey is spec.kubeProviderRef, its namespace defaulted to the trigger's own.
-func (t *FluxTrigger) KubeProviderKey() types.NamespacedName {
+// KubeProviderKey is spec.kubeProviderRef, its namespace defaulted to the applier claim's own.
+func (t *FluxClaim) KubeProviderKey() types.NamespacedName {
return kubeProviderKey(t.Spec.KubeProviderRef, t.Namespace)
}
// ApplierKind is Kustomization.
-func (t *FluxTrigger) ApplierKind() string { return ApplierKindKustomization }
+func (t *FluxClaim) ApplierKind() string { return ApplierKindKustomization }
-// Applier is spec.kustomizationRef, its namespace defaulted to the trigger's own.
-func (t *FluxTrigger) Applier() types.NamespacedName {
+// Applier is spec.kustomizationRef, its namespace defaulted to the applier claim's own.
+func (t *FluxClaim) Applier() types.NamespacedName {
return types.NamespacedName{
Namespace: defaultedNamespace(t.Spec.KustomizationRef.Namespace, t.Namespace),
Name: t.Spec.KustomizationRef.Name,
@@ -198,7 +198,7 @@ func (t *FluxTrigger) Applier() types.NamespacedName {
}
// SourceBinding is status.source's url and branch.
-func (t *FluxTrigger) SourceBinding() (string, string, bool) {
+func (t *FluxClaim) SourceBinding() (string, string, bool) {
if t.Status.Source == nil {
return "", "", false
}
@@ -206,7 +206,7 @@ func (t *FluxTrigger) SourceBinding() (string, string, bool) {
}
// SharedSource is the GitRepository in status.source.
-func (t *FluxTrigger) SharedSource() (types.NamespacedName, bool) {
+func (t *FluxClaim) SharedSource() (types.NamespacedName, bool) {
if t.Status.Source == nil {
return types.NamespacedName{}, false
}
@@ -215,20 +215,20 @@ func (t *FluxTrigger) SharedSource() (types.NamespacedName, bool) {
}
// Operation is status.operation.
-func (t *FluxTrigger) Operation() *ApplyOperation { return t.Status.Operation }
+func (t *FluxClaim) Operation() *ApplyOperation { return t.Status.Operation }
// RepairPending is status.repairPending.
-func (t *FluxTrigger) RepairPending() bool { return t.Status.RepairPending }
+func (t *FluxClaim) RepairPending() bool { return t.Status.RepairPending }
// SetOperation replaces status.operation and status.repairPending.
-func (t *FluxTrigger) SetOperation(operation *ApplyOperation, repairPending bool) {
+func (t *FluxClaim) SetOperation(operation *ApplyOperation, repairPending bool) {
t.Status.Operation, t.Status.RepairPending = operation, repairPending
}
// PreviousSuspendState is status.previousSuspendState.
-func (t *FluxTrigger) PreviousSuspendState() SuspendState { return t.Status.PreviousSuspendState }
+func (t *FluxClaim) PreviousSuspendState() SuspendState { return t.Status.PreviousSuspendState }
// SetPreviousSuspendState replaces status.previousSuspendState.
-func (t *FluxTrigger) SetPreviousSuspendState(state SuspendState) {
+func (t *FluxClaim) SetPreviousSuspendState(state SuspendState) {
t.Status.PreviousSuspendState = state
}
diff --git a/api/v1alpha3/gittarget_types.go b/api/v1alpha3/gittarget_types.go
index 865635992..9e05e92ab 100644
--- a/api/v1alpha3/gittarget_types.go
+++ b/api/v1alpha3/gittarget_types.go
@@ -33,7 +33,7 @@ import (
// accepted, so it stays in the schema only to be refused by name, on spec for the same ratcheting
// reason as the branch fields above.
// +kubebuilder:validation:XValidation:rule="!has(self.clusterProviderRef)",message="spec.clusterProviderRef was replaced by spec.kubeProviderRef, which names a namespaced KubeProvider"
-// +kubebuilder:validation:XValidation:rule="!has(self.onRefusal) || self.onRefusal != 'Reapply' || has(self.applyTriggerRef)",message="spec.onRefusal: Reapply needs spec.applyTriggerRef, the trigger asked to reapply"
+// +kubebuilder:validation:XValidation:rule="!has(self.onRefusal) || self.onRefusal != 'Reapply' || has(self.applierClaimRef)",message="spec.onRefusal: Reapply needs spec.applierClaimRef, the applier claim asked to reapply"
type GitTargetSpec struct {
// GitBranchRef names the GitBranch this target writes: the repository branch, and the credential
// it is written with. Namespace omitted means this GitTarget's own namespace. A GitBranch in
@@ -79,18 +79,18 @@ type GitTargetSpec struct {
// be guessing on Flux's behalf, and the guess would drive a write. A reference the user declares
// claims nothing. See docs/design/apply-trigger.md.
- // ApplyTriggerRef names the apply trigger, in this GitTarget's namespace, that holds the applier
- // of this folder: a FluxTrigger (a Flux Kustomization) or an ArgoCDTrigger (an Argo CD
+ // ApplierClaimRef names the applier claim, in this GitTarget's namespace, that holds the applier
+ // of this folder: a FluxClaim (a Flux Kustomization) or an ArgoCDClaim (an Argo CD
// Application). With it set, GitOps Reverser decides when that applier applies, always after
// publishing the edits it holds: after a commit it did not push, after a refused edit
// (spec.onRefusal: Reapply), after an apply reverted a newer API edit, after the API created an
// object in the folder (so the applier owns it and prunes it later), and once when the operator
- // starts or the trigger is first named, after the folder's initial snapshots are in Git. Objects
- // another applier owns are not mirrored into the folder. The trigger's applier must apply this target's repository and
- // branch; the ApplyTriggerReady condition says whether it does. Several GitTargets may name one
- // trigger, and it is asked once for all of them.
+ // starts or the applier claim is first named, after the folder's initial snapshots are in Git. Objects
+ // another applier owns are not mirrored into the folder. The applier claim's applier must apply this target's repository and
+ // branch; the ApplierClaimReady condition says whether it does. Several GitTargets may name one
+ // applier claim, and it is asked once for all of them.
// +optional
- ApplyTriggerRef *ApplyTriggerReference `json:"applyTriggerRef,omitempty"`
+ ApplierClaimRef *ApplierClaimReference `json:"applierClaimRef,omitempty"`
// OnRefusal decides what happens when a live edit is refused because it has no legal
// destination in the Git folder. A refusal produces no commit, so nothing reverts the edit
@@ -98,12 +98,12 @@ type GitTargetSpec struct {
//
// "Ignore", the default, records the refusal in conditions and events and does nothing else.
//
- // "Reapply" asks this target's applier (through spec.applyTriggerRef, which it requires) to
+ // "Reapply" asks this target's applier (through spec.applierClaimRef, which it requires) to
// apply the current tip after publishing the edits pending on the branch, which reverts the
// refused edit and nothing that was waiting to be published. Only this
// target's applier is asked. If its GitRepository has to fetch to reach the tip, the
- // Kustomizations a FluxTrigger holds stay suspended and ignore the new artifact; only the
- // consumers no trigger holds, listed in the trigger's status.source.otherConsumers, apply it, as
+ // Kustomizations a FluxClaim holds stay suspended and ignore the new artifact; only the
+ // consumers no applier claim holds, listed in the applier claim's status.source.otherConsumers, apply it, as
// they would for any push.
//
// It fires only for an edit to a document the folder ALREADY holds, in a write that removes no
@@ -542,7 +542,7 @@ type GitTargetRetentionStatus struct {
// +kubebuilder:printcolumn:name="LayoutResolved",type=string,JSONPath=`.status.conditions[?(@.type=="LayoutResolved")].reason`,priority=1
// +kubebuilder:printcolumn:name="GitPathAccepted",type=string,JSONPath=`.status.conditions[?(@.type=="GitPathAccepted")].status`,priority=1
// +kubebuilder:printcolumn:name="RenderMatchesLive",type=string,JSONPath=`.status.conditions[?(@.type=="RenderMatchesLive")].status`,priority=1
-// +kubebuilder:printcolumn:name="ApplyTriggerReady",type=string,JSONPath=`.status.conditions[?(@.type=="ApplyTriggerReady")].status`,priority=1
+// +kubebuilder:printcolumn:name="ApplierClaimReady",type=string,JSONPath=`.status.conditions[?(@.type=="ApplierClaimReady")].status`,priority=1
// +kubebuilder:printcolumn:name="StreamsRunning",type=string,JSONPath=`.status.conditions[?(@.type=="StreamsRunning")].status`,priority=1
// +kubebuilder:printcolumn:name="SourceReachable",type=string,JSONPath=`.status.conditions[?(@.type=="SourceClusterReachable")].reason`,priority=1
// +kubebuilder:printcolumn:name="Encryption",type=string,JSONPath=`.spec.encryption.provider`,priority=1
diff --git a/api/v1alpha3/helpers_test.go b/api/v1alpha3/helpers_test.go
index 35b8d5905..f16a5c4f8 100644
--- a/api/v1alpha3/helpers_test.go
+++ b/api/v1alpha3/helpers_test.go
@@ -314,12 +314,12 @@ func TestSourceCluster_IsTheProvidersNamespacedName(t *testing.T) {
assert.NotEmpty(t, unset.SourceCluster())
}
-// TestApplyTrigger_AppliersDefaultToTheTriggersNamespace: a reference with no namespace names an
-// applier in the trigger's own namespace, as Flux defaults a sourceRef.
-func TestApplyTrigger_AppliersDefaultToTheTriggersNamespace(t *testing.T) {
- flux := &FluxTrigger{
+// TestApplierClaim_AppliersDefaultToTheClaimsNamespace: a reference with no namespace names an
+// applier in the applier claim's own namespace, as Flux defaults a sourceRef.
+func TestApplierClaim_AppliersDefaultToTheClaimsNamespace(t *testing.T) {
+ flux := &FluxClaim{
ObjectMeta: metav1.ObjectMeta{Namespace: "team-a"},
- Spec: FluxTriggerSpec{
+ Spec: FluxClaimSpec{
KubeProviderRef: meta.NamespacedObjectReference{Name: "hub"},
KustomizationRef: meta.NamespacedObjectReference{Name: "apps"},
},
@@ -327,78 +327,78 @@ func TestApplyTrigger_AppliersDefaultToTheTriggersNamespace(t *testing.T) {
assert.Equal(t, "team-a/apps", flux.Applier().String())
assert.Equal(t, "team-a/hub", flux.KubeProviderKey().String())
- argo := &ArgoCDTrigger{
+ argo := &ArgoCDClaim{
ObjectMeta: metav1.ObjectMeta{Namespace: "team-a"},
- Spec: ArgoCDTriggerSpec{
+ Spec: ArgoCDClaimSpec{
ApplicationRef: meta.NamespacedObjectReference{Namespace: "argocd", Name: "apps"},
},
}
assert.Equal(t, "argocd/apps", argo.Applier().String())
}
-// TestArgoCDTrigger_SourceBindingNamesTheBranch: targetRevision may spell a branch as refs/heads/,
+// TestArgoCDClaim_SourceBindingNamesTheBranch: targetRevision may spell a branch as refs/heads/,
// and an empty one is Argo CD's HEAD, which follows no branch GitOps Reverser writes.
-func TestArgoCDTrigger_SourceBindingNamesTheBranch(t *testing.T) {
- trigger := &ArgoCDTrigger{}
- _, _, known := trigger.SourceBinding()
+func TestArgoCDClaim_SourceBindingNamesTheBranch(t *testing.T) {
+ claim := &ArgoCDClaim{}
+ _, _, known := claim.SourceBinding()
assert.False(t, known)
for revision, branch := range map[string]string{"main": "main", "refs/heads/main": "main", "": "HEAD"} {
- trigger.Status.Source = &ArgoCDTriggerSource{RepoURL: "https://example.com/r.git", TargetRevision: revision}
- url, got, known := trigger.SourceBinding()
+ claim.Status.Source = &ArgoCDClaimSource{RepoURL: "https://example.com/r.git", TargetRevision: revision}
+ url, got, known := claim.SourceBinding()
assert.True(t, known)
assert.Equal(t, "https://example.com/r.git", url)
assert.Equal(t, branch, got, "targetRevision %q", revision)
}
}
-// TestNewApplyTrigger_KnowsBothKinds: a kind this release does not know is nil, never a guess.
-// TestApplyTrigger_SharedSource: a FluxTrigger shares the GitRepository it last read; an
-// ArgoCDTrigger shares nothing.
-func TestApplyTrigger_SharedSource(t *testing.T) {
- flux := &FluxTrigger{}
+// TestNewApplierClaim_KnowsBothKinds: a kind this release does not know is nil, never a guess.
+// TestApplierClaim_SharedSource: a FluxClaim shares the GitRepository it last read; an
+// ArgoCDClaim shares nothing.
+func TestApplierClaim_SharedSource(t *testing.T) {
+ flux := &FluxClaim{}
_, known := flux.SharedSource()
- assert.False(t, known, "a trigger that has not read its source shares none yet")
+ assert.False(t, known, "an applier claim that has not read its source shares none yet")
- flux.Status.Source = &FluxTriggerSource{
+ flux.Status.Source = &FluxClaimSource{
GitRepository: meta.NamespacedObjectReference{Namespace: "flux-system", Name: "platform"},
}
source, known := flux.SharedSource()
assert.True(t, known)
assert.Equal(t, "flux-system/platform", source.String())
- argo := &ArgoCDTrigger{}
+ argo := &ArgoCDClaim{}
_, known = argo.SharedSource()
assert.False(t, known, "every sync pins its own revision")
}
-func TestNewApplyTrigger_KnowsBothKinds(t *testing.T) {
- assert.IsType(t, &FluxTrigger{}, NewApplyTrigger(ApplyTriggerKindFlux))
- assert.IsType(t, &ArgoCDTrigger{}, NewApplyTrigger(ApplyTriggerKindArgoCD))
- assert.Nil(t, NewApplyTrigger("WebhookTrigger"))
+func TestNewApplierClaim_KnowsBothKinds(t *testing.T) {
+ assert.IsType(t, &FluxClaim{}, NewApplierClaim(ApplierClaimKindFlux))
+ assert.IsType(t, &ArgoCDClaim{}, NewApplierClaim(ApplierClaimKindArgoCD))
+ assert.Nil(t, NewApplierClaim("WebhookClaim"))
}
-// TestApplyTrigger_StatusAccessors: both kinds answer the shared interface from their own status,
+// TestApplierClaim_StatusAccessors: both kinds answer the shared interface from their own status,
// and SetOperation replaces the operation and repairPending together.
-func TestApplyTrigger_StatusAccessors(t *testing.T) {
+func TestApplierClaim_StatusAccessors(t *testing.T) {
operation := &ApplyOperation{Phase: ApplyOperationRunning, Commit: "abc"}
- flux := &FluxTrigger{}
- argo := &ArgoCDTrigger{}
- for _, trigger := range []ApplyTrigger{flux, argo} {
- assert.Nil(t, trigger.Operation())
- assert.False(t, trigger.RepairPending())
- trigger.SetOperation(operation, true)
- assert.Equal(t, operation, trigger.Operation())
- assert.True(t, trigger.RepairPending())
- trigger.SetObservedGeneration(3)
- assert.NotNil(t, trigger.StatusConditions())
+ flux := &FluxClaim{}
+ argo := &ArgoCDClaim{}
+ for _, claim := range []ApplierClaim{flux, argo} {
+ assert.Nil(t, claim.Operation())
+ assert.False(t, claim.RepairPending())
+ claim.SetOperation(operation, true)
+ assert.Equal(t, operation, claim.Operation())
+ assert.True(t, claim.RepairPending())
+ claim.SetObservedGeneration(3)
+ assert.NotNil(t, claim.StatusConditions())
}
assert.Equal(t, int64(3), flux.Status.ObservedGeneration)
assert.Equal(t, int64(3), argo.Status.ObservedGeneration)
- assert.Equal(t, ApplyTriggerKindFlux, flux.TriggerKind())
+ assert.Equal(t, ApplierClaimKindFlux, flux.ClaimKind())
assert.Equal(t, ApplierKindKustomization, flux.ApplierKind())
- assert.Equal(t, ApplyTriggerKindArgoCD, argo.TriggerKind())
+ assert.Equal(t, ApplierClaimKindArgoCD, argo.ClaimKind())
assert.Equal(t, ApplierKindApplication, argo.ApplierKind())
assert.Equal(t, types.NamespacedName{}, argo.KubeProviderKey(),
"an omitted kubeProviderRef names nothing: there is no default provider")
@@ -407,7 +407,7 @@ func TestApplyTrigger_StatusAccessors(t *testing.T) {
_, _, known := flux.SourceBinding()
assert.False(t, known)
- flux.Status.Source = &FluxTriggerSource{URL: "https://example.com/r.git", Branch: "main"}
+ flux.Status.Source = &FluxClaimSource{URL: "https://example.com/r.git", Branch: "main"}
url, branch, known := flux.SourceBinding()
assert.True(t, known)
assert.Equal(t, "https://example.com/r.git", url)
diff --git a/api/v1alpha3/kubeprovider_types.go b/api/v1alpha3/kubeprovider_types.go
index 04636a5af..0924006bb 100644
--- a/api/v1alpha3/kubeprovider_types.go
+++ b/api/v1alpha3/kubeprovider_types.go
@@ -94,10 +94,10 @@ type KubeProviderCrossNamespace struct {
// +optional
Sources bool `json:"sources,omitempty"`
- // Appliers lets a FluxTrigger or ArgoCDTrigger reaching its applier through this provider name a
+ // Appliers lets a FluxClaim or ArgoCDClaim reaching its applier through this provider name a
// Kustomization or Application in another namespace than its own. False (the default): only
- // the trigger's own namespace, which in a remote cluster is the namespace of the same name; a
- // trigger naming another is Configured=False (ApplierNamespaceNotAllowed).
+ // the applier claim's own namespace, which in a remote cluster is the namespace of the same name; an
+ // applier claim naming another is Configured=False (ApplierNamespaceNotAllowed).
// +optional
Appliers bool `json:"appliers,omitempty"`
}
@@ -193,8 +193,8 @@ type KubeProviderStatus struct {
// +kubebuilder:printcolumn:name="Message",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].message`,priority=1
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
-// KubeProvider names a source cluster: the cluster a GitTarget mirrors from, or an apply trigger
-// reaches its applier in. It holds the credential that reaches it (a kubeconfig Secret in its own
+// KubeProvider is a connection to a Kubernetes cluster and the identity used there. A GitTarget
+// mirrors from it; an applier claim reaches its applier through it. It holds the credential that reaches it (a kubeconfig Secret in its own
// namespace, or the operator's own identity where the installation allows it), which control-cluster
// namespaces may use it (its own, and those spec.accessFrom admits), and the audit route its
// attribution facts are partitioned under.
@@ -270,7 +270,7 @@ func (p *KubeProvider) AllowsCrossNamespaceSources() bool {
return p.Spec.AllowCrossNamespace != nil && p.Spec.AllowCrossNamespace.Sources
}
-// AllowsCrossNamespaceAppliers reports whether a trigger reaching its applier through this provider
+// AllowsCrossNamespaceAppliers reports whether an applier claim reaching its applier through this provider
// may name one in another namespace than its own (spec.allowCrossNamespace.appliers).
func (p *KubeProvider) AllowsCrossNamespaceAppliers() bool {
return p.Spec.AllowCrossNamespace != nil && p.Spec.AllowCrossNamespace.Appliers
diff --git a/api/v1alpha3/kubeprovider_types_test.go b/api/v1alpha3/kubeprovider_types_test.go
index 772760652..8c6672a71 100644
--- a/api/v1alpha3/kubeprovider_types_test.go
+++ b/api/v1alpha3/kubeprovider_types_test.go
@@ -144,11 +144,11 @@ func TestKubeProviderKey_DefaultsToReferrerNamespace(t *testing.T) {
target.Spec.KubeProviderRef = meta.NamespacedObjectReference{Name: "remote"}
assert.Equal(t, types.NamespacedName{Namespace: "tenant-a", Name: "remote"}, target.KubeProviderKey())
- flux := &FluxTrigger{ObjectMeta: metav1.ObjectMeta{Namespace: "tenant-a"}}
+ flux := &FluxClaim{ObjectMeta: metav1.ObjectMeta{Namespace: "tenant-a"}}
flux.Spec.KubeProviderRef = meta.NamespacedObjectReference{Name: "mgmt", Namespace: "platform"}
assert.Equal(t, types.NamespacedName{Namespace: "platform", Name: "mgmt"}, flux.KubeProviderKey())
- argo := &ArgoCDTrigger{ObjectMeta: metav1.ObjectMeta{Namespace: "tenant-a"}}
+ argo := &ArgoCDClaim{ObjectMeta: metav1.ObjectMeta{Namespace: "tenant-a"}}
argo.Spec.KubeProviderRef = meta.NamespacedObjectReference{Name: "mgmt"}
assert.Equal(t, types.NamespacedName{Namespace: "tenant-a", Name: "mgmt"}, argo.KubeProviderKey())
}
diff --git a/api/v1alpha3/namespace_matcher_test.go b/api/v1alpha3/namespace_matcher_test.go
index 4b9b5b9cb..4e2c848db 100644
--- a/api/v1alpha3/namespace_matcher_test.go
+++ b/api/v1alpha3/namespace_matcher_test.go
@@ -131,7 +131,7 @@ func TestKubeProvider_DelegationFlagDefaultsClosed(t *testing.T) {
assert.False(t, provider.AllowsCrossNamespaceSources(),
"source-namespace delegation must never be on by default")
assert.False(t, provider.AllowsCrossNamespaceAppliers(),
- "nor may a trigger reach another namespace's applier by default")
+ "nor may an applier claim reach another namespace's applier by default")
provider.Spec.AllowCrossNamespace = &KubeProviderCrossNamespace{Appliers: true}
assert.False(t, provider.AllowsCrossNamespaceSources(), "each power is granted on its own")
assert.True(t, provider.AllowsCrossNamespaceAppliers())
diff --git a/api/v1alpha3/status_accessors.go b/api/v1alpha3/status_accessors.go
index fbf7c7cde..e813db1a4 100644
--- a/api/v1alpha3/status_accessors.go
+++ b/api/v1alpha3/status_accessors.go
@@ -70,17 +70,17 @@ func (r *ClusterWatchRule) SetObservedGeneration(generation int64) {
func (r *ClusterWatchRule) StatusConditions() *[]metav1.Condition { return &r.Status.Conditions }
// SetObservedGeneration records the generation this object's status describes.
-func (t *FluxTrigger) SetObservedGeneration(generation int64) {
+func (t *FluxClaim) SetObservedGeneration(generation int64) {
t.Status.ObservedGeneration = generation
}
// StatusConditions returns the condition set to write through.
-func (t *FluxTrigger) StatusConditions() *[]metav1.Condition { return &t.Status.Conditions }
+func (t *FluxClaim) StatusConditions() *[]metav1.Condition { return &t.Status.Conditions }
// SetObservedGeneration records the generation this object's status describes.
-func (t *ArgoCDTrigger) SetObservedGeneration(generation int64) {
+func (t *ArgoCDClaim) SetObservedGeneration(generation int64) {
t.Status.ObservedGeneration = generation
}
// StatusConditions returns the condition set to write through.
-func (t *ArgoCDTrigger) StatusConditions() *[]metav1.Condition { return &t.Status.Conditions }
+func (t *ArgoCDClaim) StatusConditions() *[]metav1.Condition { return &t.Status.Conditions }
diff --git a/api/v1alpha3/zz_generated.deepcopy.go b/api/v1alpha3/zz_generated.deepcopy.go
index 047ffc88d..f4af1e505 100644
--- a/api/v1alpha3/zz_generated.deepcopy.go
+++ b/api/v1alpha3/zz_generated.deepcopy.go
@@ -49,42 +49,42 @@ func (in *AgeRecipientsSpec) DeepCopy() *AgeRecipientsSpec {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *ApplyOperation) DeepCopyInto(out *ApplyOperation) {
+func (in *ApplierClaimReference) DeepCopyInto(out *ApplierClaimReference) {
*out = *in
- in.RequestedAt.DeepCopyInto(&out.RequestedAt)
- if in.FinishedAt != nil {
- in, out := &in.FinishedAt, &out.FinishedAt
- *out = (*in).DeepCopy()
- }
}
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplyOperation.
-func (in *ApplyOperation) DeepCopy() *ApplyOperation {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplierClaimReference.
+func (in *ApplierClaimReference) DeepCopy() *ApplierClaimReference {
if in == nil {
return nil
}
- out := new(ApplyOperation)
+ out := new(ApplierClaimReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *ApplyTriggerReference) DeepCopyInto(out *ApplyTriggerReference) {
+func (in *ApplyOperation) DeepCopyInto(out *ApplyOperation) {
*out = *in
+ in.RequestedAt.DeepCopyInto(&out.RequestedAt)
+ if in.FinishedAt != nil {
+ in, out := &in.FinishedAt, &out.FinishedAt
+ *out = (*in).DeepCopy()
+ }
}
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplyTriggerReference.
-func (in *ApplyTriggerReference) DeepCopy() *ApplyTriggerReference {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplyOperation.
+func (in *ApplyOperation) DeepCopy() *ApplyOperation {
if in == nil {
return nil
}
- out := new(ApplyTriggerReference)
+ out := new(ApplyOperation)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *ArgoCDTrigger) DeepCopyInto(out *ArgoCDTrigger) {
+func (in *ArgoCDClaim) DeepCopyInto(out *ArgoCDClaim) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
@@ -92,18 +92,18 @@ func (in *ArgoCDTrigger) DeepCopyInto(out *ArgoCDTrigger) {
in.Status.DeepCopyInto(&out.Status)
}
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDTrigger.
-func (in *ArgoCDTrigger) DeepCopy() *ArgoCDTrigger {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDClaim.
+func (in *ArgoCDClaim) DeepCopy() *ArgoCDClaim {
if in == nil {
return nil
}
- out := new(ArgoCDTrigger)
+ out := new(ArgoCDClaim)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *ArgoCDTrigger) DeepCopyObject() runtime.Object {
+func (in *ArgoCDClaim) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
@@ -111,31 +111,31 @@ func (in *ArgoCDTrigger) DeepCopyObject() runtime.Object {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *ArgoCDTriggerList) DeepCopyInto(out *ArgoCDTriggerList) {
+func (in *ArgoCDClaimList) DeepCopyInto(out *ArgoCDClaimList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
- *out = make([]ArgoCDTrigger, len(*in))
+ *out = make([]ArgoCDClaim, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDTriggerList.
-func (in *ArgoCDTriggerList) DeepCopy() *ArgoCDTriggerList {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDClaimList.
+func (in *ArgoCDClaimList) DeepCopy() *ArgoCDClaimList {
if in == nil {
return nil
}
- out := new(ArgoCDTriggerList)
+ out := new(ArgoCDClaimList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *ArgoCDTriggerList) DeepCopyObject() runtime.Object {
+func (in *ArgoCDClaimList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
@@ -143,22 +143,22 @@ func (in *ArgoCDTriggerList) DeepCopyObject() runtime.Object {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *ArgoCDTriggerSource) DeepCopyInto(out *ArgoCDTriggerSource) {
+func (in *ArgoCDClaimSource) DeepCopyInto(out *ArgoCDClaimSource) {
*out = *in
}
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDTriggerSource.
-func (in *ArgoCDTriggerSource) DeepCopy() *ArgoCDTriggerSource {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDClaimSource.
+func (in *ArgoCDClaimSource) DeepCopy() *ArgoCDClaimSource {
if in == nil {
return nil
}
- out := new(ArgoCDTriggerSource)
+ out := new(ArgoCDClaimSource)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *ArgoCDTriggerSpec) DeepCopyInto(out *ArgoCDTriggerSpec) {
+func (in *ArgoCDClaimSpec) DeepCopyInto(out *ArgoCDClaimSpec) {
*out = *in
out.KubeProviderRef = in.KubeProviderRef
if in.RemovedClusterProviderRef != nil {
@@ -169,18 +169,18 @@ func (in *ArgoCDTriggerSpec) DeepCopyInto(out *ArgoCDTriggerSpec) {
out.ApplicationRef = in.ApplicationRef
}
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDTriggerSpec.
-func (in *ArgoCDTriggerSpec) DeepCopy() *ArgoCDTriggerSpec {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDClaimSpec.
+func (in *ArgoCDClaimSpec) DeepCopy() *ArgoCDClaimSpec {
if in == nil {
return nil
}
- out := new(ArgoCDTriggerSpec)
+ out := new(ArgoCDClaimSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *ArgoCDTriggerStatus) DeepCopyInto(out *ArgoCDTriggerStatus) {
+func (in *ArgoCDClaimStatus) DeepCopyInto(out *ArgoCDClaimStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
@@ -191,7 +191,7 @@ func (in *ArgoCDTriggerStatus) DeepCopyInto(out *ArgoCDTriggerStatus) {
}
if in.Source != nil {
in, out := &in.Source, &out.Source
- *out = new(ArgoCDTriggerSource)
+ *out = new(ArgoCDClaimSource)
**out = **in
}
if in.Operation != nil {
@@ -201,12 +201,12 @@ func (in *ArgoCDTriggerStatus) DeepCopyInto(out *ArgoCDTriggerStatus) {
}
}
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDTriggerStatus.
-func (in *ArgoCDTriggerStatus) DeepCopy() *ArgoCDTriggerStatus {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDClaimStatus.
+func (in *ArgoCDClaimStatus) DeepCopy() *ArgoCDClaimStatus {
if in == nil {
return nil
}
- out := new(ArgoCDTriggerStatus)
+ out := new(ArgoCDClaimStatus)
in.DeepCopyInto(out)
return out
}
@@ -605,7 +605,7 @@ func (in *EncryptionSpec) DeepCopy() *EncryptionSpec {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *FluxTrigger) DeepCopyInto(out *FluxTrigger) {
+func (in *FluxClaim) DeepCopyInto(out *FluxClaim) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
@@ -613,18 +613,18 @@ func (in *FluxTrigger) DeepCopyInto(out *FluxTrigger) {
in.Status.DeepCopyInto(&out.Status)
}
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxTrigger.
-func (in *FluxTrigger) DeepCopy() *FluxTrigger {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxClaim.
+func (in *FluxClaim) DeepCopy() *FluxClaim {
if in == nil {
return nil
}
- out := new(FluxTrigger)
+ out := new(FluxClaim)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *FluxTrigger) DeepCopyObject() runtime.Object {
+func (in *FluxClaim) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
@@ -632,31 +632,31 @@ func (in *FluxTrigger) DeepCopyObject() runtime.Object {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *FluxTriggerList) DeepCopyInto(out *FluxTriggerList) {
+func (in *FluxClaimList) DeepCopyInto(out *FluxClaimList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
- *out = make([]FluxTrigger, len(*in))
+ *out = make([]FluxClaim, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxTriggerList.
-func (in *FluxTriggerList) DeepCopy() *FluxTriggerList {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxClaimList.
+func (in *FluxClaimList) DeepCopy() *FluxClaimList {
if in == nil {
return nil
}
- out := new(FluxTriggerList)
+ out := new(FluxClaimList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *FluxTriggerList) DeepCopyObject() runtime.Object {
+func (in *FluxClaimList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
@@ -664,7 +664,7 @@ func (in *FluxTriggerList) DeepCopyObject() runtime.Object {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *FluxTriggerSource) DeepCopyInto(out *FluxTriggerSource) {
+func (in *FluxClaimSource) DeepCopyInto(out *FluxClaimSource) {
*out = *in
out.GitRepository = in.GitRepository
if in.OtherConsumers != nil {
@@ -674,18 +674,18 @@ func (in *FluxTriggerSource) DeepCopyInto(out *FluxTriggerSource) {
}
}
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxTriggerSource.
-func (in *FluxTriggerSource) DeepCopy() *FluxTriggerSource {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxClaimSource.
+func (in *FluxClaimSource) DeepCopy() *FluxClaimSource {
if in == nil {
return nil
}
- out := new(FluxTriggerSource)
+ out := new(FluxClaimSource)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *FluxTriggerSpec) DeepCopyInto(out *FluxTriggerSpec) {
+func (in *FluxClaimSpec) DeepCopyInto(out *FluxClaimSpec) {
*out = *in
out.KubeProviderRef = in.KubeProviderRef
if in.RemovedClusterProviderRef != nil {
@@ -696,18 +696,18 @@ func (in *FluxTriggerSpec) DeepCopyInto(out *FluxTriggerSpec) {
out.KustomizationRef = in.KustomizationRef
}
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxTriggerSpec.
-func (in *FluxTriggerSpec) DeepCopy() *FluxTriggerSpec {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxClaimSpec.
+func (in *FluxClaimSpec) DeepCopy() *FluxClaimSpec {
if in == nil {
return nil
}
- out := new(FluxTriggerSpec)
+ out := new(FluxClaimSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *FluxTriggerStatus) DeepCopyInto(out *FluxTriggerStatus) {
+func (in *FluxClaimStatus) DeepCopyInto(out *FluxClaimStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
@@ -718,7 +718,7 @@ func (in *FluxTriggerStatus) DeepCopyInto(out *FluxTriggerStatus) {
}
if in.Source != nil {
in, out := &in.Source, &out.Source
- *out = new(FluxTriggerSource)
+ *out = new(FluxClaimSource)
(*in).DeepCopyInto(*out)
}
if in.Operation != nil {
@@ -728,12 +728,12 @@ func (in *FluxTriggerStatus) DeepCopyInto(out *FluxTriggerStatus) {
}
}
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxTriggerStatus.
-func (in *FluxTriggerStatus) DeepCopy() *FluxTriggerStatus {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxClaimStatus.
+func (in *FluxClaimStatus) DeepCopy() *FluxClaimStatus {
if in == nil {
return nil
}
- out := new(FluxTriggerStatus)
+ out := new(FluxClaimStatus)
in.DeepCopyInto(out)
return out
}
@@ -1273,9 +1273,9 @@ func (in *GitTargetSpec) DeepCopyInto(out *GitTargetSpec) {
*out = new(EncryptionSpec)
(*in).DeepCopyInto(*out)
}
- if in.ApplyTriggerRef != nil {
- in, out := &in.ApplyTriggerRef, &out.ApplyTriggerRef
- *out = new(ApplyTriggerReference)
+ if in.ApplierClaimRef != nil {
+ in, out := &in.ApplierClaimRef, &out.ApplierClaimRef
+ *out = new(ApplierClaimReference)
**out = **in
}
if in.Placement != nil {
diff --git a/charts/gitops-reverser/README.md b/charts/gitops-reverser/README.md
index 6436a4631..ccf98fcf0 100644
--- a/charts/gitops-reverser/README.md
+++ b/charts/gitops-reverser/README.md
@@ -19,7 +19,7 @@ For the chart's optional starter `quickstart` block, see [`docs/configuration.md
- ✅ **Git synchronization**: Push Kubernetes changes back to Git repositories
- ✅ **Single-pod stability**: 1 replica by default while multi-pod support is in progress
- ✅ **Automatic CRD installation**: KubeProvider, GitProvider, GitBranch, GitTarget, WatchRule,
- ClusterWatchRule, CommitRequest, FluxTrigger, and ArgoCDTrigger CRDs installed automatically
+ ClusterWatchRule, CommitRequest, FluxClaim, and ArgoCDClaim CRDs installed automatically
- ✅ **Watch-based ingestion**: object state comes from the Kubernetes watch API
- ✅ **Optional audit attribution**: name commit authors from kube-apiserver audit events over HTTPS
- ✅ **Prometheus metrics**: Built-in monitoring support
@@ -184,8 +184,8 @@ nodeSelector:
| `controllerManager.branchBufferMaxSize` | Maximum in-memory event buffer per branch worker. Bounds the open commit window plus the writes retained for replay until a push succeeds. While the remote cannot be reached, retained writes that reach it close the worker to new writes, saves and resyncs until they are published; the watch delivers a refused event again | `8Mi` |
| `controllerManager.commitRequestTTL` | How long a `CommitRequest` is kept once it finishes. Written onto the request as the `configbutler.ai/delete-after` annotation; remove or edit that to keep one. `"0"` writes none | `48h` |
| `git.refreshInterval` | How often an idle `GitTarget` re-proves where its branch is on the Git remote. `"0"` disables it | `10m` |
-| `applierAccess.flux` | Grant the operator `get`, `list` and `patch` on Flux `Kustomizations`, `get` and `patch` on `GitRepositories`, `list` on `HelmReleases` and `HelmCharts` (to find a source's other consumers), and `create` on `SelfSubjectAccessReviews`, so it can hold the `Kustomization` a `FluxTrigger` names suspended and resume it for each apply | `false` |
-| `applierAccess.argocd` | Grant the operator `get`, `update` and `patch` on Argo CD `Applications`, and `create` on `SelfSubjectAccessReviews`, so it can hold the automated sync of the `Application` an `ArgoCDTrigger` names off and start its syncs | `false` |
+| `applierAccess.flux` | Grant the operator `get`, `list` and `patch` on Flux `Kustomizations`, `get` and `patch` on `GitRepositories`, `list` on `HelmReleases` and `HelmCharts` (to find a source's other consumers), and `create` on `SelfSubjectAccessReviews`, so it can hold the `Kustomization` a `FluxClaim` names suspended and resume it for each apply | `false` |
+| `applierAccess.argocd` | Grant the operator `get`, `update` and `patch` on Argo CD `Applications`, and `create` on `SelfSubjectAccessReviews`, so it can hold the automated sync of the `Application` an `ArgoCDClaim` names off and start its syncs | `false` |
| `auditService.type` | Service type for the dedicated audit Service | `NodePort` |
| `auditService.nodePort` | Fixed NodePort for the audit Service when `auditService.type=NodePort` | `30444` |
| `auditService.clusterIP` | Optional fixed ClusterIP for the dedicated audit Service | `""` |
@@ -219,7 +219,7 @@ nodeSelector:
| `kubeProvider.secretRef.key` | Key within that Secret. Empty reads `value` then `value.yaml` | `""` |
| `kubeProvider.accessFrom` | Policy (`names` and/or `selector`) for which **other** control-cluster namespaces may reference this provider; the release namespace always may. The default empty selector admits every namespace | `{selector: {}}` |
| `kubeProvider.allowCrossNamespace.sources` | Let a `WatchRule` in an admitted namespace watch other namespaces, `"*"` included. A delegation to every admitted namespace, not proof that it owns what it reads | `false` |
-| `kubeProvider.allowCrossNamespace.appliers` | Let a `FluxTrigger` or `ArgoCDTrigger` in an admitted namespace suspend and apply a `Kustomization` or `Application` in another namespace, such as `flux-system`. A delegation to every admitted namespace, not proof that it owns the applier or the repository | `false` |
+| `kubeProvider.allowCrossNamespace.appliers` | Let a `FluxClaim` or `ArgoCDClaim` in an admitted namespace suspend and apply a `Kustomization` or `Application` in another namespace, such as `flux-system`. A delegation to every admitted namespace, not proof that it owns the applier or the repository | `false` |
| `servers.admission.enabled` | Install the validate-operator-types admission webhook that captures CommitRequest authors (a form of author attribution). Enabled by default; a no-op until `queue.redis.addr` is set | `true` |
| `rbac.create` | Create the manager ClusterRole and its binding | `true` |
| `rbac.watchTypes.mode` | Which types a `WatchRule` may read. `any` grants cluster-wide read on everything, including every Secret in the cluster. `selected` grants read on `rbac.watchTypes.selected` only, so the reverser cannot list or watch Secrets (it keeps `get` on named Secrets it is pointed at). See [`docs/rbac.md`](../../docs/rbac.md) | `any` |
@@ -288,8 +288,8 @@ This chart automatically manages the following CRDs:
- **`watchrules.configbutler.ai`** - Namespaced watch rules
- **`clusterwatchrules.configbutler.ai`** - Cluster-scoped watch rules
- **`commitrequests.configbutler.ai`** - One-shot "save now" signals that finalize the open commit window
-- **`fluxtriggers.configbutler.ai`** - Apply triggers holding a Flux `Kustomization` for bi-directional GitOps
-- **`argocdtriggers.configbutler.ai`** - Apply triggers holding an Argo CD `Application` for bi-directional GitOps
+- **`fluxclaims.configbutler.ai`** - Applier claims holding a Flux `Kustomization` for bi-directional GitOps
+- **`argocdclaims.configbutler.ai`** - Applier claims holding an Argo CD `Application` for bi-directional GitOps
### CRD Lifecycle
@@ -309,10 +309,10 @@ kubectl apply -f https://github.com/ConfigButler/gitops-reverser/releases/downlo
To manually remove CRDs after uninstallation:
```bash
-kubectl delete crd gitproviders.configbutler.ai gittargets.configbutler.ai watchrules.configbutler.ai clusterwatchrules.configbutler.ai commitrequests.configbutler.ai fluxtriggers.configbutler.ai argocdtriggers.configbutler.ai kubeproviders.configbutler.ai gitbranches.configbutler.ai gitreceivers.configbutler.ai
+kubectl delete crd gitproviders.configbutler.ai gittargets.configbutler.ai watchrules.configbutler.ai clusterwatchrules.configbutler.ai commitrequests.configbutler.ai fluxclaims.configbutler.ai argocdclaims.configbutler.ai kubeproviders.configbutler.ai gitbranches.configbutler.ai gitreceivers.configbutler.ai
```
-Delete the triggers while the operator still runs, before this ([Uninstallation](#uninstallation)).
+Delete the applier claims while the operator still runs, before this ([Uninstallation](#uninstallation)).
> ⚠️ **Warning**: Deleting CRDs will also delete all custom resources of those types!
@@ -378,19 +378,19 @@ If upgrading from earlier chart versions:
## Uninstallation
-**Hand Flux its `Kustomization`s back first, while the operator still runs.** A `FluxTrigger` that
-holds its `Kustomization` carries the finalizer `configbutler.ai/flux-trigger`, and only the running
+**Hand Flux its `Kustomization`s back first, while the operator still runs.** A `FluxClaim` that
+holds its `Kustomization` carries the finalizer `configbutler.ai/flux-claim`, and only the running
operator removes it, once it has handed the `Kustomization` back. One left after `helm uninstall`
-keeps its namespace and the `fluxtriggers` CRD from being deleted, and its `Kustomization` stays
-suspended. Delete your `GitTarget`s, or the triggers, before uninstalling, and wait until they are
+keeps its namespace and the `fluxclaims` CRD from being deleted, and its `Kustomization` stays
+suspended. Delete your `GitTarget`s, or the applier claims, before uninstalling, and wait until they are
gone:
```bash
-kubectl delete fluxtriggers,argocdtriggers --all --all-namespaces --wait
+kubectl delete fluxclaims,argocdclaims --all --all-namespaces --wait
```
-A trigger that never held its `Kustomization`, such as one no `GitTarget` names, carries no
-finalizer. An `ArgoCDTrigger` never carries one.
+An applier claim that never held its `Kustomization`, such as one no `GitTarget` names, carries no
+finalizer. An `ArgoCDClaim` never carries one.
```bash
# Uninstall chart
@@ -400,7 +400,7 @@ helm uninstall gitops-reverser --namespace gitops-reverser
kubectl delete namespace gitops-reverser
# Delete CRDs (optional, but removes all custom resources)
-kubectl delete crd gitproviders.configbutler.ai gittargets.configbutler.ai watchrules.configbutler.ai clusterwatchrules.configbutler.ai commitrequests.configbutler.ai fluxtriggers.configbutler.ai argocdtriggers.configbutler.ai kubeproviders.configbutler.ai gitbranches.configbutler.ai gitreceivers.configbutler.ai
+kubectl delete crd gitproviders.configbutler.ai gittargets.configbutler.ai watchrules.configbutler.ai clusterwatchrules.configbutler.ai commitrequests.configbutler.ai fluxclaims.configbutler.ai argocdclaims.configbutler.ai kubeproviders.configbutler.ai gitbranches.configbutler.ai gitreceivers.configbutler.ai
```
## Troubleshooting
@@ -489,7 +489,7 @@ resources:
New versions are automatically released via GitHub Actions:
-1. Push to `main` branch triggers release-please
+1. Push to `main` branch applier claims release-please
2. Docker images built for `linux/amd64` and `linux/arm64`
3. Helm chart packaged and pushed to `ghcr.io`
4. Release notes include installation instructions
diff --git a/charts/gitops-reverser/templates/applier-rbac.yaml b/charts/gitops-reverser/templates/applier-rbac.yaml
index 4718be07e..14c476920 100644
--- a/charts/gitops-reverser/templates/applier-rbac.yaml
+++ b/charts/gitops-reverser/templates/applier-rbac.yaml
@@ -1,8 +1,8 @@
{{- if and .Values.rbac.create .Values.applierAccess.flux }}
-# What GitOps Reverser needs to drive a Kustomization a FluxTrigger holds: read and patch the
+# What GitOps Reverser needs to drive a Kustomization a FluxClaim holds: read and patch the
# Kustomization and its GitRepository (it suspends and resumes the one and asks the other to fetch),
# list the GitRepository's consumers (Kustomizations, and HelmReleases with the HelmCharts built
-# from it; without it the trigger lists none in status.source.otherConsumers), and create SelfSubjectAccessReviews so the operator can check it holds that
+# from it; without it the applier claim lists none in status.source.otherConsumers), and create SelfSubjectAccessReviews so the operator can check it holds that
# access before relying on it. Separate from the manager role because it is optional and
# names another project's API: an install without Flux grants nothing on it. See
# docs/design/apply-trigger.md.
diff --git a/charts/gitops-reverser/values.schema.json b/charts/gitops-reverser/values.schema.json
index fe4601aa1..10412ad91 100644
--- a/charts/gitops-reverser/values.schema.json
+++ b/charts/gitops-reverser/values.schema.json
@@ -416,7 +416,7 @@
},
"appliers": {
"type": "boolean",
- "description": "A FluxTrigger or ArgoCDTrigger may name a Kustomization or Application in another namespace."
+ "description": "A FluxClaim or ArgoCDClaim may name a Kustomization or Application in another namespace."
}
}
}
diff --git a/charts/gitops-reverser/values.yaml b/charts/gitops-reverser/values.yaml
index 190a50549..e16bbf555 100644
--- a/charts/gitops-reverser/values.yaml
+++ b/charts/gitops-reverser/values.yaml
@@ -120,14 +120,14 @@ git:
refreshInterval: 10m
-# Optional RBAC that lets GitOps Reverser hold the applier an apply trigger names in this cluster:
-# a FluxTrigger's Kustomization, suspended and resumed for each apply, or an ArgoCDTrigger's
+# Optional RBAC that lets GitOps Reverser hold the applier an applier claim names in this cluster:
+# a FluxClaim's Kustomization, suspended and resumed for each apply, or an ArgoCDClaim's
# Application, synced for each apply. Off unless asked for, since one-way mirroring needs neither.
applierAccess:
# Grant get/patch on kustomize.toolkit.fluxcd.io Kustomizations and source.toolkit.fluxcd.io
# GitRepositories, list on Kustomizations, HelmReleases and HelmCharts (to find a
# GitRepository's other consumers), plus create on SelfSubjectAccessReviews (to check that access
- # before relying on it). Cluster-wide, because a trigger's applier can live in any namespace.
+ # before relying on it). Cluster-wide, because an applier claim's applier can live in any namespace.
flux: false
# Grant get/update/patch on argoproj.io Applications (a sync is started by updating the
# Application's operation, as Argo CD's own automated sync does, and its automated sync is held off
@@ -371,7 +371,7 @@ kubeProvider:
selector: {}
# What a namespace this provider admits may reach OUTSIDE itself through the provider's
# identity. Each is a power of its own: sources lets a WatchRule read other namespaces' objects
- # into its own Git ("*" included); appliers lets a FluxTrigger or ArgoCDTrigger suspend and apply
+ # into its own Git ("*" included); appliers lets a FluxClaim or ArgoCDClaim suspend and apply
# a Kustomization or Application in another namespace. It is a delegation to every admitted
# namespace, not proof that a namespace owns what it reaches.
allowCrossNamespace:
diff --git a/cmd/main.go b/cmd/main.go
index 6de44244a..0a7a37bb0 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -185,18 +185,18 @@ func main() {
// for anything before a GitTarget's initial snapshots are in Git: both read the streams.
workerManager.SetSnapshotHooks(watchMgr.InitialSnapshotsSettled, watchMgr.RequestRecheckForGitTarget)
// The branch workers decide when a GitTarget's applier may apply; the dispatcher delivers it,
- // off their queues, to the applier its apply trigger holds, in the trigger's cluster. The status
- // writer records each operation on the trigger, also off their queues.
+ // off their queues, to the applier its applier claim holds, in the applier claim's cluster. The status
+ // writer records each operation on the applier claim, also off their queues.
applyDispatcher := applier.NewDispatcher(
mgr.GetClient(), watchMgr.ApplierDynamicClient, mgr.GetEventRecorderFor("gittarget"),
ctrl.Log.WithName("applier"))
applyDispatcher.APIReader = mgr.GetAPIReader()
fatalIfErr(mgr.Add(applyDispatcher), "unable to add apply request dispatcher to manager")
workerManager.SetApplyRequester(applyDispatcher)
- triggerStatusWriter := applier.NewStatusWriter(mgr.GetAPIReader(), mgr.GetClient(),
+ claimStatusWriter := applier.NewStatusWriter(mgr.GetAPIReader(), mgr.GetClient(),
ctrl.Log.WithName("applier").WithName("status"))
- fatalIfErr(mgr.Add(triggerStatusWriter), "unable to add apply trigger status writer to manager")
- workerManager.SetApplyTriggerStatusWriter(triggerStatusWriter)
+ fatalIfErr(mgr.Add(claimStatusWriter), "unable to add applier claim status writer to manager")
+ workerManager.SetApplierClaimStatusWriter(claimStatusWriter)
// WatchRule controller (with WatchManager reference for dynamic reconciliation)
fatalIfErr((&controller.WatchRuleReconciler{
@@ -386,8 +386,8 @@ func main() {
setupLog.Error(err, "unable to create controller", "controller", "GitTarget")
os.Exit(1)
}
- for _, kind := range []string{configbutleraiv1alpha3.ApplyTriggerKindFlux, configbutleraiv1alpha3.ApplyTriggerKindArgoCD} {
- fatalIfErr((&controller.ApplyTriggerReconciler{
+ for _, kind := range []string{configbutleraiv1alpha3.ApplierClaimKindFlux, configbutleraiv1alpha3.ApplierClaimKindArgoCD} {
+ fatalIfErr((&controller.ApplierClaimReconciler{
Client: mgr.GetClient(),
Kind: kind,
Recorder: mgr.GetEventRecorderFor(strings.ToLower(kind)),
diff --git a/config/crd/bases/configbutler.ai_argocdtriggers.yaml b/config/crd/bases/configbutler.ai_argocdclaims.yaml
similarity index 89%
rename from config/crd/bases/configbutler.ai_argocdtriggers.yaml
rename to config/crd/bases/configbutler.ai_argocdclaims.yaml
index 089785364..2e5dc4ce5 100644
--- a/config/crd/bases/configbutler.ai_argocdtriggers.yaml
+++ b/config/crd/bases/configbutler.ai_argocdclaims.yaml
@@ -4,14 +4,14 @@ kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.23.0
- name: argocdtriggers.configbutler.ai
+ name: argocdclaims.configbutler.ai
spec:
group: configbutler.ai
names:
- kind: ArgoCDTrigger
- listKind: ArgoCDTriggerList
- plural: argocdtriggers
- singular: argocdtrigger
+ kind: ArgoCDClaim
+ listKind: ArgoCDClaimList
+ plural: argocdclaims
+ singular: argocdclaim
scope: Namespaced
versions:
- additionalPrinterColumns:
@@ -57,10 +57,10 @@ spec:
schema:
openAPIV3Schema:
description: |-
- ArgoCDTrigger holds an Argo CD Application's automated sync off for GitOps Reverser, which starts
+ ArgoCDClaim holds an Argo CD Application's automated sync off for GitOps Reverser, which starts
every sync it runs, each after publishing the edits pending on the branch: after a commit
GitOps Reverser did not push, after a refused edit, after a reverted edit, and to adopt what the
- API created or converge the Application (CatchUp). GitTargets name it with spec.applyTriggerRef.
+ API created or converge the Application (CatchUp). GitTargets name it with spec.applierClaimRef.
properties:
apiVersion:
description: |-
@@ -80,20 +80,20 @@ spec:
metadata:
type: object
spec:
- description: spec defines the Application the trigger holds
+ description: spec defines the Application the applier claim holds
properties:
applicationRef:
description: |-
ApplicationRef is the Argo CD Application that applies the folders of the GitTargets naming
- this trigger. It must have a single spec.source whose targetRevision names a branch. The
- trigger holds its automated sync off: it applies spec.syncPolicy.automated.enabled: false,
+ this applier claim. It must have a single spec.source whose targetRevision names a branch. The
+ applier claim holds its automated sync off: it applies spec.syncPolicy.automated.enabled: false,
which keeps automated.prune for the syncs GitOps Reverser starts, switches it off again when
somebody switches it on, and gives it up when it lets go. An Application an ApplicationSet
generated, or whose switch another field manager applies, must have automated sync off
- already: the switch is theirs, and the trigger only checks it. GitOps Reverser starts each
+ already: the switch is theirs, and the applier claim only checks it. GitOps Reverser starts each
sync by setting the Application's operation, as Argo CD's automated sync does, pinned to the
commit it checked, and never replaces an operation that is running. Its namespace defaults to
- the trigger's namespace. Immutable.
+ the applier claim's namespace. Immutable.
properties:
name:
description: Name of the referent.
@@ -107,7 +107,7 @@ spec:
type: object
x-kubernetes-validations:
- message: spec.applicationRef is immutable; delete and recreate the
- ArgoCDTrigger to hold another Application
+ ArgoCDClaim to hold another Application
rule: self == oldSelf
- message: spec.applicationRef.name must not be empty
rule: self.name != ''
@@ -126,7 +126,7 @@ spec:
description: |-
KubeProviderRef names the KubeProvider whose cluster holds the Application, such as a
management cluster running Argo CD for the source cluster. Namespace omitted means this
- trigger's own namespace. The provider must admit this trigger's namespace: its own, or one its
+ applier claim's own namespace. The provider must admit this applier claim's namespace: its own, or one its
spec.accessFrom admits. Its identity needs get, update and patch on the Application. Immutable.
properties:
name:
@@ -143,7 +143,7 @@ spec:
- message: spec.kubeProviderRef.name must not be empty
rule: self.name != ''
- message: spec.kubeProviderRef is immutable; delete and recreate
- the ArgoCDTrigger to hold an Application elsewhere
+ the ArgoCDClaim to hold an Application elsewhere
rule: self == oldSelf
required:
- applicationRef
@@ -154,22 +154,22 @@ spec:
which names a namespaced KubeProvider
rule: '!has(self.clusterProviderRef)'
status:
- description: status defines the observed state of ArgoCDTrigger
+ description: status defines the observed state of ArgoCDClaim
properties:
conditions:
description: |-
- Conditions are Configured (an eligible GitTarget names the trigger; the Application exists,
- syncs a single source, has automated sync off or a switch the trigger may hold, may be
- written, and no other ArgoCDTrigger holds it), Held (AutomatedSyncOff: automated sync is off,
+ Conditions are Configured (an eligible GitTarget names the applier claim; the Application exists,
+ syncs a single source, has automated sync off or a switch the applier claim may hold, may be
+ written, and no other ArgoCDClaim holds it), Held (AutomatedSyncOff: automated sync is off,
held by GitOps Reverser or kept off by the switch's owner; Releasing: being handed back;
- NotReferenced; NotConfigured), and the kstatus Ready/Reconciling/Stalled trio. A trigger acts only while an
+ NotReferenced; NotConfigured), and the kstatus Ready/Reconciling/Stalled trio. An applier claim acts only while an
eligible GitTarget references it: one in its namespace that names it, is admitted by its
- KubeProviders, and writes the repository and branch the Application syncs. A trigger no
+ KubeProviders, and writes the repository and branch the Application syncs. An applier claim no
GitTarget names reads nothing in the Application's cluster and is Configured=Unknown,
Ready=True, both NotReferenced; one named only by GitTargets that are not eligible is
- Configured NotBound or a refusal. Of the ArgoCDTriggers in line for one Application in one
+ Configured NotBound or a refusal. Of the ArgoCDClaims in line for one Application in one
cluster, one holds it: the one holding it already, then the one created first. The others are
- Configured=False (HeldByAnotherTrigger) until it lets go, and the next in line takes it over.
+ Configured=False (HeldByAnotherClaim) until it lets go, and the next in line takes it over.
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
@@ -271,7 +271,7 @@ spec:
type: string
reason:
description: |-
- Reason is why the apply was asked for. With several GitTargets on the trigger, it is the
+ Reason is why the apply was asked for. With several GitTargets on the applier claim, it is the
strongest of their reasons.
enum:
- ForeignCommit
@@ -300,7 +300,7 @@ spec:
type: object
previousSuspendState:
description: |-
- PreviousSuspendState is how the Application stood before the trigger first switched its
+ PreviousSuspendState is how the Application stood before the applier claim first switched its
automated sync off, which handing it back restores: NotSuspended (automated sync was on) or
Suspended (it was off). Recorded with the first switch-off, and cleared once the Application is
handed back. Absent, a hand-back gives the switch up and leaves what the Application's own spec
@@ -319,7 +319,7 @@ spec:
source:
description: |-
Source is the Application's spec.source, as last read, published while an eligible GitTarget
- references the trigger. A GitTarget naming this trigger must write the repository it syncs, on
+ references the applier claim. A GitTarget naming this applier claim must write the repository it syncs, on
the branch its targetRevision names.
properties:
repoURL:
diff --git a/config/crd/bases/configbutler.ai_fluxtriggers.yaml b/config/crd/bases/configbutler.ai_fluxclaims.yaml
similarity index 91%
rename from config/crd/bases/configbutler.ai_fluxtriggers.yaml
rename to config/crd/bases/configbutler.ai_fluxclaims.yaml
index e1a22c031..3c704f139 100644
--- a/config/crd/bases/configbutler.ai_fluxtriggers.yaml
+++ b/config/crd/bases/configbutler.ai_fluxclaims.yaml
@@ -4,14 +4,14 @@ kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.23.0
- name: fluxtriggers.configbutler.ai
+ name: fluxclaims.configbutler.ai
spec:
group: configbutler.ai
names:
- kind: FluxTrigger
- listKind: FluxTriggerList
- plural: fluxtriggers
- singular: fluxtrigger
+ kind: FluxClaim
+ listKind: FluxClaimList
+ plural: fluxclaims
+ singular: fluxclaim
scope: Namespaced
versions:
- additionalPrinterColumns:
@@ -57,10 +57,10 @@ spec:
schema:
openAPIV3Schema:
description: |-
- FluxTrigger holds a Flux Kustomization for GitOps Reverser, which starts every apply it makes,
+ FluxClaim holds a Flux Kustomization for GitOps Reverser, which starts every apply it makes,
each after publishing the edits pending on the branch: after a commit GitOps Reverser did not
push, after a refused edit, after a reverted edit, and to adopt what the API created or converge
- the Kustomization (CatchUp). GitTargets name it with spec.applyTriggerRef.
+ the Kustomization (CatchUp). GitTargets name it with spec.applierClaimRef.
properties:
apiVersion:
description: |-
@@ -80,7 +80,7 @@ spec:
metadata:
type: object
spec:
- description: spec defines the Kustomization the trigger holds
+ description: spec defines the Kustomization the applier claim holds
properties:
clusterProviderRef:
description: |-
@@ -97,7 +97,7 @@ spec:
description: |-
KubeProviderRef names the KubeProvider whose cluster holds the Kustomization, such as a
management cluster running Flux for the source cluster. Namespace omitted means this
- trigger's own namespace. The provider must admit this trigger's namespace: its own, or one its
+ applier claim's own namespace. The provider must admit this applier claim's namespace: its own, or one its
spec.accessFrom admits. Its identity needs get and patch on the Kustomization and its GitRepository. Immutable.
properties:
name:
@@ -114,13 +114,13 @@ spec:
- message: spec.kubeProviderRef.name must not be empty
rule: self.name != ''
- message: spec.kubeProviderRef is immutable; delete and recreate
- the FluxTrigger to hold a Kustomization elsewhere
+ the FluxClaim to hold a Kustomization elsewhere
rule: self == oldSelf
kustomizationRef:
description: |-
KustomizationRef is the Flux Kustomization that applies the folders of the GitTargets naming
- this trigger. Its spec.sourceRef must be a GitRepository that follows a branch. Its namespace
- defaults to the trigger's namespace. Immutable.
+ this applier claim. Its spec.sourceRef must be a GitRepository that follows a branch. Its namespace
+ defaults to the applier claim's namespace. Immutable.
properties:
name:
description: Name of the referent.
@@ -134,7 +134,7 @@ spec:
type: object
x-kubernetes-validations:
- message: spec.kustomizationRef is immutable; delete and recreate
- the FluxTrigger to hold another Kustomization
+ the FluxClaim to hold another Kustomization
rule: self == oldSelf
- message: spec.kustomizationRef.name must not be empty
rule: self.name != ''
@@ -147,23 +147,23 @@ spec:
which names a namespaced KubeProvider
rule: '!has(self.clusterProviderRef)'
status:
- description: status defines the observed state of FluxTrigger
+ description: status defines the observed state of FluxClaim
properties:
conditions:
description: |-
- Conditions are Configured (an eligible GitTarget names the trigger; the Kustomization exists,
- applies a GitRepository that follows a branch, may be patched, and no other FluxTrigger holds
+ Conditions are Configured (an eligible GitTarget names the applier claim; the Kustomization exists,
+ applies a GitRepository that follows a branch, may be patched, and no other FluxClaim holds
it), Held (Suspended: suspended under GitOps Reverser with the last suspend handled; Applying:
resumed for an apply GitOps Reverser started; Suspending: a suspend is not handled yet;
Releasing: being handed back; NotReferenced; NotConfigured), and the kstatus
- Ready/Reconciling/Stalled trio. A trigger acts only while an eligible GitTarget references it:
+ Ready/Reconciling/Stalled trio. An applier claim acts only while an eligible GitTarget references it:
one in its namespace that names it, is admitted by its KubeProviders, and writes the
- repository and branch the GitRepository fetches. A trigger no GitTarget names reads nothing
+ repository and branch the GitRepository fetches. An applier claim no GitTarget names reads nothing
in the Kustomization's cluster and is Configured=Unknown, Ready=True, both NotReferenced; one
named only by GitTargets that are not eligible is Configured NotBound or a refusal. Of the
- FluxTriggers in line for one Kustomization in one cluster, one holds it: the one with an apply
+ FluxClaims in line for one Kustomization in one cluster, one holds it: the one with an apply
in flight, then the one holding it already, then the one created first. The others are
- Configured=False (HeldByAnotherTrigger) until it lets go, and the next in line takes it over.
+ Configured=False (HeldByAnotherClaim) until it lets go, and the next in line takes it over.
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
@@ -265,7 +265,7 @@ spec:
type: string
reason:
description: |-
- Reason is why the apply was asked for. With several GitTargets on the trigger, it is the
+ Reason is why the apply was asked for. With several GitTargets on the applier claim, it is the
strongest of their reasons.
enum:
- ForeignCommit
@@ -294,7 +294,7 @@ spec:
type: object
previousSuspendState:
description: |-
- PreviousSuspendState is how the Kustomization stood before the trigger first suspended it,
+ PreviousSuspendState is how the Kustomization stood before the applier claim first suspended it,
which handing it back restores: one that was Suspended stays suspended, one that was
NotSuspended is resumed. Recorded with the first suspend, and cleared once the Kustomization is
handed back. Absent, a hand-back resumes the Kustomization.
@@ -311,7 +311,7 @@ spec:
source:
description: |-
Source is the GitRepository the Kustomization applies, as last read, published while an
- eligible GitTarget references the trigger. A GitTarget naming this trigger must write the
+ eligible GitTarget references the applier claim. A GitTarget naming this applier claim must write the
repository and branch it fetches.
properties:
branch:
@@ -334,7 +334,7 @@ spec:
otherConsumers:
description: |-
OtherConsumers are the other objects in the cluster that apply this GitRepository and that no
- FluxTrigger holds: Kustomizations, and HelmReleases whose chart comes from it. Each applies a
+ FluxClaim holds: Kustomizations, and HelmReleases whose chart comes from it. Each applies a
new artifact the moment it arrives. Information only: it never affects Ready.
items:
description: |-
diff --git a/config/crd/bases/configbutler.ai_gittargets.yaml b/config/crd/bases/configbutler.ai_gittargets.yaml
index 5186301a4..d4c6bd3d5 100644
--- a/config/crd/bases/configbutler.ai_gittargets.yaml
+++ b/config/crd/bases/configbutler.ai_gittargets.yaml
@@ -48,8 +48,8 @@ spec:
name: RenderMatchesLive
priority: 1
type: string
- - jsonPath: .status.conditions[?(@.type=="ApplyTriggerReady")].status
- name: ApplyTriggerReady
+ - jsonPath: .status.conditions[?(@.type=="ApplierClaimReady")].status
+ name: ApplierClaimReady
priority: 1
type: string
- jsonPath: .status.conditions[?(@.type=="StreamsRunning")].status
@@ -104,27 +104,28 @@ spec:
spec:
description: spec defines the desired state of GitTarget
properties:
- applyTriggerRef:
+ applierClaimRef:
description: |-
- ApplyTriggerRef names the apply trigger, in this GitTarget's namespace, that holds the applier
- of this folder: a FluxTrigger (a Flux Kustomization) or an ArgoCDTrigger (an Argo CD
+ ApplierClaimRef names the applier claim, in this GitTarget's namespace, that holds the applier
+ of this folder: a FluxClaim (a Flux Kustomization) or an ArgoCDClaim (an Argo CD
Application). With it set, GitOps Reverser decides when that applier applies, always after
publishing the edits it holds: after a commit it did not push, after a refused edit
(spec.onRefusal: Reapply), after an apply reverted a newer API edit, after the API created an
object in the folder (so the applier owns it and prunes it later), and once when the operator
- starts or the trigger is first named, after the folder's initial snapshots are in Git. Objects
- another applier owns are not mirrored into the folder. The trigger's applier must apply this target's repository and
- branch; the ApplyTriggerReady condition says whether it does. Several GitTargets may name one
- trigger, and it is asked once for all of them.
+ starts or the applier claim is first named, after the folder's initial snapshots are in Git. Objects
+ another applier owns are not mirrored into the folder. The applier claim's applier must apply this target's repository and
+ branch; the ApplierClaimReady condition says whether it does. Several GitTargets may name one
+ applier claim, and it is asked once for all of them.
properties:
kind:
- description: 'Kind is the trigger''s kind: FluxTrigger or ArgoCDTrigger.'
+ description: 'Kind is the applier claim''s kind: FluxClaim or
+ ArgoCDClaim.'
enum:
- - FluxTrigger
- - ArgoCDTrigger
+ - FluxClaim
+ - ArgoCDClaim
type: string
name:
- description: Name is the trigger's name.
+ description: Name is the applier claim's name.
minLength: 1
type: string
required:
@@ -372,12 +373,12 @@ spec:
"Ignore", the default, records the refusal in conditions and events and does nothing else.
- "Reapply" asks this target's applier (through spec.applyTriggerRef, which it requires) to
+ "Reapply" asks this target's applier (through spec.applierClaimRef, which it requires) to
apply the current tip after publishing the edits pending on the branch, which reverts the
refused edit and nothing that was waiting to be published. Only this
target's applier is asked. If its GitRepository has to fetch to reach the tip, the
- Kustomizations a FluxTrigger holds stay suspended and ignore the new artifact; only the
- consumers no trigger holds, listed in the trigger's status.source.otherConsumers, apply it, as
+ Kustomizations a FluxClaim holds stay suspended and ignore the new artifact; only the
+ consumers no applier claim holds, listed in the applier claim's status.source.otherConsumers, apply it, as
they would for any push.
It fires only for an edit to a document the folder ALREADY holds, in a write that removes no
@@ -571,9 +572,9 @@ spec:
- message: spec.clusterProviderRef was replaced by spec.kubeProviderRef,
which names a namespaced KubeProvider
rule: '!has(self.clusterProviderRef)'
- - message: 'spec.onRefusal: Reapply needs spec.applyTriggerRef, the trigger
- asked to reapply'
- rule: '!has(self.onRefusal) || self.onRefusal != ''Reapply'' || has(self.applyTriggerRef)'
+ - message: 'spec.onRefusal: Reapply needs spec.applierClaimRef, the applier
+ claim asked to reapply'
+ rule: '!has(self.onRefusal) || self.onRefusal != ''Reapply'' || has(self.applierClaimRef)'
status:
description: status defines the observed state of GitTarget
properties:
diff --git a/config/crd/bases/configbutler.ai_kubeproviders.yaml b/config/crd/bases/configbutler.ai_kubeproviders.yaml
index b4d54cede..9570a54d3 100644
--- a/config/crd/bases/configbutler.ai_kubeproviders.yaml
+++ b/config/crd/bases/configbutler.ai_kubeproviders.yaml
@@ -43,8 +43,8 @@ spec:
schema:
openAPIV3Schema:
description: |-
- KubeProvider names a source cluster: the cluster a GitTarget mirrors from, or an apply trigger
- reaches its applier in. It holds the credential that reaches it (a kubeconfig Secret in its own
+ KubeProvider is a connection to a Kubernetes cluster and the identity used there. A GitTarget
+ mirrors from it; an applier claim reaches its applier through it. It holds the credential that reaches it (a kubeconfig Secret in its own
namespace, or the operator's own identity where the installation allows it), which control-cluster
namespaces may use it (its own, and those spec.accessFrom admits), and the audit route its
attribution facts are partitioned under.
@@ -142,10 +142,10 @@ spec:
properties:
appliers:
description: |-
- Appliers lets a FluxTrigger or ArgoCDTrigger reaching its applier through this provider name a
+ Appliers lets a FluxClaim or ArgoCDClaim reaching its applier through this provider name a
Kustomization or Application in another namespace than its own. False (the default): only
- the trigger's own namespace, which in a remote cluster is the namespace of the same name; a
- trigger naming another is Configured=False (ApplierNamespaceNotAllowed).
+ the applier claim's own namespace, which in a remote cluster is the namespace of the same name; an
+ applier claim naming another is Configured=False (ApplierNamespaceNotAllowed).
type: boolean
sources:
description: |-
diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml
index 576105788..ce7cb62c6 100644
--- a/config/crd/kustomization.yaml
+++ b/config/crd/kustomization.yaml
@@ -1,10 +1,10 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- - bases/configbutler.ai_argocdtriggers.yaml
+ - bases/configbutler.ai_argocdclaims.yaml
- bases/configbutler.ai_clusterwatchrules.yaml
- bases/configbutler.ai_commitrequests.yaml
- - bases/configbutler.ai_fluxtriggers.yaml
+ - bases/configbutler.ai_fluxclaims.yaml
- bases/configbutler.ai_gitbranches.yaml
- bases/configbutler.ai_gitproviders.yaml
- bases/configbutler.ai_gitreceivers.yaml
diff --git a/config/rbac/argocd-applier-role.yaml b/config/rbac/argocd-applier-role.yaml
index 1a8757b56..6bb39fa35 100644
--- a/config/rbac/argocd-applier-role.yaml
+++ b/config/rbac/argocd-applier-role.yaml
@@ -1,11 +1,11 @@
-# What GitOps Reverser needs to drive an Argo CD Application an ArgoCDTrigger holds: read
+# What GitOps Reverser needs to drive an Argo CD Application an ArgoCDClaim holds: read
# the Application, set its operation to a sync (an update of the whole object, as Argo CD's own
# SetAppOperation makes it), hold its automated sync off (a server-side apply, so a patch), and
# check that it may.
#
# It is kept OUT of role.yaml because no GitTarget needs it until one names an Application, and an
# install that does not use Argo CD should not hold update rights on Applications. Drop this pair
-# when no ArgoCDTrigger exists (Helm: `applierAccess.argocd: false`, the
+# when no ArgoCDClaim exists (Helm: `applierAccess.argocd: false`, the
# default there). For an Application in another cluster, grant the same to the identity of the
# ClusterProvider's kubeconfig there.
apiVersion: rbac.authorization.k8s.io/v1
diff --git a/config/rbac/flux-applier-role.yaml b/config/rbac/flux-applier-role.yaml
index 53224e353..5d1f89c8d 100644
--- a/config/rbac/flux-applier-role.yaml
+++ b/config/rbac/flux-applier-role.yaml
@@ -1,12 +1,12 @@
-# What GitOps Reverser needs to drive a Flux Kustomization a FluxTrigger holds: read the
+# What GitOps Reverser needs to drive a Flux Kustomization a FluxClaim holds: read the
# Kustomization and the GitRepository it applies from, suspend and resume the Kustomization and
# stamp reconcile.fluxcd.io/requestedAt on either, list the consumers of the GitRepository
-# (Kustomizations, and HelmReleases with the HelmCharts built from it; without it the trigger lists
+# (Kustomizations, and HelmReleases with the HelmCharts built from it; without it the applier claim lists
# none in status.source.otherConsumers), and check that it may.
#
# It is kept OUT of role.yaml because no GitTarget needs it until one names an applier, and an
# install that does not use Flux should not hold patch rights on Flux objects. Drop this pair when
-# no FluxTrigger exists (Helm: `applierAccess.flux: false`, the default there). For a
+# no FluxClaim exists (Helm: `applierAccess.flux: false`, the default there). For a
# remote source cluster, grant the same to the identity of the ClusterProvider's kubeconfig there.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml
index daec1484e..b57da979b 100644
--- a/config/rbac/kustomization.yaml
+++ b/config/rbac/kustomization.yaml
@@ -7,10 +7,10 @@ resources:
# See docs/rbac.md; the Helm chart renders the same pair from `rbac.watchTypes`.
- watch-any-role.yaml
- watch-any-rolebinding.yaml
- # Patch rights on Flux Kustomizations and GitRepositories, for FluxTriggers. See the file.
+ # Patch rights on Flux Kustomizations and GitRepositories, for FluxClaims. See the file.
- flux-applier-role.yaml
- flux-applier-rolebinding.yaml
- # Update rights on Argo CD Applications, for ArgoCDTriggers. See the file.
+ # Update rights on Argo CD Applications, for ArgoCDClaims. See the file.
- argocd-applier-role.yaml
- argocd-applier-rolebinding.yaml
- cluster-role-jane-aggregated.yaml
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index 74270e3ca..5baf7a213 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -46,8 +46,8 @@ rules:
- apiGroups:
- configbutler.ai
resources:
- - argocdtriggers
- - fluxtriggers
+ - argocdclaims
+ - fluxclaims
- gitbranches
verbs:
- get
@@ -58,8 +58,8 @@ rules:
- apiGroups:
- configbutler.ai
resources:
- - argocdtriggers/finalizers
- - fluxtriggers/finalizers
+ - argocdclaims/finalizers
+ - fluxclaims/finalizers
- gitbranches/finalizers
- gitproviders/finalizers
verbs:
@@ -67,10 +67,10 @@ rules:
- apiGroups:
- configbutler.ai
resources:
- - argocdtriggers/status
+ - argocdclaims/status
- clusterwatchrules/status
- commitrequests/status
- - fluxtriggers/status
+ - fluxclaims/status
- gitbranches/status
- gitproviders/status
- gitreceivers/status
diff --git a/config/samples/README.md b/config/samples/README.md
index 6a53ae7bd..3c903b18d 100644
--- a/config/samples/README.md
+++ b/config/samples/README.md
@@ -12,13 +12,13 @@ These samples are quick starting points for common GitOps Reverser setups.
- `kubeprovider.yaml`: A `KubeProvider` for a remote cluster, with its kubeconfig Secret in its own
namespace, showing `accessFrom` and `allowCrossNamespace`.
- `clusterwatchrule.yaml`: Minimal `ClusterWatchRule` for cluster-scoped resources.
-- `fluxtrigger.yaml`, `argocdtrigger.yaml`: An apply trigger holding the Flux `Kustomization` or
- the Argo CD `Application` that applies `example-target`'s folder, for its `spec.applyTriggerRef`.
+- `fluxclaim.yaml`, `argocdclaim.yaml`: An applier claim holding the Flux `Kustomization` or
+ the Argo CD `Application` that applies `example-target`'s folder, for its `spec.applierClaimRef`.
- `commitrequest.yaml`: Minimal `CommitRequest` — an on-demand "save" signal that finalizes a
`GitTarget`'s open commit window. It uses `metadata.generateName`, so apply it with
`kubectl create -f`: `kubectl apply` refuses a generated name.
Every sample references `example-target` / `example-branch` / `example-provider`, the same names the chart's
`quickstart` values use, so the set is internally consistent and can be applied together. The
-`GitTarget` and the triggers mirror through `local` in `gitops-reverser`, the `KubeProvider` the chart
+`GitTarget` and the applier claims mirror through `local` in `gitops-reverser`, the `KubeProvider` the chart
renders in its release namespace.
diff --git a/config/samples/argocdclaim.yaml b/config/samples/argocdclaim.yaml
new file mode 100644
index 000000000..ff879d348
--- /dev/null
+++ b/config/samples/argocdclaim.yaml
@@ -0,0 +1,18 @@
+# An ArgoCDClaim holds the Argo CD Application, with automated sync off, that syncs
+# example-target's folder. Name it from the GitTarget with
+# spec.applierClaimRef: {kind: ArgoCDClaim, name: example-claim}. See docs/bi-directional-argocd.md.
+apiVersion: configbutler.ai/v1alpha3
+kind: ArgoCDClaim
+metadata:
+ name: example-claim
+ namespace: default
+spec:
+ # The cluster the Application is in: here the chart's `local` KubeProvider, the operator's own.
+ kubeProviderRef:
+ name: local
+ namespace: gitops-reverser
+ # In another namespace than the applier claim's, so the KubeProvider must set
+ # allowCrossNamespace.appliers: true, or the applier claim reports ApplierNamespaceNotAllowed.
+ applicationRef:
+ namespace: argocd
+ name: example-apps
diff --git a/config/samples/argocdtrigger.yaml b/config/samples/argocdtrigger.yaml
deleted file mode 100644
index 6c9a34c41..000000000
--- a/config/samples/argocdtrigger.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-# An ArgoCDTrigger holds the Argo CD Application, with automated sync off, that syncs
-# example-target's folder. Name it from the GitTarget with
-# spec.applyTriggerRef: {kind: ArgoCDTrigger, name: example-trigger}. See docs/bi-directional-argocd.md.
-apiVersion: configbutler.ai/v1alpha3
-kind: ArgoCDTrigger
-metadata:
- name: example-trigger
- namespace: default
-spec:
- # The cluster the Application is in: here the chart's `local` KubeProvider, the operator's own.
- kubeProviderRef:
- name: local
- namespace: gitops-reverser
- # In another namespace than the trigger's, so the KubeProvider must set
- # allowCrossNamespace.appliers: true, or the trigger reports ApplierNamespaceNotAllowed.
- applicationRef:
- namespace: argocd
- name: example-apps
diff --git a/config/samples/fluxclaim.yaml b/config/samples/fluxclaim.yaml
new file mode 100644
index 000000000..2921d769b
--- /dev/null
+++ b/config/samples/fluxclaim.yaml
@@ -0,0 +1,18 @@
+# A FluxClaim holds the Flux Kustomization that applies example-target's folder. Name it from the
+# GitTarget with spec.applierClaimRef: {kind: FluxClaim, name: example-claim}. See
+# docs/bi-directional-flux.md.
+apiVersion: configbutler.ai/v1alpha3
+kind: FluxClaim
+metadata:
+ name: example-claim
+ namespace: default
+spec:
+ # The cluster the Kustomization is in: here the chart's `local` KubeProvider, the operator's own.
+ kubeProviderRef:
+ name: local
+ namespace: gitops-reverser
+ # In another namespace than the applier claim's, so the KubeProvider must set
+ # allowCrossNamespace.appliers: true, or the applier claim reports ApplierNamespaceNotAllowed.
+ kustomizationRef:
+ namespace: flux-system
+ name: example-apps
diff --git a/config/samples/fluxtrigger.yaml b/config/samples/fluxtrigger.yaml
deleted file mode 100644
index da330c3d9..000000000
--- a/config/samples/fluxtrigger.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-# A FluxTrigger holds the Flux Kustomization that applies example-target's folder. Name it from the
-# GitTarget with spec.applyTriggerRef: {kind: FluxTrigger, name: example-trigger}. See
-# docs/bi-directional-flux.md.
-apiVersion: configbutler.ai/v1alpha3
-kind: FluxTrigger
-metadata:
- name: example-trigger
- namespace: default
-spec:
- # The cluster the Kustomization is in: here the chart's `local` KubeProvider, the operator's own.
- kubeProviderRef:
- name: local
- namespace: gitops-reverser
- # In another namespace than the trigger's, so the KubeProvider must set
- # allowCrossNamespace.appliers: true, or the trigger reports ApplierNamespaceNotAllowed.
- kustomizationRef:
- namespace: flux-system
- name: example-apps
diff --git a/config/samples/kubeprovider.yaml b/config/samples/kubeprovider.yaml
index 8ab6e55dd..7bb8c52f0 100644
--- a/config/samples/kubeprovider.yaml
+++ b/config/samples/kubeprovider.yaml
@@ -25,7 +25,7 @@ spec:
tier: trusted
# What those namespaces may reach outside themselves, each granted on purpose. sources: a
# WatchRule may choose which SOURCE namespaces to mirror; while false (the default) it watches
- # only its own namespace, and `sourceNamespace: "*"` is refused outright. appliers: a trigger may
+ # only its own namespace, and `sourceNamespace: "*"` is refused outright. appliers: an applier claim may
# suspend and apply a Kustomization or Application in another namespace. Both delegate to every
# admitted namespace; neither proves that a namespace owns what it reaches.
allowCrossNamespace:
diff --git a/docs/INDEX.md b/docs/INDEX.md
index 7680f6cd2..42007fbd2 100644
--- a/docs/INDEX.md
+++ b/docs/INDEX.md
@@ -152,8 +152,8 @@ so you never have to read a proposal to find out it already shipped.
| [`watch-and-catalog-architecture.md`](design/watch-and-catalog-architecture.md) | the target three-layer watch model — **needs a human call before building** |
| [`metrics-observability-plan.md`](design/metrics-observability-plan.md) | **built.** the canonical metrics document, rewritten 2026-09 and shipped in the same wave. Reads the product as two ingestion paths — object state through watch, authorship through audit — joining at the resolver, and gives each stage one counter with a bounded `outcome` so the flow and its loss paths are drawable. Records what three review rounds corrected, including two reversals worth not re-litigating: the fact-loss counters must NOT be merged (an eviction counts a fact, a trim gap an occurrence, a decode error a whole batch), and a cumulative counter cannot replace a current count, which is why `status.streams` and `status.retainedDocuments` stayed. The instrument inventory lives in [`interpreting-metrics.md`](interpreting-metrics.md), not here. Open: the Grafana JSON and alert rules |
| [`reconcile-triggering.md`](design/reconcile-triggering.md) | which controllers still fail to wake up |
-| [`applier-echoes.md`](design/applier-echoes.md) | **built 2026-10-20, released as one change.** The applier's own write, back through the watch, is mirrored and re-planned onto a newer tip on replay, reverting a Git-side push. Two ideas close every way a stale picture can reach a commit: a write that changes nothing does not exist, and a pending write carries the actions decided on its tree, not a picture. The applier's write in the apply window is not mirrored (F1); a write is upserts and deletes decided once, so a replay never sweeps again and a save keeps its objects (F2); a resync never decides from a snapshot collected while Git was ahead or across an apply, by an apply epoch (F3); another applier's object is not mirrored into a triggered folder (F4); flush, then apply (S1); `CatchUp` only to adopt API creates and to converge, for Flux and Argo CD alike, and no apply before every initial snapshot is published (S2). Carries the collisions left open, the promises, the code plan, thirteen e2e scenarios, and the twelve decisions taken while building (among them: siblings do not count for F3, a 5-minute hold, initial snapshots exempt from F3, the prefetch removed, no heal resync, and the flush covering the sibling workers) |
-| [`apply-trigger.md`](design/apply-trigger.md) | **built for Flux and Argo CD; a webhook applier is the aim.** Why GitOps Reverser decides when Git is applied, and why the applier never applies on its own: a Flux `Kustomization` is held suspended by its `FluxTrigger` and resumed for each apply, an Argo CD `Application`'s automated sync is held off by its `ArgoCDTrigger`. Carries what building it taught (timers are triggers, a long interval is not exclusive control, a decision must hold at the write, a retry is a decision so only the worker makes one, an apply is an operation whose end is observed), the kustomize-controller v1.9.6 facts the suspended lifecycle rests on, its operation (resume token, outcome, the suspend after the apply), who owns `spec.suspend` (a trigger acts only while an eligible `GitTarget` references it, one trigger per applier), restart recovery, shared sources, the measured ~50 ms cost, flush then apply, `CatchUp` as adopt and converge for Flux and Argo CD alike, the apply window with the applier's own writes kept out of Git, resyncs held off while Git is ahead, another applier's objects kept out of a triggered folder, and the decisions of 2026-10-17, 2026-10-19, 2026-10-20 and 2026-10-23 (the rule, the `allowCrossNamespace.appliers` grant, `Held`, the reversals of 2026-10-20, Argo CD's automated sync held off like a suspend) |
+| [`applier-echoes.md`](design/applier-echoes.md) | **built 2026-10-20, released as one change.** The applier's own write, back through the watch, is mirrored and re-planned onto a newer tip on replay, reverting a Git-side push. Two ideas close every way a stale picture can reach a commit: a write that changes nothing does not exist, and a pending write carries the actions decided on its tree, not a picture. The applier's write in the apply window is not mirrored (F1); a write is upserts and deletes decided once, so a replay never sweeps again and a save keeps its objects (F2); a resync never decides from a snapshot collected while Git was ahead or across an apply, by an apply epoch (F3); another applier's object is not mirrored into a coordinated folder (F4); flush, then apply (S1); `CatchUp` only to adopt API creates and to converge, for Flux and Argo CD alike, and no apply before every initial snapshot is published (S2). Carries the collisions left open, the promises, the code plan, thirteen e2e scenarios, and the twelve decisions taken while building (among them: siblings do not count for F3, a 5-minute hold, initial snapshots exempt from F3, the prefetch removed, no heal resync, and the flush covering the sibling workers) |
+| [`apply-trigger.md`](design/apply-trigger.md) | **built for Flux and Argo CD; a webhook applier is the aim.** Why GitOps Reverser decides when Git is applied, and why the applier never applies on its own: a Flux `Kustomization` is held suspended by its `FluxClaim` and resumed for each apply, an Argo CD `Application`'s automated sync is held off by its `ArgoCDClaim`. Carries what building it taught (timers are triggers, a long interval is not exclusive control, a decision must hold at the write, a retry is a decision so only the worker makes one, an apply is an operation whose end is observed), the kustomize-controller v1.9.6 facts the suspended lifecycle rests on, its operation (resume token, outcome, the suspend after the apply), who owns `spec.suspend` (an applier claim acts only while an eligible `GitTarget` references it, one applier claim per applier), restart recovery, shared sources, the measured ~50 ms cost, flush then apply, `CatchUp` as adopt and converge for Flux and Argo CD alike, the apply window with the applier's own writes kept out of Git, resyncs held off while Git is ahead, another applier's objects kept out of a coordinated folder, and the decisions of 2026-10-17, 2026-10-19, 2026-10-20 and 2026-10-23 (the rule, the `allowCrossNamespace.appliers` grant, `Held`, the reversals of 2026-10-20, Argo CD's automated sync held off like a suspend) |
| [`push-cooldown.md`](design/push-cooldown.md) | **partly built.** Whether the fixed `5s` push cooldown still earns its place now that the commit window exists. Ordinary timer closures already space one author writing one target, but identity changes, saves with shorter timers and buffer-limit closures commit sooner, and serialization does not batch queued commits, so the cooldown is what batches them. Option C, the failure backoff, is built in #412 and shares the push timer; removing the success wait would now remove only `lastPushAt` and its branch, so that decision waits on the event-loop measurements in §9 |
| [`multi-source-audit-ingress-hardening.md`](design/multi-source-audit-ingress-hardening.md) | how independent sources authenticate to a named audit route, when annotation routing is trustworthy, and how multi-provider ingestion remains fair |
| [`release-image-reuse-plan.md`](design/release-image-reuse-plan.md) | PRs 2–5 unstarted |
@@ -170,7 +170,7 @@ itself non-binding. Read them as history that the code still points at.
| Doc | Open question |
|---|---|
-| [`push-notification-and-reconcile-trigger.md`](design/push-notification-and-reconcile-trigger.md) | **built, and superseded** by [`apply-trigger.md`](design/apply-trigger.md): the record of how the apply trigger was decided. Kept here because its Part 1, how the branch worker reads the remote (the pre-push fetch, the push advertisement, base trust), is cited from the Git code by section number |
+| [`push-notification-and-reconcile-trigger.md`](design/push-notification-and-reconcile-trigger.md) | **built, and superseded** by [`apply-trigger.md`](design/apply-trigger.md): the record of how the applier claim was decided. Kept here because its Part 1, how the branch worker reads the remote (the pre-push fetch, the push advertisement, base trust), is cited from the Git code by section number |
| [`vocabulary-cleanup.md`](design/vocabulary-cleanup.md) | **built.** The breaking release that made the API obey [`definitions.md`](definitions.md): condition reasons that restated their type are `Succeeded`, `GitRepoConfigNotFound` is `GitProviderNotFound`, printer columns are named for what they read and the dependency projections are gone, a bare commit hash is `commit` in every status field, and the source-cluster provider's throttles are `spec.client`. Kept here because `definitions.md` cites it as the record of which audit findings the existing rulebooks already settled: the `insecure` flag split, bare-noun booleans, `--redis-addr`, and the lowercase `sops` enum |
| [`created-root-namespace.md`](design/created-root-namespace.md) | **built.** One question with five answers: what namespace a `kustomization.yaml` the operator CREATES should carry. Decided **B, never write one** — `spec.serializeNamespace: false` means the artifact does not encode its deployment namespace, and adding a root must not quietly change that contract; the namespace comes from the documents when the field is unset or `true`, and from the installer (Flux `targetNamespace`, Argo `destination.namespace`) when it is `false`. Records the three facts an earlier draft got wrong (a namespace-less root is ordinary, both installers supply one, and what refused it was our own fidelity gate rather than kustomize), and carries the scoped fidelity rule that follows: the namespace is ignored in the render comparison ONLY when the governing root sets none, so a root that declares `namespace: shop` still rejects a live `billing` object. Shipped in #328, which is also why [`kustomization_bootstrap.go`](../internal/git/kustomization_bootstrap.go) cites the page by path. Also the sibling call: under `useKustomize` a placement no `resources:` list would name is refused rather than committed unrendered |
| [`watch-manager-ownership.md`](design/watch-manager-ownership.md) | A rule edit used to be applied inline by the controller worker that observed it, and it re-planned EVERY GitTarget rather than the one the rule names: 1256 plan reconciles across 28 targets in one e2e run, peaking at 78 in a second, behind two network calls, on a shared worker pool. The watch manager had no owner, so eleven mutexes stood in for one. Now controllers post a trigger naming a GitTarget and return, one loop owns the plan and paces itself, and repeated triggers for one target collapse into a single pass. The debounce is framed around how the config is actually edited: a GitTarget and its rules are one piece of configuration applied together, so a per-target ROLLING SILENCE window of 2s (max wait ~10s) turns a five-object `kubectl apply` into one pass, the same mechanism `DefaultCommitWindow` already uses one layer down on the write path. That reverses an earlier revision's "never debounce the first declaration": declaring a GitTarget the instant it lands means declaring it with no rules yet, which manufactures a transient EMPTY plan on every cold start, and an empty plan is what vacuously cleared a write divergence in the fidelity gate. States the contract as "one settled configuration adjustment, not one function invocation": the window is a heuristic and never a correctness boundary (Kubernetes has no apply-complete event), a per-target DIRTY SEQUENCE means a change arriving mid-pass is never lost, and the pass reads a coherent rule-store snapshot rather than the rule that triggered it. Carries the deletion inventory, because the point is that the system got smaller: four trigger mechanisms collapsed to one (`signalCatalogRefresh` and `catalogRefreshCh` are gone), `refreshRunningTargetWatches` and its running-set filter are gone (that filter is why a target whose first declare never completed was never picked up again), and six mutexes went for stated reasons, with `RenderFidelityGate.mu` and the two event-channel locks kept and justified. The four steps shipped: the local-cluster discovery call is bounded (a real defect — the legacy non-context `ServerGroupsAndResources()` ran with no deadline at all), the owner loop carries the debounce, dirty sequence, per-target deadline and 2s/5s/10s/30s/1m backoff, the plan now carries NO lock while the projection is a published snapshot, and catalog invalidation is scoped by diffing each target's rendered plan across the re-projection. "What shipped" records where the implementation departed from the page, including the one bug only e2e caught: streams were parented to the PASS context, which a deadline cancels the moment the pass returns, so every stream died the instant its plan was applied — and it reads like health, because the plan logs `start:1`, every later pass reports `keep:1` and never restarts it, and nothing logs an error while readiness sits at `Replaying` and every WatchRule sits `Ready=False`. A stream's parent is the manager's lifetime; the pass deadline bounds the pass. A second e2e catch is a BEHAVIORAL consequence worth knowing: toggling a rule off and on inside the settle window is no longer a replay — it used to tear the stream down and re-establish it because each apply replanned synchronously, and it is now one pass over a plan that never changed. Correct (a net-zero change is no change; widening `prune.mode` remains the supported force) but a real difference in what an operator gesture does. Both specs that broke were also gating on the wrong thing: asking a GitTarget "are all your streams running" about a change to ONE rule, which a target that is already mirroring answers True to before that rule has been planned, and which a different controller publishes than the one that compiled the rule — so the rule's OWN StreamsRunning is the gate, and `waitForWatchRuleStreamsRunning` existed unused for exactly this. Departures: reports became a published snapshot rather than a second channel; ISOLATION came from taking the I/O off the loop rather than from the deadline (a pass never dials, the shared refresh runs on its own goroutine, and the deadline is the backstop it should have been — a first cut that kept two network calls on the loop had one unreachable cluster holding every healthy target, which is the same availability failure relocated); DELETION names an incarnation resolved when it is queued, because both production callers react to a NotFound and carry no UID, so a UID-less delete matched everything and could tear down the successor of a same-name recreate; and persistent failure surfaces as `WatchPlanFailing`, where pending means "no pass has ever landed", not "dirty right now". Step 3's type-to-target index did not earn its staleness. Still open: whether the settle window ever needs to be configurable |
diff --git a/docs/TODO.md b/docs/TODO.md
index b5037a629..31bdce51b 100644
--- a/docs/TODO.md
+++ b/docs/TODO.md
@@ -208,8 +208,8 @@ rebuild fails is no longer dropped: it stays pending for that retry
lands.
- [ ] Better branching and promotion strategies.
-- [ ] A webhook applier, so an apply trigger can hold a tool other than Flux or Argo CD. Flux and
- Argo CD are built; see the [apply trigger design](design/apply-trigger.md).
+- [ ] A webhook applier, so an applier claim can hold a tool other than Flux or Argo CD. Flux and
+ Argo CD are built; see the [applier claim design](design/apply-trigger.md).
Research work:
diff --git a/docs/UPGRADING.md b/docs/UPGRADING.md
index 46882b934..f391f9a0a 100644
--- a/docs/UPGRADING.md
+++ b/docs/UPGRADING.md
@@ -7,6 +7,17 @@ guidance that the changelog's breaking-change entries link to.
We are pre-1.0, so breaking changes bump the **minor** version (release-please is configured with
`bump-minor-pre-major`) rather than the major. Read the relevant entry before upgrading across it.
+## `FluxTrigger` and `ArgoCDTrigger` are now `FluxClaim` and `ArgoCDClaim`
+
+**Breaking, with no migration path.** Before upgrading, delete every `FluxTrigger` and
+`ArgoCDTrigger` (a `FluxTrigger` hands its `Kustomization` back to Flux, an `ArgoCDTrigger` its
+`Application`'s automated sync), then delete their CRDs. After upgrading, recreate them under the
+new kinds with the same `spec`, and on each `GitTarget` replace `spec.applyTriggerRef` with
+`spec.applierClaimRef`. The `GitTarget` condition is `ApplierClaimReady` (reason
+`ApplierClaimNotFound`), the reason `HeldByAnotherTrigger` is `HeldByAnotherClaim`, and the
+finalizers are `configbutler.ai/flux-claim` and `configbutler.ai/argocd-claim`. The entries below
+use the old names. See [applier claim](definitions.md#applying-back).
+
## An `ArgoCDTrigger` holds its `Application`'s automated sync off
An `ArgoCDTrigger` no longer only checks that automated sync is off: it switches it off, with a
@@ -315,7 +326,7 @@ status update until the field changes. The old CRD rejects `Reapply`, so move in
`ApplierNamespaceNotAllowed`.
4. Set `onRefusal: Reapply` where you want it.
-See [who applies this folder](configuration.md#who-applies-this-folder-specapplytriggerref) and
+See [who applies this folder](configuration.md#who-applies-this-folder-specapplierclaimref) and
[reverting a refused edit](configuration.md#reverting-a-refused-edit-speconrefusal).
A trigger also changes what else applies the folder. A `FluxTrigger` holds its `Kustomization`
diff --git a/docs/api-first-publication.md b/docs/api-first-publication.md
index b5d8ae99b..64614b7fc 100644
--- a/docs/api-first-publication.md
+++ b/docs/api-first-publication.md
@@ -30,7 +30,7 @@ worker can plan locally and spend its network budget on publication.
This guide describes the shipped behavior. Retained work is retried on the worker's own backoff;
[failure timing](#failure-timing-and-memory-limits) says what that means in practice. The
[push receiver](configuration.md#gitreceiver) lets a Git host report
-a push, the [apply trigger design](design/apply-trigger.md) covers what follows one, and
+a push, the [applier claim design](design/apply-trigger.md) covers what follows one, and
[architecture](architecture.md) covers the wider operator.
## The structures and their responsibilities
@@ -238,7 +238,7 @@ preserved within the supported layout rules. See
This is the consequence of API-first ownership: the captured API object drives the supported
manifest edit. A Git-side change reaches the cluster through an applier such as Flux or Argo CD.
-Reverser does not apply Git to Kubernetes itself; with an apply trigger it decides when the applier
+Reverser does not apply Git to Kubernetes itself; with an applier claim it decides when the applier
does.
The [deferred three-way comparison investigation](future/git-api-three-way-comparison.md) records
@@ -352,7 +352,7 @@ fetched manifests, which is why a receiver delivery never triggers one.
When Flux or Argo CD applies the same path on its own schedule, its refresh and apply cadence also
affects whether a live edit survives long enough to be published. An
-[apply trigger](configuration.md#who-applies-this-folder-specapplytriggerref) takes that clock
+[applier claim](configuration.md#who-applies-this-folder-specapplierclaimref) takes that clock
away: the applier then applies only when GitOps Reverser asks, after a flush that publishes every
write decided before the apply came due. An edit decided after the flush is recorded in the apply
window, so an apply that overwrites it is withheld and repaired. A push of GitOps Reverser's own
diff --git a/docs/architecture.md b/docs/architecture.md
index 9f8143d54..677997959 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -50,8 +50,13 @@ deleting the definition (and every object of the kind with it) and recreating bo
These are the design decisions to keep in your head while reading or changing the code:
-**The Kubernetes API is the source of truth.** Git is a materialized mirror of desired state from the
-API. State is ingested by **watch**; these paths never treat Git as authority. When a push conflicts
+**The Kubernetes API is the source of truth for what GitOps Reverser writes.** Git is a
+materialized mirror of desired state from the API, ingested by **watch**; GitOps Reverser never
+reads Git as object state. An edit made in Git reaches the cluster only through Flux or Argo CD.
+When a `GitTarget` names an applier claim, those applies are coordinated: GitOps Reverser first
+publishes the API edits it has decided, then asks the applier to apply. The watch observes the
+result, and the applier's own writes during that apply (its echoes) are not mirrored, because Git
+already holds them. When a push conflicts
with a newer remote commit, the operator fetches the new remote state, resets its local clone, and
replays its retained writes from the API.
@@ -82,7 +87,7 @@ which with writes retained says their local commits are gone and must be replaye
two would let one kind of doubt clear another. Either one makes the next cycle fetch. The
failure direction is safe by construction: a stale "untrusted" costs one fetch, while a stale "trusted"
is caught by the compare-and-swap on the next push. For a new write branch, the worker checks the
-parent's advertised tip before sending the push. A moved parent triggers fetch, reset, and replay.
+parent's advertised tip before sending the push. A moved parent applier claims fetch, reset, and replay.
The server's compare-and-swap protects creation of the write branch with `Old = zero`; it does not
lock the parent. The parent can still move after the advertisement, so what is guaranteed is the
parent's tip as that advertisement showed it. **The corollary is the rule to keep in your head
@@ -128,7 +133,7 @@ publish them over the incoming change.
**GitOps Reverser decides when Git is applied to the cluster.** An apply must not revert an edit
that is live and not yet in Git, and only the worker writing the branch can see those edits. So a
-`GitTarget` names an **apply trigger**, which holds its **applier** so that it applies nothing on
+`GitTarget` names an **applier claim**, which holds its **applier** so that it applies nothing on
its own (a Flux `Kustomization` is held suspended, an Argo CD `Application` held with automated sync off),
and the branch worker sends **apply requests** instead. A request is sent only **after a flush**:
when an apply comes due, the worker closes the open commit window, publishes every write decided so
@@ -143,17 +148,17 @@ Continuous editing therefore never holds an apply back. A request goes out:
- to catch up (`CatchUp`), for a Flux `Kustomization` and an Argo CD `Application` alike: to adopt
an object the API created in the folder, because an applier prunes only what it applied and a
later Git-side delete would otherwise leave the object running; and to converge, once when the
- worker starts and once when a `GitTarget` first names the trigger, unless the applier already
+ worker starts and once when a `GitTarget` first names the applier claim, unless the applier already
applied the tip. A push of GitOps Reverser's own asks for nothing: the cluster already runs the
edits it holds.
No applier is asked for anything before every initial snapshot of the `GitTarget`s it covers is
published, so the edits made while the operator was down reach Git before any apply. The worker
keeps each reason pending until an apply operation answers it, and decides every retry the same
-way. A request runs off the publication path and cannot fail a publication; the trigger's
+way. A request runs off the publication path and cannot fail a publication; the applier claim's
`status.operation` records the operation it started. **Built:**
the Flux `Kustomization` applier and the Argo CD `Application` applier, its automated sync held off.
-**The aim:** a webhook for any other tool. See [the apply trigger design](design/apply-trigger.md).
+**The aim:** a webhook for any other tool. See [the applier claim design](design/apply-trigger.md).
**Redis/Valkey is optional but advised.** The default configured-author mode runs without it: a plain
`helm install` comes up healthy and watches initialize from current state on startup. When an endpoint
@@ -280,7 +285,7 @@ Git destination. `KubeProvider` supplies the source connection and authorization
`GitProvider` supplies the repository, credentials, commit settings, and push policy, and a
`GitBranch` holds one branch of that repository for every `GitTarget` that writes it. A
`GitReceiver` is the endpoint the Git host's push webhook calls; nothing references it, because a
-delivery is routed by the repository it names. `FluxTrigger` and `ArgoCDTrigger` hold the Flux
+delivery is routed by the repository it names. `FluxClaim` and `ArgoCDClaim` hold the Flux
`Kustomization` or Argo CD `Application` that applies a folder back, so GitOps Reverser decides
when it applies.
@@ -290,7 +295,7 @@ graph LR
WR[WatchRule] -->|gitTargetRef| GT[GitTarget]
CWR[ClusterWatchRule] -->|gitTargetRef| GT
CR[CommitRequest] -->|gitTargetRef| GT
- GT -->|applyTriggerRef| TR["FluxTrigger /\nArgoCDTrigger"]
+ GT -->|applierClaimRef| TR["FluxClaim /\nArgoCDClaim"]
GT -->|kubeProviderRef| CP[KubeProvider]
GT -->|gitBranchRef| GB[GitBranch]
GB -->|gitProviderRef| GP[GitProvider]
@@ -329,8 +334,8 @@ open their source watches through that context. The contexts are keyed by the pr
recreated under the same name is a new connection.
The applier is not one of the ten kinds. It belongs to Flux or Argo CD, lives in the cluster the
-trigger's `spec.kubeProviderRef` names (the source cluster, or a management cluster), and is
-reached through that `KubeProvider`'s identity. The trigger is ours: it holds the applier, and
+applier claim's `spec.kubeProviderRef` names (the source cluster, or a management cluster), and is
+reached through that `KubeProvider`'s identity. The applier claim is ours: it holds the applier, and
records the apply operation it started.
| CRD | Scope | One line role |
@@ -343,8 +348,8 @@ records the apply operation it started.
| `GitBranch` | namespaced | one repository branch, held for every `GitTarget` that writes it |
| `GitProvider` | namespaced | a Git repo + credentials + commit/signing config |
| `GitReceiver` | namespaced | the endpoint a Git host notifies on a push, for every `GitProvider` whose `spec.url` a delivery names |
-| `FluxTrigger` | namespaced | the Flux `Kustomization` GitOps Reverser asks to apply, and the apply in flight |
-| `ArgoCDTrigger` | namespaced | the Argo CD `Application` GitOps Reverser asks to sync, and the sync in flight |
+| `FluxClaim` | namespaced | the Flux `Kustomization` GitOps Reverser asks to apply, and the apply in flight |
+| `ArgoCDClaim` | namespaced | the Argo CD `Application` GitOps Reverser asks to sync, and the sync in flight |
### WatchRule / ClusterWatchRule
@@ -436,12 +441,12 @@ Key fields:
- `spec.commit.window`: when this target's commit windows close: `idleTimeout` (default `5s`) of
silence, or `maxDuration` (default `1m`) after the window opened.
- `spec.commit.message`: `liveTemplate` / `reconcileTemplate` Go templates.
-- `spec.applyTriggerRef`: optional; the `FluxTrigger` (a Flux `Kustomization` GitOps Reverser holds
- suspended and asks to apply) or `ArgoCDTrigger` (an Argo CD `Application` whose automated sync
- GitOps Reverser holds off, and whose syncs it starts) in this namespace. In a folder a trigger applies, an object
+- `spec.applierClaimRef`: optional; the `FluxClaim` (a Flux `Kustomization` GitOps Reverser holds
+ suspended and asks to apply) or `ArgoCDClaim` (an Argo CD `Application` whose automated sync
+ GitOps Reverser holds off, and whose syncs it starts) in this namespace. In a coordinated folder, an object
another applier owns (by Flux's owner labels or Argo CD's tracking annotation) is not mirrored.
- `spec.onRefusal`: `Ignore` (default) or `Reapply`, which asks the applier to reapply the tip when
- an edit to an existing document is refused. `Reapply` requires `spec.applyTriggerRef`.
+ an edit to an existing document is refused. `Reapply` requires `spec.applierClaimRef`.
`spec.commit` describes the folder, not the connection, so two `GitTarget`s sharing one `GitBranch`
may batch and phrase their commits differently. A branch worker serves a repository branch, through
@@ -471,13 +476,13 @@ Status has a kstatus-compatible summary layer plus domain conditions:
source API the data plane can currently reach.
- `GitPathAccepted` explains the target side: the selected Git path is safe for the operator to
materialize.
-- `ApplyTriggerReady`, present only with `spec.applyTriggerRef`, says the trigger is `Ready` (its
+- `ApplierClaimReady`, present only with `spec.applierClaimRef`, says the applier claim is `Ready` (its
applier exists, can be written, and is held: a `Kustomization` suspended, an `Application`'s
automated sync off) and that the
applier applies this repository and branch. It is a gate.
- `status.streams` is a bounded count summary, not a per-type list.
-A trigger has `Configured` (a `FluxTrigger` also `Held`) and the kstatus trio, `status.source`
+An applier claim has `Configured` (a `FluxClaim` also `Held`) and the kstatus trio, `status.source`
(what its applier applies, as last read), and `status.operation` and `status.repairPending`, which
the branch worker writes at every move of the operation it started.
@@ -567,8 +572,11 @@ namespace tracks a repository.
- **Source**: [api/v1alpha3/kubeprovider_types.go](../api/v1alpha3/kubeprovider_types.go)
- **Controller**: [internal/controller/kubeprovider_controller.go](../internal/controller/kubeprovider_controller.go)
-`KubeProvider` is the read-side peer of `GitProvider`. A `GitTarget`, `FluxTrigger` or `ArgoCDTrigger`
-references it by the required, immutable `spec.kubeProviderRef`; an omitted `namespace` means the
+`KubeProvider` is the Kubernetes-side peer of `GitProvider`: a connection to one cluster and the
+identity used there. What it's used for depends on who names it: a `GitTarget` reads its source
+cluster through it, and an applier claim reaches its `Kustomization` or `Application` through it,
+often in a management cluster. A `GitTarget`, `FluxClaim` or `ArgoCDClaim` references it by the
+required, immutable `spec.kubeProviderRef`; an omitted `namespace` means the
referrer's own, and nothing falls back to a default provider. Its credential is exactly one of two
immutable arms (delete and recreate the provider to repoint it):
@@ -712,7 +720,7 @@ flowchart TD
RESOLVED --> WINDOW
UNRESOLVED --> WINDOW
- WINDOW --> TIMER{Close trigger}
+ WINDOW --> TIMER{Close applier claim}
TIMER -->|silence timer| BASE
TIMER -->|buffer limit or resync boundary| BASE
@@ -803,7 +811,7 @@ when `spec.onRefusal: Reapply` is set; `Repair`, to an applier whose late write
edit the apply window withheld; and `CatchUp`, to a `Kustomization` or an `Application` alike, to
adopt an object the API created in a folder that applier applies (a live `ADDED` event whose newest
managers are not an applier's, or an object a resync added to Git) and to converge, once when the
-worker starts and once when a `GitTarget` first names the trigger. A converge of the commit the
+worker starts and once when a `GitTarget` first names the applier claim. A converge of the commit the
applier last applied asks nothing; an adopt is sent even then, because applying the same commit
again is what stamps the owner labels or tracking annotation on the new object. A push of the
worker's own leaves nothing pending.
@@ -821,17 +829,17 @@ hand, whichever namespace its `GitTarget` is in.
Which apply is due lives on the worker, per applier: one attempt out at a time, and its own retry
clock. The dispatcher in `internal/applier` carries out one attempt off the worker's queue and
reports back. It reads each `GitTarget` again first (the same incarnation by uid, still naming this
-applier through its trigger on this branch, still admitted, still consenting to a `Refusal`), checks that the
+applier through its applier claim on this branch, still admitted, still consenting to a `Refusal`), checks that the
`Kustomization`'s source fetches this repository and branch, has the `GitRepository` fetch the
commit when its artifact lacks it, asks the worker whether its decision still holds (the commit is
-still the tip), writes the operation through to the trigger's
-`status.operation`, and only then resumes the `Kustomization` the trigger holds suspended, with a
+still the tip), writes the operation through to the applier claim's
+`status.operation`, and only then resumes the `Kustomization` the applier claim holds suspended, with a
`requestedAt` token. It follows the apply until that token is handled, suspends the `Kustomization`
again with a second token, and reports the operation complete once that one is handled
([`apply-trigger.md`](design/apply-trigger.md#the-flux-applier-suspended)). It never retries:
an attempt that does not deliver gives the worker back what it carried, a refusal only while it still
stands, and the worker decides the retry after a flush, once that applier's backoff has run out. A delivered
-request records an `ApplyRequested` Event, and the worker records the operation on the trigger's
+request records an `ApplyRequested` Event, and the worker records the operation on the applier claim's
`status.operation`. A failed request never reaches back
into the publication that caused it.
@@ -1262,7 +1270,7 @@ rather than being pruned. Both protect against accidental Git deletions on a dis
[typeset-owns-discovery-grace.md](spec/typeset-owns-discovery-grace.md)). Every active source context has
its own catalog and registry, so a CRD served only by `prod-eu-1` is never resolved for a target mirroring
`prod-us-1`. Catalogs refresh at startup and on the 30-second reconciliation cadence. CRD/APIService
-trigger informers run only in the **control plane**, where the operator's own CRDs live; a remote source's
+applier claim informers run only in the **control plane**, where the operator's own CRDs live; a remote source's
API-surface change is discovered on that periodic refresh. A target's plan pass never calls discovery
itself; for a source nothing has observed yet, it asks for a refresh at once.
@@ -1312,7 +1320,7 @@ The watch manager is a controller runtime `Runnable` (`NeedLeaderElection`). It
discovery, the per-GitTarget watch sets, and the watched type tables for GitTargets. Its object-state
intake is the watches themselves. Every active `KubeProvider` has an independent source context; its
catalog, registry, dynamic client, and reachability can fail or recover without borrowing facts from another
-source. Discovery trigger informers (CRDs / APIServices) run only in the control plane and accelerate its
+source. Discovery applier claim informers (CRDs / APIServices) run only in the control plane and accelerate its
catalog refresh; remote source catalogs use the periodic refresh.
On `Start` it bootstraps the RuleStore, refreshes the API catalog and TypeRegistry, resolves rule
@@ -1348,9 +1356,9 @@ a new rule starts watching gets a new watch opened (with a
`sendInitialEvents` replay) and a type no longer claimed has its watch closed.
A **control-plane** CRD / APIService change reaches the same re-resolution through the watch manager
-instead. Its trigger informers wake a catalog refresh, which re-resolves the watched type tables and
+instead. Its applier claim informers wake a catalog refresh, which re-resolves the watched type tables and
brings the running watches into line. The GitTarget controller does not watch those objects. A remote
-CRD / APIService change has no trigger informer at all; the next periodic source-catalog refresh
+CRD / APIService change has no applier claim informer at all; the next periodic source-catalog refresh
performs the same re-resolution.
### Mark and sweep resync
@@ -1370,7 +1378,7 @@ write side is shared with live writes):
- desired resources are upserted through the same content derived path as live writes;
- existing managed documents that are watched but absent from the desired set are deleted, under
`spec.prune.mode: Always` only;
-- in a folder an apply trigger applies, an object another applier owns (by Flux's owner labels or
+- in a coordinated folder, an object another applier owns (by Flux's owner labels or
Argo CD's tracking annotation, read before sanitize strips them) is neither upserted nor treated
as missing, so its document is left alone and counted in `apply_ownership_conflicts_total`;
- the operator's own build directives (`kustomization.yaml`, `.sops.yaml`) and other allowlisted auxiliary
@@ -1422,8 +1430,8 @@ stops a fact being published by something that cannot actually prove it.
| Where is branch B on the remote, and when was that proved? | the branch worker, on its queue | `GitBranch.status.remote`, written at most once a minute |
| What is this repository being used for? | the `GitBranch`es that name its `GitProvider` | `kubectl get gitbranch`, and each one's `status.gitTargetCount` |
| Is this folder accepted, streaming and matching live? | the watch plane, per `GitTarget` | the `GitTarget`'s conditions and `status.placement` |
-| Does the applier need to apply, and is now safe? | the branch worker, on its queue | the trigger's `status.operation` and an `ApplyRequested` Event |
-| Can the applier be asked at all? | the trigger controller, reading the applier in its cluster | the trigger's `Configured`, and the `GitTarget`'s `ApplyTriggerReady` |
+| Does the applier need to apply, and is now safe? | the branch worker, on its queue | the applier claim's `status.operation` and an `ApplyRequested` Event |
+| Can the applier be asked at all? | the applier claim controller, reading the applier in its cluster | the applier claim's `Configured`, and the `GitTarget`'s `ApplierClaimReady` |
A branch worker is **keyed** by the repository branch, normalized as `internal/repourl` compares
URLs, and writes for the `GitBranch` that holds it, with that `GitBranch`'s `GitProvider`
@@ -1671,7 +1679,7 @@ unsafely: logged per-resource, counted in the resync summary as `placementSkippe
- **Bootstrap** ([bootstrapped_repo_template.go](../internal/git/bootstrapped_repo_template.go)): the
first write to a GitTarget path stages a `README.md`, a `.gittargetignore` and, when encryption is
configured, a `.sops.yaml` with age recipient rules. Existing files are preserved. For a `GitTarget`
- that names an apply trigger, a resync into a folder the branch does not hold yet commits these
+ that names an applier claim, a resync into a folder the branch does not hold yet commits these
files even with no object to write, because the folder has to exist before it is applied: Argo CD
retries a sync of a missing path for minutes, and Flux fails it. The bootstrap files are never
deleted, so the folder outlives its last resource.
@@ -1739,9 +1747,9 @@ Controllers watch their dependencies so dependents reconcile quickly after spec
Namespace label changes re-check provider-reference access. Rule changes re-resolve selected
collections. It deliberately does **not** watch encryption Secrets, so their recovery is picked up
by periodic reconciliation without retaining
- every Secret value in the control-plane cache. It watches the apply triggers its `GitTarget`s name,
- for `ApplyTriggerReady`.
-- `ApplyTriggerReconciler` (one per trigger kind) watches its triggers, the `GitTarget`s naming
+ every Secret value in the control-plane cache. It watches the applier claims its `GitTarget`s name,
+ for `ApplierClaimReady`.
+- `ApplierClaimReconciler` (one per applier claim kind) watches its applier claims, the `GitTarget`s naming
them, `GitProvider`s, `KubeProvider`s and `Namespace` labels. It does not watch appliers: it
reads the Flux `Kustomization` or Argo CD `Application` on its tick to set `Configured`, so a
cluster without the Flux or Argo CD CRDs needs no informer for them.
@@ -1780,7 +1788,7 @@ flowchart TD
R -->|no| Rj[No receiver; foreign pushes found by rejection and refresh]
Ri --> F
Rj --> F
- F[Create Watch Manager + EventRouter; inject TypeRegistry] --> P[Create apply dispatcher + trigger status writer; wire them to the workers]
+ F[Create Watch Manager + EventRouter; inject TypeRegistry] --> P[Create apply dispatcher + applier claim status writer; wire them to the workers]
P --> G[Register WatchRule + ClusterWatchRule controllers]
G --> H{redis-addr set?}
H -->|yes| Hi[Create Redis cursor store + wire WatchCursorStore + readiness gate]
@@ -1791,7 +1799,7 @@ flowchart TD
Hq -->|no| J[Configured-author: no fact streams or index; audit webhook skipped]
I --> K[Setup + register Watch Manager]
J --> K
- K --> L[Register GitProvider + KubeProvider + GitTarget + FluxTrigger + ArgoCDTrigger + CommitRequest controllers; admission webhooks]
+ K --> L[Register GitProvider + KubeProvider + GitTarget + FluxClaim + ArgoCDClaim + CommitRequest controllers; admission webhooks]
L --> M[Add cert watchers + health checks]
M --> N[mgr.Start]
```
@@ -1858,7 +1866,7 @@ reason) and `stale` (the tip moved before the patch). It counts attempts, not
the requests still due. `apply_echoes_total{outcome}` counts the applier's own writes the apply
window kept out of Git (`not_mirrored`) or mirrored because no window was open (`late`);
`apply_ownership_conflicts_total` counts objects another applier owns that were kept out of a
-triggered folder; and `stale_snapshots_total{outcome}` counts resync snapshots discarded because an
+coordinated folder; and `stale_snapshots_total{outcome}` counts resync snapshots discarded because an
apply made them stale, or used after the 5-minute bound.
`receiver_deliveries_total{receiver_namespace,receiver_name,type,outcome}` counts receiver
deliveries per `GitReceiver`: `notified`, `unknown_repository` (no `GitProvider` has the repository),
@@ -1896,7 +1904,7 @@ remote cluster is visible. **Secret encryption** is one counter, `_secret_encryp
**Configuration state** is the one family that is not about flow.
`gitopsreverser_resource_condition{kind,resource_namespace,resource_name,type,status,reason}`
publishes the kstatus trio of every object a human declared (`GitTarget`, `WatchRule`,
-`ClusterWatchRule`, `GitProvider`, `KubeProvider`, `FluxTrigger`, `ArgoCDTrigger`) as one series
+`ClusterWatchRule`, `GitProvider`, `KubeProvider`, `FluxClaim`, `ArgoCDClaim`) as one series
per possible status, of which exactly one is `1`. It is recorded at `reconcileStatus.commit()`, the
single status choke point every controller but the `CommitRequest` one uses, and the object's
entry is dropped when the object goes: a condition series that outlives its object reports
@@ -1937,13 +1945,13 @@ Current limitations:
is refused with more than one replica, and it loses every unjoined fact on restart by design; a
multi-replica install must use the Redis transport.
- **No pull request creation;** the operator writes directly to branches.
-- **GitOps Reverser is the only path to a deployment.** With an apply trigger, the applier applies
+- **GitOps Reverser is the only path to a deployment.** With an applier claim, the applier applies
nothing on its own (a `Kustomization` is held suspended, an `Application` has automated sync
off), so while the operator is down a Git-side push reaches the cluster only through a manual
sync or `flux resume`, which the operator suspends again when it starts.
- **Only Flux and Argo CD can be appliers yet,** and an Argo CD `Application` only with automated
sync off: one that syncs on its own is refused, not driven. The applier may live in any cluster a
- `KubeProvider` reaches (the trigger's `spec.kubeProviderRef`), such as a management cluster.
+ `KubeProvider` reaches (the applier claim's `spec.kubeProviderRef`), such as a management cluster.
- **An apply request cannot reserve time.** An edit made while the requested apply runs, to a field
the applier sets, is reverted on the cluster. Its value stays in Git, because the applier's write
over it is not mirrored, and the next apply restores it; until then the cluster runs the older
@@ -1965,7 +1973,7 @@ Current limitations:
| Package | Role |
|---|---|
| [api/v1alpha3/](../api/v1alpha3/) | CRD types |
-| [internal/applier/](../internal/applier/) | apply requests: the Flux and Argo CD clients, the dispatcher that carries out each attempt, the trigger status writer, and the `Configured` check |
+| [internal/applier/](../internal/applier/) | apply requests: the Flux and Argo CD clients, the dispatcher that carries out each attempt, the applier claim status writer, and the `Configured` check |
| [cmd/](../cmd/) | operator entry point and server setup |
| [internal/auditutil/](../internal/auditutil/) | audit identity, objectRef, and subresource helpers feeding attribution facts |
| [internal/controller/](../internal/controller/) | Kubernetes reconcilers |
@@ -2011,7 +2019,7 @@ Deeper dives live under [docs/design/](design/):
- [Kubernetes API resource catalog](facts/kubernetes-api-resource-catalog.md)
- [Status and conditions guide](spec/status-conditions-guide.md)
- [The support contract: what we edit, what we refuse, and why](design/support-boundary/support-contract.md)
-- [The apply trigger](design/apply-trigger.md)
+- [The applier claim](design/apply-trigger.md)
- [Git credentials interop](finished/git-credentials-interop.md)
- [SOPS/age key management](finished/sops-repo-bootstrap-and-key-management-architecture.md)
- [Commit signing](commit-signing.md)
diff --git a/docs/bi-directional-argocd.md b/docs/bi-directional-argocd.md
index fe282296f..bcd33087a 100644
--- a/docs/bi-directional-argocd.md
+++ b/docs/bi-directional-argocd.md
@@ -6,7 +6,7 @@ everything else exactly as they are. [Bi-directional GitOps](bi-directional.md)
this page is the complete Argo CD setup: what to change, what to declare, how to check it works,
and which tests prove each part.
-The `ArgoCDTrigger` holds the `Application`'s automated sync **off**, and switches it off again
+The `ArgoCDClaim` holds the `Application`'s automated sync **off**, and switches it off again
whenever somebody switches it on, so every sync it runs is one GitOps Reverser started, pinned to a
commit it checked. Argo CD still renders, applies, prunes, runs hooks and reports health; it no
longer decides when.
@@ -15,11 +15,11 @@ longer decides when.
| Before | With GitOps Reverser |
| --- | --- |
-| `spec.syncPolicy.automated` syncs every new commit | Automated sync is off: the trigger holds `automated.enabled: false`, and hands it back when it goes |
+| `spec.syncPolicy.automated` syncs every new commit | Automated sync is off: the applier claim holds `automated.enabled: false`, and hands it back when it goes |
| A sync starts when Argo CD's refresh finds a new commit | A sync starts when GitOps Reverser sets the `Application`'s `operation`, pinned to a commit |
| `selfHeal` reverts live drift | Nothing reverts live drift by itself; GitOps Reverser publishes it to Git |
| The Git host's push webhook goes to Argo CD | It also goes to GitOps Reverser's receiver; Argo CD's may stay |
-| Nothing ties a folder to its `Application` | An `ArgoCDTrigger` holds it, and the `GitTarget` names the trigger in `spec.applyTriggerRef` |
+| Nothing ties a folder to its `Application` | An `ArgoCDClaim` holds it, and the `GitTarget` names the applier claim in `spec.applierClaimRef` |
Argo CD's timed refresh and its `/api/webhook` can stay as they are. With automated sync off, a
refresh only **compares**: it reports `Synced` or `OutOfSync`, and applies nothing.
@@ -35,28 +35,28 @@ whatever is pending at that moment ([why that matters](bi-directional.md#why-the
[support-boundary record](design/support-boundary/argocd-bi-directional.md) has Argo CD's code paths
for each of these, and for the settings that look like a fix and are not.
-**The trigger holds it off.** It applies `spec.syncPolicy.automated.enabled: false` with a
+**The applier claim holds it off.** It applies `spec.syncPolicy.automated.enabled: false` with a
server-side apply under its own field manager, `gitops-reverser`, and touches nothing else of
`automated`, so `prune` and `selfHeal` stay as they are. Switching automated sync on again (Argo
CD's *Enable Auto-Sync*, `kubectl edit`) is undone within a minute, with a
`ClaimedResourceReconciled` Event; a sync Argo CD started in that minute runs to its end,
unchecked against the branch.
-Deleting the trigger, or no longer naming it from any `GitTarget`, hands the switch back as it
-was: syncing on its own if it did before, manual if it was. The trigger records which in
+Deleting the applier claim, or no longer naming it from any `GitTarget`, hands the switch back as it
+was: syncing on its own if it did before, manual if it was. The applier claim records which in
`status.previousSuspendState` when it first switches it off: `NotSuspended` for automated sync on,
-`Suspended` for off. `kubectl get argocdtrigger -o wide` shows it.
+`Suspended` for off. `kubectl get argocdclaim -o wide` shows it.
-**Unless somebody else owns the switch.** Two owners would fight over it, so the trigger never
+**Unless somebody else owns the switch.** Two owners would fight over it, so the applier claim never
writes a switch that is somebody else's, and only checks it:
- An `Application` an `ApplicationSet` generated: the `ApplicationSet` rewrites its spec from its
template on every reconcile. Set `automated: {enabled: false, prune: true}` in the template.
- A switch another field manager applies with server-side apply (Flux, Helm, an app of apps with
`ServerSideApply=true`): it sets the switch on every apply. Set `enabled: false` in the manifest it
- applies, or leave `enabled` out of it and the trigger holds the switch.
+ applies, or leave `enabled` out of it and the applier claim holds the switch.
-While that owner keeps automated sync off, the trigger is `Held` and GitOps Reverser syncs as usual.
-While it has it on, the trigger reports `Configured=False` with `OwnedByApplicationSet` or
+While that owner keeps automated sync off, the applier claim is `Held` and GitOps Reverser syncs as usual.
+While it has it on, the applier claim reports `Configured=False` with `OwnedByApplicationSet` or
`AutomatedSyncOwnedElsewhere`, and GitOps Reverser never syncs the `Application`.
**A catch-up is about ownership.** With automated sync off, Argo CD applies nothing on its own.
@@ -66,14 +66,14 @@ object the API creates is different. Argo CD prunes only objects that carry its
and only a sync stamps it, so an object created through the API in the folder would survive the
deletion of its file in Git. An `Application` therefore gets a `CatchUp` exactly as a Flux
`Kustomization` does: to adopt an object the API created, and to converge, once when the operator
-starts and once when a `GitTarget` first names the trigger ([one sync](#one-sync)).
+starts and once when a `GitTarget` first names the applier claim ([one sync](#one-sync)).
## Requirements
| Requirement | Why | When it is not met |
| --- | --- | --- |
-| Argo CD 3.1 or later | `spec.syncPolicy.automated.enabled` exists from 3.1.0, and is the only way to switch automated sync off and keep `prune` | `ArgoCDVersionNotSupported`: the trigger checks with a server-side dry run that the API keeps `enabled`, and writes nothing to an `Application` whose API does not |
-| Automated sync off: held by the trigger, or kept off by the `ApplicationSet` or field manager that owns the switch | GitOps Reverser starts every sync, after a flush | `OwnedByApplicationSet` or `AutomatedSyncOwnedElsewhere`; the `Application` is never synced by GitOps Reverser |
+| Argo CD 3.1 or later | `spec.syncPolicy.automated.enabled` exists from 3.1.0, and is the only way to switch automated sync off and keep `prune` | `ArgoCDVersionNotSupported`: the applier claim checks with a server-side dry run that the API keeps `enabled`, and writes nothing to an `Application` whose API does not |
+| Automated sync off: held by the applier claim, or kept off by the `ApplicationSet` or field manager that owns the switch | GitOps Reverser starts every sync, after a flush | `OwnedByApplicationSet` or `AutomatedSyncOwnedElsewhere`; the `Application` is never synced by GitOps Reverser |
| A single Git source, `spec.source`, on the `GitProvider`'s repository | A sync is pinned to one commit of one repository | `UnsupportedSource` for `spec.sources` or `spec.sourceHydrator`; `SourceMismatch` for another repository |
| `targetRevision` is the `GitTarget`'s branch (`main` or `refs/heads/main`) | The tip GitOps Reverser checks is the tip of that branch | `SourceMismatch` for `HEAD`, an empty `targetRevision`, a tag or a commit |
| The source renders the folder GitOps Reverser writes: `directory.recurse: true` for a plain directory, or a kustomization at the path | GitOps Reverser writes `////.yaml` | A non-recursive directory sees no files, and the sync applies nothing |
@@ -90,7 +90,7 @@ test against the code that validates and drives the applier (see
The Argo CD side. The `Application` follows exactly the branch the `GitTarget` writes, and its
automated sync is switched off while keeping `prune` for the syncs GitOps Reverser starts. The
-trigger would switch it off anyway; written in the manifest, it also stays off once the trigger
+applier claim would switch it off anyway; written in the manifest, it also stays off once the applier claim
hands the switch back:
```yaml
@@ -119,10 +119,10 @@ spec:
```
The GitOps Reverser side. The `GitProvider` points at the same repository (any URL form of it),
-which is how the `GitReceiver` the Git host's push webhook calls finds it; an `ArgoCDTrigger` holds
+which is how the `GitReceiver` the Git host's push webhook calls finds it; an `ArgoCDClaim` holds
the `Application`, in the cluster the chart's `local` `KubeProvider` names (the operator's own,
-through `spec.inCluster`), and the `GitTarget` names the trigger. The `Application` is in `argocd`
-and the trigger in `team-a`, so the provider lets `team-a`'s triggers reach another namespace
+through `spec.inCluster`), and the `GitTarget` names the applier claim. The `Application` is in `argocd`
+and the applier claim in `team-a`, so the provider lets `team-a`'s applier claims reach another namespace
(`allowCrossNamespace.appliers`); with the Helm chart, set
`kubeProvider.allowCrossNamespace.appliers: true` instead of declaring it. A `KubeProvider` with
`inCluster` that you declare yourself is honored only once it is listed in
@@ -192,13 +192,13 @@ spec:
name: local
namespace: gitops-reverser
path: apps/checkout
- applyTriggerRef:
- kind: ArgoCDTrigger
+ applierClaimRef:
+ kind: ArgoCDClaim
name: checkout
onRefusal: Reapply
---
apiVersion: configbutler.ai/v1alpha3
-kind: ArgoCDTrigger
+kind: ArgoCDClaim
metadata:
name: checkout
namespace: team-a
@@ -217,18 +217,18 @@ push is synced in seconds, after any pending edits are replayed onto it.
## The `Application`
-The `GitTarget`'s `ApplyTriggerReady` is `True` only when the `Application` syncs the branch the
-`GitTarget` writes, from a single source, with automated sync off. The trigger checks these when it
+The `GitTarget`'s `ApplierClaimReady` is `True` only when the `Application` syncs the branch the
+`GitTarget` writes, from a single source, with automated sync off. The applier claim checks these when it
reconciles, for every `GitTarget` naming it, and acts only for one that matches. All of them are
checked again before every sync, so an `Application` changed after the check is still never synced
against them.
| Rule | Reported when broken |
| --- | --- |
-| `spec.source.repoURL` is the `GitProvider`'s repository. The forms compare as one repository: `https://` and `ssh://`, `git@host:org/repo` (SCP style), a trailing `.git` or `/`, letter case, and the default port | `SourceMismatch`: `the applier of ArgoCDTrigger checkout applies , not `, on the `GitTarget`, and on the trigger when no `GitTarget` naming it matches |
-| `spec.source.targetRevision` is the `spec.branch` of the `GitTarget`'s `GitBranch`, as the name or as `refs/heads/`. `HEAD` (and an empty `targetRevision`), a tag and a commit never follow the branch | `SourceMismatch`: `the applier of ArgoCDTrigger checkout applies branch "HEAD", not main`, on the same objects |
-| A single `spec.source`: no `spec.sources`, no `spec.sourceHydrator` | trigger `UnsupportedSource`: `syncs only a single-source Application` |
-| Automated sync is off, or the trigger may switch it off: nobody else owns `spec.syncPolicy.automated.enabled`. Off is Argo CD's own rule: `automated` is absent, or has `enabled: false`; an `automated` block with no `enabled` is on | trigger `OwnedByApplicationSet` or `AutomatedSyncOwnedElsewhere`, only while automated sync is on |
+| `spec.source.repoURL` is the `GitProvider`'s repository. The forms compare as one repository: `https://` and `ssh://`, `git@host:org/repo` (SCP style), a trailing `.git` or `/`, letter case, and the default port | `SourceMismatch`: `the applier of ArgoCDClaim checkout applies , not `, on the `GitTarget`, and on the applier claim when no `GitTarget` naming it matches |
+| `spec.source.targetRevision` is the `spec.branch` of the `GitTarget`'s `GitBranch`, as the name or as `refs/heads/`. `HEAD` (and an empty `targetRevision`), a tag and a commit never follow the branch | `SourceMismatch`: `the applier of ArgoCDClaim checkout applies branch "HEAD", not main`, on the same objects |
+| A single `spec.source`: no `spec.sources`, no `spec.sourceHydrator` | applier claim `UnsupportedSource`: `syncs only a single-source Application` |
+| Automated sync is off, or the applier claim may switch it off: nobody else owns `spec.syncPolicy.automated.enabled`. Off is Argo CD's own rule: `automated` is absent, or has `enabled: false`; an `automated` block with no `enabled` is on | applier claim `OwnedByApplicationSet` or `AutomatedSyncOwnedElsewhere`, only while automated sync is on |
**Prune.** A sync GitOps Reverser starts prunes when `spec.syncPolicy.automated.prune` is `true`,
read from the `automated` block that `enabled: false` switches off, exactly as automated sync reads
@@ -242,37 +242,37 @@ labels name a Flux `Kustomization`, is not mirrored into the folder: a live chan
mirrored, and a resync neither writes it nor removes its document. Two appliers applying one object
fight each other. See [objects another applier owns](bi-directional.md#objects-another-applier-owns).
-## The `ArgoCDTrigger`
+## The `ArgoCDClaim`
```yaml
apiVersion: configbutler.ai/v1alpha3
-kind: ArgoCDTrigger
+kind: ArgoCDClaim
metadata:
name: checkout
namespace: team-a # the GitTargets naming it are in this namespace
spec:
kubeProviderRef: # the cluster the Application lives in; required
- name: management # namespace omitted: the trigger's own
+ name: management # namespace omitted: the applier claim's own
applicationRef:
- namespace: argocd # default: the trigger's namespace
+ namespace: argocd # default: the applier claim's namespace
name: checkout
```
`kubeProviderRef` names the cluster that holds the `Application`: the operator's own cluster for an
`inCluster` provider such as the chart's `local`, or a management cluster for a hub Argo CD that
-deploys to other clusters. The provider must admit the trigger's namespace: its own, or one its
+deploys to other clusters. The provider must admit the applier claim's namespace: its own, or one its
`accessFrom` admits. The `GitTarget`'s own `spec.kubeProviderRef` stays the cluster it mirrors. An
-`Application` in another namespace than the trigger's, such as `argocd`, needs
-`allowCrossNamespace.appliers: true` on that `KubeProvider`; without it the trigger reports
+`Application` in another namespace than the applier claim's, such as `argocd`, needs
+`allowCrossNamespace.appliers: true` on that `KubeProvider`; without it the applier claim reports
`ApplierNamespaceNotAllowed`. That is a delegation, not owner consent: every namespace the provider
admits may then sync any `Application` in reach of its identity whose source is the repository its
`GitProvider` names, and nothing proves it owns the `Application` or may write to the repository. See
[what a namespace may reach outside itself](configuration.md#what-a-namespace-may-reach-outside-itself-specallowcrossnamespace).
-Both references are immutable: delete and recreate the trigger to hold another `Application`. A
-trigger that holds the switch carries the finalizer `configbutler.ai/argocd-trigger`: deleting it,
+Both references are immutable: delete and recreate the applier claim to hold another `Application`. An
+applier claim that holds the switch carries the finalizer `configbutler.ai/argocd-claim`: deleting it,
or no longer naming it from any eligible `GitTarget`, hands the switch back first: as it was
-before the trigger held it, or, for an `Application` an `ApplicationSet` generated since, as the
-`ApplicationSet` set it. Where giving the switch up would leave it otherwise, the trigger sets that
+before the applier claim held it, or, for an `Application` an `ApplicationSet` generated since, as the
+`ApplicationSet` set it. Where giving the switch up would leave it otherwise, the applier claim sets that
value and leaves it under its field manager, for the switch's owner to change.
### What state the `Application` is in
@@ -280,13 +280,13 @@ value and leaves it under its field manager, for the switch's owner to change.
```mermaid
stateDiagram-v2
[*] --> OnItsOwn
- OnItsOwn --> Idle: trigger Configured, switches automated sync off (Adopted)
+ OnItsOwn --> Idle: applier claim Configured, switches automated sync off (Adopted)
Idle --> SyncRequested: GitOps Reverser sets operation, pinned to the commit
SyncRequested --> Syncing: Argo CD starts the operation
Syncing --> Idle: Succeeded, Failed or Error
Idle --> Syncing: somebody else syncs (Sync button, argocd app sync)
Idle --> Idle: automated sync switched on, and off again (ClaimedResourceReconciled)
- Idle --> OnItsOwn: trigger deleted or no longer named, switch handed back (Released)
+ Idle --> OnItsOwn: applier claim deleted or no longer named, switch handed back (Released)
note right of OnItsOwn
Argo CD syncs on its own,
@@ -298,30 +298,30 @@ stateDiagram-v2
end note
```
-A trigger acts only while an eligible `GitTarget` references it: one in the trigger's namespace that
+An applier claim acts only while an eligible `GitTarget` references it: one in the applier claim's namespace that
names it, is admitted by its `KubeProvider`s, and writes the repository and branch the
-`Application` syncs. A trigger nothing eligible references starts no sync and is not in line for
+`Application` syncs. An applier claim nothing eligible references starts no sync and is not in line for
the `Application`; one no `GitTarget` names also reads nothing in the `Application`'s cluster.
-The trigger reports `Configured` and `Held`, and its `Ready` includes both:
+The applier claim reports `Configured` and `Held`, and its `Ready` includes both:
| Reason | Means | Readiness |
| --- | --- | --- |
-| `Succeeded` | An eligible `GitTarget` names the trigger, and the `Application` exists, syncs a single source, has automated sync off or a switch the trigger may hold, and may be updated and patched | ready |
-| `NotReferenced` (`Unknown`) | No `GitTarget` names the trigger. Nothing is read or written | ready: `Ready=True`, `NotReferenced` |
-| `NotBound` (`Unknown`) | No `GitTarget` naming the trigger is eligible yet, and one cannot be judged yet | progressing |
-| `SourceMismatch`, `NamespaceNotAuthorized` or `NotBound` (`False`) | Every `GitTarget` naming the trigger is refused: it writes another repository or branch, or is not admitted. The message lists each | stalled |
-| `ApplierNotFound` | No such `Application` in the applier's cluster, or Argo CD is not installed there | progressing: `Application`s are often created after the trigger |
+| `Succeeded` | An eligible `GitTarget` names the applier claim, and the `Application` exists, syncs a single source, has automated sync off or a switch the applier claim may hold, and may be updated and patched | ready |
+| `NotReferenced` (`Unknown`) | No `GitTarget` names the applier claim. Nothing is read or written | ready: `Ready=True`, `NotReferenced` |
+| `NotBound` (`Unknown`) | No `GitTarget` naming the applier claim is eligible yet, and one cannot be judged yet | progressing |
+| `SourceMismatch`, `NamespaceNotAuthorized` or `NotBound` (`False`) | Every `GitTarget` naming the applier claim is refused: it writes another repository or branch, or is not admitted. The message lists each | stalled |
+| `ApplierNotFound` | No such `Application` in the applier's cluster, or Argo CD is not installed there | progressing: `Application`s are often created after the applier claim |
| `UnsupportedSource` | The `Application` has several sources, a source hydrator, or no source | stalled |
| `ArgoCDVersionNotSupported` | The `Application`'s API has no `spec.syncPolicy.automated.enabled`: Argo CD before 3.1. Nothing is written | stalled: upgrade Argo CD |
| `OwnedByApplicationSet` | Automated sync is on, and an `ApplicationSet` generated the `Application`, so its template owns the switch | stalled: set `automated.enabled: false` in the template |
| `AutomatedSyncOwnedElsewhere` | Automated sync is on, and another field manager applies the switch | stalled: set `enabled: false` in the manifest it applies, or leave it out |
| `Forbidden` | The applier cluster's identity may not update or patch the `Application` | stalled |
-| `NamespaceNotAuthorized` | The `kubeProviderRef` names a provider in another namespace whose `accessFrom` does not admit the trigger's namespace | stalled |
+| `NamespaceNotAuthorized` | The `kubeProviderRef` names a provider in another namespace whose `accessFrom` does not admit the applier claim's namespace | stalled |
| `KubeProviderNotFound` | The `kubeProviderRef` names a provider that does not exist | progressing |
| `AuditRouteConflict` | The provider carries an audit route another namespace holds, so it is not used | stalled |
-| `ApplierNamespaceNotAllowed` | The `Application` is in another namespace than the trigger's, and the `KubeProvider` does not set `allowCrossNamespace.appliers` | stalled |
-| `HeldByAnotherTrigger` | Another `ArgoCDTrigger` in line for the same `Application` in the same cluster, one an eligible `GitTarget` references, holds it (it holds the switch already, or was created first); this one starts no sync, and takes over once that one lets go | stalled: delete one of the two |
+| `ApplierNamespaceNotAllowed` | The `Application` is in another namespace than the applier claim's, and the `KubeProvider` does not set `allowCrossNamespace.appliers` | stalled |
+| `HeldByAnotherClaim` | Another `ArgoCDClaim` in line for the same `Application` in the same cluster, one an eligible `GitTarget` references, holds it (it holds the switch already, or was created first); this one starts no sync, and takes over once that one lets go | stalled: delete one of the two |
`Held` says whether automated sync is off:
@@ -329,19 +329,19 @@ The trigger reports `Configured` and `Held`, and its `Ready` includes both:
| --- | --- |
| `AutomatedSyncOff` (`True`) | Automated sync is off: held off by GitOps Reverser, or kept off by the `ApplicationSet` or field manager that owns the switch (the message says which) |
| `Releasing` (`False`) | The switch is being handed back |
-| `NotReferenced` (`False`), `NotConfigured` (`Unknown`) | Nothing is held: no `GitTarget` names the trigger, or it is not `Configured` |
+| `NotReferenced` (`False`), `NotConfigured` (`Unknown`) | Nothing is held: no `GitTarget` names the applier claim, or it is not `Configured` |
-A sync that has not completed within 5 minutes also makes the trigger `Ready=False` with
+A sync that has not completed within 5 minutes also makes the applier claim `Ready=False` with
`OperationUnresolved`, a progressing state that clears when the sync completes.
-The `GitTarget` reports `ApplyTriggerReady`: `True` when the trigger is `Ready` and its
+The `GitTarget` reports `ApplierClaimReady`: `True` when the applier claim is `Ready` and its
`Application` syncs this repository and branch, `SourceMismatch` when it syncs another,
-`ApplyTriggerNotFound` when the trigger does not exist, `Unknown` (`NotReferenced`) until the
-trigger has seen that the `GitTarget` names it, and otherwise the trigger's own `Ready` reason.
+`ApplierClaimNotFound` when the applier claim does not exist, `Unknown` (`NotReferenced`) until the
+applier claim has seen that the `GitTarget` names it, and otherwise the applier claim's own `Ready` reason.
## Permissions
-GitOps Reverser looks the `Application` up in the cluster of the trigger's `kubeProviderRef`,
+GitOps Reverser looks the `Application` up in the cluster of the applier claim's `kubeProviderRef`,
with the identity of that `KubeProvider`. That identity needs:
| API group | Resource | Verbs |
@@ -350,7 +350,7 @@ with the identity of that `KubeProvider`. That identity needs:
| `authorization.k8s.io` | `selfsubjectaccessreviews` | `create`, to check the one above |
A sync is started by an `update` of the `Application`, the way Argo CD's own automated sync starts
-one, and automated sync is held off with a server-side apply, a `patch`. A trigger whose switch an
+one, and automated sync is held off with a server-side apply, a `patch`. An applier claim whose switch an
`ApplicationSet` or another field manager owns never patches, and needs no `patch`. For the
operator's own cluster, install the chart with `applierAccess.argocd: true`, or apply
[`config/rbac/argocd-applier-role.yaml`](../config/rbac/argocd-applier-role.yaml) and its binding.
@@ -413,7 +413,7 @@ retry still counts as running: Argo CD keeps its `operation` set until it comple
**Hooks run on every sync**, as they would on an automated one: every `ForeignCommit` asks every
applier on the branch, whether or not the push touched its folder, and a `CatchUp` syncs after an API
-create in the folder and when a `GitTarget` first names the trigger.
+create in the folder and when a `GitTarget` first names the applier claim.
**The apply window.** Until 10 seconds after the sync's operation completes, however long that
takes, Argo CD's own write to an object nobody edited is the sync landing, and is not mirrored. An
@@ -475,14 +475,14 @@ the folder's `WatchRule`s.
### Switching automated sync back on
-The trigger switches it off again within a minute, but a sync Argo CD starts in that minute applies
+The applier claim switches it off again within a minute, but a sync Argo CD starts in that minute applies
the tip over whatever is pending. To have the `Application` sync on its own again, delete the
-trigger, or stop naming it from the `GitTarget`: the hand-back gives the switch up.
+applier claim, or stop naming it from the `GitTarget`: the hand-back gives the switch up.
### Applying the switch on from a client-side app of apps
A parent `Application` that applies this one with Argo CD's default client-side apply writes the
-switch with an update, which the trigger cannot tell from a click in the UI. With `enabled: true` in
+switch with an update, which the applier claim cannot tell from a click in the UI. With `enabled: true` in
the child's manifest and `selfHeal` on the parent, the two switch it back and forth. Leave `enabled`
out of that manifest, or set it to `false`.
@@ -490,9 +490,9 @@ out of that manifest, or set it to `false`.
The `ApplicationSet` controller writes its template back over any change to the `Application`s it
generates, so the switch is the template's: the template must carry
-`automated: {enabled: false, prune: true}` (or no `automated` block). The trigger checks it and
-never writes it. With automated sync on, the trigger is `OwnedByApplicationSet`, and its message
-says what to change. Pointing a trigger at the `ApplicationSet` itself is not supported.
+`automated: {enabled: false, prune: true}` (or no `automated` block). The applier claim checks it and
+never writes it. With automated sync on, the applier claim is `OwnedByApplicationSet`, and its message
+says what to change. Pointing an applier claim at the `ApplicationSet` itself is not supported.
### Relying on `Replace=true`
@@ -504,12 +504,12 @@ Argo CD's in the apply window, but no test measures it yet. The default apply, c
```bash
# The Application is found, its automated sync is held off, and it may be updated; and it syncs this target's branch.
-kubectl get argocdtrigger checkout -n team-a
+kubectl get argocdclaim checkout -n team-a
kubectl get gittarget checkout -n team-a \
- -o jsonpath='{.status.conditions[?(@.type=="ApplyTriggerReady")]}{"\n"}'
+ -o jsonpath='{.status.conditions[?(@.type=="ApplierClaimReady")]}{"\n"}'
# What GitOps Reverser last asked for, why, and where that sync stands.
-kubectl get argocdtrigger checkout -n team-a -o jsonpath='{.status.operation}{"\n"}'
+kubectl get argocdclaim checkout -n team-a -o jsonpath='{.status.operation}{"\n"}'
# The last sync: who started it, which commit, and how it ended. A request asks, it never reports.
kubectl get application checkout -n argocd \
@@ -525,18 +525,18 @@ it.
| Symptom | Cause | Fix |
| --- | --- | --- |
-| Trigger `Configured=False`, `OwnedByApplicationSet` | An `ApplicationSet` generated the `Application`, and its template has automated sync on | Set `automated: {enabled: false, prune: true}` in the `ApplicationSet`'s template |
-| Trigger `Configured=False`, `AutomatedSyncOwnedElsewhere` | Another field manager applies the switch on; the message names it | Set `enabled: false` in the manifest it applies, or leave `enabled` out of it |
-| A Normal `ClaimedResourceReconciled` Event | Somebody switched automated sync on, and the trigger switched it off again | Delete the trigger to have the `Application` sync on its own; see [switching it back on](#switching-automated-sync-back-on) |
-| Trigger `Configured=False`, `ArgoCDVersionNotSupported` | Argo CD before 3.1, whose `Application` has no `enabled` | Upgrade Argo CD to 3.1 or later |
-| Trigger `Configured=False`, `ApplierNotFound` | No `Application` with that namespace and name in the trigger's cluster, or Argo CD is not installed there | Create it, or recreate the trigger with the right `applicationRef`; an omitted namespace is the trigger's |
-| `ApplyTriggerReady=False`, `SourceMismatch`, or trigger `UnsupportedSource` | The message names the broken [`Application` rule](#the-application) | Fix the `Application`; a sync that was waiting is asked for afterward |
-| Trigger `Configured=False`, `ApplierNamespaceNotAllowed` | The `Application` is in another namespace than the trigger, such as `argocd` | Set `allowCrossNamespace.appliers: true` on the trigger's `KubeProvider` (Helm: `kubeProvider.allowCrossNamespace.appliers`), or move the trigger and `GitTarget` to the `Application`'s namespace |
-| Trigger `Configured=Unknown`, `NotBound` | A `GitTarget` names the trigger, but cannot be judged yet: its `GitProvider` or `KubeProvider` does not exist | Create the missing object; the message names it |
-| Trigger `Configured=False`, `Forbidden` | The identity lacks one of the [permissions](#permissions) | Grant it, or set `applierAccess.argocd: true` |
+| `ArgoCDClaim` `Configured=False`, `OwnedByApplicationSet` | An `ApplicationSet` generated the `Application`, and its template has automated sync on | Set `automated: {enabled: false, prune: true}` in the `ApplicationSet`'s template |
+| `ArgoCDClaim` `Configured=False`, `AutomatedSyncOwnedElsewhere` | Another field manager applies the switch on; the message names it | Set `enabled: false` in the manifest it applies, or leave `enabled` out of it |
+| A Normal `ClaimedResourceReconciled` Event | Somebody switched automated sync on, and the applier claim switched it off again | Delete the applier claim to have the `Application` sync on its own; see [switching it back on](#switching-automated-sync-back-on) |
+| `ArgoCDClaim` `Configured=False`, `ArgoCDVersionNotSupported` | Argo CD before 3.1, whose `Application` has no `enabled` | Upgrade Argo CD to 3.1 or later |
+| `ArgoCDClaim` `Configured=False`, `ApplierNotFound` | No `Application` with that namespace and name in the applier claim's cluster, or Argo CD is not installed there | Create it, or recreate the applier claim with the right `applicationRef`; an omitted namespace is the applier claim's |
+| `ApplierClaimReady=False`, `SourceMismatch`, or applier claim `UnsupportedSource` | The message names the broken [`Application` rule](#the-application) | Fix the `Application`; a sync that was waiting is asked for afterward |
+| `ArgoCDClaim` `Configured=False`, `ApplierNamespaceNotAllowed` | The `Application` is in another namespace than the applier claim, such as `argocd` | Set `allowCrossNamespace.appliers: true` on the applier claim's `KubeProvider` (Helm: `kubeProvider.allowCrossNamespace.appliers`), or move the applier claim and `GitTarget` to the `Application`'s namespace |
+| `ArgoCDClaim` `Configured=Unknown`, `NotBound` | A `GitTarget` names the applier claim, but cannot be judged yet: its `GitProvider` or `KubeProvider` does not exist | Create the missing object; the message names it |
+| `ArgoCDClaim` `Configured=False`, `Forbidden` | The identity lacks one of the [permissions](#permissions) | Grant it, or set `applierAccess.argocd: true` |
| A Warning `ApplyRequestFailed` Event | A request could not be delivered, such as a sync somebody else started that did not end within 5 minutes | GitOps Reverser keeps asking after its backoff, at most every five minutes |
| A Warning `ApplyFailed` Event | The sync ended `Failed` or `Error`; the message carries Argo CD's | Fix what the message names; GitOps Reverser asks again after its backoff |
-| A Warning `ApplyUnresolved` Event, trigger `OperationUnresolved` | The sync has not completed in 5 minutes: hooks, waves, or Argo CD's own retries | Nothing is asked of the `Application` until it completes; look at its operation state |
+| A Warning `ApplyUnresolved` Event, applier claim `OperationUnresolved` | The sync has not completed in 5 minutes: hooks, waves, or Argo CD's own retries | Nothing is asked of the `Application` until it completes; look at its operation state |
| A file deleted in Git leaves its object running | The `Application` has no `automated.prune: true` | Add `automated: {enabled: false, prune: true}` |
| The last sync stays `Running`: `Sync operation blocked by sync window` | A sync window blocks automated syncs | Remove the window from the `AppProject`; see [sync windows](#sync-windows-that-block-automated-syncs) |
| A commit appears on every sync, adding or removing a `Job` | A `WatchRule` covers the objects a hook creates | Keep hook kinds out of the `WatchRule`s; see [hooks](#watching-the-objects-your-hooks-create) |
@@ -548,7 +548,7 @@ GitOps Reverser becomes the only path to a deployment. While it is down, a Git-s
cluster through a manual sync (`argocd app sync`, or the UI), which is safe then because nothing is
publishing. When it starts again, its first look at the remote asks for no `ForeignCommit`. A
`CatchUp` that converges is pending instead, and is asked only once every initial snapshot of the
-`GitTarget`s naming the trigger is published: a sync before that could revert edits made while the
+`GitTarget`s naming the applier claim is published: a sync before that could revert edits made while the
operator was down, before its watches caught them. An `Application` whose last successful sync
applied the tip is asked nothing.
@@ -558,11 +558,11 @@ applied the tip is asked nothing.
| --- | --- |
| The [complete example](#a-complete-example) decodes strictly, reports `Configured=True`, binds the `GitTarget`, and its request sets a sync pinned to the commit | [`argocd_guide_test.go`](../internal/applier/argocd_guide_test.go) |
| The operation is the one automated sync builds, but for the pinned revision, the initiator and a pinned retry; a running sync is waited for and never replaced; a sync started between the read and the update makes it fail; an unanswered update is settled by a guarded write; every `Configured` reason, the `ApplicationSet` and field-manager owners of the switch among them; when a sync counts as finished | [`argocd_test.go`](../internal/applier/argocd_test.go) |
-| The trigger switches automated sync off, keeping `prune`; switches it off again when somebody switches it on; hands it back on deletion and when no longer named, as it stood before (`status.previousSuspendState`) or as an `ApplicationSet` that adopted it set it; leaves it held off for a trigger in line; never writes a switch an `ApplicationSet` or another field manager owns; never writes one where Argo CD before 3.1 refuses it | [`argocdtrigger_hold_test.go`](../internal/controller/argocdtrigger_hold_test.go) |
-| Holding the switch and handing it back on a real API server: a manual `Application` is manual again, one with automated sync on syncs on its own again, an owner's own `enabled: false` stays, an `ApplicationSet`'s adopted value stays; a schema without `enabled` refuses the apply, and nothing is written | [`argocdtrigger_apiserver_test.go`](../internal/controller/argocdtrigger_apiserver_test.go) |
-| An `Application` converges once a `GitTarget` first names its trigger, is asked on a `ForeignCommit` and a `Refusal`, and adopts an API create even at the tip; Argo CD's late write over a newer edit is withheld, by its own field managers | [`apply_trigger_test.go`](../internal/git/apply_trigger_test.go), [`apply_catchup_test.go`](../internal/git/apply_catchup_test.go), [`apply_window_test.go`](../internal/git/apply_window_test.go) |
+| The applier claim switches automated sync off, keeping `prune`; switches it off again when somebody switches it on; hands it back on deletion and when no longer named, as it stood before (`status.previousSuspendState`) or as an `ApplicationSet` that adopted it set it; leaves it held off for an applier claim in line; never writes a switch an `ApplicationSet` or another field manager owns; never writes one where Argo CD before 3.1 refuses it | [`argocdclaim_hold_test.go`](../internal/controller/argocdclaim_hold_test.go) |
+| Holding the switch and handing it back on a real API server: a manual `Application` is manual again, one with automated sync on syncs on its own again, an owner's own `enabled: false` stays, an `ApplicationSet`'s adopted value stays; a schema without `enabled` refuses the apply, and nothing is written | [`argocdclaim_apiserver_test.go`](../internal/controller/argocdclaim_apiserver_test.go) |
+| An `Application` converges once a `GitTarget` first names its applier claim, is asked on a `ForeignCommit` and a `Refusal`, and adopts an API create even at the tip; Argo CD's late write over a newer edit is withheld, by its own field managers | [`applier_claim_test.go`](../internal/git/applier_claim_test.go), [`apply_catchup_test.go`](../internal/git/apply_catchup_test.go), [`apply_window_test.go`](../internal/git/apply_window_test.go) |
| An object another applier owns, read from its tracking annotation, is neither mirrored nor swept | [`apply_ownership_test.go`](../internal/git/apply_ownership_test.go) |
-| A trigger nothing names reads nothing and is `Ready`; the binding over several `GitTarget`s; `ApplierNamespaceNotAllowed`; a sync whose `.operation` was removed while it ran ends | [`applytrigger_rule_test.go`](../internal/controller/applytrigger_rule_test.go), [`argocd_test.go`](../internal/applier/argocd_test.go) |
+| An applier claim nothing names reads nothing and is `Ready`; the binding over several `GitTarget`s; `ApplierNamespaceNotAllowed`; a sync whose `.operation` was removed while it ran ends | [`applierclaim_rule_test.go`](../internal/controller/applierclaim_rule_test.go), [`argocd_test.go`](../internal/applier/argocd_test.go) |
| Two API edits seconds apart keep the later value, with no further commit | [Bi-directional (Argo CD)](../test/e2e/argocd_bi_directional_e2e_test.go) |
| A Git-side push is synced by a `ForeignCommit` request, pinned to that commit | Bi-directional (Argo CD) |
| A refused edit with `onRefusal: Reapply` is reverted by an Argo CD sync | Bi-directional (Argo CD) |
diff --git a/docs/bi-directional-flux.md b/docs/bi-directional-flux.md
index 16facc0a3..1fd8d2139 100644
--- a/docs/bi-directional-flux.md
+++ b/docs/bi-directional-flux.md
@@ -5,8 +5,8 @@ This page is for a folder that GitOps Reverser writes and a Flux `Kustomization`
does. [Bi-directional GitOps](bi-directional.md) explains the model; this page is the complete Flux
setup: what to change, what to declare, how to check it works, and which tests prove each part.
-A `FluxTrigger` holds the `Kustomization` **suspended** while it is idle, for as long as a
-`GitTarget` that writes its folder names the trigger. For each apply, GitOps Reverser resumes it,
+A `FluxClaim` holds the `Kustomization` **suspended** while it is idle, for as long as a
+`GitTarget` that writes its folder names the applier claim. For each apply, GitOps Reverser resumes it,
waits for that one reconcile to end, and suspends it again. Flux still fetches, renders, applies,
prunes and reports health; it never applies on its own.
@@ -17,7 +17,7 @@ prunes and reports health; it never applies on its own.
| The `Kustomization` applies on its interval, on every new artifact, and on a controller restart | It is suspended, and applies only when GitOps Reverser resumes it for one apply |
| A failed apply is retried after `retryInterval` | It is suspended again after a failure; GitOps Reverser retries after its own backoff |
| The Git host's push webhook goes to a Flux `Receiver` | It goes to GitOps Reverser's receiver; the Flux `Receiver` for this repository is removed |
-| Nothing ties a folder to its `Kustomization` | A `FluxTrigger` holds it, and the `GitTarget` names the trigger in `spec.applyTriggerRef` |
+| Nothing ties a folder to its `Kustomization` | A `FluxClaim` holds it, and the `GitTarget` names the applier claim in `spec.applierClaimRef` |
| `flux get ks` shows it ready | It shows it `Suspended`, which is how it is held |
## Why suspended, and not a long interval
@@ -38,7 +38,7 @@ a unit test against the code that validates and drives the applier (see
[what the tests prove](#what-the-tests-prove)), so this example cannot drift from what is accepted.
The Flux side. The `GitRepository` follows exactly the branch the `GitTarget` writes. The
-`Kustomization`'s manifest does **not** set `suspend`: the `FluxTrigger` holds that field.
+`Kustomization`'s manifest does **not** set `suspend`: the `FluxClaim` holds that field.
```yaml
apiVersion: source.toolkit.fluxcd.io/v1
@@ -69,10 +69,10 @@ spec:
```
The GitOps Reverser side. The `GitProvider` points at the same repository (any URL form of it),
-which is how the `GitReceiver` the Git host's push webhook calls finds it; a `FluxTrigger` holds the
+which is how the `GitReceiver` the Git host's push webhook calls finds it; a `FluxClaim` holds the
`Kustomization`, in the cluster the chart's `local` `KubeProvider` names (the operator's own,
-through `spec.inCluster`), and the `GitTarget` names the trigger. The `Kustomization` is in
-`flux-system` and the trigger in `team-a`, so the provider lets `team-a`'s triggers reach another
+through `spec.inCluster`), and the `GitTarget` names the applier claim. The `Kustomization` is in
+`flux-system` and the applier claim in `team-a`, so the provider lets `team-a`'s applier claims reach another
namespace (`allowCrossNamespace.appliers`); with the Helm chart, set
`kubeProvider.allowCrossNamespace.appliers: true` instead of declaring it. A `KubeProvider` with
`inCluster` that you declare yourself is honored only once it is listed in
@@ -142,13 +142,13 @@ spec:
name: local
namespace: gitops-reverser
path: apps/checkout
- applyTriggerRef:
- kind: FluxTrigger
+ applierClaimRef:
+ kind: FluxClaim
name: checkout
onRefusal: Reapply
---
apiVersion: configbutler.ai/v1alpha3
-kind: FluxTrigger
+kind: FluxClaim
metadata:
name: checkout
namespace: team-a
@@ -167,18 +167,18 @@ the Git host's push webhook at GitOps Reverser's receiver instead
## The `GitRepository`
-The `GitTarget`'s `ApplyTriggerReady` is `True` only when the `Kustomization`'s source fetches the
-branch the `GitTarget` writes. The trigger checks the rules about the source when it reconciles, and
+The `GitTarget`'s `ApplierClaimReady` is `True` only when the `Kustomization`'s source fetches the
+branch the `GitTarget` writes. The applier claim checks the rules about the source when it reconciles, and
the repository and branch of every `GitTarget` naming it: it holds the `Kustomization` only for one
-that matches. The `GitTarget` reports its own match against the source the trigger publishes. All
+that matches. The `GitTarget` reports its own match against the source the applier claim publishes. All
of them are checked again before every apply.
| Rule | Reported when broken |
| --- | --- |
-| `spec.url` is the `GitProvider`'s repository. The forms compare as one repository: `https://` and `ssh://`, `git@host:org/repo` (SCP style), a trailing `.git` or `/`, letter case, and the default port | `SourceMismatch`: `the applier of FluxTrigger checkout applies , not `, on the `GitTarget`, and on the trigger when no `GitTarget` naming it matches |
-| `spec.ref.branch` is exactly the `spec.branch` of the `GitTarget`'s `GitBranch` | `SourceMismatch`: `the applier of FluxTrigger checkout applies branch "release", not main`, on the same objects |
-| `spec.ref` pins nothing stronger: no `commit`, `name`, `semver` or `tag`, even alongside the branch. Flux resolves those before the branch, so the artifact would never follow it | trigger `UnsupportedSource`: `pins spec.ref.tag "v1.0.0", so it never follows a branch` |
-| `spec.ref.branch` is set | trigger `UnsupportedSource`: `names no spec.ref.branch` |
+| `spec.url` is the `GitProvider`'s repository. The forms compare as one repository: `https://` and `ssh://`, `git@host:org/repo` (SCP style), a trailing `.git` or `/`, letter case, and the default port | `SourceMismatch`: `the applier of FluxClaim checkout applies , not `, on the `GitTarget`, and on the applier claim when no `GitTarget` naming it matches |
+| `spec.ref.branch` is exactly the `spec.branch` of the `GitTarget`'s `GitBranch` | `SourceMismatch`: `the applier of FluxClaim checkout applies branch "release", not main`, on the same objects |
+| `spec.ref` pins nothing stronger: no `commit`, `name`, `semver` or `tag`, even alongside the branch. Flux resolves those before the branch, so the artifact would never follow it | applier claim `UnsupportedSource`: `pins spec.ref.tag "v1.0.0", so it never follows a branch` |
+| `spec.ref.branch` is set | applier claim `UnsupportedSource`: `names no spec.ref.branch` |
A suspended `Kustomization` ignores a new artifact, so the source's own `interval` decides when it
fetches, never when anything applies. Keep it longer than an apply takes, `1h` in the example: an
@@ -188,8 +188,8 @@ GitOps Reverser checked. That is logged, counted in `apply_revision_mismatches_t
covered by the apply window, but it is avoidable. An apply of an *older* revision than the one
checked counts as failed, and GitOps Reverser asks again.
-**Shared sources.** Several `Kustomization`s may read one `GitRepository`. The trigger lists its
-source's other consumers in `status.source.otherConsumers`: the `Kustomization`s no `FluxTrigger`
+**Shared sources.** Several `Kustomization`s may read one `GitRepository`. The applier claim lists its
+source's other consumers in `status.source.otherConsumers`: the `Kustomization`s no `FluxClaim`
holds and the `HelmRelease`s whose chart comes from it, directly or through a `HelmChart` their
`chartRef` names. Each of them applies a new artifact the moment it arrives. The list is
information only and never affects `Ready`: one source for `apps` and `infra` is a normal setup.
@@ -197,11 +197,11 @@ Appliers on one branch that share a source apply one at a time.
## The `Kustomization`
-- `spec.sourceRef` names a `GitRepository`. An `OCIRepository` or a `Bucket` makes the trigger
+- `spec.sourceRef` names a `GitRepository`. An `OCIRepository` or a `Bucket` makes the applier claim
report `UnsupportedSource`: GitOps Reverser writes Git, and can only ask a Git source to fetch.
- **The manifest omits `spec.suspend`.** Whatever applies the `Kustomization` object (a parent
`Kustomization`, Helm, flux-operator) must leave the field alone, or it resumes the
- `Kustomization` on its next apply. When another field manager applies `spec.suspend`, the trigger
+ `Kustomization` on its next apply. When another field manager applies `spec.suspend`, the applier claim
reports `SuspendOwnedElsewhere`, naming the manager, and holds nothing. A one-off write such as
`flux resume` is no such owner; see [overriding by hand](#overriding-by-hand).
- `spec.interval` and `spec.retryInterval` decide nothing while it is suspended.
@@ -209,15 +209,15 @@ Appliers on one branch that share a source apply one at a time.
with its apply window open, for as long as it runs.
- `spec.dependsOn`: a dependency whose apply failed is suspended with its generation unapplied, so
its dependents wait until GitOps Reverser's retry of it succeeds.
-- It lives in the trigger's namespace unless the trigger's `KubeProvider` sets
- `allowCrossNamespace.appliers: true`; a trigger naming one elsewhere without it reports
+- It lives in the applier claim's namespace unless the applier claim's `KubeProvider` sets
+ `allowCrossNamespace.appliers: true`; an applier claim naming one elsewhere without it reports
`ApplierNamespaceNotAllowed`. A `Kustomization` in `flux-system` therefore needs the grant. It is
a delegation, not owner consent: every namespace the provider admits may then
suspend and apply any `Kustomization` in reach of the provider's identity whose source fetches the
repository its `GitProvider` names. Nothing proves that the namespace owns the `Kustomization`, or
may write to that repository: the binding compares URLs. See
[what a namespace may reach outside itself](configuration.md#what-a-namespace-may-reach-outside-itself-specallowcrossnamespace).
-- Several `GitTarget`s may name one `FluxTrigger`, for a `Kustomization` whose `path` covers several
+- Several `GitTarget`s may name one `FluxClaim`, for a `Kustomization` whose `path` covers several
folders. It is applied once for all of them.
- Two `GitProvider`s may name one repository. Before either branch worker asks, both flush: the
other worker publishes the writes it decided so far too, and a `GitRepository` they share serves
@@ -229,33 +229,33 @@ Appliers on one branch that share a source apply one at a time.
nor removes its document. Two appliers applying one object fight each other. See
[objects another applier owns](bi-directional.md#objects-another-applier-owns).
-## The `FluxTrigger`
+## The `FluxClaim`
```yaml
apiVersion: configbutler.ai/v1alpha3
-kind: FluxTrigger
+kind: FluxClaim
metadata:
name: checkout
namespace: team-a # the GitTargets naming it are in this namespace
spec:
kubeProviderRef: # the cluster Flux runs in; required
name: local
- namespace: gitops-reverser # default: the trigger's namespace
+ namespace: gitops-reverser # default: the applier claim's namespace
kustomizationRef:
- namespace: flux-system # default: the trigger's namespace
+ namespace: flux-system # default: the applier claim's namespace
name: checkout
```
`kubeProviderRef` names the cluster that holds the `Kustomization`: the operator's own cluster for
an `inCluster` provider such as the chart's `local`, or a management cluster where Flux runs. The
-provider must admit the trigger's namespace: its own, or one its `accessFrom` admits. Both
-references are immutable: delete and recreate the trigger to hold another `Kustomization`.
+provider must admit the applier claim's namespace: its own, or one its `accessFrom` admits. Both
+references are immutable: delete and recreate the applier claim to hold another `Kustomization`.
-### How the trigger holds the `Kustomization`
+### How the applier claim holds the `Kustomization`
-A trigger acts only while an eligible `GitTarget` references it: one in the trigger's namespace that
-names it in `spec.applyTriggerRef`, is admitted by its `KubeProvider`s, and writes the repository
-and branch the `Kustomization`'s `GitRepository` fetches. A trigger no `GitTarget` names holds
+An applier claim acts only while an eligible `GitTarget` references it: one in the applier claim's namespace that
+names it in `spec.applierClaimRef`, is admitted by its `KubeProvider`s, and writes the repository
+and branch the `Kustomization`'s `GitRepository` fetches. An applier claim no `GitTarget` names holds
nothing, reads nothing in the `Kustomization`'s cluster, and is `Ready`. One that only ineligible
`GitTarget`s name holds nothing either, and its `Configured` says why.
@@ -268,15 +268,15 @@ stateDiagram-v2
Suspending --> Suspended: the suspend is handled
Suspended --> Applying: an operation is recorded as Requested
Applying --> Suspended: the operation completed, its suspend handled
- Applying --> Suspending: nobody followed it past the grace, the trigger suspends it
+ Applying --> Suspending: nobody followed it past the grace, the applier claim suspends it
Suspended --> Suspending: found resumed (flux resume) or suspended by another (flux suspend)
Suspended --> NotConfigured: the applier's configuration turns invalid, left suspended
- Suspended --> Releasing: no eligible reference left, or trigger deleted
+ Suspended --> Releasing: no eligible reference left, or applier claim deleted
Applying --> Releasing: the same, suspended first
Releasing --> Suspended: a reference returns before the release
Releasing --> NotReferenced: released, Flux applies on its own again
Releasing --> NotConfigured: released, while a refused GitTarget still names it
- Releasing --> [*]: trigger deleted
+ Releasing --> [*]: applier claim deleted
note right of NotConfigured
Configured says why
@@ -287,85 +287,85 @@ stateDiagram-v2
end note
```
-- **Adopting.** Once an eligible `GitTarget` names it and it is `Configured`, the trigger puts its
- finalizer `configbutler.ai/flux-trigger` in place, suspends the `Kustomization` with a server-side
+- **Adopting.** Once an eligible `GitTarget` names it and it is `Configured`, the applier claim puts its
+ finalizer `configbutler.ai/flux-claim` in place, suspends the `Kustomization` with a server-side
apply under the field manager `gitops-reverser` and a new `reconcile.fluxcd.io/requestedAt` token,
and records an `Adopted` Event. The suspend is handled once the `Kustomization` reports that
token: reconciles of one object never overlap, so by then nothing that was running before is.
-- **Several `GitTarget`s.** One eligible `GitTarget` among those naming the trigger is enough. While
- none is, and one cannot be judged yet (its `GitProvider` is missing), the trigger reports
+- **Several `GitTarget`s.** One eligible `GitTarget` among those naming the applier claim is enough. While
+ none is, and one cannot be judged yet (its `GitProvider` is missing), the applier claim reports
`NotBound` and waits. Once each is refused, it reports the reason they share (`SourceMismatch`,
`NamespaceNotAuthorized`), or `NotBound` for mixed ones, with every `GitTarget` and its reason in
the message.
-- **One trigger per `Kustomization`.** A trigger is in line for its `Kustomization` while an
+- **One applier claim per `Kustomization`.** An applier claim is in line for its `Kustomization` while an
eligible `GitTarget` references it, and while it holds the `Kustomization` or is handing it back.
When two in line name the same `Kustomization` in the same cluster, one holds it: the one with an
apply in flight, otherwise the one that holds it already, otherwise the oldest. The other reports
- `HeldByAnotherTrigger`, does nothing, and takes over once the holder lets go. A trigger nothing
+ `HeldByAnotherClaim`, does nothing, and takes over once the holder lets go. An applier claim nothing
eligible references is not in line, so it keeps no other out.
-- **The trigger hands the `Kustomization` back** when no eligible `GitTarget` references it any
+- **The applier claim hands the `Kustomization` back** when no eligible `GitTarget` references it any
more, and when it is deleted. `Held` reads `Releasing`: it suspends the `Kustomization` once more
if an apply has it resumed, waits until that is handled, and releases `spec.suspend`, so Flux
applies it on its own again (a `Released` Event). A `Kustomization` that somebody suspended
- before the trigger held it stays suspended. The trigger records how it stood in
+ before the applier claim held it stays suspended. The applier claim records how it stood in
`status.previousSuspendState` (`Suspended` or `NotSuspended`) before it first suspends it, and
- `kubectl get fluxtrigger -o wide` shows it.
- Until that is done it stays the holder, so no other trigger acts on the `Kustomization`. One
+ `kubectl get fluxclaim -o wide` shows it.
+ Until that is done it stays the holder, so no other applier claim acts on the `Kustomization`. One
whose `spec.suspend` another manager also applies stays suspended, and the Event names that
- manager. With another `FluxTrigger` in line, the `Kustomization` stays suspended for that one,
+ manager. With another `FluxClaim` in line, the `Kustomization` stays suspended for that one,
which inherits `status.previousSuspendState`. A
`KubeProvider` that is gone does not stop the hand-back: it goes on through whatever still
reaches the cluster, which for an `inCluster` provider is the operator's own. When nothing
- does, or the hand-back has not finished 10 minutes after `Held` turned `Releasing`, the trigger
+ does, or the hand-back has not finished 10 minutes after `Held` turned `Releasing`, the applier claim
lets go with a `ReleaseFailed` Event saying where the `Kustomization` was left. A reference that
returns before the release is done holds the `Kustomization` again; one that returns after adopts
it anew.
- **Deleting or re-creating a `GitTarget` hands the `Kustomization` back.** Flux then applies the
- Git tip at once, which overwrites live edits not yet in Git, and the trigger adopts the
+ Git tip at once, which overwrites live edits not yet in Git, and the applier claim adopts the
`Kustomization` again once the `GitTarget` is back. That is what Flux does without GitOps
Reverser, and with no `GitTarget` nobody would carry those edits to Git anyway.
-- **A trigger whose configuration turns invalid keeps its hold.** It reports `Held=Unknown`
+- **An applier claim whose configuration turns invalid keeps its hold.** It reports `Held=Unknown`
(`NotConfigured`), and the message says the `Kustomization` is left suspended under GitOps
- Reverser's field manager; deleting the trigger, or its `GitTarget`s, hands it back.
+ Reverser's field manager; deleting the applier claim, or its `GitTarget`s, hands it back.
-The trigger reports `Configured` and `Held`, and its `Ready` includes both:
+The applier claim reports `Configured` and `Held`, and its `Ready` includes both:
| Reason | Means | Readiness |
| --- | --- | --- |
-| `Configured`: `Succeeded` | An eligible `GitTarget` names the trigger, and the `Kustomization` exists, applies a `GitRepository` that follows a branch, and may be patched | ready |
-| `Configured`: `NotReferenced` (`Unknown`) | No `GitTarget` names the trigger. Nothing is read or written | ready: `Ready=True`, `NotReferenced` |
-| `Configured`: `NotBound` (`Unknown`) | No `GitTarget` naming the trigger is eligible yet, and one cannot be judged yet | progressing |
-| `Configured`: `SourceMismatch`, `NamespaceNotAuthorized` or `NotBound` (`False`) | Every `GitTarget` naming the trigger is refused: it writes another repository or branch, or is not admitted. The message lists each | stalled |
-| `Configured`: `ApplierNotFound` | No such `Kustomization` in the applier's cluster, or Flux is not installed there | progressing: Flux objects are often created after the trigger |
+| `Configured`: `Succeeded` | An eligible `GitTarget` names the applier claim, and the `Kustomization` exists, applies a `GitRepository` that follows a branch, and may be patched | ready |
+| `Configured`: `NotReferenced` (`Unknown`) | No `GitTarget` names the applier claim. Nothing is read or written | ready: `Ready=True`, `NotReferenced` |
+| `Configured`: `NotBound` (`Unknown`) | No `GitTarget` naming the applier claim is eligible yet, and one cannot be judged yet | progressing |
+| `Configured`: `SourceMismatch`, `NamespaceNotAuthorized` or `NotBound` (`False`) | Every `GitTarget` naming the applier claim is refused: it writes another repository or branch, or is not admitted. The message lists each | stalled |
+| `Configured`: `ApplierNotFound` | No such `Kustomization` in the applier's cluster, or Flux is not installed there | progressing: Flux objects are often created after the applier claim |
| `Configured`: `UnsupportedSource` | The source is not a `GitRepository`, or breaks one of the [`GitRepository` rules](#the-gitrepository) | stalled |
| `Configured`: `SuspendOwnedElsewhere` | Another field manager applies `spec.suspend` | stalled: remove the field from the manifest it applies |
| `Configured`: `Forbidden` | The applier cluster's identity may not patch the `Kustomization` or its `GitRepository` | stalled |
-| `Configured`: `NamespaceNotAuthorized` | The `kubeProviderRef` names a provider in another namespace whose `accessFrom` does not admit the trigger's namespace | stalled |
+| `Configured`: `NamespaceNotAuthorized` | The `kubeProviderRef` names a provider in another namespace whose `accessFrom` does not admit the applier claim's namespace | stalled |
| `Configured`: `KubeProviderNotFound` | The `kubeProviderRef` names a provider that does not exist | progressing |
| `Configured`: `AuditRouteConflict` | The provider carries an audit route another namespace holds, so it is not used | stalled |
-| `Configured`: `ApplierNamespaceNotAllowed` | The `Kustomization` is in another namespace than the trigger's, and the `KubeProvider` does not set `allowCrossNamespace.appliers` | stalled |
-| `Configured`: `HeldByAnotherTrigger` | Another `FluxTrigger` holds this `Kustomization` | stalled: delete one of the two |
+| `Configured`: `ApplierNamespaceNotAllowed` | The `Kustomization` is in another namespace than the applier claim's, and the `KubeProvider` does not set `allowCrossNamespace.appliers` | stalled |
+| `Configured`: `HeldByAnotherClaim` | Another `FluxClaim` holds this `Kustomization` | stalled: delete one of the two |
| `Held`: `Suspended` | Suspended under GitOps Reverser, and the last suspend is handled. Nothing applies | ready |
| `Held`: `Applying` | An apply GitOps Reverser started is in flight: `status.operation` shows it | ready |
| `Held`: `Suspending` | Just suspended; waiting for the `Kustomization` to handle it | progressing |
| `Held`: `Releasing` | Handing the `Kustomization` back: suspending it if needed, then releasing `spec.suspend`, within 10 minutes | progressing |
-| `Held`: `NotReferenced` | Nothing to hold: no `GitTarget` names the trigger | ready |
-| `Held`: `NotConfigured` | The trigger is not `Configured`, so nothing is held, or the `Kustomization` is left suspended as the trigger last held it (the message says which) | follows `Configured` |
+| `Held`: `NotReferenced` | Nothing to hold: no `GitTarget` names the applier claim | ready |
+| `Held`: `NotConfigured` | The applier claim is not `Configured`, so nothing is held, or the `Kustomization` is left suspended as the applier claim last held it (the message says which) | follows `Configured` |
| `Ready`: `OperationUnresolved` | An apply has not completed within 5 minutes, or the `Kustomization` was [resumed by hand](#overriding-by-hand) | progressing; it clears when the apply does |
-`Ready=True` for a trigger no `GitTarget` names is deliberate: it does all it is asked to, which is
-nothing. A Flux health check reads `Ready`, so a `Kustomization` that creates the trigger and waits
+`Ready=True` for an applier claim no `GitTarget` names is deliberate: it does all it is asked to, which is
+nothing. A Flux health check reads `Ready`, so a `Kustomization` that creates the applier claim and waits
for it would otherwise never become ready, and the `Kustomization` that creates the `GitTarget`
after it would never apply.
-The `GitTarget` reports `ApplyTriggerReady`: `True` when the trigger is `Ready` and its
+The `GitTarget` reports `ApplierClaimReady`: `True` when the applier claim is `Ready` and its
`GitRepository` fetches this repository and branch, `SourceMismatch` when it fetches another,
-`ApplyTriggerNotFound` when the trigger does not exist, `Unknown` (`NotReferenced`) until the
-trigger has seen that the `GitTarget` names it, and otherwise the trigger's own `Ready` reason.
+`ApplierClaimNotFound` when the applier claim does not exist, `Unknown` (`NotReferenced`) until the
+applier claim has seen that the `GitTarget` names it, and otherwise the applier claim's own `Ready` reason.
## Permissions
-GitOps Reverser looks the `Kustomization` up in the cluster of the trigger's `kubeProviderRef`,
+GitOps Reverser looks the `Kustomization` up in the cluster of the applier claim's `kubeProviderRef`,
with the identity of that `KubeProvider`. That identity needs:
| API group | Resource | Verbs |
@@ -380,7 +380,7 @@ For the operator's own cluster, install the chart with `applierAccess.flux: true
[`config/rbac/flux-applier-role.yaml`](../config/rbac/flux-applier-role.yaml) and its binding. For a
remote cluster, grant the same to the identity in the `KubeProvider`'s kubeconfig. The chart's
grant is cluster-wide for convenience; GitOps Reverser only patches the `Kustomization` and
-`GitRepository` a trigger names.
+`GitRepository` an applier claim names.
## One apply
@@ -397,7 +397,7 @@ them into its inventory. A `Kustomization` a `GitTarget` names for the first tim
`reconcile.fluxcd.io/requestedAt` on the `GitRepository` and waits for that fetch. The suspended
`Kustomization` ignores the new artifact.
2. Reads the artifact again and checks that the decision still holds.
-3. Records the operation on the trigger's `status.operation` (`Requested`), so a restart finds it.
+3. Records the operation on the applier claim's `status.operation` (`Requested`), so a restart finds it.
4. Resumes the `Kustomization`: one server-side apply sets `spec.suspend: false` and a new
`requestedAt`, guarded by the `uid` and `resourceVersion` it read. The reconcile that write
enqueues applies (`Running`).
@@ -410,7 +410,7 @@ them into its inventory. A `Kustomization` a `GitTarget` names for the first tim
```mermaid
sequenceDiagram
participant Rev as GitOps Reverser
- participant Trig as FluxTrigger status
+ participant Trig as FluxClaim status
participant Src as GitRepository
participant Ks as Kustomization
@@ -435,14 +435,14 @@ anything.
**An apply that takes long is followed, not timed out.** After 5 minutes the operation is
`Unresolved`: the `Kustomization` is suspended at once, which stops whatever would follow but not
the apply already running, and an `ApplyUnresolved` Event is recorded. An operation nobody follows
-for a minute (its `GitTarget` deleted mid-apply, for example) is suspended by the trigger, completes
+for a minute (its `GitTarget` deleted mid-apply, for example) is suspended by the applier claim, completes
as failed, and records an `OperationUnfollowed` Event. A resumed `Kustomization` is never left with
nothing watching it.
**The apply window.** From the moment an operation is asked for until 10 seconds after it completes,
a write by `kustomize-controller` alone to an object nobody edited is the apply landing, and is not
mirrored. A write by `kustomize-controller` over a newer API edit, one made after the flush included,
-is withheld from Git, and GitOps Reverser asks for a `Repair`; the trigger shows `repairPending`
+is withheld from Git, and GitOps Reverser asks for a `Repair`; the applier claim shows `repairPending`
until that apply succeeds. Only a `kustomize-controller` entry as new as the edit or newer counts:
undoing an edit can leave an older one as the newest entry, and that undo is mirrored. When
`kustomize-controller` and another manager wrote in the same second, the write is mirrored and
@@ -456,24 +456,24 @@ tip, the consumers GitOps Reverser holds ignore the new artifact, and one in
## Overriding by hand
- **`flux resume ks checkout`** applies the `Kustomization` at once, unchecked. Within a minute the
- trigger suspends it again, records a `ClaimedResourceReconciled` Event, and shows the apply as an
+ applier claim suspends it again, records a `ClaimedResourceReconciled` Event, and shows the apply as an
`Unresolved` operation that completes once nothing it started is running. It does so every time.
- **`flux reconcile ks checkout`** does nothing visible while the `Kustomization` is suspended: Flux
marks the request handled without applying.
- **`flux suspend ks checkout`** is taken back with a token of GitOps Reverser's.
-- To hand the `Kustomization` back to Flux for good, delete the `FluxTrigger`, or stop naming it from
+- To hand the `Kustomization` back to Flux for good, delete the `FluxClaim`, or stop naming it from
every `GitTarget`.
## Checking that it works
```bash
# The applier is found, applies a branch, may be patched, and is held; and it applies this branch.
-kubectl get fluxtrigger checkout -n team-a
+kubectl get fluxclaim checkout -n team-a
kubectl get gittarget checkout -n team-a \
- -o jsonpath='{.status.conditions[?(@.type=="ApplyTriggerReady")]}{"\n"}'
+ -o jsonpath='{.status.conditions[?(@.type=="ApplierClaimReady")]}{"\n"}'
# The last apply, why, and where it stands; and what else applies the same source.
-kubectl get fluxtrigger checkout -n team-a -o jsonpath='{.status.operation}{"\n"}{.status.source}{"\n"}'
+kubectl get fluxclaim checkout -n team-a -o jsonpath='{.status.operation}{"\n"}{.status.source}{"\n"}'
# {"appliedRevision":"main@sha1:4f2c1ab9...","commit":"4f2c1ab9...","phase":"Succeeded","reason":"ForeignCommit",...}
# Each apply asked for, each one that could not be asked or did not complete.
@@ -490,19 +490,19 @@ flux get ks checkout -n flux-system
| Symptom | Cause | Fix |
| --- | --- | --- |
-| Trigger `Configured=False`, `ApplierNotFound` | No `Kustomization` with that namespace and name in the trigger's cluster, or Flux is not installed there | Create it, or recreate the trigger with the right `kustomizationRef`; an omitted namespace is the trigger's |
-| Trigger `Configured=False`, `SuspendOwnedElsewhere` | The manifest that applies the `Kustomization` sets `spec.suspend` | Remove the field from that manifest |
-| `ApplyTriggerReady=False`, `SourceMismatch`, or trigger `UnsupportedSource` | The message names the broken [`GitRepository` rule](#the-gitrepository) | Fix the `GitRepository`; the trigger then adopts the `Kustomization`, and the apply that was waiting is asked for |
-| Trigger `Configured=False`, `ApplierNamespaceNotAllowed` | The `Kustomization` is in another namespace than the trigger, such as `flux-system` | Set `allowCrossNamespace.appliers: true` on the trigger's `KubeProvider` (Helm: `kubeProvider.allowCrossNamespace.appliers`), or move the trigger and `GitTarget` to the `Kustomization`'s namespace |
-| Trigger `Configured=Unknown`, `NotBound` | A `GitTarget` names the trigger, but cannot be judged yet: its `GitProvider` or `KubeProvider` does not exist | Create the missing object; the message names it |
-| Trigger `Ready=True`, `NotReferenced`, and the `Kustomization` is not suspended | No `GitTarget` names the trigger, so it holds nothing | Name it in the `GitTarget`'s `spec.applyTriggerRef` |
-| Trigger `Configured=False`, `Forbidden` | The identity lacks one of the [permissions](#permissions) | Grant it, or set `applierAccess.flux: true` |
+| `FluxClaim` `Configured=False`, `ApplierNotFound` | No `Kustomization` with that namespace and name in the applier claim's cluster, or Flux is not installed there | Create it, or recreate the applier claim with the right `kustomizationRef`; an omitted namespace is the applier claim's |
+| `FluxClaim` `Configured=False`, `SuspendOwnedElsewhere` | The manifest that applies the `Kustomization` sets `spec.suspend` | Remove the field from that manifest |
+| `ApplierClaimReady=False`, `SourceMismatch`, or applier claim `UnsupportedSource` | The message names the broken [`GitRepository` rule](#the-gitrepository) | Fix the `GitRepository`; the applier claim then adopts the `Kustomization`, and the apply that was waiting is asked for |
+| `FluxClaim` `Configured=False`, `ApplierNamespaceNotAllowed` | The `Kustomization` is in another namespace than the applier claim, such as `flux-system` | Set `allowCrossNamespace.appliers: true` on the applier claim's `KubeProvider` (Helm: `kubeProvider.allowCrossNamespace.appliers`), or move the applier claim and `GitTarget` to the `Kustomization`'s namespace |
+| `FluxClaim` `Configured=Unknown`, `NotBound` | A `GitTarget` names the applier claim, but cannot be judged yet: its `GitProvider` or `KubeProvider` does not exist | Create the missing object; the message names it |
+| `FluxClaim` `Ready=True`, `NotReferenced`, and the `Kustomization` is not suspended | No `GitTarget` names the applier claim, so it holds nothing | Name it in the `GitTarget`'s `spec.applierClaimRef` |
+| `FluxClaim` `Configured=False`, `Forbidden` | The identity lacks one of the [permissions](#permissions) | Grant it, or set `applierAccess.flux: true` |
| A Warning `ApplyRequestFailed` Event | An apply could not be asked for; the message says why | GitOps Reverser keeps asking after its backoff, at most every five minutes |
| An object in the folder is never mirrored, and the log says `Not mirroring an object another applier owns` | Another `Kustomization` or an Argo CD `Application` applies it; `apply_ownership_conflicts_total` counts it | Let one applier own it: take it out of the other applier's source, or stop watching it from this `GitTarget` |
| A Warning `ApplyFailed` Event | Flux ran the apply and reported it failed | Read the `Kustomization`'s status; GitOps Reverser retries on its backoff |
-| A Warning `ApplyUnresolved` Event, trigger `OperationUnresolved` | The apply has not completed in 5 minutes | It is suspended and still followed; look at the `Kustomization`'s status and health checks |
+| A Warning `ApplyUnresolved` Event, applier claim `OperationUnresolved` | The apply has not completed in 5 minutes | It is suspended and still followed; look at the `Kustomization`'s status and health checks |
| A Warning `OperationUnfollowed` Event | An apply was left in flight with nothing following it | It was suspended and completed as failed. A recurring one is a bug worth reporting with the operator's logs |
-| A Warning `ReleaseFailed` Event | A trigger being deleted, or no longer referenced, could not hand its `Kustomization` back: a write it may not make, a `KubeProvider` gone with no way left to its cluster, or 10 minutes passed | The message says where it was left; `flux resume` it if it should apply on its own |
+| A Warning `ReleaseFailed` Event | An applier claim being deleted, or no longer referenced, could not hand its `Kustomization` back: a write it may not make, a `KubeProvider` gone with no way left to its cluster, or 10 minutes passed | The message says where it was left; `flux resume` it if it should apply on its own |
| A dependent `Kustomization` waits | Its dependency's last apply failed | Fix the dependency's failure; GitOps Reverser's retry unblocks it |
| A Git-side push takes minutes to reach the cluster | No receiver, so the refresh finds it | Configure [the push webhook](bi-directional.md#telling-gitops-reverser-that-somebody-else-pushed) |
| A refused edit is not reverted | The target is suspended, `onRefusal` is not `Reapply`, or the refusal is not eligible | See [reverting a refused edit](configuration.md#reverting-a-refused-edit-speconrefusal) |
@@ -520,11 +520,11 @@ it is back, or until somebody runs `flux resume`, after which GitOps Reverser su
that process starts, the `Kustomization` stays resumed, so Flux may also apply a new artifact on
its own in between.
-An apply that was running when it stopped is not lost. The operation is recorded on the trigger
+An apply that was running when it stopped is not lost. The operation is recorded on the applier claim
before the `Kustomization` is resumed, so the next process follows it to its end by its tokens, with
the apply window open. After its first look at the remote, a `CatchUp` that converges is pending for
every `Kustomization`, and no `ForeignCommit`. It is asked only once every initial snapshot of the
-`GitTarget`s naming the trigger is published: an apply before that could revert edits made while the
+`GitTarget`s naming the applier claim is published: an apply before that could revert edits made while the
operator was down, before its watches caught them. A `Kustomization` whose last successful operation
applied the tip is asked nothing.
@@ -535,16 +535,16 @@ applied the tip is asked nothing.
| The [complete example](#a-complete-example) decodes strictly, reports `Configured=True`, binds the `GitTarget`, and an apply fetches and resumes the `Kustomization`; the manifest omits `spec.suspend` | [`flux_guide_test.go`](../internal/applier/flux_guide_test.go) |
| Every fact of kustomize-controller v1.9.6 the lifecycle relies on, in a fake that applies them | [`fluxsim`](../internal/applier/fluxsim/fluxsim_test.go) |
| An operation is not finished when the revision is attempted; a token handled while suspended is no apply; a resume alone reverts a refusal; a suspend does not stop a running apply; the artifact is rechecked before the resume; a failed apply is suspended and Flux's requeue applies nothing; a shared source applies only the consumer asked | [`flux_lifecycle_test.go`](../internal/applier/flux_lifecycle_test.go), [`flux_consumers_test.go`](../internal/applier/flux_consumers_test.go) |
-| Adoption, an external resume suspended again with an Event each time, `SuspendOwnedElsewhere`, the hand-back on deletion (a `KubeProvider` gone included), and the source's other consumers | [`fluxtrigger_hold_test.go`](../internal/controller/fluxtrigger_hold_test.go) |
-| A trigger nothing names reads nothing and is `Ready`; the first eligible `GitTarget` adopts and the last releases, suspending an apply in flight first; a reference returning mid-release holds again; the binding over several `GitTarget`s; an older trigger not in line keeps none out, and a retiring holder stays first until its release is done; `ApplierNamespaceNotAllowed` | [`applytrigger_rule_test.go`](../internal/controller/applytrigger_rule_test.go) |
+| Adoption, an external resume suspended again with an Event each time, `SuspendOwnedElsewhere`, the hand-back on deletion (a `KubeProvider` gone included), and the source's other consumers | [`fluxclaim_hold_test.go`](../internal/controller/fluxclaim_hold_test.go) |
+| An applier claim nothing names reads nothing and is `Ready`; the first eligible `GitTarget` adopts and the last releases, suspending an apply in flight first; a reference returning mid-release holds again; the binding over several `GitTarget`s; an older applier claim not in line keeps none out, and a retiring holder stays first until its release is done; `ApplierNamespaceNotAllowed` | [`applierclaim_rule_test.go`](../internal/controller/applierclaim_rule_test.go) |
| Every operation checks the target again (incarnation, branch, admission, consent) and the source binding before it resumes, records itself first, and never asks for something weaker; a stop mid-apply suspends the `Kustomization` | [`dispatcher_test.go`](../internal/applier/dispatcher_test.go) |
-| A failed attempt is decided again; each `Kustomization` keeps its own backoff; a shared one is asked once; appliers sharing a source apply one at a time; nothing is sent while no flush can be made; an undo of an edit in the apply window is mirrored, and the applier's own write is not | [`apply_attempt_test.go`](../internal/git/apply_attempt_test.go), [`apply_groups_test.go`](../internal/git/apply_groups_test.go), [`apply_trigger_test.go`](../internal/git/apply_trigger_test.go), [`apply_window_test.go`](../internal/git/apply_window_test.go) |
-| A newly named `Kustomization` converges once; the startup converge asks nothing of one at the tip; an API create is adopted even at the tip; nothing is asked before the initial snapshots are published, or for an empty tip | [`apply_catchup_test.go`](../internal/git/apply_catchup_test.go), [`apply_trigger_test.go`](../internal/git/apply_trigger_test.go) |
+| A failed attempt is decided again; each `Kustomization` keeps its own backoff; a shared one is asked once; appliers sharing a source apply one at a time; nothing is sent while no flush can be made; an undo of an edit in the apply window is mirrored, and the applier's own write is not | [`apply_attempt_test.go`](../internal/git/apply_attempt_test.go), [`apply_groups_test.go`](../internal/git/apply_groups_test.go), [`applier_claim_test.go`](../internal/git/applier_claim_test.go), [`apply_window_test.go`](../internal/git/apply_window_test.go) |
+| A newly named `Kustomization` converges once; the startup converge asks nothing of one at the tip; an API create is adopted even at the tip; nothing is asked before the initial snapshots are published, or for an empty tip | [`apply_catchup_test.go`](../internal/git/apply_catchup_test.go), [`applier_claim_test.go`](../internal/git/applier_claim_test.go) |
| An object another applier owns is neither mirrored nor swept | [`apply_ownership_test.go`](../internal/git/apply_ownership_test.go) |
-| A trigger nothing names is `Ready` and holds nothing; named from a `GitTarget` on another branch than its source, it is `SourceMismatch` and adopts nothing | [Manager Flux Applier](../test/e2e/flux_applier_e2e_test.go) |
-| A `ForeignCommit` for a `SourceMismatch` applier never stamps the other branch's source and records `ApplyRequestFailed`; once the source is fixed the trigger adopts the `Kustomization` and Flux reaches the tip; dropping the reference hands it back, and naming the trigger again adopts it anew | Manager Flux Applier |
+| An applier claim nothing names is `Ready` and holds nothing; named from a `GitTarget` on another branch than its source, it is `SourceMismatch` and adopts nothing | [Manager Flux Applier](../test/e2e/flux_applier_e2e_test.go) |
+| A `ForeignCommit` for a `SourceMismatch` applier never stamps the other branch's source and records `ApplyRequestFailed`; once the source is fixed the applier claim adopts the `Kustomization` and Flux reaches the tip; dropping the reference hands it back, and naming the applier claim again adopts it anew | Manager Flux Applier |
| A Gitea push webhook to the operator's receiver gets a Git-side push applied; with no receiver and no API edit, the refresh finds one | Manager Flux Applier |
-| A failed apply is suspended again and retried; a `flux resume` is suspended again with a `ClaimedResourceReconciled` Event; deleting the trigger hands the `Kustomization` back | Manager Flux Applier |
+| A failed apply is suspended again and retried; a `flux resume` is suspended again with a `ClaimedResourceReconciled` Event; deleting the applier claim hands the `Kustomization` back | Manager Flux Applier |
| A manager restarted during an apply suspends the `Kustomization` as it stops; the new one completes that same operation and leaves it suspended | [Flux Restart Recovery](../test/e2e/flux_restart_recovery_e2e_test.go) |
| Two API edits seconds apart end at the later value with no extra commit | [Bi-directional (Flux)](../test/e2e/flux_bi_directional_e2e_test.go) |
| A Git-side push with an API edit on top lands both, and Flux applies the replayed commit | Bi-directional (Flux) |
diff --git a/docs/bi-directional.md b/docs/bi-directional.md
index 22928c956..885856fda 100644
--- a/docs/bi-directional.md
+++ b/docs/bi-directional.md
@@ -15,29 +15,33 @@ ordinary GitOps and needs nothing from this page.
| --- | --- |
| GitOps Reverser mirrors a folder that nothing applies: an audit trail, a backup, the start of a repository | A `GitTarget` and its watch rules |
| Flux or Argo CD applies other folders or other repositories, next to GitOps Reverser | Nothing. Flux and Argo CD stay exactly as they are |
-| Flux or Argo CD applies a folder that GitOps Reverser writes | An apply trigger, as this page describes |
+| Flux or Argo CD applies a folder that GitOps Reverser writes | An applier claim, as this page describes |
```mermaid
flowchart TD
Q1{"Does Flux or Argo CD apply a folder that GitOps Reverser writes?"}
Q1 -->|no| ONEWAY["One-way. Mirror only, or GitOps as usual beside it. Nothing on this page applies"]
- Q1 -->|yes| Q2{"Does an apply trigger hold that applier?"}
- Q2 -->|yes| BI["Bi-directional. GitOps Reverser decides when it applies"]
- Q2 -->|no| EXPOSED["Shared folder without a trigger. Works, and the applier can revert live edits"]
+ Q1 -->|yes| Q2{"Does the GitTarget name an applier claim?"}
+ Q2 -->|yes| BI["Coordinated folder. GitOps Reverser decides when it applies"]
+ Q2 -->|no| EXPOSED["Uncoordinated shared folder. Works, and the applier can revert live edits"]
style ONEWAY fill:#e8f5e9,stroke:#43a047
style BI fill:#e8f5e9,stroke:#43a047
style EXPOSED fill:#fff3e0,stroke:#ff9800
```
-Everything bi-directional is opt-in and off by default: the apply triggers, the push receiver,
+Everything bi-directional is opt-in and off by default: the applier claims, the push receiver,
`onRefusal: Reapply`, the chart's `applierAccess` grants, and the `KubeProvider`'s
-`allowCrossNamespace.appliers`, which a trigger needs to hold an applier outside its own namespace,
-such as one in `flux-system` or `argocd`. A `GitTarget` without `spec.applyTriggerRef` never reads,
-patches or syncs anything of Flux's or Argo CD's, and neither does a trigger no `GitTarget` names, so
+`allowCrossNamespace.appliers`, which an applier claim needs to hold an applier outside its own namespace,
+such as one in `flux-system` or `argocd`. A `GitTarget` without `spec.applierClaimRef` never reads,
+patches or syncs anything of Flux's or Argo CD's, and neither does an applier claim no `GitTarget` names, so
a `Kustomization` keeps its interval and an `Application` keeps automated sync.
-**A shared folder without a trigger works, and is exposed.** The applier keeps its own schedule,
+Naming an applier claim makes the `GitTarget`'s folder
+[coordinated](definitions.md#applying-back). This describes the setup; `ApplierClaimReady`
+reports whether the applier claim is ready.
+
+**An uncoordinated shared folder works, and is exposed.** The applier keeps its own schedule,
and that schedule cannot see the edits GitOps Reverser has not pushed yet
([why](#why-the-applier-cannot-decide-for-itself)). An apply at the wrong moment reverts a live edit,
and GitOps Reverser then commits the revert as the applier's change. With Argo CD's
@@ -51,16 +55,20 @@ GitOps Reverser decides when the cluster is applied from Git. Flux or Argo CD ke
rendering, applying, pruning and reporting health, and applies when GitOps Reverser asks. Three
things set that up:
-1. **An apply trigger holds the applier.** A `FluxTrigger` holds a Flux `Kustomization`, an
- `ArgoCDTrigger` an Argo CD `Application`, and the `GitTarget` names the trigger in
- `spec.applyTriggerRef`. A trigger acts only while an eligible `GitTarget` names it: one in its
+1. **An applier claim holds the applier.** A `FluxClaim` holds a Flux `Kustomization`, an
+ `ArgoCDClaim` an Argo CD `Application`, and the `GitTarget` names the applier claim in
+ `spec.applierClaimRef`. An applier claim acts only while an eligible `GitTarget` names it: one in its
namespace, admitted by its `KubeProvider`s, that writes the repository and branch the applier
fetches.
-2. **The applier's own triggers are off.** The `FluxTrigger` keeps its `Kustomization` suspended;
- the `ArgoCDTrigger` keeps its `Application`'s automated sync off.
+2. **The applier's own schedule is off.** The `FluxClaim` keeps its `Kustomization` suspended;
+ the `ArgoCDClaim` keeps its `Application`'s automated sync off.
3. **The Git host's push webhook reaches GitOps Reverser**, so a Git-side push is applied in
seconds. See [telling GitOps Reverser that somebody else pushed](#telling-gitops-reverser-that-somebody-else-pushed).
+
+
```mermaid
flowchart LR
subgraph CLUSTER["Kubernetes cluster"]
@@ -86,13 +94,13 @@ The two appliers get the same contract by different means:
| | Flux | Argo CD |
| --- | --- | --- |
-| Held by | A `FluxTrigger` | An `ArgoCDTrigger` |
-| Kept from applying on its own by | `spec.suspend`, which the trigger owns | `spec.syncPolicy.automated.enabled: false`, which the trigger owns, unless the `ApplicationSet` that generated the `Application` owns it |
+| Held by | A `FluxClaim` | An `ArgoCDClaim` |
+| Kept from applying on its own by | `spec.suspend`, which the applier claim owns | `spec.syncPolicy.automated.enabled: false`, which the applier claim owns, unless the `ApplicationSet` that generated the `Application` owns it |
| One apply is | A resume for one reconcile, then a suspend | A sync operation pinned to the commit |
| When it is asked to apply | [The same four reasons](#when-gitops-reverser-asks-for-an-apply), `CatchUp` included | The same four reasons |
| The tool's own push webhook | Removed: GitOps Reverser's receiver replaces it | May stay: with automated sync off it only compares |
| A human override | `flux resume` applies once; it is suspended again within a minute | Sync applies; GitOps Reverser waits for it to end. Automated sync switched on is switched off again within a minute |
-| Handing it back | Delete the trigger, or stop naming it from every `GitTarget`: the `Kustomization` is resumed, or stays suspended if it was before | Delete the trigger, or stop naming it from every `GitTarget`: automated sync is as it was before, unless an `ApplicationSet` or another field manager owns the switch by then, which keeps their value |
+| Handing it back | Delete the applier claim, or stop naming it from every `GitTarget`: the `Kustomization` is resumed, or stays suspended if it was before | Delete the applier claim, or stop naming it from every `GitTarget`: automated sync is as it was before, unless an `ApplicationSet` or another field manager owns the switch by then, which keeps their value |
| Complete setup | [Bi-directional GitOps with Flux](bi-directional-flux.md) | [Bi-directional GitOps with Argo CD](bi-directional-argocd.md) |
## Why the applier cannot decide for itself
@@ -179,7 +187,7 @@ sequenceDiagram
| `ForeignCommit` | The branch gained a commit GitOps Reverser did not push | Every applier declared on the branch |
| `Refusal` | An edit was refused and the target has `onRefusal: Reapply` | The refusing target's applier |
| `Repair` | An apply reverted an API edit made after the flush; Git kept the edit | That applier |
-| `CatchUp` | To **adopt**: the API created an object in a folder the applier applies, or a resync added an object to Git. To **converge**: once when the operator starts, and once when a `GitTarget` first names the trigger | To adopt, that folder's applier; to converge, every applier on the branch at start, and the newly named one |
+| `CatchUp` | To **adopt**: the API created an object in a folder the applier applies, or a resync added an object to Git. To **converge**: once when the operator starts, and once when a `GitTarget` first names the applier claim | To adopt, that folder's applier; to converge, every applier on the branch at start, and the newly named one |
The same rule holds for a Flux `Kustomization` and an Argo CD `Application`. The two differ only in
how an apply is carried out, never in when one is asked for.
@@ -199,31 +207,31 @@ as already applied, because applying the same commit again is what stamps the ob
creates is one apply: a pending `CatchUp` is one flag, and one attempt per applier is out at a time.
**A converge asks nothing when it is already done.** The branch worker keeps, for each applier, the
-commit its last successful operation applied, and recovers it on restart from the trigger's
+commit its last successful operation applied, and recovers it on restart from the applier claim's
`status.operation`. A converge of that commit asks nothing.
**No apply before every initial snapshot is published.** An applier is asked for nothing, for any
reason, until every resource collection of every `GitTarget` it covers has a settled snapshot
(applied, refused or failed) and the branch worker holds no resync of that `GitTarget` still to
publish. At startup that is the moment the edits made while the operator was down are in Git; for a
-`GitTarget` that newly names the trigger, the moment its folder holds the cluster. An earlier apply
+`GitTarget` that newly names the applier claim, the moment its folder holds the cluster. An earlier apply
would revert what is not mirrored yet. A `GitTarget` with no collection planned yet (its
`WatchRule` not resolved, or a CRD not discovered) and one whose watch is `Blocked` hold their
applier back the same way. A reason that comes due before stays pending, and so does one while the
remote does not carry the branch, because there is no commit to apply.
-**The folder exists before it is applied.** A resync of a `GitTarget` that names a trigger, into a
+**The folder exists before it is applied.** A resync of a `GitTarget` that names an applier claim, into a
folder the branch does not hold yet, commits the folder's bootstrap files (`README.md`,
`.gittargetignore`, and `.sops.yaml` with encryption) even when there is no object to write: Argo CD
retries a sync of a missing path for minutes, and Flux fails it. The bootstrap files are never
deleted, so the folder outlives its last resource.
-Several `GitTarget`s may name one trigger, for a `Kustomization` or `Application` whose path covers
+Several `GitTarget`s may name one applier claim, for a `Kustomization` or `Application` whose path covers
several folders: the applier is asked once for all of them.
## Objects another applier owns
-A folder a trigger applies belongs to that trigger's applier. An object that another applier owns
+A coordinated folder belongs to its applier. An object that another applier owns
is not mirrored into it: a live change to it is not mirrored, and a resync neither writes it nor
removes its document, even under `prune.mode: Always`. Mirrored, it would be applied by this applier
too, and two appliers applying one object fight each other.
@@ -232,12 +240,12 @@ The owner is read from the object before its bookkeeping is stripped: Flux's
`kustomize.toolkit.fluxcd.io/name` and `kustomize.toolkit.fluxcd.io/namespace` labels, or else Argo
CD's `argocd.argoproj.io/tracking-id` annotation. Each excluded object is logged once and counted in
`apply_ownership_conflicts_total`. An object no applier owns, or one the folder's own applier owns,
-is mirrored as usual. A folder with no trigger excludes nothing: mirroring another applier's objects
+is mirrored as usual. A folder with no applier claim excludes nothing: mirroring another applier's objects
one way is what such a folder can exist for.
## The life of an apply
-An apply is an **operation**, recorded on the trigger's `status.operation` before the applier is
+An apply is an **operation**, recorded on the applier claim's `status.operation` before the applier is
asked, so a restarted operator finds it and follows it to its end:
```mermaid
@@ -269,8 +277,8 @@ operation is in flight (`Requested`, `Running`, `Settling` or `Unresolved`), the
nothing else.
- **Just before it asks, GitOps Reverser checks the decision again**: the commit is still the branch
- tip, each `GitTarget` still names this trigger on this branch, is still admitted by its
- `KubeProvider`s, and, for a `Refusal`, still consents; and the trigger still holds the applier.
+ tip, each `GitTarget` still names this applier claim on this branch, is still admitted by its
+ `KubeProvider`s, and, for a `Refusal`, still consents; and the applier claim still holds the applier.
When any of that changed, it asks nothing and decides again, after a new flush. It never asks for
something weaker than what it was told.
- **The apply's own writes are not mirrored.** Inside the apply window, a write whose newest field
@@ -421,7 +429,7 @@ until something applies Git over it.
| `Ignore` (default) | Report the refusal | Recovery is left to a human or a later apply |
| `Reapply` | Ask the target's applier to apply the tip, after a flush | The refused edit is reverted; nothing still waiting to be published is |
-`Reapply` needs an apply trigger. It fires only for an edit to a document the folder already holds,
+`Reapply` needs an applier claim. It fires only for an edit to a document the folder already holds,
never for a write that removes one, and a reverted edit does not clear the refusal: the target
returns to `Ready=True` only after a resync covering the refused scope. The eligibility rules are in
[reverting a refused edit](configuration.md#reverting-a-refused-edit-speconrefusal).
@@ -431,16 +439,13 @@ returns to `Ready=True` only after a resync covering the refused scope. The elig
- **An edit made after the flush, while a requested apply runs,** is reverted on the cluster until
the `Repair`. It keeps its value in Git. It can happen only during an apply GitOps Reverser asked
for: after a foreign push, a refusal with `Reapply`, a `Repair`, or a `CatchUp`.
-- **An edit through another `GitProvider` on the same repository and branch, made after the
- flush,** is not protected. The flush publishes what that worker decided before the apply, but it
- has no apply window, so it mirrors the apply's revert of a later edit as an edit of its own.
- **A human** running `flux resume` or pressing Sync applies whenever they do. GitOps Reverser
suspends a resumed `Kustomization` again within a minute.
- **GitOps Reverser becomes the only path to a deployment.** While it is down, the shared folders
wait: a push reaches the cluster only through a manual sync or `flux resume`. A graceful stop
suspends a `Kustomization` it had resumed; after a crash, the apply that was running finishes,
and the next process suspends it. Monitor the operator the way you monitor the applier.
-- **A trigger may hold any applier its `KubeProvider` reaches.** With
+- **An applier claim may hold any applier its `KubeProvider` reaches.** With
`allowCrossNamespace.appliers`, every namespace the provider admits may hold a `Kustomization` or
`Application` in another namespace whose source is the repository its `GitProvider` names. That
is a delegation by the provider's owner: nothing proves the namespace owns the applier, or may
diff --git a/docs/components.md b/docs/components.md
index e80182d53..9c4e0c458 100644
--- a/docs/components.md
+++ b/docs/components.md
@@ -23,7 +23,7 @@ the target watches supply *state*, the branch worker owns *Git*, and audit only
```mermaid
flowchart TB
subgraph CFG["Config plane (operator's own cluster)"]
- CRD["api/v1alpha3 GitTarget · WatchRule · ClusterWatchRule GitProvider · GitBranch · KubeProvider · CommitRequest FluxTrigger · ArgoCDTrigger"]
+ CRD["api/v1alpha3 GitTarget · WatchRule · ClusterWatchRule GitProvider · GitBranch · KubeProvider · CommitRequest FluxClaim · ArgoCDClaim"]
CTRL["internal/controller reconcilers"]
ADM["internal/webhook admission handlers"]
AUTHZ["internal/authz namespace admission"]
@@ -124,7 +124,7 @@ write to that branch goes through its single event loop.
| Document editor | [`internal/git/manifestedit/`](../internal/git/manifestedit/) | edits one YAML document in place inside a multi-document file |
| Manifest report | [`internal/manifestreport/`](../internal/manifestreport/) | projects a live object into a comparable manifest |
| Encryption and signing | [`internal/git/sops_encryptor.go`](../internal/git/sops_encryptor.go), [`internal/sshsig/`](../internal/sshsig/) | SOPS for sensitive types, SSH signatures for commits |
-| Apply requests | [`internal/applier/`](../internal/applier/) | the Flux and Argo CD clients, the dispatcher that carries out each apply attempt a branch worker decides, and the trigger's `Configured` check |
+| Apply requests | [`internal/applier/`](../internal/applier/) | the Flux and Argo CD clients, the dispatcher that carries out each apply attempt a branch worker decides, and the applier claim's `Configured` check |
| Push receiver | [`internal/webhook/receiver.go`](../internal/webhook/receiver.go) | `/git-push/` deliveries to a `GitReceiver`, routed by the repository and branch they name to the branch worker of each `GitProvider` whose `spec.url` matches, as a hint to read the remote |
### Attribution
@@ -190,7 +190,7 @@ flowchart LR
Two more paths reach the operator without being watches at all. The audit webhook is a **push** from
kube-apiserver, and the admission webhooks are synchronous requests. Neither observes state. The
-apply trigger reconciler reads each trigger's `Kustomization` or `Application` with a GET on its own
+applier claim reconciler reads each applier claim's `Kustomization` or `Application` with a GET on its own
tick rather than a watch, so a cluster without Flux or Argo CD needs no informer for them.
### Why two type observers is correct
diff --git a/docs/configuration.md b/docs/configuration.md
index 9e0de2a5c..f0d18f16e 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -14,7 +14,7 @@ The short version:
- `ClusterWatchRule` does the same for cluster-scoped resources
- `CommitRequest` optionally saves: it closes its author's commit window on its own timers, with
its own message if it has one
-- `FluxTrigger` and `ArgoCDTrigger` hold the Flux `Kustomization` or Argo CD `Application` that
+- `FluxClaim` and `ArgoCDClaim` hold the Flux `Kustomization` or Argo CD `Application` that
applies a folder back, so GitOps Reverser decides when it applies
The chart's optional `quickstart` values are a convenience layer that creates starter
@@ -26,7 +26,7 @@ attribution later only when you need named Kubernetes users or service accounts
## How the objects fit together
-
+
The usual flow is:
@@ -38,10 +38,10 @@ The usual flow is:
5. Create a `CommitRequest` only when you want a save: your writes in one commit, on the request's
timers rather than the target's, and optionally with your message.
6. Only when Flux or Argo CD also applies that same folder back to the cluster: hold its
- `Kustomization` or `Application` with an apply trigger, name the trigger on the `GitTarget`, and
+ `Kustomization` or `Application` with an applier claim, name the applier claim on the `GitTarget`, and
point the Git host's push webhook at a [`GitReceiver`](#gitreceiver), so GitOps Reverser decides
when Git is applied. See
- [who applies this folder](#who-applies-this-folder-specapplytriggerref) and
+ [who applies this folder](#who-applies-this-folder-specapplierclaimref) and
[bi-directional GitOps](bi-directional.md). Flux and Argo CD applying other folders need nothing.
That means one repository connection can back multiple branches, one branch multiple targets, and
@@ -102,7 +102,7 @@ Set exactly one of `secretRef` and `inCluster`. Both are immutable: delete and r
| `inCluster` | none; set `secretRef` instead | `{}` uses the operator's own identity. Honored only for a provider listed in `--in-cluster-kube-providers`. See [the operator's own identity](#the-operators-own-identity-specincluster) |
| `accessFrom` | **no other namespace** | Other control-cluster namespaces whose objects may use this provider, by name or label selector. Its own namespace is always admitted; an empty `selector: {}` admits every namespace |
| `allowCrossNamespace.sources` | `false` | Whether rules may watch namespaces other than their own, `*` included. See [cross-namespace grants](#what-a-namespace-may-reach-outside-itself-specallowcrossnamespace) |
-| `allowCrossNamespace.appliers` | `false` | Whether triggers may name a `Kustomization` or `Application` in another namespace than their own |
+| `allowCrossNamespace.appliers` | `false` | Whether applier claims may name a `Kustomization` or `Application` in another namespace than their own |
| `client.qps` | `20` | Client rate limit for this source cluster. Refused beside `inCluster` |
| `client.burst` | `30` | Client burst for this source cluster. Refused beside `inCluster` |
| `attribution.auditRoute` | `.` | Audit route this provider joins. A route is held by one namespace. See [audit route](#audit-route-and-auditfactsreceived) |
@@ -124,7 +124,7 @@ The destination fields are immutable: to move a target, delete it and create a n
| `placement.useKustomize` | off | Keep the folder a kustomize folder. See [keeping the folder a kustomize folder](#keeping-the-folder-a-kustomize-folder-specplacementusekustomize) |
| `serializeNamespace` | inferred per document | Whether a committed document carries its own `metadata.namespace`. An explicit `false` admits exactly one source namespace. See [whether documents carry their namespace](#whether-documents-carry-their-namespace-specserializenamespace) |
| `prune.mode` | `OnEvent` | Which deletions reach Git. See [deletion policy](#deletion-policy-specprunemode) |
-| `applyTriggerRef` | the applier applies on its own schedule | The `FluxTrigger` or `ArgoCDTrigger`, in this namespace, holding the applier of this folder, which GitOps Reverser then asks to apply. See [who applies this folder](#who-applies-this-folder-specapplytriggerref) |
+| `applierClaimRef` | the applier applies on its own schedule | The `FluxClaim` or `ArgoCDClaim`, in this namespace, holding the applier of this folder, which GitOps Reverser then asks to apply. See [who applies this folder](#who-applies-this-folder-specapplierclaimref) |
| `onRefusal` | `Ignore` | What happens to an edit with no legal destination. See [reverting a refused edit](#reverting-a-refused-edit-speconrefusal) |
| `encryption` | off | SOPS + age encryption for sensitive resources |
| `suspend` | `false` | Stop writing without deleting the target. See [stopping a target from writing](#stopping-a-target-from-writing-specsuspend) |
@@ -152,23 +152,23 @@ The same shape as a `WatchRule`, for cluster-scoped types. It selects no namespa
| `rules[].apiVersions` | the preferred served version | API versions to match. `["*"]` watches every served version |
| `rules[].objectSelector` | every object | Label selector the API server applies to the item's LIST and WATCH. See [selecting objects by label](#selecting-objects-by-label-rulesobjectselector) |
-### `FluxTrigger` (namespaced): the Flux `Kustomization` GitOps Reverser asks to apply
+### `FluxClaim` (namespaced): the Flux `Kustomization` GitOps Reverser asks to apply
-The references are immutable: to hold another `Kustomization`, delete the trigger and create a new one.
+The references are immutable: to hold another `Kustomization`, delete the applier claim and create a new one.
| Field | Default | What it does |
|---|---|---|
-| `kubeProviderRef` | **required** | The `KubeProvider` whose cluster holds the `Kustomization`: the trigger's own namespace, or `namespace` names one whose `accessFrom` admits it |
-| `kustomizationRef` | **required** | The `Kustomization` that applies the folders of the `GitTarget`s naming this trigger. See [who applies this folder](#who-applies-this-folder-specapplytriggerref) |
+| `kubeProviderRef` | **required** | The `KubeProvider` whose cluster holds the `Kustomization`: the applier claim's own namespace, or `namespace` names one whose `accessFrom` admits it |
+| `kustomizationRef` | **required** | The `Kustomization` that applies the folders of the `GitTarget`s naming this applier claim. See [who applies this folder](#who-applies-this-folder-specapplierclaimref) |
-### `ArgoCDTrigger` (namespaced): the Argo CD `Application` GitOps Reverser asks to sync
+### `ArgoCDClaim` (namespaced): the Argo CD `Application` GitOps Reverser asks to sync
-The references are immutable: to hold another `Application`, delete the trigger and create a new one.
+The references are immutable: to hold another `Application`, delete the applier claim and create a new one.
| Field | Default | What it does |
|---|---|---|
-| `kubeProviderRef` | **required** | The `KubeProvider` whose cluster holds the `Application`: the trigger's own namespace, or `namespace` names one whose `accessFrom` admits it |
-| `applicationRef` | **required** | The `Application`, with automated sync off, that syncs the folders of the `GitTarget`s naming this trigger. See [who applies this folder](#who-applies-this-folder-specapplytriggerref) |
+| `kubeProviderRef` | **required** | The `KubeProvider` whose cluster holds the `Application`: the applier claim's own namespace, or `namespace` names one whose `accessFrom` admits it |
+| `applicationRef` | **required** | The `Application`, with automated sync off, that syncs the folders of the `GitTarget`s naming this applier claim. See [who applies this folder](#who-applies-this-folder-specapplierclaimref) |
### `CommitRequest` (namespaced): save now
@@ -184,25 +184,68 @@ The references are immutable: to hold another `Application`, delete the trigger
-## Why both provider types are namespaced
+## Sharing connections across namespaces
-| Object | Scope | What it represents |
-|---|---|---|
-| `GitProvider` | Namespace | A Git destination and the credentials allowed to write it. A repository is normally one team's write boundary, so the provider and its Secret sit in that team's namespace. |
-| `KubeProvider` | Namespace | One connection to a Kubernetes source cluster and the credential it is read with. The credential is a Secret, so the provider sits beside it in its owner's namespace, and its owner decides who else may use it. |
+A platform team usually owns the repository credential, the cluster connections and the endpoint the
+Git host calls; application teams only decide what to mirror where. The kinds are cut along that
+line. What holds a credential (`GitProvider`, `KubeProvider`, `GitReceiver`, and the `GitBranch`
+that claims a branch with that credential) sits in its owner's namespace with its Secret. What a team
+decides (`GitTarget`, `WatchRule`) sits in the team's namespace, and reaches the shared objects by
+reference.
+
+
-Each admits the objects in its own namespace, and its `spec.accessFrom` admits other namespaces. A
-team can keep its own connection to a remote cluster in its own namespace, and a platform team can
-publish one connection that several namespaces use.
+| Object | How other namespaces use it |
+|---|---|
+| `GitProvider` | Its `spec.accessFrom` admits other namespaces, by name or label selector. A `GitTarget` there writes through a `GitBranch` of the provider's namespace, named with `spec.gitBranchRef.namespace` |
+| `GitBranch` | Lives beside its `GitProvider`, so claiming a branch is an act of the credential's owner. Only one `GitBranch` holds a repository branch; see [who writes a repository branch](#who-writes-a-repository-branch) |
+| `KubeProvider` | Its `spec.accessFrom` admits the `GitTarget`s and applier claims of other control-cluster namespaces. It selects nothing in the source cluster and grants nothing there |
+| `GitReceiver` | Nothing names it, and nothing needs admitting: it serves every `GitProvider`, in any namespace, whose `spec.url` names the repository a push reports |
-Neither has a default. The operator cannot infer a safe repository, branch, or write credential, nor
+```yaml
+# platform: the credential, the branch it claims, and the namespaces it admits
+apiVersion: configbutler.ai/v1alpha3
+kind: GitProvider
+metadata: {name: platform-repo, namespace: platform}
+spec:
+ url: git@github.com:example-org/platform.git
+ secretRef: {name: git-creds}
+ allowedBranches: ["team-a/*", "team-b/*"]
+ accessFrom:
+ names: [team-a, team-b]
+---
+apiVersion: configbutler.ai/v1alpha3
+kind: GitBranch
+metadata: {name: team-a-live, namespace: platform}
+spec:
+ gitProviderRef: {name: platform-repo}
+ branch: team-a/live
+---
+# team-a: what to mirror, and where
+apiVersion: configbutler.ai/v1alpha3
+kind: GitTarget
+metadata: {name: live, namespace: team-a}
+spec:
+ gitBranchRef: {name: team-a-live, namespace: platform}
+ kubeProviderRef: {name: local, namespace: gitops-reverser}
+ path: live-cluster
+```
+
+**Push events route themselves.** The platform team points the Git host's push webhook, or a GitHub
+App's or an organization's single webhook, at one [`GitReceiver`](#gitreceiver). A verified push
+names a repository and a branch, and wakes the branch worker of every `GitProvider` whose
+`spec.url` names that repository, whichever namespace it is in, for that branch only. Nobody wires a
+team to the receiver: a team whose new `GitTarget` writes a new branch gets its push notices from
+the first push on. A team that wants an endpoint of its own creates a `GitReceiver` in its own
+namespace.
+
+Nothing has a default. The operator cannot infer a safe repository, branch, or write credential, nor
which cluster a folder means, so every `GitTarget` names its `GitBranch` and its `KubeProvider`.
Nothing falls back to the provider the chart renders.
-`KubeProvider.spec.accessFrom` decides which control-cluster namespaces may contain `GitTarget`s
-and triggers that reference the provider. It does not select namespaces in the source cluster and
-grants nothing there.
-
## `GitProvider`
`GitProvider` defines the Git remote, credentials, allowed branches, and commit behavior. A
@@ -524,7 +567,7 @@ the audit listener requires. See [push receiver settings](#push-receiver-setting
## `KubeProvider`
`KubeProvider` names the Kubernetes cluster a `GitTarget` mirrors **from**, and the cluster an apply
-trigger reaches its applier in. It is the read-side peer of `GitProvider`: a target has one source
+applier claim reaches its applier in. It is the read-side peer of `GitProvider`: a target has one source
cluster and one Git destination. It is namespaced, and it sets exactly one credential:
- `spec.secretRef`: a Secret in the provider's own namespace holding a kubeconfig for the source
@@ -562,14 +605,14 @@ before it uses it, without dialing the cluster, and reports the verdict as `Vali
| `KubeConfigInsecureTLSNotAllowed` | sets `insecure-skip-tls-verify`. Allowed with `--insecure-kubeconfig-tls` |
| `KubeConfigFileReferenceNotAllowed` | names a token, certificate or key by file path, which the operator would read from its own Pod. Always refused: embed the `*-data` form instead |
-A `KubeProvider` admits the `GitTarget`s and triggers in its own namespace. `accessFrom` admits
+A `KubeProvider` admits the `GitTarget`s and applier claims in its own namespace. `accessFrom` admits
other namespaces, and is evaluated against namespaces in the **control cluster**, where those
objects live. In this example, a `GitTarget` in `platform-connections`, in `team-a`, or in a
control-cluster namespace with the shown label may reference `prod-eu-1`. `names` and `selector` are
ORed, and an omitted policy admits no other namespace. A selector is only as strong as the right to
set the labels it matches: anyone who can label a namespace can admit it, so select on labels only
the platform can set, or use `names`. The check runs when the referrer is
-reconciled, not at admission: a `GitTarget` or trigger the provider does not admit is accepted by
+reconciled, not at admission: a `GitTarget` or applier claim the provider does not admit is accepted by
the API server and reports `NamespaceNotAuthorized`, and it recovers on its own once the provider
admits it.
@@ -612,7 +655,7 @@ uses it, so it is not throttled per provider.
```yaml
allowCrossNamespace: # what an admitted namespace may reach outside itself
sources: false # a WatchRule may watch other namespaces, "*" included
- appliers: false # a trigger may name a Kustomization or Application in another namespace
+ appliers: false # an applier claim may name a Kustomization or Application in another namespace
```
Both default to `false`, and each is a power of its own:
@@ -623,9 +666,9 @@ Both default to `false`, and each is a power of its own:
*choice* of source namespace and grants nothing on its own, since the credential still has to be
able to read what is chosen.
- **`appliers`** lets a namespace *suspend and apply* another namespace's deployments: a
- `FluxTrigger` or `ArgoCDTrigger` may name a `Kustomization` or `Application` outside its own
- namespace, such as `flux-system`. While it is `false`, a trigger naming one elsewhere reports
- `Configured=False` (`ApplierNamespaceNotAllowed`). The trigger's own namespace is always allowed;
+ `FluxClaim` or `ArgoCDClaim` may name a `Kustomization` or `Application` outside its own
+ namespace, such as `flux-system`. While it is `false`, an applier claim naming one elsewhere reports
+ `Configured=False` (`ApplierNamespaceNotAllowed`). The applier claim's own namespace is always allowed;
in a remote cluster that is the namespace of the same name.
**It is a delegation, not owner consent.** You trust every namespace the provider admits, its own
@@ -663,7 +706,7 @@ spec:
**A route is held by one namespace.** Providers in the holding namespace may share it, as above;
a provider in another namespace that carries it reports `Stalled=True` with reason
-`AuditRouteConflict` and is not used, and the GitTargets and triggers naming it are refused with the
+`AuditRouteConflict` and is not used, and the GitTargets and applier claims naming it are refused with the
same reason. Its `Validated` stays what its credential earned. The namespace a route starts with,
the segment before its first dot, holds it when a provider there carries it, so a default route is
always its own provider's. Otherwise the namespace of the oldest provider carrying it holds it
@@ -872,9 +915,9 @@ The important fields are:
omit it and each document's namespace is inferred from the folder
- `spec.prune`: which deletion paths may remove documents from this target's folder (see
[Deletion policy](#deletion-policy-specprunemode)); omit it for the safe default
-- `spec.applyTriggerRef`: the `FluxTrigger` or `ArgoCDTrigger` holding the Flux `Kustomization` or
+- `spec.applierClaimRef`: the `FluxClaim` or `ArgoCDClaim` holding the Flux `Kustomization` or
Argo CD `Application` that applies this folder, so GitOps Reverser decides when it applies (see
- [Who applies this folder](#who-applies-this-folder-specapplytriggerref)); omit it when nothing
+ [Who applies this folder](#who-applies-this-folder-specapplierclaimref)); omit it when nothing
applies the folder back
- `spec.onRefusal`: whether a refused edit asks the applier to reapply Git (see
[Reverting a refused edit](#reverting-a-refused-edit-speconrefusal))
@@ -960,8 +1003,8 @@ The most useful status fields are:
- `status.retention`: how many documents `spec.prune.mode` is keeping, and under which mode.
- `LayoutResolved`: what the last scan resolved about the folder's shape, with
`status.placement` carrying the detail. See below.
-- `ApplyTriggerReady`: true when the named trigger is `Ready` and its applier applies this folder's
- repository and branch. Present only when `spec.applyTriggerRef` is set. The trigger's own
+- `ApplierClaimReady`: true when the named applier claim is `Ready` and its applier applies this folder's
+ repository and branch. Present only when `spec.applierClaimRef` is set. The applier claim's own
`status.operation` says what was last asked of the applier, and why.
Use conditions for automation.
@@ -987,7 +1030,7 @@ summary reason, while the domain conditions name the axis to fix:
| `KubeProviderReady=False` or `SourceClusterReachable=False` | Source-cluster credentials and access. |
| `GitPathAccepted=False` | Repository files under `spec.path`; the message includes the issue kind, path, and actor when known. |
| `RenderMatchesLive=False` | Fields that no longer match the rendered source, often transformer-owned values. |
-| `ApplyTriggerReady=False` | The trigger named in `spec.applyTriggerRef`, and its applier: its source, its automated sync (an `Application`), and the permission to write it. Commits still flow; apply requests may not. |
+| `ApplierClaimReady=False` | The applier claim named in `spec.applierClaimRef`, and its applier: its source, its automated sync (an `Application`), and the permission to write it. Commits still flow; apply requests may not. |
A green `GitTarget` with a red `WatchRule` or `ClusterWatchRule` means the destination may be fine,
but the selected source objects are not being watched or authorized. Inspect the rule's `Ready`,
@@ -1049,7 +1092,7 @@ targets split a burst. Atomic writes, a resync, buffer limits, shutdown, an `att
a push notice from the [receiver](#gitreceiver), and the
[flush](bi-directional.md#flush-then-apply) before an apply can close a window early. Push cooldown
is independent: a zero timer does not promise an immediate remote push. See the
-[complete trigger rules](spec/commit-window-refactor.md).
+[complete applier claim rules](spec/commit-window-refactor.md).
Both values are **Go duration strings with a mandatory unit**, at most `24h`, checked by the API
server: `"750ms"`, `"1.5m"` and `"1m30s"` are accepted, while `"30"`, `".5s"`, `"5 seconds"` and
@@ -1192,7 +1235,7 @@ backlog of the values it passed through.
**Suspend stops writes, not deployments.** A suspended target's applier is still asked to apply a
commit somebody else pushed, because with
-[`spec.applyTriggerRef`](#who-applies-this-folder-specapplytriggerref) set nothing else asks it,
+[`spec.applierClaimRef`](#who-applies-this-folder-specapplierclaimref) set nothing else asks it,
and a paused mirror must not stall Git-side changes. It requests nothing for a refusal, since that
request exists to act on a live edit the target is no longer publishing.
@@ -1369,37 +1412,43 @@ tick". [`gitopsreverser_git_fetches_total{reason="refresh"}`](interpreting-metri
is what it costs.
A refresh that finds a commit GitOps Reverser did not push is a foreign commit like any other: the
-target's [applier](#who-applies-this-folder-specapplytriggerref) is asked to apply it after a flush.
+target's [applier](#who-applies-this-folder-specapplierclaimref) is asked to apply it after a flush.
That makes the refresh the backstop for deployments when no push webhook reaches the
operator. It is not a latency mechanism: it buys bounded staleness with no inbound surface and no
Git-host configuration. Seconds-fresh is the job of the
[push receiver](#gitreceiver).
-### Who applies this folder (`spec.applyTriggerRef`)
+### Who applies this folder (`spec.applierClaimRef`)
Set this only when Flux or Argo CD applies the folder this `GitTarget` writes. A folder nothing
applies, and every folder Flux or Argo CD applies that GitOps Reverser does not write, needs no
-trigger: leave the field out, and GitOps Reverser never touches Flux or Argo CD.
+applier claim: leave the field out, and GitOps Reverser never touches Flux or Argo CD.
+
+
-When the folder is shared, an **apply trigger** holds the object that applies it (its applier), and
-GitOps Reverser decides when it applies: a `FluxTrigger` holds a Flux `Kustomization`, an
-`ArgoCDTrigger` an Argo CD `Application`. The `GitTarget` names the trigger, in its own namespace.
+When the folder is shared, an **applier claim** holds the object that applies it (its applier), and
+GitOps Reverser decides when it applies: a `FluxClaim` holds a Flux `Kustomization`, an
+`ArgoCDClaim` an Argo CD `Application`. Naming one makes the folder
+[coordinated](definitions.md#applying-back). The `GitTarget` names the applier claim, in its own
+namespace.
[Bi-directional GitOps](bi-directional.md) is the model, and the complete setups, with the
lifecycle of an apply, every condition reason and the permissions, are
[bi-directional GitOps with Flux](bi-directional-flux.md) and
[bi-directional GitOps with Argo CD](bi-directional-argocd.md). The reason it has to be this way is
-the [apply trigger design](design/apply-trigger.md).
+the [applier claim design](design/apply-trigger.md).
-**A trigger acts only while an eligible `GitTarget` references it**: one in its namespace that names
-it, is admitted by its `KubeProvider`s, and writes the repository and branch the applier fetches.
-Until one does, the trigger reads and writes nothing in the applier's cluster, and reports
-`Configured=Unknown` and `Ready=True`, both `NotReferenced`, so the two may be created in either
-order. A `FluxTrigger` that held its `Kustomization` hands it back once no eligible `GitTarget`
+**An applier claim acts only while an eligible `GitTarget` references it**: one in its namespace
+that names it, is admitted by its `KubeProvider`s, and writes the repository and branch the applier
+fetches. Until one does, the applier claim reads and writes nothing in the applier's cluster, and
+reports `Configured=Unknown` and `Ready=True`, both `NotReferenced`, so the two may be created in
+either order. An applier claim that held its applier hands it back once no eligible `GitTarget`
references it any more.
```yaml
apiVersion: configbutler.ai/v1alpha3
-kind: FluxTrigger
+kind: FluxClaim
metadata:
name: checkout
namespace: team-a
@@ -1408,7 +1457,7 @@ spec:
name: local
namespace: gitops-reverser
kustomizationRef:
- namespace: flux-system # default: the trigger's namespace
+ namespace: flux-system # default: the applier claim's namespace
name: checkout
---
apiVersion: configbutler.ai/v1alpha3
@@ -1418,40 +1467,44 @@ metadata:
namespace: team-a
spec:
# gitBranchRef, kubeProviderRef, path ...
- applyTriggerRef:
- kind: FluxTrigger # or ArgoCDTrigger
+ applierClaimRef:
+ kind: FluxClaim # or ArgoCDClaim
name: checkout
```
| Field | Default | What it does |
| --- | --- | --- |
-| `FluxTrigger.spec.kubeProviderRef`, `ArgoCDTrigger.spec.kubeProviderRef` | required | The `KubeProvider` whose cluster holds the applier, in the trigger's namespace unless `namespace` names another. Immutable |
-| `FluxTrigger.spec.kustomizationRef` | required | A Flux `Kustomization` whose `sourceRef` is a `GitRepository` that follows a branch. Immutable |
-| `ArgoCDTrigger.spec.applicationRef` | required | An Argo CD `Application` on a single source whose `targetRevision` names a branch. The trigger holds its automated sync off, unless an `ApplicationSet` or another field manager owns the switch and keeps it off. Immutable |
-| `GitTarget.spec.applyTriggerRef` | none | The trigger, by `kind` and `name`, in the `GitTarget`'s namespace |
-
-**One trigger, several folders.** Several `GitTarget`s may name one trigger: their folders are
-applied by the same applier, and it is asked once for all of them. Each one's repository and branch
-must be the ones the applier applies, or that `GitTarget`'s `ApplyTriggerReady` is `False` with
-`SourceMismatch`; one that matches is enough for the trigger to act. One applier is held by one
-trigger: a second trigger of the same kind in line for the same applier in the same cluster reports
-`HeldByAnotherTrigger` and does nothing. A trigger no eligible `GitTarget` references is not in line,
-so it keeps no other out.
-
-**Where the applier is looked up.** In the cluster the trigger's `spec.kubeProviderRef` names,
+| `FluxClaim.spec.kubeProviderRef`, `ArgoCDClaim.spec.kubeProviderRef` | required | The `KubeProvider` whose cluster holds the applier, in the applier claim's namespace unless `namespace` names another. Immutable |
+| `FluxClaim.spec.kustomizationRef` | required | A Flux `Kustomization` whose `sourceRef` is a `GitRepository` that follows a branch. Immutable |
+| `ArgoCDClaim.spec.applicationRef` | required | An Argo CD `Application` on a single source whose `targetRevision` names a branch. The applier claim holds its automated sync off, unless an `ApplicationSet` or another field manager owns the switch and keeps it off. Immutable |
+| `GitTarget.spec.applierClaimRef` | none | The applier claim, by `kind` and `name`, in the `GitTarget`'s namespace |
+
+**One applier claim, several folders.** Several `GitTarget`s may name one applier claim: their
+folders are applied by the same applier, and it is asked once for all of them. Each one's repository
+and branch must be the ones the applier applies, or that `GitTarget`'s `ApplierClaimReady` is
+`False` with `SourceMismatch`; one that matches is enough for the applier claim to act.
+
+**One applier claim holds an applier, per `KubeProvider`.** A second applier claim of the same kind
+in line for the same applier through the same `KubeProvider` reports `HeldByAnotherClaim` and does
+nothing. An applier claim no eligible `GitTarget` references is not in line, so it keeps no other
+out. GitOps Reverser can't tell that two `KubeProvider`s reach the same cluster: two applier claims
+naming one `Kustomization` through two providers would both hold it, each resuming and suspending
+it under the other. Name one `KubeProvider` for every applier claim that reaches a given cluster.
+
+**Where the applier is looked up.** In the cluster the applier claim's `spec.kubeProviderRef` names,
with the identity that [`KubeProvider`](#kubeprovider) uses. Name a provider for a management
cluster when Flux or Argo CD runs there; an `inCluster` provider is the operator's own cluster. That
-provider must admit the trigger's namespace, as for mirroring, or the trigger is `Configured=False`
-with `NamespaceNotAuthorized` (or `KubeProviderNotFound`). An
-applier in another namespace than the trigger's, such as `flux-system` in the example above, also
-needs the provider's [`allowCrossNamespace.appliers`](#what-a-namespace-may-reach-outside-itself-specallowcrossnamespace),
-or the trigger is `Configured=False` with `ApplierNamespaceNotAllowed`. For the operator's own
+provider must admit the applier claim's namespace, as for mirroring, or the applier claim is
+`Configured=False` with `NamespaceNotAuthorized` (or `KubeProviderNotFound`). An applier in another
+namespace than the applier claim's, such as `flux-system` in the example above, also needs the
+provider's [`allowCrossNamespace.appliers`](#what-a-namespace-may-reach-outside-itself-specallowcrossnamespace),
+or the applier claim is `Configured=False` with `ApplierNamespaceNotAllowed`. For the operator's own
cluster, the chart grants the applier permissions with `applierAccess.flux: true` and
`applierAccess.argocd: true`.
```yaml
apiVersion: configbutler.ai/v1alpha3
-kind: FluxTrigger
+kind: FluxClaim
metadata:
name: prod-eu-1-apps
namespace: team-a
@@ -1466,11 +1519,10 @@ spec:
**When a request is made.** After a [flush](bi-directional.md#flush-then-apply): when an apply comes
due, the branch worker closes the open commit window, publishes the writes decided up to that
moment at once, and asks for the tip that publication produced. An edit decided after that is
-recorded in the apply window, so an apply that writes over it is withheld from Git and repaired. The
-branch workers of other `GitProvider`s writing the same repository and branch flush too, and the
-request waits until they have. A registered `CommitRequest` (a save sets its own commit boundary), a
+recorded in the apply window, so an apply that writes over it is withheld from Git and repaired. A
+registered `CommitRequest` (a save sets its own commit boundary), a
parent branch being recovered, or a checkout a failed write left dirty holds the request back. No
-applier is asked for anything until every initial snapshot of every `GitTarget` naming its trigger
+applier is asked for anything until every initial snapshot of every `GitTarget` naming its applier claim
is published, and nothing is asked while the remote does not carry the branch. The rule is the same
for a `Kustomization` and an `Application`.
@@ -1479,7 +1531,7 @@ for a `Kustomization` and an `Application`.
| `ForeignCommit` | The branch gained a commit GitOps Reverser did not push. The pending writes are replayed onto it and pushed first, so the request names that pushed commit | Every applier declared on the branch, once per applier |
| `Refusal` | An eligible refused edit, with `spec.onRefusal: Reapply` | This target's applier |
| `Repair` | An apply reverted an API edit made after the flush; Git kept the edit | That applier |
-| `CatchUp` | To adopt: the API created an object in the folder, or a resync added an object to Git. To converge: once when the operator starts, and once when a `GitTarget` first names the trigger | To adopt, that folder's applier; to converge, every applier declared on the branch at start, and the newly named one |
+| `CatchUp` | To adopt: the API created an object in the folder, or a resync added an object to Git. To converge: once when the operator starts, and once when a `GitTarget` first names the applier claim | To adopt, that folder's applier; to converge, every applier declared on the branch at start, and the newly named one |
An API edit asks for no apply, and neither does a push of GitOps Reverser's own: the cluster already
runs what they hold. A create is different, because an applier prunes only what it applied: an
@@ -1489,29 +1541,29 @@ file would leave it running. An adopt is therefore applied even at the commit th
applied, since that apply is what stamps the object. A converge asks nothing when the applier's last
successful operation, as `status.operation` records it, applied the tip.
-**An object another applier owns is not mirrored into the folder.** When a `GitTarget` names a
-trigger, an object whose Flux owner labels (`kustomize.toolkit.fluxcd.io/name` and
+**An object another applier owns is not mirrored into the folder.** When a `GitTarget` names an
+applier claim, an object whose Flux owner labels (`kustomize.toolkit.fluxcd.io/name` and
`kustomize.toolkit.fluxcd.io/namespace`) or Argo CD tracking annotation
-(`argocd.argoproj.io/tracking-id`) name an applier other than the trigger's is excluded: a live
+(`argocd.argoproj.io/tracking-id`) name an applier other than the applier claim's is excluded: a live
change to it is not mirrored, and a resync neither writes it nor removes its document, even under
`prune.mode: Always`. Two appliers applying one object fight each other. Each excluded object is
logged once and counted in `gitopsreverser_apply_ownership_conflicts_total`. A `GitTarget` without
-`spec.applyTriggerRef` excludes nothing: mirroring another applier's objects one way is what such a
+`spec.applierClaimRef` excludes nothing: mirroring another applier's objects one way is what such a
folder can exist for.
-**What it reports.** The trigger reports its applier and the operation it is running. The branch
+**What it reports.** The applier claim reports its applier and the operation it is running. The branch
worker writes `status.operation` and `status.repairPending` at every move of the operation, so they are
current without anything reconciling:
```yaml
-kind: FluxTrigger
+kind: FluxClaim
status:
conditions:
- type: Configured
status: "True"
reason: Succeeded
message: Kustomization flux-system/checkout applies GitRepository flux-system/platform, ...
- - type: Held # FluxTrigger only
+ - type: Held # FluxClaim only
status: "True"
reason: Suspended # Suspended | Applying | Suspending | Releasing | NotReferenced | NotConfigured
- type: Ready
@@ -1520,7 +1572,7 @@ status:
gitRepository: {namespace: flux-system, name: platform}
url: https://github.com/acme/platform
branch: main
- otherConsumers: # what else applies this GitRepository, held by no FluxTrigger
+ otherConsumers: # what else applies this GitRepository, held by no FluxClaim
- {kind: HelmRelease, namespace: platform, name: ingress}
operation:
phase: Succeeded # Requested | Running | Settling | Unresolved | Succeeded | Failed
@@ -1541,43 +1593,43 @@ status:
| `operation.suspendToken` | Flux only: the token of the suspend after the apply. Once it is handled, nothing that started before the suspend is still running |
| `operation.appliedRevision` | The revision the applier reports it applied. It can differ from `commit` when the branch moved between the check and the apply |
| `repairPending` | `true` while an apply reverted an API edit made after its flush: Git kept the edit, and the cluster runs the older value until the `Repair` |
-| `source.otherConsumers` | Flux only: the `Kustomization`s and `HelmRelease`s that apply the same `GitRepository` and that no `FluxTrigger` holds. Information only; it never affects `Ready` |
+| `source.otherConsumers` | Flux only: the `Kustomization`s and `HelmRelease`s that apply the same `GitRepository` and that no `FluxClaim` holds. Information only; it never affects `Ready` |
A `Kustomization` resumed outside GitOps Reverser (`flux resume`) is suspended again and recorded as
an `Unresolved` operation with no `reason` and no `commit`, which completes once the suspend is
handled.
-**Conditions.** `Configured` is a gate, and the trigger's `Ready` aggregates it; a `FluxTrigger` is
+**Conditions.** `Configured` is a gate, and the applier claim's `Ready` aggregates it; a `FluxClaim` is
also `Ready` only once `Held` is `True` (`Suspended`, or `Applying` while an apply of GitOps
Reverser's is in flight). Either kind is `Ready=False` (`OperationUnresolved`) while its operation
-is `Unresolved`. An unreferenced trigger is `Ready=True` (`NotReferenced`). The
-`GitTarget`s naming the trigger are reported as `NotBound` (`Unknown` while one cannot be judged
+is `Unresolved`. An unreferenced applier claim is `Ready=True` (`NotReferenced`). The
+`GitTarget`s naming the applier claim are reported as `NotBound` (`Unknown` while one cannot be judged
yet, `False` once each is refused for different reasons), or as the refusal they share,
`SourceMismatch` or `NamespaceNotAuthorized`, every `GitTarget` and its reason in the message. The
other `False` reasons are `ApplierNotFound`, `UnsupportedSource`, `ArgoCDVersionNotSupported`,
`OwnedByApplicationSet` and `AutomatedSyncOwnedElsewhere` (Argo CD), `SuspendOwnedElsewhere` (Flux),
-`Forbidden`, `NamespaceNotAuthorized` (the trigger's own namespace),
-`KubeProviderNotFound`, `AuditRouteConflict`, `ApplierNamespaceNotAllowed` and `HeldByAnotherTrigger`; each guide's
+`Forbidden`, `NamespaceNotAuthorized` (the applier claim's own namespace),
+`KubeProviderNotFound`, `AuditRouteConflict`, `ApplierNamespaceNotAllowed` and `HeldByAnotherClaim`; each guide's
table says what they mean for its applier and how to fix them
-([Flux](bi-directional-flux.md#the-fluxtrigger), [Argo CD](bi-directional-argocd.md#the-argocdtrigger)).
-The `GitTarget` reports `ApplyTriggerReady`, and its `Ready` aggregates it: `True` when the trigger
-is `Ready` and its applier applies this target's repository and branch, otherwise the trigger's own
-`Ready` reason, `ApplyTriggerNotFound`, `SourceMismatch`, or `Unknown` (`NotReferenced`) until the
-trigger has seen that the `GitTarget` names it. Each delivered request records an
+([Flux](bi-directional-flux.md#the-fluxclaim), [Argo CD](bi-directional-argocd.md#the-argocdclaim)).
+The `GitTarget` reports `ApplierClaimReady`, and its `Ready` aggregates it: `True` when the applier claim
+is `Ready` and its applier applies this target's repository and branch, otherwise the applier claim's own
+`Ready` reason, `ApplierClaimNotFound`, `SourceMismatch`, or `Unknown` (`NotReferenced`) until the
+applier claim has seen that the `GitTarget` names it. Each delivered request records an
`ApplyRequested` Event on the `GitTarget`, and one that could not be delivered records
`ApplyRequestFailed` once.
-The applier is read on the trigger's own reconcile tick rather than watched, so a cluster without
+The applier is read on the applier claim's own reconcile tick rather than watched, so a cluster without
the Flux or Argo CD CRDs costs nothing: every 15 seconds while it is expected to clear on its own,
-such as an applier created after its trigger, and otherwise every minute for a `FluxTrigger` (that
-read is what finds a `flux resume`) and every 5 minutes for an `ArgoCDTrigger`. A `FluxTrigger` that
-is `Suspending` is read every 2 seconds. Annotate the trigger with
+such as an applier created after its applier claim, and otherwise every minute for a `FluxClaim` (that
+read is what finds a `flux resume`) and every 5 minutes for an `ArgoCDClaim`. A `FluxClaim` that
+is `Suspending` is read every 2 seconds. Annotate the applier claim with
`reconcile.configbutler.ai/requestedAt` to have it read now.
-**Deleting a `FluxTrigger` hands the `Kustomization` back to Flux,** through the finalizer
-`configbutler.ai/flux-trigger`, unless another `FluxTrigger` is in line for it, and so does the last
-eligible `GitTarget` that stops naming it. A trigger that never held its `Kustomization` carries no
-finalizer. The [Flux guide](bi-directional-flux.md#how-the-trigger-holds-the-kustomization) has the
+**Deleting a `FluxClaim` hands the `Kustomization` back to Flux,** through the finalizer
+`configbutler.ai/flux-claim`, unless another `FluxClaim` is in line for it, and so does the last
+eligible `GitTarget` that stops naming it. An applier claim that never held its `Kustomization` carries no
+finalizer. The [Flux guide](bi-directional-flux.md#how-the-applier-claim-holds-the-kustomization) has the
details.
### Reverting a refused edit (`spec.onRefusal`)
@@ -1589,21 +1641,21 @@ something applies Git over it. `spec.onRefusal` decides whether GitOps Reverser
```yaml
spec:
path: apps/checkout
- applyTriggerRef: {kind: FluxTrigger, name: checkout}
+ applierClaimRef: {kind: FluxClaim, name: checkout}
onRefusal: Reapply # default: Ignore
```
| Value | What happens |
| --- | --- |
| `Ignore` (default) | The refusal is reported and nothing else happens |
-| `Reapply` | After a flush, this target's [applier](#who-applies-this-folder-specapplytriggerref) is asked to apply the current tip, with reason `Refusal`. The refused edit is reverted when the applier applies it; the request asks, and the applier's own status says whether it did |
+| `Reapply` | After a flush, this target's [applier](#who-applies-this-folder-specapplierclaimref) is asked to apply the current tip, with reason `Refusal`. The refused edit is reverted when the applier applies it; the request asks, and the applier's own status says whether it did |
-`Reapply` requires `spec.applyTriggerRef`; a target without one is rejected at admission. The request goes
+`Reapply` requires `spec.applierClaimRef`; a target without one is rejected at admission. The request goes
to this target's applier alone, and it goes out after a flush, so an allowed edit still in a
commit window is published before the apply and is not reverted with the refused one. An
`Application` is asked to sync the tip. When a `Kustomization`'s `GitRepository` has to fetch to
-reach the tip, the `Kustomization`s a `FluxTrigger` holds stay suspended and ignore the new artifact;
-only the consumers no trigger holds, listed in the trigger's `status.source.otherConsumers`, apply
+reach the tip, the `Kustomization`s a `FluxClaim` holds stay suspended and ignore the new artifact;
+only the consumers no applier claim holds, listed in the applier claim's `status.source.otherConsumers`, apply
it, as they would for any push. The
[bi-directional guide](bi-directional.md#choosing-speconrefusal) covers when to choose it.
@@ -1630,7 +1682,7 @@ that arrive while a request is outstanding share the next one rather than each m
**A refusal nobody corrects is requested once, not once per recheck.** The target keeps re-reading
its folder and keeps refusing, and an observation of the same objects with the same issues is
already covered by the request made for it. Anything different (a new value, another object, a
-different refusal) is a new trigger. So is the same edit made a second time after the applier
+different refusal) is a new request. So is the same edit made a second time after the applier
reverted it, because a write of that resource type was accepted in between.
Reapplying does not clear the refusal condition: recovery still requires a successful resync
diff --git a/docs/definitions.md b/docs/definitions.md
index ea1b2ea39..1041bd0f9 100644
--- a/docs/definitions.md
+++ b/docs/definitions.md
@@ -43,10 +43,14 @@ vocabulary follows.
**Control cluster.** The cluster the operator runs in, and where its own ten kinds live. When a
document says "a namespace" with no qualifier, it means a control-cluster namespace.
-**Source cluster.** The cluster whose objects are mirrored into Git. A `KubeProvider` names one, by
-its namespace and name. It is the same cluster as the control cluster on a single-cluster install,
+**Source cluster.** The cluster whose objects are mirrored into Git. A `GitTarget` names it through
+its `KubeProvider`. It is the same cluster as the control cluster on a single-cluster install,
which is why the two words have to stay distinct even when they point at one machine:
-`KubeProvider.spec.inCluster` means "the control cluster, acting as a source".
+`KubeProvider.spec.inCluster` means "the control cluster, reached with the operator's own
+identity".
+
+**KubeProvider.** A connection to one cluster and the identity used there; a connection, not a role.
+The same provider can be a `GitTarget`'s source cluster and an applier claim's management cluster.
**Mirror.** The projection of source-cluster objects into a Git folder. It is a noun for the result
and a verb for the act. The operator mirrors; it does not "sync", "export" or "back up", and those
@@ -58,7 +62,7 @@ folder has exactly one `GitTarget`, and one `GitTarget` has exactly one folder.
The two directions, which share the word "source" and must not share anything else:
- **Access** is the control-cluster direction: which control-cluster namespaces may reference a
- provider (`KubeProvider` or `GitProvider`) from a `GitTarget` or trigger. A provider's own
+ provider (`KubeProvider` or `GitProvider`) from a `GitTarget` or applier claim. A provider's own
namespace always may; `spec.accessFrom` admits the others (the spelling is Flux's, on purpose).
- **Source namespace** is the source-cluster direction: which source-cluster namespace a rule reads
objects from. `WatchRule.spec.rules[].sourceNamespace`, gated by
@@ -137,6 +141,8 @@ lowercase in prose, `BranchWorker` as the Go type.
a push session on the remote. Only the holder gets a branch worker; another `GitBranch` for the same
repository branch reports `BranchConflict`. Say "branch claim", or that a `GitBranch` "holds" its
branch; never a bare "claim", which names a rule's demand on the read side. `BranchClaims` in Go.
+The **applier claim** is the cluster side of the same idea: one holds an applier, and another
+reports `HeldByAnotherClaim`.
**Write branch.** The branch a `GitTarget` writes to: its `GitBranch`'s `spec.branch`. It must be in
the `GitProvider`'s `allowedBranches`.
@@ -202,34 +208,55 @@ and the two are not interchangeable.
## Applying back
**Applier.** The object that applies a folder to its source cluster: a Flux `Kustomization` or an
-Argo CD `Application`. An apply trigger names it. Not
-"reconciler", which also names this operator's own controllers, and not "orchestrator", which says
-nothing about what it does to the cluster.
-
-**Apply trigger.** This operator's resource that holds one applier and starts every apply it makes: a
-`FluxTrigger` (`spec.kustomizationRef`) or an `ArgoCDTrigger` (`spec.applicationRef`). A
-`GitTarget` names one with `spec.applyTriggerRef` and reports it in its `ApplyTriggerReady`
-condition. The trigger is never called the applier: the applier is the object it triggers. Not
-"Argo trigger", which names a trigger of Argo Events.
-
-**Eligible.** A `GitTarget` is eligible for the trigger it names when it is in the trigger's
-namespace, not being deleted, admitted by its `KubeProvider`s, and writes the repository and
-branch the applier fetches. A trigger acts only while an eligible `GitTarget` references it; one
-nothing references is `NotReferenced`. Eligibility is judged from those inputs alone, never from
-the `GitTarget`'s `Ready`. "Bound" names a trigger with at least one eligible `GitTarget`.
-
-**In line.** The triggers that may hold one applier: those an eligible `GitTarget` references, and a
-`FluxTrigger` that holds the `Kustomization` or is handing it back. One of them holds it; the
-others report `HeldByAnotherTrigger`.
-
-**Held.** How a `FluxTrigger` reports its `Kustomization`, from the trigger's side: `Suspended`
-(suspended under GitOps Reverser's field manager, the last suspend handled, nothing applies),
-`Applying` (resumed for an apply GitOps Reverser started), `Suspending` (a suspend was sent, and
-Flux has not handled its token yet), `Releasing` (being handed back to Flux), `NotReferenced`
-(nothing to hold) and `NotConfigured`. A suspend counts as done only once it is handled: by then no
+Argo CD `Application`. An applier claim names it. Not "reconciler", which also names this
+operator's own controllers, and not "orchestrator", which says nothing about what it does to the
+cluster.
+
+**Applier claim.** This operator's resource that declares GitOps Reverser should take and retain
+control of when one applier applies: a `FluxClaim` (`spec.kustomizationRef`) or an `ArgoCDClaim`
+(`spec.applicationRef`), each with the `KubeProvider` that reaches it. That control covers apply
+timing and nothing else: the applier still fetches, renders, applies, prunes and reports health.
+The applier claim does not say when to apply either; the branch worker decides that. Among applier
+claims that reach an applier through the same `KubeProvider`, one holds it and the others report
+`HeldByAnotherClaim`; applier claims made through two `KubeProvider`s for one cluster are not
+arbitrated (see **In line**). Whether an applier claim holds its applier is **Held**, not
+**Bound**. While it holds a `Kustomization`, a `FluxClaim` keeps it suspended and resumes it only
+for an apply GitOps Reverser asks for. While it holds an `Application`, an `ArgoCDClaim` keeps
+automated sync off and starts each sync itself. Deleting either hands the applier back as
+`status.previousSuspendState` recorded it. A `GitTarget` names one with `spec.applierClaimRef` and
+reports it in its `ApplierClaimReady` condition. Write "applier claim" or the kind name, never a
+bare "claim" (see **Branch claim**). The applier claim is never called the applier: the applier is
+the object it claims.
+
+**Coordinated.** A folder configured for GitOps Reverser to decide when its applier applies.
+Naming an applier claim makes a `GitTarget`'s folder coordinated. The word also describes the
+`GitTarget` that writes it. It describes the setup; `ApplierClaimReady` reports whether the applier
+claim is ready. A shared folder applied without that coordination is **uncoordinated**: the applier
+keeps its own schedule and can revert live edits before they reach Git.
+
+**Eligible.** A `GitTarget` is eligible for the applier claim it names when it is in the applier
+claim's namespace, not being deleted, admitted by its `KubeProvider`s, and writes the repository
+and branch the applier fetches. An applier claim acts only while an eligible `GitTarget` references
+it; one nothing references is `NotReferenced`. Eligibility is judged from those inputs alone, never
+from the `GitTarget`'s `Ready`. **Bound** names an applier claim with at least one eligible
+`GitTarget`; it says nothing about whether the applier claim holds its applier, which is **Held**.
+
+**In line.** The applier claims that may hold one applier: those an eligible `GitTarget`
+references, and one that holds the applier or is handing it back. One of them holds it; the others
+report `HeldByAnotherClaim`. Applier claims are in line together only when they reach the applier
+through the same `KubeProvider`. GitOps Reverser cannot tell that two `KubeProvider`s reach the same
+cluster, so name one `KubeProvider` for every applier claim that reaches a given cluster.
+
+**Held.** How an applier claim reports its applier, from its own side. For a `FluxClaim`:
+`Suspended` (suspended under GitOps Reverser's field manager, the last suspend handled, nothing
+applies), `Applying` (resumed for an apply GitOps Reverser started), `Suspending` (a suspend was
+sent, and Flux has not handled its token yet), `Releasing` (being handed back to Flux). For an
+`ArgoCDClaim`: `AutomatedSyncOff` (held off by GitOps Reverser, or kept off by whoever owns the
+switch) and `Releasing`. Either may be `NotReferenced` (nothing to hold) or `NotConfigured`. A
+suspend counts as done only once it is handled: by then no
reconcile that started before it is still running. Not "fenced", and not "resumed", which says what
-happens to the `Kustomization` rather than what the trigger does. **Hand back** is releasing
-`spec.suspend`, so Flux applies on its own again.
+happens to the `Kustomization` rather than what the applier claim does. **Hand back** is releasing
+`spec.suspend`, or switching automated sync back, so the applier applies on its own again.
**Apply request.** What GitOps Reverser sends an applier: apply this commit, for this reason
(`ForeignCommit`, `Refusal`, `Repair`, `CatchUp`; a `CatchUp` adopts or converges, both defined
@@ -237,7 +264,7 @@ below). It asks; the operation it starts is followed until the applier's status
completed. A reason is **pending** for an applier from
the moment the branch worker decides it until an attempt **carries** it; an operation that
succeeds **answers** it, and one that does not gives it back to pending. A pending `Repair` shows
-on the trigger as `status.repairPending`.
+on the applier claim as `status.repairPending`.
**Flush.** What a branch worker does before it sends an apply request: it closes the open commit
window, takes the **flush mark** (the last write decided so far), and publishes every write up to
@@ -258,13 +285,13 @@ folder. An applier prunes only what it applied, and an apply is what stamps an o
Flux's owner labels and inventory, Argo CD's tracking annotation. So an object nobody applied stays
on the cluster when Git later deletes its file. An adopt is pending once the branch worker observes
an API create in a folder an applier applies, or a resync adds an object to Git, and it is never
-skipped as already applied. A trigger's `Adopted` Event names something else, the trigger taking
+skipped as already applied. An applier claim's `Adopted` Event names something else, the applier claim taking
hold of its `Kustomization`'s `spec.suspend`.
**Converge.** A `CatchUp` that applies the tip because nothing says the applier runs it: once when
-the branch worker starts, and once when a `GitTarget` first names the trigger (per incarnation, by
+the branch worker starts, and once when a `GitTarget` first names the applier claim (per incarnation, by
uid). The worker keeps, per applier, the commit its last succeeded operation applied, recovered on
-restart from the trigger's `status.operation`, and a converge of that commit asks nothing.
+restart from the applier claim's `status.operation`, and a converge of that commit asks nothing.
**Apply epoch.** A counter on the branch worker, raised at every send of an apply attempt and at
every end of one. When a snapshot's collection starts, the watch side reads it, with whether **Git
@@ -492,5 +519,6 @@ outage.
| namespace access (bare) | access, or source namespace | two directions, two names |
| reconciler, orchestrator (for Flux or Argo CD) | applier | "reconciler" also names our own controllers |
| owed, owes, debt, obligation | pending, decided, sent, waiting for | an accounting word names no state: say what is pending, for whom, and what ends it |
-| fence, fenced (for what a trigger does to its applier) | suspend, held | the `Kustomization` is suspended, and the trigger holds it; say which, and whether the suspend is handled |
+| fence, fenced (for what an applier claim does to its applier) | suspend, held | the `Kustomization` is suspended, and the applier claim holds it; say which, and whether the suspend is handled |
+| trigger, apply trigger (for an applier claim) | applier claim | the resource does not declare when to apply, and "trigger" already names watch-plan, API-surface and Argo Events triggers |
| quiet branch | flush | no apply waits for a branch with nothing pending; it goes after a flush |
diff --git a/docs/design/applier-echoes.md b/docs/design/applier-echoes.md
index 6c83256b0..1ae72e9a4 100644
--- a/docs/design/applier-echoes.md
+++ b/docs/design/applier-echoes.md
@@ -477,14 +477,14 @@ Read from the code at `47b171b9`.
| Construct | Where | Why it can go |
|---|---|---|
-| `branchQuiet` | [`apply_trigger.go`](../../internal/git/apply_trigger.go) | the quiet branch is the wait S1 replaces with the flush |
+| `branchQuiet` | [`apply_trigger.go`](../../internal/git/applier_claim.go) | the quiet branch is the wait S1 replaces with the flush |
| `workWaiting` | `apply_trigger.go` | queued work items, here or on a sibling, no longer hold an apply |
| The "nothing pending" half of `decisionHolds` (`busyWriting`, `inflightItems`, `siblingBusy`) | `apply_trigger.go` | `ApplyRequest.Current` checks only that the remote is still at the commit asked for |
| `branchSiblings.othersBusy`, `BranchWorker.siblingBusy`, `publishWriteBusy`, the `busyWriting` atomic, and the `wakeOthers` calls for quietness | `apply_siblings.go` (since removed), [`branch_worker.go`](../../internal/git/branch_worker.go) | quietness across siblings existed only for the quiet branch; the claims for source groups stay |
| `stashDeferredHeal`, `applyDeferredHeals`, `deferredHeals` | [`resync_flush.go`](../../internal/git/resync_flush.go), `branch_worker.go` | no snapshot is parked anymore (F3) |
| The re-run of the mark-and-sweep on replay | `resync_flush.go` (`applyResyncToWorktree`), [`commit_executor.go`](../../internal/git/commit_executor.go) | a replayed resync applies its decided actions (F2) |
| `ApplierRef.CatchesUp` | [`apply_request.go`](../../internal/git/apply_request.go) | both appliers catch up (S2) |
-| `FluxTrigger.spec.quietPeriod`, `EffectiveQuietPeriod` on both trigger types and the `ApplyTrigger` interface, `DefaultApplierQuietPeriod` | [`api/v1alpha3`](../../api/v1alpha3/applytrigger_types.go) | nothing waits for time to pass before a `CatchUp`; the CRDs are regenerated |
+| `FluxTrigger.spec.quietPeriod`, `EffectiveQuietPeriod` on both trigger types and the `ApplyTrigger` interface, `DefaultApplierQuietPeriod` | [`api/v1alpha3`](../../api/v1alpha3/applierclaim_types.go) | nothing waits for time to pass before a `CatchUp`; the CRDs are regenerated |
| `branchTarget.quietPeriod`, `applierState.quietPeriod`, `quietSince`, the quiet term of `dueAt`, the reset of `quietSince` in `decide` | `apply_trigger.go`, `branch_worker.go`, [`pending_writes_loop.go`](../../internal/git/pending_writes_loop.go) | they only timed the quiet period |
| The `OutcomeCurrent` of a `ForeignCommit` or `CatchUp` for a suspended `Kustomization` already at the commit | [`internal/applier/flux.go`](../../internal/applier/flux.go) | the worker decides from what each applier last applied |
| The `OutcomeCurrent` for an empty commit, with the stamp of the source | `flux.go`, [`internal/applier/argocd.go`](../../internal/applier/argocd.go) | the worker sends nothing for an empty tip |
@@ -547,7 +547,7 @@ queue-depth gauge.
[`../configuration.md`](../configuration.md) and [`../UPGRADING.md`](../UPGRADING.md): "on a quiet
branch" becomes "after a flush", `quietPeriod` goes, adopt and converge are described for both
appliers, and F4's ownership rule is stated where a triggered folder is set up.
-- [`config/samples/fluxtrigger.yaml`](../../config/samples/fluxtrigger.yaml), the field comment in
+- [`config/samples/fluxtrigger.yaml`](../../config/samples/fluxclaim.yaml), the field comment in
[`gittarget_types.go`](../../api/v1alpha3/gittarget_types.go), and the comments in
[`internal/telemetry/apply_requests.go`](../../internal/telemetry/apply_requests.go) and
[`internal/telemetry/apply_window.go`](../../internal/telemetry/apply_window.go).
@@ -685,7 +685,7 @@ The build departs from the plan above in twelve places, decided on 2026-10-20:
- [`internal/git/apply_window.go`](../../internal/git/apply_window.go): `withholdLateApply`.
- [`internal/git/commit_executor.go`](../../internal/git/commit_executor.go): planning a write.
- [`internal/git/resync_flush.go`](../../internal/git/resync_flush.go): the resync.
-- [`internal/git/apply_trigger.go`](../../internal/git/apply_trigger.go): the flush and `CatchUp`.
+- [`internal/git/apply_trigger.go`](../../internal/git/applier_claim.go): the flush and `CatchUp`.
- [`internal/git/apply_epoch.go`](../../internal/git/apply_epoch.go): stale snapshots (F3).
- [`internal/git/apply_ownership.go`](../../internal/git/apply_ownership.go): another applier's
objects (F4), and adopt.
diff --git a/docs/design/apply-trigger.md b/docs/design/apply-trigger.md
index b9fd00f55..f208f6357 100644
--- a/docs/design/apply-trigger.md
+++ b/docs/design/apply-trigger.md
@@ -1,5 +1,8 @@
# The apply trigger
+**Renamed.** The apply trigger kinds are now applier claims: `FluxClaim` and `ArgoCDClaim`, named
+from a `GitTarget` with `spec.applierClaimRef`. This page keeps the names it was written with. See [applier claim](../definitions.md#applying-back).
+
> **design**: decided 2026-10-16, extended 2026-10-17, 2026-10-19, 2026-10-20 and 2026-10-23; built:
> the Flux applier (its `Kustomization` held suspended), the Argo CD applier (its automated sync
> held off), the
@@ -12,7 +15,7 @@
> How to use it: [`../bi-directional.md`](../bi-directional.md),
> [`../bi-directional-flux.md`](../bi-directional-flux.md) and
> [`../bi-directional-argocd.md`](../bi-directional-argocd.md).
-> Fields: [`../configuration.md`](../configuration.md#who-applies-this-folder-specapplytriggerref).
+> Fields: [`../configuration.md`](../configuration.md#who-applies-this-folder-specapplierclaimref).
**GitOps Reverser decides when Git is applied to the cluster, and the applier never applies on its
own.** Flux and Argo CD keep fetching, rendering, applying, pruning and reporting health. A Flux
@@ -731,7 +734,7 @@ server-side apply under `gitops-reverser`, touching nothing else of `automated`,
again when somebody switches it on (`ClaimedResourceReconciled`), and on hand-back gives the switch
up, which leaves the `Application` as its own spec says, unless that is not how it stood before
(`status.previousSuspendState`) or how an `ApplicationSet` that adopted it since set it (measured in envtest:
-[`argocdtrigger_apiserver_test.go`](../../internal/controller/argocdtrigger_apiserver_test.go)). So
+[`argocdtrigger_apiserver_test.go`](../../internal/controller/argocdclaim_apiserver_test.go)). So
its timed refresh and its webhook only compare, and every sync it runs is one GitOps Reverser asked
for, pinned to a commit the branch worker checked. A switch an `ApplicationSet` or another field
manager owns is never written, only checked: such an `Application` with automated sync on is
@@ -855,7 +858,7 @@ adopted by one apply.
| Claim | Test |
| --- | --- |
-| The decision: the baseline, the reasons, a `ForeignCommit` asking every applier and answering everything else pending, nothing sent while no flush can be made, a push notice closing the window | [`apply_trigger_test.go`](../../internal/git/apply_trigger_test.go) |
+| The decision: the baseline, the reasons, a `ForeignCommit` asking every applier and answering everything else pending, nothing sent while no flush can be made, a push notice closing the window | [`apply_trigger_test.go`](../../internal/git/applier_claim_test.go) |
| `CatchUp`: the startup converge asks nothing of an applier at the tip, an API create is adopted even at the tip, a first naming converges once, nothing is asked before the initial snapshots are published, an empty tip asks nothing | [`apply_catchup_test.go`](../../internal/git/apply_catchup_test.go) |
| A snapshot collected across an apply, or arriving while Git is ahead, is discarded; initial snapshots are exempt; a branch with no trigger never waits; snapshots are held no longer than the limit | [`apply_epoch_test.go`](../../internal/git/apply_epoch_test.go) |
| Another applier's object is not mirrored from a live write, and a snapshot of it is neither upserted nor swept | [`apply_ownership_test.go`](../../internal/git/apply_ownership_test.go) |
@@ -863,14 +866,14 @@ adopted by one apply.
| The operation is recorded before the write that starts it, moves through its phases on the trigger's status, an attempt that asked nothing restores the record before it, and a withheld rollback makes a `Repair` due | [`apply_operation_test.go`](../../internal/git/apply_operation_test.go), [`status_writer_test.go`](../../internal/applier/status_writer_test.go) |
| Every attempt rechecks targets, asks for nothing weaker than it was told, and resumes no `Kustomization` on another branch; a stop mid-apply suspends the `Kustomization` | [`dispatcher_test.go`](../../internal/applier/dispatcher_test.go), [`flux_test.go`](../../internal/applier/flux_test.go) |
| The suspended lifecycle against the source facts: an apply is not finished when attempted; a token handled while suspended is no apply; a resume alone reverts a refusal; a suspend does not stop a running apply; the artifact is rechecked before the resume; a failed apply is suspended and Flux's requeue applies nothing; a shared source applies only the consumer asked | [`flux_lifecycle_test.go`](../../internal/applier/flux_lifecycle_test.go), [`flux_consumers_test.go`](../../internal/applier/flux_consumers_test.go), [`fluxsim`](../../internal/applier/fluxsim/fluxsim_test.go) |
-| Adoption, `SuspendOwnedElsewhere`, an external resume suspended again with an Event every time, the hand-back on deletion (a `ClusterProvider` gone included), and the source's other consumers | [`fluxtrigger_hold_test.go`](../../internal/controller/fluxtrigger_hold_test.go) |
-| The rule: a trigger nothing names reads nothing and is `Ready`; the first eligible reference adopts and the last releases, suspending an apply in flight first; a reference returning mid-release holds again; a re-created `GitTarget` releases and re-adopts; the binding over several `GitTarget`s; an older trigger not in line keeps none out, and a retiring holder stays first until its release is done; a holder left unconfigured says the `Kustomization` is suspended; `ApplierNamespaceNotAllowed` for both kinds | [`applytrigger_rule_test.go`](../../internal/controller/applytrigger_rule_test.go) |
+| Adoption, `SuspendOwnedElsewhere`, an external resume suspended again with an Event every time, the hand-back on deletion (a `ClusterProvider` gone included), and the source's other consumers | [`fluxtrigger_hold_test.go`](../../internal/controller/fluxclaim_hold_test.go) |
+| The rule: a trigger nothing names reads nothing and is `Ready`; the first eligible reference adopts and the last releases, suspending an apply in flight first; a reference returning mid-release holds again; a re-created `GitTarget` releases and re-adopts; the binding over several `GitTarget`s; an older trigger not in line keeps none out, and a retiring holder stays first until its release is done; a holder left unconfigured says the `Kustomization` is suspended; `ApplierNamespaceNotAllowed` for both kinds | [`applytrigger_rule_test.go`](../../internal/controller/applierclaim_rule_test.go) |
| `allowAnySourceNamespace: true` is refused on create and on any spec change, a stored one included; `false` and its removal are accepted; a `WatchRule` that relied on it is refused naming the new field | `removed_allow_any_source_namespace_test.go` (removed with `ClusterProvider`), [`watchrule_source_namespace_test.go`](../../internal/controller/watchrule_source_namespace_test.go) |
| A restarted worker follows an operation left in flight with its window open, restores `repairPending`, and follows an apply started elsewhere; one recorded but never resumed ends `Failed` | [`apply_recovery_test.go`](../../internal/git/apply_recovery_test.go), [`dispatcher_test.go`](../../internal/applier/dispatcher_test.go) |
| Appliers sharing a source have one operation at a time and take turns, across workers too; a shared source applies only the consumer asked | [`apply_groups_test.go`](../../internal/git/apply_groups_test.go), [`flux_consumers_test.go`](../../internal/applier/flux_consumers_test.go) |
-| An applier in another cluster is reached through its own `ClusterProvider`, behind that provider's `accessFrom` | [`dispatcher_test.go`](../../internal/applier/dispatcher_test.go), [`kubeprovider_admission_test.go`](../../internal/authz/kubeprovider_admission_test.go), [`applytrigger_controller_test.go`](../../internal/controller/applytrigger_controller_test.go) |
+| An applier in another cluster is reached through its own `ClusterProvider`, behind that provider's `accessFrom` | [`dispatcher_test.go`](../../internal/applier/dispatcher_test.go), [`kubeprovider_admission_test.go`](../../internal/authz/kubeprovider_admission_test.go), [`applytrigger_controller_test.go`](../../internal/controller/applierclaim_controller_test.go) |
| The applier's late write over a newer edit is withheld, and nothing that could be the edit is: a tie, and an undo that leaves an older applier entry, are mirrored; the apply itself is not mirrored, a delayed echo of the attempt before included; an applier entry older than the send is an edit; an applier write with no window open is late | [`apply_window_test.go`](../../internal/git/apply_window_test.go), [`last_field_managers_test.go`](../../internal/watch/last_field_managers_test.go) |
-| A newly named applier is caught up | [`apply_trigger_test.go`](../../internal/git/apply_trigger_test.go) |
+| A newly named applier is caught up | [`apply_trigger_test.go`](../../internal/git/applier_claim_test.go) |
| The receiver's path names no provider, and every miss answers the same | [`receiver_test.go`](../../internal/webhook/receiver_test.go) |
| The original race: two API edits end at the later value with no extra commit | [bi-directional Flux corner](../../test/e2e/flux_bi_directional_e2e_test.go) |
| A trigger nothing names is `Ready` and holds nothing; `SourceMismatch` adopts nothing and never stamps the other branch's source; fixed, the `Kustomization` is adopted and Flux reaches the tip; dropping the reference hands it back and naming the trigger again adopts it; a Gitea webhook and the refresh alone each get a push applied; a failed apply is suspended and retried by the worker; a `flux resume` is suspended again; deleting the trigger hands the `Kustomization` back | [Manager Flux Applier](../../test/e2e/flux_applier_e2e_test.go) |
@@ -878,8 +881,8 @@ adopted by one apply.
| A refused edit is reverted by a resume of its `Kustomization` | [Refusal reapply](../../test/e2e/refusal_reapply_e2e_test.go) |
| The Flux guide's example is accepted by the code | [`flux_guide_test.go`](../../internal/applier/flux_guide_test.go) |
| An Argo CD sync is the one automated sync builds, pinned to the commit, with a pinned retry; a running operation is waited out and never replaced; an operation set between the read and the update makes it fail; one whose `.operation` was removed while it ran ends; every `Configured` reason, `OwnedByApplicationSet` and `AutomatedSyncOwnedElsewhere` among them; when a sync counts as finished | [`argocd_test.go`](../../internal/applier/argocd_test.go), [`dispatcher_test.go`](../../internal/applier/dispatcher_test.go) |
-| An `Application` is asked on a `ForeignCommit` and on a `Refusal`; Argo CD's late write over a newer edit is withheld by its own managers | [`apply_trigger_test.go`](../../internal/git/apply_trigger_test.go), [`apply_window_test.go`](../../internal/git/apply_window_test.go) |
-| Each trigger kind holds one applier in one cluster, immutably; a trigger nothing names is `Ready` with no finalizer, and a `GitTarget` naming it re-runs its check | [`applytrigger_schema_test.go`](../../internal/controller/applytrigger_schema_test.go) |
+| An `Application` is asked on a `ForeignCommit` and on a `Refusal`; Argo CD's late write over a newer edit is withheld by its own managers | [`apply_trigger_test.go`](../../internal/git/applier_claim_test.go), [`apply_window_test.go`](../../internal/git/apply_window_test.go) |
+| Each trigger kind holds one applier in one cluster, immutably; a trigger nothing names is `Ready` with no finalizer, and a `GitTarget` naming it re-runs its check | [`applytrigger_schema_test.go`](../../internal/controller/applierclaim_schema_test.go) |
| With Argo CD: two API edits keep the later value with no extra commit; a Git-side push is synced by a pinned `ForeignCommit`; a refused edit is reverted by a sync; automated sync switched on is switched off again; a generated `Application` with automated sync on is `OwnedByApplicationSet` and never synced | [bi-directional Argo CD corner](../../test/e2e/argocd_bi_directional_e2e_test.go) |
| The Argo CD guide's example is accepted by the code | [`argocd_guide_test.go`](../../internal/applier/argocd_guide_test.go) |
diff --git a/docs/future/git-api-three-way-comparison.md b/docs/future/git-api-three-way-comparison.md
index 02baa9ad7..15f52ecb4 100644
--- a/docs/future/git-api-three-way-comparison.md
+++ b/docs/future/git-api-three-way-comparison.md
@@ -479,7 +479,7 @@ needed for those forms, but neither every patch nor every update reveals an exac
## Why faster Git delivery helps but does not settle the merge
Faster notification reduces how long the engine plans against stale Git state. The push receiver
-of the [apply trigger](../design/apply-trigger.md) shortens that latency, independently of this
+of the [applier claim](../design/apply-trigger.md) shortens that latency, independently of this
investigation.
The timestamp example already gives Reverser the Git change before Alice edits. The remaining gap
diff --git a/docs/images/config-basics.excalidraw.svg b/docs/images/config-basics.excalidraw.svg
index da7044368..88e4b4d5f 100644
--- a/docs/images/config-basics.excalidraw.svg
+++ b/docs/images/config-basics.excalidraw.svg
@@ -1,2 +1,3 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/images/config-bi-directional.excalidraw.svg b/docs/images/config-bi-directional.excalidraw.svg
new file mode 100644
index 000000000..6810787a0
--- /dev/null
+++ b/docs/images/config-bi-directional.excalidraw.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/docs/images/config-cluster.excalidraw.svg b/docs/images/config-cluster.excalidraw.svg
deleted file mode 100644
index 5539c4f8e..000000000
--- a/docs/images/config-cluster.excalidraw.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/images/config-platform-shared.excalidraw.svg b/docs/images/config-platform-shared.excalidraw.svg
new file mode 100644
index 000000000..d7efbf944
--- /dev/null
+++ b/docs/images/config-platform-shared.excalidraw.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/docs/images/overview.excalidraw.svg b/docs/images/overview.excalidraw.svg
index c34d2c7ef..f363d4471 100644
--- a/docs/images/overview.excalidraw.svg
+++ b/docs/images/overview.excalidraw.svg
@@ -1,2 +1,2 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/installing-apps-as-krm.md b/docs/installing-apps-as-krm.md
index 6bf612c61..673c82eb6 100644
--- a/docs/installing-apps-as-krm.md
+++ b/docs/installing-apps-as-krm.md
@@ -105,7 +105,7 @@ Endpoints/EndpointSlices, Leases, ControllerRevisions, Jobs, CronJobs — but it
does **not** ignore Deployments, ReplicaSets, Services, or ConfigMaps, because
those are often exactly the intent you *do* want to capture. Drawing the line is
your job, and it is worth doing deliberately. The one line the operator draws
-itself is for a folder an apply trigger applies: it leaves out objects another
+itself is for a coordinated folder: it leaves out objects another
Flux `Kustomization` or Argo CD `Application` owns
([why](bi-directional.md#objects-another-applier-owns)).
diff --git a/docs/interpreting-metrics.md b/docs/interpreting-metrics.md
index 8d145124a..71b6c7ae7 100644
--- a/docs/interpreting-metrics.md
+++ b/docs/interpreting-metrics.md
@@ -82,7 +82,7 @@ without a kubeconfig.
| `resource_condition` | gauge | `kind`, `resource_namespace`, `resource_name`, `type`, `status`, `reason` |
- `kind` is `GitTarget`, `WatchRule`, `ClusterWatchRule`, `GitProvider`, `GitBranch`,
- `GitReceiver`, `KubeProvider`, `FluxTrigger` or `ArgoCDTrigger`: the objects a human declares.
+ `GitReceiver`, `KubeProvider`, `FluxClaim` or `ArgoCDClaim`: the objects a human declares.
- `type` is the kstatus trio: `Ready` (the summary), `Reconciling` (working on it) and `Stalled`
(given up, and nothing will retry without a human).
- `status` is `True`, `False` or `Unknown`.
@@ -829,18 +829,18 @@ are unbounded, and both are in the log line at the write site.
## Apply requests and the push receiver
-With `GitTarget.spec.applyTriggerRef` set, the branch worker decides when the Flux `Kustomization`
-or Argo CD `Application` the trigger holds applies, and the push receiver tells it that somebody
-else pushed. See [who applies this folder](configuration.md#who-applies-this-folder-specapplytriggerref).
+With `GitTarget.spec.applierClaimRef` set, the branch worker decides when the Flux `Kustomization`
+or Argo CD `Application` the applier claim holds applies, and the push receiver tells it that somebody
+else pushed. See [who applies this folder](configuration.md#who-applies-this-folder-specapplierclaimref).
| Metric | Type | Labels | Meaning |
| --- | --- | --- | --- |
-| `apply_requests_total` | counter | `applier_kind`, `reason`, `outcome` | One per attempt to one applier. `applier_kind` is `kustomization` (a Flux `Kustomization`) or `application` (an Argo CD `Application`); `reason` is `ForeignCommit` / `Refusal` / `Repair` / `CatchUp`, for either kind (`CatchUp` adopts an object the API created in the folder, or converges once when the worker starts and once when a `GitTarget` first names the trigger); `outcome` is `sent` (the applier was asked and the ask landed; it says nothing about how the apply went. What needs no apply, a converge of the commit the applier last applied or a branch the remote does not carry, the branch worker never sends, so it is not counted), `failed` (could not ask, including an applier that applies another repository or branch, an `Application` with automated sync on, or one whose running sync did not end within 5 minutes; the branch worker keeps the reason pending and decides the retry after a flush, so a failure that persists keeps counting at its backoff) or `dropped` (no `GitTarget` may still ask for that reason: each was deleted or recreated, moved to another branch or applier, is no longer admitted by its `KubeProvider`, or withdrew its consent to a `Refusal`) or `stale` (asked nothing because the branch tip moved before the ask landed; the worker decides again at once, at the new tip, and frequent stale attempts mean the branch keeps moving within an attempt's lifetime) |
+| `apply_requests_total` | counter | `applier_kind`, `reason`, `outcome` | One per attempt to one applier. `applier_kind` is `kustomization` (a Flux `Kustomization`) or `application` (an Argo CD `Application`); `reason` is `ForeignCommit` / `Refusal` / `Repair` / `CatchUp`, for either kind (`CatchUp` adopts an object the API created in the folder, or converges once when the worker starts and once when a `GitTarget` first names the applier claim); `outcome` is `sent` (the applier was asked and the ask landed; it says nothing about how the apply went. What needs no apply, a converge of the commit the applier last applied or a branch the remote does not carry, the branch worker never sends, so it is not counted), `failed` (could not ask, including an applier that applies another repository or branch, an `Application` with automated sync on, or one whose running sync did not end within 5 minutes; the branch worker keeps the reason pending and decides the retry after a flush, so a failure that persists keeps counting at its backoff) or `dropped` (no `GitTarget` may still ask for that reason: each was deleted or recreated, moved to another branch or applier, is no longer admitted by its `KubeProvider`, or withdrew its consent to a `Refusal`) or `stale` (asked nothing because the branch tip moved before the ask landed; the worker decides again at once, at the new tip, and frequent stale attempts mean the branch keeps moving within an attempt's lifetime) |
| `apply_revision_mismatches_total` | counter | `applier_kind` | One per apply operation that succeeded on another revision than the commit GitOps Reverser checked. A `Kustomization` applies whatever its `GitRepository` artifact holds when it reconciles; the artifact is read again just before the resume, so this is somebody fetching the source in between (a person, or the source's own interval). The apply window still withholds a late write over a newer edit; a steady rate means something else keeps fetching the source |
| `apply_window_collisions_total` | counter | `provider_namespace`, `provider_name`, `branch`, `outcome` | One per write by the applier, while an apply it was asked for runs, to an object the API side edited after the flush before that request: the apply arriving late over a newer edit. `outcome` is `withheld` (proven to be the applier's write, by its field manager alone, with an entry as new as the edit or newer: `kustomize-controller`, or `argocd-controller` / `argocd-application-controller`; so not mirrored: Git keeps the API's value, and a `Repair` of the applier follows, which restores it on the cluster) or `ambiguous` (the applier and another field manager wrote in the same second, so it was mirrored as ever; if it was the apply, the revert is in Git as the applier's commit). An applier entry older than the edit is not counted: it is the API's undo of an edit, mirrored |
| `apply_echoes_total` | counter | `provider_namespace`, `provider_name`, `branch`, `outcome` | One per write by the applier's own field managers alone, judged by the apply window: an apply GitOps Reverser asked for landing, object by object. `outcome` is `not_mirrored` (the applier wrote the object last, at or after the earliest send of the apply windows still open, and nobody edited it in the window: the apply itself, whose content is the commit Git already holds, so it is not mirrored) or `late` (the applier's managers alone wrote the object last with no apply window open: an echo arriving more than 10 seconds after its operation completed, or an apply somebody else started; it is mirrored as an edit). `not_mirrored` rises with every apply and needs no action. A steady `late` rate means applies keep landing outside the windows, and such a write can revert a newer Git-side change to the object: look for a person or a tool applying the folder by hand (`flux reconcile`, a manual sync), or for watch events reaching the worker long after the apply |
-| `apply_ownership_conflicts_total` | counter | `provider_namespace`, `provider_name`, `branch` | One per object another applier owns that the branch worker kept out of a folder an apply trigger applies: a live write not mirrored, or a snapshot object neither upserted nor swept. The owner is read from Flux's owner labels (`kustomize.toolkit.fluxcd.io/name` and `/namespace`) or Argo CD's tracking annotation (`argocd.argoproj.io/tracking-id`). Any non-zero rate is a misconfiguration: another `Kustomization` or `Application` applies objects into a namespace the folder mirrors, and two appliers applying one object fight each other. The worker logs each object once, naming both appliers; narrow the `WatchRule`, or move the object to one applier |
-| `stale_snapshots_total` | counter | `provider_namespace`, `provider_name`, `branch`, `outcome` | One per resync snapshot the branch worker found stale: collected while Git was ahead of the cluster (a commit pushed and not yet applied), collected across an apply, or arriving while Git is ahead. `outcome` is `discarded` (not decided; a fresh snapshot of its `GitTarget` is asked for once Git is no longer ahead, and the `GitTarget`'s live writes are held back by the render-fidelity gate until it lands) or `used` (the `GitTarget`'s snapshots had been held for 5 minutes while an apply kept failing, so this one was decided anyway, and whatever the unapplied commit changed in its objects is reverted, as the API-wins rule says). `discarded` comes in short bursts around applies and needs no action; a discard is not a resync failure and is not counted in `git_resync_failures_total`. Any `used` means an applier is failing for minutes at a time: read the trigger's `status.operation` and the `ApplyFailed` Events. See [stale snapshots](spec/reconcile-via-watchlist-mark-and-sweep.md#stale-snapshots-are-discarded) |
+| `apply_ownership_conflicts_total` | counter | `provider_namespace`, `provider_name`, `branch` | One per object another applier owns that the branch worker kept out of a coordinated folder: a live write not mirrored, or a snapshot object neither upserted nor swept. The owner is read from Flux's owner labels (`kustomize.toolkit.fluxcd.io/name` and `/namespace`) or Argo CD's tracking annotation (`argocd.argoproj.io/tracking-id`). Any non-zero rate is a misconfiguration: another `Kustomization` or `Application` applies objects into a namespace the folder mirrors, and two appliers applying one object fight each other. The worker logs each object once, naming both appliers; narrow the `WatchRule`, or move the object to one applier |
+| `stale_snapshots_total` | counter | `provider_namespace`, `provider_name`, `branch`, `outcome` | One per resync snapshot the branch worker found stale: collected while Git was ahead of the cluster (a commit pushed and not yet applied), collected across an apply, or arriving while Git is ahead. `outcome` is `discarded` (not decided; a fresh snapshot of its `GitTarget` is asked for once Git is no longer ahead, and the `GitTarget`'s live writes are held back by the render-fidelity gate until it lands) or `used` (the `GitTarget`'s snapshots had been held for 5 minutes while an apply kept failing, so this one was decided anyway, and whatever the unapplied commit changed in its objects is reverted, as the API-wins rule says). `discarded` comes in short bursts around applies and needs no action; a discard is not a resync failure and is not counted in `git_resync_failures_total`. Any `used` means an applier is failing for minutes at a time: read the applier claim's `status.operation` and the `ApplyFailed` Events. See [stale snapshots](spec/reconcile-via-watchlist-mark-and-sweep.md#stale-snapshots-are-discarded) |
| `apply_operations_unresolved` | gauge | `provider_namespace`, `provider_name`, `branch` | Apply operations on the branch that have not completed 5 minutes after they were requested. Each is still followed, holds its applier's apply window open, and holds back the applier's next request until it completes, so a value that stays above zero is an applier stuck mid-apply: read its status, and the `ApplyUnresolved` Event on the `GitTarget`s. Read at scrape time |
| `receiver_deliveries_total` | counter | `receiver_namespace`, `receiver_name`, `type`, `outcome` | One per verified-or-refused delivery to a `GitReceiver`'s `/git-push/` path. `type` is its `spec.type`; `outcome` is `notified` (handed to at least one branch worker), `unknown_repository` (no `GitProvider`, in any namespace, has the repository the delivery names under any URL it carries), `unknown_branch` (one has, but no branch worker writes that branch for it), `ignored` (a ping, an event other than a push, or a tag or other non-branch ref), `rejected` (a missing or wrong signature or token, or an oversized body) or `invalid` (verified but unparseable, or naming no repository). Every outcome but `rejected` and `invalid` answers `202`. `unknown_repository` and `unknown_branch` are expected on an organization or GitHub App hook, which delivers for every repository and branch. On a repository's own hook, a steady `unknown_repository` rate means the payload names the repository by a URL no provider's `spec.url` matches, such as an SSH alias: the affected `GitBranch` has no `status.lastPushNoticeAt` |
@@ -871,7 +871,7 @@ max by (provider_namespace, provider_name, branch) (gitopsreverser_apply_operati
# Git-side changes; something applies the folder outside the windows.
sum by (provider_namespace, provider_name, branch) (
rate(gitopsreverser_apply_echoes_total{outcome="late"}[1h])) > 0
-# Another applier's objects inside a triggered folder: a misconfiguration, logged per object.
+# Another applier's objects inside a coordinated folder: a misconfiguration, logged per object.
sum by (provider_namespace, provider_name, branch) (
increase(gitopsreverser_apply_ownership_conflicts_total[1h])) > 0
# Snapshots used although Git was ahead: an applier has been failing for at least 5 minutes.
@@ -1241,7 +1241,7 @@ histogram_quantile(0.95,
## Watch plane owner
-One loop owns the watch plane: controllers post a trigger naming a GitTarget and return, and the
+One loop owns the watch plane: controllers post an applier claim naming a GitTarget and return, and the
owner runs one plan pass per target once that target has been quiet for a 2 s rolling silence
window (hard cap ~10 s). Design:
[watch-manager-ownership.md](design/watch-manager-ownership.md).
@@ -1287,7 +1287,7 @@ sum by (gittarget_namespace, gittarget_name) (
```
**Is the silence window earning its keep?** One `kubectl apply` of a GitTarget and four WatchRules
-should show four coalesced triggers and one pass. A coalescing ratio near zero under a busy config
+should show four coalesced applier claims and one pass. A coalescing ratio near zero under a busy config
plane means edits are arriving further apart than the window, which is not a fault — it is the
window doing nothing:
@@ -1348,7 +1348,7 @@ sum by (outcome) (rate(gitopsreverser_secret_encryptions_total[5m]))
| `rate(gitopsreverser_attribution_fact_stream_decode_errors_total[10m]) > 0` | A schema or version mismatch on the fact stream; facts are being skipped and lost. |
| `(time() - …_fact_follower_last_success_timestamp_seconds > 600) or (…_transport_info == 1 unless on() …_fact_follower_last_success_timestamp_seconds)`, `for: 10m` | The fact follower is wedged; attribution is degrading to committer-authored cluster-wide. Both arms are needed — see below. |
| `rate(gitopsreverser_git_resync_failures_total[15m]) > 0` sustained | Background resyncs are not committing; the folder relies on steady-state events to catch up. |
-| `increase(gitopsreverser_apply_ownership_conflicts_total[1h]) > 0` | Another `Kustomization` or `Application` applies objects into a folder an apply trigger applies; they are kept out of Git, and the log names both appliers. |
+| `increase(gitopsreverser_apply_ownership_conflicts_total[1h]) > 0` | Another `Kustomization` or `Application` applies objects into a coordinated folder; they are kept out of Git, and the log names both appliers. |
| `increase(gitopsreverser_stale_snapshots_total{outcome="used"}[1h]) > 0` | An applier has been failing for at least 5 minutes, so a resync decided a snapshot taken while Git was ahead and reverted part of the unapplied commit. |
| `gitopsreverser_api_catalog_group_versions{state="degraded"} > 0` | Part of the API surface is hidden behind a broken APIService. |
| `time() - gitopsreverser_watch_plan_oldest_dirty_since_timestamp_seconds > 120`, `for: 5m` | A GitTarget cannot be planned — its source cluster or catalog is unreachable — and its mirror is not converging. The `time() -` is not optional: the gauge is a Unix timestamp, so a bare `> 120` is true of every instant since 1970 and would fire on ordinary planning. |
diff --git a/docs/quickstart.md b/docs/quickstart.md
index a3086e578..534ecaf4e 100644
--- a/docs/quickstart.md
+++ b/docs/quickstart.md
@@ -8,7 +8,7 @@ there and writing them to
chart also renders the `local` `KubeProvider` in the `gitops-reverser` namespace, the operator's own
cluster, which the starter target names as its source.
-
+
**Prerequisites:** a Kubernetes cluster with `kubectl`, Helm 3, and cert-manager for TLS.
diff --git a/docs/rbac.md b/docs/rbac.md
index e799dc93f..da6172899 100644
--- a/docs/rbac.md
+++ b/docs/rbac.md
@@ -17,10 +17,10 @@ Two more ClusterRoles exist only when asked for, for a folder that is both writt
([`bi-directional.md`](bi-directional.md)): `-applier-flux` (chart value
`applierAccess.flux`; for kustomize,
[`config/rbac/flux-applier-role.yaml`](../config/rbac/flux-applier-role.yaml)) lets a
-`FluxTrigger` suspend and resume its `Kustomization`, and `-applier-argocd`
+`FluxClaim` suspend and resume its `Kustomization`, and `-applier-argocd`
(`applierAccess.argocd`;
[`config/rbac/argocd-applier-role.yaml`](../config/rbac/argocd-applier-role.yaml)) lets an
-`ArgoCDTrigger` start its `Application`'s syncs.
+`ArgoCDClaim` start its `Application`'s syncs.
They are separate because **RBAC is additive**. A wildcard read folded into the manager role
would grant cluster-wide Secret `list`/`watch` no matter how narrow the Secret rule beside
diff --git a/docs/reviews/20261022-apply-binding-rename.md b/docs/reviews/20261022-apply-binding-rename.md
new file mode 100644
index 000000000..d5b466e2d
--- /dev/null
+++ b/docs/reviews/20261022-apply-binding-rename.md
@@ -0,0 +1,479 @@
+# Applier claim rename and doc corrections: work plan
+
+Status: proposal, 2026-10-22. The kind name was settled on 2026-10-23: **`FluxClaim` /
+`ArgoCDClaim`**, referenced as `spec.applierClaimRef`. The original review was checked against
+`feat/gitreceiver` at `a4928dcf`. The coordinated-folder terminology is already in definitions.md
+and bi-directional.md.
+
+This plan folds in three rounds:
+
+- an external review that proposed renaming `FluxTrigger` / `ArgoCDTrigger`, listed four doc
+ corrections, and found an ownership caveat;
+- that reviewer's follow-up on the first draft, which moved the name from "Applier" to "Binding" and
+ corrected C1, the PR scope and O2. See [What the follow-up changed](#what-the-follow-up-changed);
+- a naming discussion that weighed active names against Binding and chose Claim, and the
+ reviewer's agreement with three qualifications. See
+ [What the naming discussion changed](#what-the-naming-discussion-changed).
+
+There are no users yet, so the rename is a **hard cut**: no migration path, no compatibility
+period, and no superseded field kept around to reject.
+
+## What to do
+
+**Decided 2026-10-23: one PR, stacked on #437, in separate commits.** #432 has merged, so nothing
+forces the doc corrections to land first, and writing them in trigger terms would only have them
+rewritten a commit later. The sections below still say "PR A" and "PR B" for the two halves:
+
+**PR A: doc corrections.** C1–C3 and the ownership limitation (O1), written straight in claim
+terms. It changes a Go comment and a godoc that flows into the generated CRDs.
+
+**PR B: the rename.** `FluxTrigger` → `FluxClaim`, `ArgoCDTrigger` → `ArgoCDClaim`,
+`spec.applyTriggerRef` → `spec.applierClaimRef`, removed outright. C4 is limited to text swaps
+(`ClusterProvider` → `KubeProvider`) and deleting the unused SVG; the structural redraw of
+config-basics is done by hand in Excalidraw, here or in a follow-up.
+
+**Not in this PR:** a code fix for the ownership limitation (O2/O3). Decide on that separately.
+
+**Since #437** (Argo CD automated sync held off, as a suspend): an `ArgoCDClaim` holds its
+`Application` the way a `FluxClaim` holds its `Kustomization`, so the definition below says so, and
+**Held** covers both kinds. The Argo CD finalizer `configbutler.ai/argocd-trigger` becomes
+`configbutler.ai/argocd-claim`; `argocdtrigger_hold*.go`, `argocdtrigger_apiserver_test.go` and
+`applytrigger_holder_test.go` are renamed with the rest. #437's own names stay:
+`status.previousSuspendState`, `SuspendState`, `ArgoCDVersionNotSupported` and the
+`ClaimedResourceReconciled` Event, which already reads in claim terms.
+
+## The vocabulary
+
+| Word | Means | Status |
+| --- | --- | --- |
+| **Applier** | The external Flux `Kustomization` or Argo CD `Application` that applies a folder | unchanged |
+| **Applier claim** | Our exclusive, persistent claim on one applier: a `FluxClaim` or an `ArgoCDClaim` | replaces "apply trigger" |
+| **Apply request** | One ask to an applier: this commit, for this reason | unchanged |
+| **Apply operation** | The execution of a request, followed until the applier's status shows it ended | unchanged |
+| **Bound** | An applier claim with at least one eligible `GitTarget` | already in [definitions.md:219](../definitions.md#applying-back); says nothing about whether it holds the applier (that is **Held**) |
+| **Coordinated** | A folder, or its `GitTarget`, configured for GitOps Reverser to control apply timing through an applier claim | describes the folder's setup; `Bound` still describes the applier claim |
+
+The sentence that ties them together:
+
+> An applier claim declares that GitOps Reverser should take and retain control of when a
+> referenced applier applies. A `GitTarget` names one with `spec.applierClaimRef`.
+
+The sentence says "declares", not "gives". An applier claim can exist without control: no eligible
+`GitTarget` references it yet, another applier claim holds the applier, or the applier isn't ready.
+
+Proposed entry for [definitions.md "Applying back"](../definitions.md#applying-back), replacing
+**Apply trigger**:
+
+> **Applier claim.** This operator's resource that declares GitOps Reverser should take and retain
+> control of when one applier applies: a `FluxClaim` (`spec.kustomizationRef`) or an `ArgoCDClaim`
+> (`spec.applicationRef`), each with the `KubeProvider` that reaches it. That control covers apply
+> timing and nothing else: the applier still fetches, renders, applies, prunes and reports health.
+> The claim does not say when to apply either; the branch worker decides that. Among applier claims
+> that reach an applier through the same `KubeProvider`, one holds it and the others report
+> `HeldByAnotherClaim`; claims made through two `KubeProvider`s for one cluster are not arbitrated
+> (see **In line**). Whether a claim holds its applier is **Held**, not **Bound**. While it holds a
+> `Kustomization`, a `FluxClaim` keeps it suspended and resumes it only for an apply GitOps Reverser
+> asks for, and deleting the claim releases the `Kustomization` back to Flux. While it holds an
+> `Application`, an `ArgoCDClaim` keeps automated sync off and starts each sync itself, and deleting
+> the claim switches automated sync back as `status.previousSuspendState` recorded it. A `GitTarget`
+> names one with `spec.applierClaimRef` and
+> reports it in `ApplierClaimReady`. The applier claim is never called the applier: the applier is
+> the object it claims.
+
+**Prose rule:** write "applier claim" or the kind name, never a bare "claim". definitions.md already
+reserves a bare "claim" for a rule's demand on the read side (`claimed` in code), and defines
+**Branch claim** for the Git side. The audit code also talks about OIDC token claims.
+
+Proposed **Coordinated** entry for definitions.md after the rename:
+
+> **Coordinated.** A folder configured for GitOps Reverser to decide when its applier applies.
+> Naming an applier claim makes a `GitTarget`'s folder coordinated. The word also describes the
+> `GitTarget` that writes it. It describes the setup; `ApplierClaimReady` reports whether the
+> applier claim is ready. A shared folder applied without that coordination is **uncoordinated**:
+> the applier keeps its own schedule and can revert live edits before they reach Git.
+
+Use these words in the bi-directional guide's decision tree: **coordinated folder** where GitOps
+Reverser controls apply timing, and **uncoordinated shared folder** where the applier keeps its own
+schedule. Keep the one-way case separate. `Bound` continues to describe the applier claim.
+
+## Why rename, and why Claim
+
+### Why drop "Trigger"
+
+- **These resources don't declare the triggering conditions.** A trigger can be persistent, so
+ that isn't the objection. The objection is that the spec only names the external object and the
+ connection to reach it. *When* to apply is decided by the branch worker (after a flush, on a
+ quiet branch, for a `ForeignCommit` / `Refusal` / `Repair` / `CatchUp`). A name that suggests
+ the object holds those conditions sends readers to the wrong place.
+- **"Trigger" already means three other things here:** watch-plan triggers
+ (`gitopsreverser_watch_plan_triggers_total`, `TriggerRuleChange`), API-surface triggers
+ (`internal/watch/api_surface_triggers*.go`), and Argo Events triggers.
+
+### Why Claim
+
+The naming question is **authority over apply timing**. GitOps Reverser takes that authority,
+keeps it, and eventually hands it back, while Flux still does the applying. The resource that
+establishes this is **exclusive and taken**: among applier claims reaching an applier through the
+same `KubeProvider`, one holds it, the others report `HeldByAnotherClaim`, and deleting the holder
+releases the applier. That is what a claim is.
+
+- **It fits the lifecycle.** An applier claim is declared, competes for the applier, holds it, and
+ releases it. Every step has a plain sentence.
+- **It separates intent from success.** A `FluxClaim` is still an applier claim while another one
+ holds the `Kustomization`, so `HeldByAnotherClaim` reads naturally. Binding and Control both
+ suggest the relationship already exists.
+- **It does not limit the authority to timing by itself.** "This is ours for now" could suggest
+ broader ownership. The definition sets the boundary, as it would for any name; Claim's advantage
+ is the lifecycle and the resource model, not a narrower meaning.
+- **The reference structure is a Kubernetes precedent.** Pod → `PersistentVolumeClaim` →
+ `PersistentVolume` is `GitTarget` → `FluxClaim` → `Kustomization`. Several pods can share one
+ PVC, just as several `GitTarget`s name one applier claim. The workload points at the claim, which
+ also settles Binding's main weakness: in a `RoleBinding` the binding names both sides, while here
+ the `GitTarget`s point at it. Naming an applier claim from a `GitTarget` is the consent to hand
+ over deployment timing. The analogy stops at the structure: a `Bound` PVC has its volume, while a
+ **Bound** applier claim only has an eligible `GitTarget` referencing it. Holding the applier is
+ **Held**.
+- **It is the same idea as the Git side's branch claim.** A `GitBranch` holds a repository branch
+ and another reports `BranchConflict`; an applier claim holds an applier and another reports
+ `HeldByAnotherClaim`. One word for one idea on both sides.
+- **It keeps the four concepts distinct.** Applier, applier claim, apply request and apply operation
+ each get their own word. "Applier" keeps its meaning in about 330 places, and `Applier()`,
+ `ApplierKind*`, `git.ApplierRef`, `ApplierNotFound` and `ApplierNamespaceNotAllowed` still name
+ the external object. `ApplierClaim.Applier()` reads correctly. holder.go's "holder" is simply the
+ applier claim that holds the applier.
+- **Against:**
+ - Neither Claim nor any other short name says "timing"; the only one that does, Scheduler,
+ misleads. The definition and the `HELD` column carry it.
+ - A bare "claim" already has other meanings here, hence the prose rule.
+ - Crossplane users know a claim as a request that gets provisioned, not a takeover of an existing
+ object.
+
+### The example
+
+```yaml
+kind: GitTarget
+metadata: {name: checkout, namespace: team-a}
+spec:
+ path: apps/checkout
+ applierClaimRef: {kind: FluxClaim, name: checkout}
+---
+kind: FluxClaim
+metadata: {name: checkout, namespace: team-a}
+spec:
+ kubeProviderRef: {name: management}
+ kustomizationRef: {namespace: flux-system, name: checkout}
+```
+
+```text
+$ kubectl get fluxclaims -n team-a
+NAME READY REASON HELD KUSTOMIZATION OPERATION AGE
+checkout True Succeeded Suspended checkout Succeeded 3d
+```
+
+The doc sentences become: "Deleting a `FluxClaim` releases the `Kustomization` back to Flux." "Two
+applier claims on one `Kustomization`: one holds it, the other reports `HeldByAnotherClaim`."
+
+### Alternatives considered
+
+| | Claim | Binding | Applier | Gate | ApplyControl |
+| --- | --- | --- | --- | --- | --- |
+| On a `GitTarget` | `applierClaimRef` | `applyBindingRef` | `applierRef` (reads best) | `applyGateRef` | `applyControlRef` |
+| Shows the authority | yes: declared, taken, released; the definition limits it to timing | no (the definition does) | yes | yes | yes, but too broadly |
+| Keeps "applier" = `Kustomization` / `Application` | yes | yes | **no**: about 330 uses re-read, plus a new generic word | yes | yes |
+| Collides with an existing word | bare "claim" (read-side demand); same sense as branch claim | no | `ApplierNotFound`, `ApplierNamespaceNotAllowed` | **Gate** ([definitions.md:321](../definitions.md), about 1,400 lines) | "controller", "reconcile" |
+| Fits Argo CD as well as Flux | yes | yes | yes | weaker: nothing is held closed | yes |
+
+- **Binding** (`FluxBinding`, `spec.applyBindingRef`) was the working name until 2026-10-23. It
+ describes the relationship but hides that the relationship is exclusive and takes authority, and
+ the `RoleBinding` precedent points the reference the wrong way.
+- **Applier** (`FluxApplier`, `spec.applierRef`) reads best on a `GitTarget`, but a clearer public
+ name produces less clear internal names. It also suggests GitOps Reverser applies manifests
+ itself, the way `kubectl apply` or cli-utils' `apply.Applier` does.
+- **Gate** (`FluxGate`, `spec.applyGateRef`) is the most accurate description of what Flux sees, and
+ Pod `spec.schedulingGates` is a good precedent. But "Gate" is already a defined term (a condition
+ `Ready` aggregates), and the acceptance, render-fidelity and write gates are everywhere.
+- **Hold** (`FluxHold`) names the mechanism and matches `Held` and holder.go. But "hold" is used
+ loosely all over the code and docs ("Git holds a commit", "hold off resyncs", "a route is held"),
+ and a Hold that also starts applies reads as a contradiction.
+- **ApplyControl / ApplyController / ApplyReconciler** name the authority, but in an operator
+ codebase they read as the reconcile loop, not a resource. "An apply control" is awkward as a
+ countable noun, and Control suggests authority over what is applied as well as when.
+- **ApplyGovernor** suggests rate limits, quotas or policy. **ApplyScheduler** suggests intervals,
+ calendars or queued jobs, and understates keeping and handing back control.
+- **ApplyCoordinator** fits publishing, requesting and tracking, but understates unilateral control.
+ Coordination still supplies **coordinated** for the folder's setup.
+- **Lease** has the right single-holder semantics but implies expiry and renewal, and collides with
+ `coordination.k8s.io/Lease`, which leader election already uses.
+- **Custody** is accurate (temporary authority, handed back) but too unusual for a kind.
+- **Link** is a shorter Binding with the same weakness. **Driver** suggests a plugin or binary.
+ **Cooperation** suggests shared control.
+
+Whichever words change, **applier**, **apply request**, and **apply operation** keep their meanings.
+
+## PR B: rename inventory
+
+### Public surface
+
+| Current | New | Where |
+| --- | --- | --- |
+| kind `FluxTrigger`, plural `fluxtriggers` | `FluxClaim`, `fluxclaims` | [fluxtrigger_types.go](../../api/v1alpha3/fluxclaim_types.go), CRD `configbutler.ai_fluxtriggers.yaml` |
+| kind `ArgoCDTrigger`, plural `argocdtriggers` | `ArgoCDClaim`, `argocdclaims` | [argocdtrigger_types.go](../../api/v1alpha3/argocdclaim_types.go), CRD file |
+| `GitTarget.spec.applyTriggerRef` (`ApplyTriggerReference`) | `spec.applierClaimRef` (`ApplierClaimReference`); old field removed, not rejected | [gittarget_types.go:82-93](../../api/v1alpha3/gittarget_types.go); enum at [applytrigger_types.go:43](../../api/v1alpha3/applierclaim_types.go) |
+| `onRefusal: Reapply` CEL rule and message ("needs spec.applyTriggerRef, the trigger asked to reapply") | names `spec.applierClaimRef`, "the applier claim" | [gittarget_types.go:36](../../api/v1alpha3/gittarget_types.go) |
+| condition `ApplyTriggerReady` and its printer column | `ApplierClaimReady` | [gittarget_apply_trigger.go:19](../../internal/controller/gittarget_applier_claim.go), [gittarget_types.go:545](../../api/v1alpha3/gittarget_types.go) |
+| reason `ApplyTriggerNotFound` | `ApplierClaimNotFound` | [gittarget_apply_trigger.go:23](../../internal/controller/gittarget_applier_claim.go) |
+| reason `HeldByAnotherTrigger` | `HeldByAnotherClaim` | [holder.go:32](../../internal/applier/holder.go) |
+| finalizer `configbutler.ai/flux-trigger` | `configbutler.ai/flux-claim` | [fluxtrigger_types.go:17](../../api/v1alpha3/fluxclaim_types.go) |
+| RBAC markers and `config/rbac/role.yaml` (`fluxtriggers`, `/status`, `/finalizers`, `argocdtriggers`) | `fluxclaims`, `argocdclaims` | [applytrigger_controller.go:139-142](../../internal/controller/applierclaim_controller.go), [gittarget_controller.go:151](../../internal/controller/gittarget_controller.go) |
+| chart README (CRD list, uninstall commands), `values.yaml:123-130,373`, `values.schema.json:419`, `templates/applier-rbac.yaml` comments | the new kinds; the keys `applierAccess` and `allowCrossNamespace.appliers` **stay** (they are about appliers) | `charts/gitops-reverser/` |
+| samples `fluxtrigger.yaml`, `argocdtrigger.yaml`, samples README | `fluxclaim.yaml`, `argocdclaim.yaml` | `config/samples/` |
+| e2e CRD wait list | the new CRD names | [Taskfile-e2e.yml:1332](../../test/e2e/Taskfile-e2e.yml) |
+
+These keep their names: the `Held` condition and its reasons, `status.operation`,
+`status.repairPending`, `ApplyReason`, `ApplyOperationPhase`, every `apply_*` metric, and the
+`applier_kind` label with its values `kustomization` / `application`.
+
+### Internal Go
+
+Rename the files:
+
+- `api/v1alpha3/{applytrigger,fluxtrigger,argocdtrigger}_types.go` → `{applierclaim,fluxclaim,argocdclaim}_types.go`
+- `internal/controller/applytrigger_*.go` → `applierclaim_*.go`
+- `fluxtrigger_hold*.go`, `fluxtrigger_suspend_apiserver_test.go` → `fluxclaim_*`
+- `gittarget_apply_trigger*.go` → `gittarget_applier_claim*.go`
+- `internal/git/apply_trigger*.go` → `applier_claim*.go`
+- `test/e2e/apply_trigger.go` → `applier_claim.go`
+
+Rename the identifiers:
+
+| Current | New |
+| --- | --- |
+| `ApplyTrigger` (interface) | `ApplierClaim` |
+| `ApplyTriggerReconciler` | `ApplierClaimReconciler` |
+| `TriggerKind()` | `ClaimKind()` |
+| `ApplyTriggerKindFlux` / `ApplyTriggerKindArgoCD` | `ApplierClaimKindFlux` / `ApplierClaimKindArgoCD` |
+| `ApplyTriggerConditionHeld` / `…Configured` | `ApplierClaimConditionHeld` / `…Configured` |
+| `ApplyTriggerStatus`, `ApplyTriggerStatusWriter`, `PublishApplyTriggerStatus`, `SetApplyTriggerStatusWriter` | `ApplierClaim…` |
+| `ResolveApplyTrigger`, `nameApplyTrigger`, `observeApplyTrigger`, `NewApplyTrigger` | `…ApplierClaim` |
+| `FluxTrigger*` / `ArgoCDTrigger*` (`Spec`, `Status`, `List`, `Source`, `Finalizer`) | `FluxClaim*` / `ArgoCDClaim*` |
+| `TriggersOfApplier`, `holderTrigger`, `errTriggerReleased`, `applyTriggerLoop`, `applyTriggerVerdict`, `applyTriggerSuspendingInterval`, `GitTargetConditionApplyTriggerReady`, `ReasonApplyTriggerNotFound` | `ClaimsOfApplier`, `holderClaim`, `errClaimReleased`, `applierClaimLoop`, `applierClaimVerdict`, `applierClaimSuspendingInterval`, `GitTargetConditionApplierClaimReady`, `ReasonApplierClaimNotFound` |
+| test helpers (`testFluxTrigger`, `createFluxTrigger`, `reconcileTrigger`, `fluxTriggerResource`, …) | `…Claim` |
+
+**Keep:** every `Applier*` identifier, since the applier is unchanged.
+
+**Do not rename** these; they are different concepts: `TriggerRuleChange`,
+`TriggerAllRuleChange`, `watchPlaneTriggers`, `WatchPlanTriggersTotal`, `crdTriggerGVR`,
+`apiServiceTriggerGVR`, `ensureAPISurfaceTriggerInformers`, `selectAPISurfaceTriggers`,
+`internal/watch/api_surface_triggers*.go`.
+
+**Do not collide with** the existing claim identifiers: the branch claim (`BranchClaims`,
+`BranchClaimant`, `NewBranchClaims`, `Claims()`), a `CommitRequest` claiming a window, the
+read-side `claimed` demand, and OIDC token claims in the audit code. Every new identifier says
+`ApplierClaim`, `FluxClaim` or `ArgoCDClaim`, never a bare `Claim`. The one exception is
+`ClaimKind()`, a method on `ApplierClaim`, where the receiver already says which claim it is.
+
+### Docs
+
+- **[definitions.md "Applying back"](../definitions.md#applying-back):**
+ - Replace **Apply trigger** with the entry above.
+ - Keep **Applier**, and change "An apply trigger names it" to "An applier claim names it".
+ - Update **Eligible**, **Bound**, **In line** and **Held** ("trigger" → "applier claim").
+ - Update **Coordinated** to the entry above, using the renamed field and condition vocabulary.
+ - In **Branch claim**, extend "never a bare 'claim'" to mention that the applier claim is the
+ cluster side of the same idea.
+- **definitions.md "Words we do not use":**
+ - Add "trigger, apply trigger (for an applier claim) → applier claim".
+ - Update the "fence" row, which says "what a trigger does to its applier".
+- **The configuration heading:** rename [Who applies this folder (`spec.applyTriggerRef`)](../configuration.md#who-applies-this-folder-specapplierclaimref)
+ to "Who applies this folder (`spec.applierClaimRef`)". That changes the anchor, so fix the 12
+ links: 8 in configuration.md, and one each in UPGRADING, api-first-publication,
+ interpreting-metrics and design/apply-trigger.
+- **Guide pages:** about 1,550 lines mention the trigger names. Most are mechanical. Read these by
+ hand, because they mix "trigger" and "applier" in the same sentences, and a bare "claim" must not
+ slip in:
+ - `bi-directional*.md` (about 180 trigger mentions)
+ - `architecture.md`
+ - `configuration.md`
+ - `rbac.md`
+ - `security-model.md`
+ - `interpreting-metrics.md` (the `apply_requests_total` row says "names the trigger")
+- **`docs/design/*` and `docs/reviews/*`:** leave as the historical record. Add one line at the top
+ of [apply-trigger.md](../design/apply-trigger.md) saying the kinds are now applier claims.
+- **[UPGRADING.md](../UPGRADING.md):** add a short entry, nothing more:
+
+ > **`FluxTrigger` and `ArgoCDTrigger` are now `FluxClaim` and `ArgoCDClaim`.** Breaking, with no
+ > migration path. Before upgrading, delete every `FluxTrigger` and `ArgoCDTrigger` (a
+ > `FluxTrigger` hands its `Kustomization` back to Flux), then delete their CRDs. After upgrading,
+ > recreate them under the new kinds with the same `spec`, and on each `GitTarget` replace
+ > `spec.applyTriggerRef` with `spec.applierClaimRef`.
+
+ Local e2e clusters: run `task clean-cluster` once after pulling PR B.
+
+### C4. Redraw the diagrams
+
+- [config-basics.excalidraw.svg](../images/config-basics.excalidraw.svg) is shown in
+ [configuration.md:29](../configuration.md) and [quickstart.md:11](../quickstart.md).
+ - It shows `ClusterProvider` and has no `GitBranch` or `KubeProvider`.
+ - The SVG embeds its Excalidraw scene, so edit it in place.
+ - Draw: `WatchRule` → `GitTarget` → `GitBranch` → `GitProvider` → `Secret`; `GitTarget` →
+ `KubeProvider` (`local`, greyed out as "usually exists"); a dashed optional row `GitTarget` →
+ `FluxClaim` → `Kustomization`; and `GitReceiver` ⇢ `GitProvider` ("wakes by spec.url").
+ - Update the alt text in both docs.
+- [overview.excalidraw.svg](../images/overview.excalidraw.svg) (README.md:42) still says
+ `ClusterProvider`. Change it to `KubeProvider`.
+- `config-cluster.excalidraw.svg` isn't referenced anywhere. Delete it.
+
+## Ownership: one holder per KubeProvider, not per cluster
+
+[holder.go:36-39](../../internal/applier/holder.go) decides that two triggers name the same applier
+by `TriggerKind()`, `KubeProviderKey()`, and the object's namespace and name. Two `KubeProvider`s
+that reach **one** cluster (for example the chart's `local`, plus a team's own provider with a
+kubeconfig for the same API server) therefore give two holders for one `Kustomization`. Both
+suspend it under the same field manager. Each reads the other's resume as a `flux resume` and
+suspends it mid-apply. Deleting one hands back a hold the other still needs. These are exactly the
+failures the comment at the top of holder.go lists. For an `Application`, both start syncs.
+
+This is a misconfiguration, not an attack, so it falls outside the "don't design against spoofed
+identity" decision. Renaming doesn't change it, though "claim" makes the gap easier to state: an
+applier claim is exclusive only among claims made through the same `KubeProvider`.
+
+- **O1 (PR A): document it.** Add a paragraph to
+ [Who applies this folder](../configuration.md#who-applies-this-folder-specapplierclaimref) and to
+ the **In line** entry in definitions.md: *"One holder per `Kustomization` or `Application` is
+ enforced among triggers that reach it through the same `KubeProvider`. GitOps Reverser can't tell
+ that two `KubeProvider`s reach the same cluster, so name one `KubeProvider` for every trigger that
+ reaches a given cluster."*
+- **O2 (later): compare cluster identity. This is partial protection.**
+ - The `KubeProvider` records the cluster's identity in status (the `kube-system` Namespace UID,
+ read once at validation), and `sameApplier` compares that instead of the provider key.
+ - Where the credential can't read that Namespace, it falls back to the provider key, and the O1
+ limitation still applies.
+ - Cost: `get` on `namespaces/kube-system`, plus a note at
+ [architecture.md:389](../architecture.md) ("reads no `Namespace` objects in a source cluster")
+ that this read is about identity, not authorization.
+- **O3 (later, complete): an object-local stamp.**
+ - Write the holder's identity into the `Kustomization` in the same server-side apply as the
+ suspend, and refuse to adopt over a live stamp that isn't ours. This also catches two operator
+ installs on one cluster.
+ - Cost: two first adopters can race, so arbitration has to work across operators, and an
+ `Application` needs a write of its own.
+
+## PR A: doc corrections
+
+PR A ships before the rename, so its text uses the current names. PR B renames them with
+everything else.
+
+### C1. Qualify "the Kubernetes API is the source of truth"
+
+[architecture.md:53](../architecture.md). The rule is still right for GitOps Reverser's own
+pipeline. What's missing is how Git edits reach the cluster, and that the applier's echoes are kept
+out of Git (see **Echo**, [definitions.md:249](../definitions.md#applying-back)). Replace the first
+two sentences with:
+
+> **The Kubernetes API is the source of truth for what GitOps Reverser writes.** Git is a
+> materialized mirror of desired state from the API, ingested by **watch**; GitOps Reverser never
+> reads Git as object state. An edit made in Git reaches the cluster only through Flux or Argo CD.
+> When a `GitTarget` names an apply trigger, those applies are coordinated: GitOps Reverser first
+> publishes the API edits it has decided, then asks the applier to apply. The watch observes the
+> result, and the applier's own writes during that apply (its echoes) are not mirrored, because
+> Git already holds them.
+
+Keep the existing sentence about push conflicts after it.
+
+### C2. Describe KubeProvider as a connection and an identity
+
+Today the docs call it the source cluster, but a trigger uses it to **write** (suspend, resume,
+sync) in a cluster that may be a management cluster.
+
+- [architecture.md:569](../architecture.md): replace "the read-side peer of `GitProvider`" with "the
+ Kubernetes-side peer of `GitProvider`: a connection to one cluster and the identity used there.
+ What it's used for depends on who names it: a `GitTarget` reads its source cluster through it,
+ and an apply trigger reaches its `Kustomization` or `Application` through it, often in a
+ management cluster."
+- [kubeprovider_types.go:196](../../api/v1alpha3/kubeprovider_types.go): change "KubeProvider names
+ a source cluster: the cluster a GitTarget mirrors from, or an apply trigger reaches its applier
+ in" to "KubeProvider is a connection to a Kubernetes cluster and the identity used there. A
+ GitTarget mirrors from it; an apply trigger reaches its applier through it." Then run
+ `task manifests`, since this godoc flows into the CRD description.
+- [definitions.md:46-49](../definitions.md#the-two-clusters-and-the-two-directions), **Source
+ cluster**: change "A `KubeProvider` names one" to "A `GitTarget` names it through its
+ `KubeProvider`". Add a **KubeProvider** entry: "A connection and identity, not a role. The same
+ provider can be a `GitTarget`'s source cluster and an apply trigger's management cluster."
+ Change "`spec.inCluster` means the control cluster, acting as a source" to "… the control
+ cluster, reached with the operator's own identity".
+
+### C3. Remove the obsolete multi-worker text
+
+Since #428 (`c49803f3`) a branch worker is keyed by repository and branch, and
+[branch_owner.go](../../internal/git/branch_owner.go) lets exactly one `GitBranch` write a
+repository branch. `apply_sibling_flush.go` is gone.
+
+- [configuration.md:1470-1471](../configuration.md): delete "The branch workers of other
+ `GitProvider`s writing the same repository and branch flush too, and the request waits until
+ they have."
+- [bi-directional.md:434](../bi-directional.md): delete the bullet "An edit through another
+ `GitProvider` on the same repository and branch …". That exposure no longer exists.
+- [internal/git/apply_trigger.go:382](../../internal/git/applier_claim.go): delete ", and so does a
+ sibling's flush" from the comment.
+
+## Validation and acceptance
+
+Both PRs run the full sequence: `task fmt` → `task generate` → `task manifests` → `task vet` →
+`task lint` → `task test` → `task test-e2e`. Push once lint and unit pass. A PR is done when
+e2e passes, in CI or locally (sequentially).
+
+**PR A is done when:**
+
+- the full sequence passes;
+- `task lint-docs` passes (anchors and links);
+- the regenerated KubeProvider CRD description shows the C2 wording.
+
+**PR B is done when:**
+
+- the full sequence passes, **plus** `task test-e2e-bi-directional`, because the Flux and Argo CD
+ specs only run in that corner;
+- `git grep -niE 'fluxtrigger|argocdtrigger|apply.?trigger'` returns nothing outside `docs/design`,
+ `docs/reviews`, `docs/UPGRADING.md`, `CHANGELOG.md` and `external-sources/`;
+- `git grep -niw 'trigger'` over `docs/*.md` returns only the unrelated senses (watch plan, API
+ surface, Argo Events) and the "Words we do not use" row;
+- definitions.md contains no entry that calls the applier claim a trigger or an applier, and no
+ bare "claim" for it.
+
+## What the follow-up changed
+
+- **The name moved from Applier to Binding**, to keep applier / apply binding / apply request /
+ apply operation distinct. The first draft's D1 ("held object" as the generic word) is gone with
+ it; it also mixed identity with lifecycle state.
+- **The argument against "Trigger" is now** that these resources don't declare triggering
+ conditions, not that a trigger sounds fire-and-forget.
+- **C1 no longer says the applied result is mirrored "like any other change".** Echoes are not
+ mirrored.
+- **C4 moved to PR B only** (the first draft had it in both). PR A now lists e2e in its validation.
+- **O2 is marked as partial protection.**
+- **The migration section is gone.** There are no users yet. The follow-up's points about
+ sequencing a handoff and about allowing a temporary compatibility mechanism would apply if that
+ changes.
+
+## What the naming discussion changed
+
+2026-10-23.
+
+- **The name moved from Binding to Claim:** `FluxClaim` / `ArgoCDClaim`, generically "applier
+ claim". Binding kept the four concepts distinct but hid the authority. Claim keeps them distinct
+ and shows that the resource exclusively takes an applier and releases it on delete.
+- **The field is `spec.applierClaimRef`, not `applyClaimRef`.** "Apply claim" can read as a claim
+ that something was applied; "applier claim" is a claim *on* the applier, and keeps "applier" for
+ the external object.
+- **The active-name families were rejected.** ApplyControl, ApplyController, ApplyReconciler,
+ ApplyGovernor, ApplyCoordinator and ApplyScheduler each either collide with operator vocabulary
+ or describe the wrong mechanism; see [Alternatives considered](#alternatives-considered).
+- **The prose rule now guards a bare "claim"** instead of a bare "binding", because definitions.md
+ already gives a bare "claim" to the read side's demand and defines the branch claim.
+- **The reviewer agreed with Claim and qualified three arguments**, all taken:
+ - Claim does not limit authority to timing by itself; the definition does. Its advantage is the
+ lifecycle (declare, compete, hold, release) and that it separates intent from success.
+ - The definition states the scope of exclusivity (applier claims through the same
+ `KubeProvider`) instead of "one applier claim holds an applier at a time", which overstated O1.
+ - **Bound** stays distinct from **Held**. The PVC analogy explains the reference structure only.
+- **The defining sentence says "declares … should take and retain control"**, so an unreferenced,
+ conflicting or not-yet-ready applier claim is still described correctly.
diff --git a/docs/security-model.md b/docs/security-model.md
index 170ea8b12..896206ee8 100644
--- a/docs/security-model.md
+++ b/docs/security-model.md
@@ -40,13 +40,13 @@ To mirror live state into Git the controller must:
one of those objects. This is a `get` by name, never a `list` or a `watch`.
- **Receive audit events.** The kube-apiserver audit webhook posts events to the audit ingress.
Those carry object metadata and, for some resources, request/response bodies.
-- **Drive an applier, only with an apply trigger.** Holding a Flux `Kustomization` takes `get` and
+- **Drive an applier, only with an applier claim.** Holding a Flux `Kustomization` takes `get` and
`patch` on it and its `GitRepository`, plus `list` on `Kustomization`s, `HelmRelease`s and
`HelmChart`s; holding an Argo CD `Application` takes `get` and `update` on it. For the operator's
own cluster the chart grants these in ClusterRoles of their own, only with `applierAccess.flux`
or `applierAccess.argocd`.
-Apart from the applier an apply trigger holds, the controller never writes to a watched type. Its
+Apart from the applier an applier claim holds, the controller never writes to a watched type. Its
write targets are Git, that applier, and the Secrets it generates itself (the signing key, and the
age key under `generateWhenMissing`).
@@ -119,7 +119,7 @@ namespace it already knows. Narrowing that further is
| Generated Secret material | Signing keys and generated age keys live in cluster Secrets. |
| `accessFrom` label selectors on `GitProvider` and `KubeProvider` | A selector admits every control-cluster namespace whose labels match, so it is only as strong as the right to set those labels. Whoever can label a namespace can admit it, including a tenant allowed to edit its own namespace. Select on labels only the platform can set, or list namespaces by `names`. |
| `KubeProvider.spec.inCluster` and `--in-cluster-kube-providers` | `inCluster` wields the operator's own identity in the cluster it runs in. It is honored only for a `KubeProvider` the flag lists by exact `namespace/name` (no wildcards, empty by default); any other is `Stalled` (`InClusterNotAuthorized`) and never used. Anyone who can create a `KubeProvider` under a listed name wields that identity through it, so list only names in namespaces you control. |
-| `KubeProvider.spec.allowCrossNamespace.appliers` | Lets every namespace the provider admits suspend and apply a `Kustomization` or `Application` in another namespace, such as `flux-system`, through the provider's identity. A delegation, not owner consent: it does not prove the namespace owns the applier or its repository. A trigger only acts while an eligible `GitTarget` names it, and the binding check compares the `GitProvider` URL, not write access. |
+| `KubeProvider.spec.allowCrossNamespace.appliers` | Lets every namespace the provider admits suspend and apply a `Kustomization` or `Application` in another namespace, such as `flux-system`, through the provider's identity. A delegation, not owner consent: it does not prove the namespace owns the applier or its repository. An applier claim only acts while an eligible `GitTarget` names it, and the binding check compares the `GitProvider` URL, not write access. |
## Secret data the controller writes to Git
diff --git a/docs/spec/attribution.md b/docs/spec/attribution.md
index f3c910b89..9b2b27a41 100644
--- a/docs/spec/attribution.md
+++ b/docs/spec/attribution.md
@@ -261,8 +261,8 @@ never admitted to. The namespace the route's first dot-segment names holds it wh
carries it; otherwise the namespace of the oldest carrier does (`creationTimestamp`, then UID).
Providers in the holding namespace may share the route: that is the case above, several
connections to one API server. A provider in another namespace that carries it reports
-`Stalled=True` with reason `AuditRouteConflict` and is not used at all: `GitTarget`s and triggers
-through it are refused with the same reason. This is a status condition the controller recomputes on
+`Stalled=True` with reason `AuditRouteConflict` and is not used at all: `GitTarget`s and applier
+claims through it are refused with the same reason. This is a status condition the controller recomputes on
every reconcile, not an admission refusal, so the provider that loses a route is admitted and then
reports why it is idle; `Validated` keeps what its credential earned.
diff --git a/docs/spec/e2e-bi-directional-corner.md b/docs/spec/e2e-bi-directional-corner.md
index f4ce34157..391bae3cd 100644
--- a/docs/spec/e2e-bi-directional-corner.md
+++ b/docs/spec/e2e-bi-directional-corner.md
@@ -361,7 +361,7 @@ suite's CRD pre-clean loop (`iceCreamCRDGroups`, which `prepareE2EClusterOnce` i
The Flux spec keeps its exact-commit-count discipline, which works because it
drives Flux as a manually triggered applier until its last phase hands the
-`Kustomization` to a `FluxTrigger`.
+`Kustomization` to a `FluxClaim`.
### 5. Push-triggered reconciliation (the webhook)
@@ -398,7 +398,7 @@ A matched webhook triggers a **`normal`** refresh (`argo.RefreshApp` with
webhook only refreshes: it makes Argo CD compare sooner, and the specs prove it
never syncs. Every sync is one GitOps Reverser starts, learning of a push through
its own receiver, which the corner registers as a second Gitea webhook. Spec E
-switches automated sync on, and the trigger switches it off again before the spec
+switches automated sync on, and the applier claim switches it off again before the spec
pushes; Spec F's `ApplicationSet` generates an `Application` with automated sync on,
which auto-syncs a folder of its own.
@@ -406,12 +406,12 @@ which auto-syncs a folder of its own.
Specs A, B, C and E run the standard of [`docs/bi-directional-argocd.md`](../bi-directional-argocd.md): the `Application`
has automated sync off (`automated: {enabled: false, prune: true}`) and is held by
-the `ArgoCDTrigger` the `GitTarget` names, so every sync it runs is one GitOps
+the `ArgoCDClaim` the `GitTarget` names, so every sync it runs is one GitOps
Reverser started, pinned to a commit it checked. The earlier phases that ran
automated sync (`selfHeal: true` losing the edit, `ignoreDifferences`, and
`selfHeal: false` with the webhook) are gone with it: they characterized an Argo CD
that synced on its own, which the standard no longer allows. Spec F is the exception: an
-`ApplicationSet` owns its `Application`'s switch, so the trigger never holds it, and starts out
+`ApplicationSet` owns its `Application`'s switch, so the applier claim never holds it, and starts out
`Configured=False` (`OwnedByApplicationSet`).
Specs A, B and E are three `It`s of one `Ordered` describe, sharing one Gitea repo,
@@ -423,7 +423,7 @@ path would each stamp their own tracking-id on the same objects and raise
The `Application` lives in the `argocd` namespace, targets
`spec.destination.namespace = `, `spec.source.path = `,
-`targetRevision: main`, `directory.recurse: true`. The trigger lives in the test
+`targetRevision: main`, `directory.recurse: true`. The applier claim lives in the test
namespace, so each describe first grants `allowCrossNamespace.appliers` on the
install's `local` `KubeProvider` in the operator's namespace, as a user must for an
`Application` in `argocd`. The
@@ -432,8 +432,8 @@ CD's own.
Ordering matters: `SetupRepo` leaves the Gitea repo **empty**, so the spec seeds
`main` first, creates the `Application` (nothing to sync yet), and only then stands
-up the reverser pipeline, whose `GitTarget` needs its trigger's `Application` to
-report `ApplyTriggerReady=True`.
+up the reverser pipeline, whose `GitTarget` needs its applier claim's `Application` to
+report `ApplierClaimReady=True`.
### Spec A — a Git-side push is synced through a pinned `ForeignCommit`
@@ -444,7 +444,7 @@ report `ApplyTriggerReady=True`.
3. Assert the `Application`'s operation state is GitOps Reverser's: initiated by
`gitops-reverser` with `automated: true`, reason `ForeignCommit` in its info,
`sync.revision` and `syncResult.revision` both the pushed commit, `Succeeded`,
- with resources. The trigger's `status.operation` names the same commit, as
+ with resources. The applier claim's `status.operation` names the same commit, as
`Succeeded`, written by the branch worker with no reconcile asked for.
4. Assert the **live** object carries `argocd.argoproj.io/tracking-id` (exact
value format) and `kubectl.kubernetes.io/last-applied-configuration`, and the
@@ -459,7 +459,7 @@ substring assertion — it catches keys nobody thought to name.
### Spec B — two quick API edits keep the later value
-The race the apply trigger exists for.
+The race the applier claim exists for.
1. Push `order-4` from the Git side; it is synced by a `ForeignCommit`.
2. Patch `container: Cup` through the API, wait for its commit, then patch
@@ -479,9 +479,9 @@ env var no overlay declaration can express.
1. Seed the base and overlay, create the `Application` on the overlay
(kustomize), and deploy it with one manual sync, before any `GitTarget` names it.
2. Drift `LOG_LEVEL` to `debug`.
-3. Create the `GitTarget` on the overlay, an `ArgoCDTrigger` holding the `Application`,
- name the trigger in the `GitTarget`'s `spec.applyTriggerRef` with
- `onRefusal: Reapply`, wait for `ApplyTriggerReady=True`, and create a `WatchRule`
+3. Create the `GitTarget` on the overlay, an `ArgoCDClaim` holding the `Application`,
+ name the applier claim in the `GitTarget`'s `spec.applierClaimRef` with
+ `onRefusal: Reapply`, wait for `ApplierClaimReady=True`, and create a `WatchRule`
on `deployments`.
4. Assert the write is refused at the boundary (`WriteBoundaryRefused`), and that
GitOps Reverser's `Refusal` sync, pinned to the tip, reverts `LOG_LEVEL` to
@@ -491,10 +491,10 @@ env var no overlay declaration can express.
1. Re-apply the `Application` with automated sync on (an `automated` block with no
`enabled`): the client-side apply removes the switch the manifest no longer has.
- Request a trigger reconcile, since the applier is read on reconcile rather than
+ Request an applier claim reconcile, since the applier is read on reconcile rather than
watched.
2. Assert the switch is `false` again, with a `ClaimedResourceReconciled` Event on the
- trigger, `Held=True` (`AutomatedSyncOff`), and `gitops-reverser` among the
+ applier claim, `Held=True` (`AutomatedSyncOff`), and `gitops-reverser` among the
`Application`'s field managers.
3. Push a Git-side change. Assert GitOps Reverser syncs it as a `ForeignCommit`
pinned to the pushed commit, and the order is live.
@@ -504,14 +504,14 @@ env var no overlay declaration can express.
1. Push a `ConfigMap` into a folder of its own, and apply an `ApplicationSet` whose
template generates an `Application` over it with automated sync on.
2. Name the generated `Application` from a `GitTarget` on that folder, through an
- `ArgoCDTrigger`. Assert the trigger's `Configured=False` with
+ `ArgoCDClaim`. Assert the applier claim's `Configured=False` with
`OwnedByApplicationSet`, its message naming the `ApplicationSet`, the
- `GitTarget`'s `ApplyTriggerReady=False` with the same reason, and no
+ `GitTarget`'s `ApplierClaimReady=False` with the same reason, and no
`gitops-reverser` field manager on the `Application`.
3. Re-apply the `ApplicationSet` with `automated: {enabled: false}` in its template.
Assert `Held=True` (`AutomatedSyncOff`, kept off by the `ApplicationSet`),
- `ApplyTriggerReady=True`, still no `gitops-reverser` field manager, and no
- finalizer on the trigger.
+ `ApplierClaimReady=True`, still no `gitops-reverser` field manager, and no
+ finalizer on the applier claim.
### Spec D — arming the landmine (phase 2)
diff --git a/docs/spec/gitpath-foreign-content-stringency.md b/docs/spec/gitpath-foreign-content-stringency.md
index 006634814..c9e3430f0 100644
--- a/docs/spec/gitpath-foreign-content-stringency.md
+++ b/docs/spec/gitpath-foreign-content-stringency.md
@@ -22,7 +22,7 @@
> [plan_flush.go](../../internal/git/plan_flush.go), enforced at the top of `flush`.
> - **Bootstrap** — a fully-commented
> [.gittargetignore](../../internal/git/bootstrapped-repo-template/.gittargetignore) staged
-> alongside README.md / .sops.yaml. A resync into a folder an apply trigger applies, which the
+> alongside README.md / .sops.yaml. A resync into a coordinated folder, which the
> branch does not hold yet, commits these files even with no object, so the applier is never
> asked to apply a missing path (`stageResyncFolder` in
> [resync_flush.go](../../internal/git/resync_flush.go)).
diff --git a/docs/spec/manifest-system.md b/docs/spec/manifest-system.md
index 53203e403..8a48e529e 100644
--- a/docs/spec/manifest-system.md
+++ b/docs/spec/manifest-system.md
@@ -71,10 +71,10 @@ API source (`Accept`), does report a type it cannot follow, or a namespace outsi
the GitTarget's scope, as a refusal.
**An object another applier owns stays out of a folder an applier applies.** When a
-GitTarget names an apply trigger, an object another applier owns (Flux's owner labels, or
+GitTarget names an applier claim, an object another applier owns (Flux's owner labels, or
Argo CD's tracking annotation) is not mirrored into its folder, and the sweep
-leaves its document alone: two appliers applying one object fight each other. A
-folder with no trigger mirrors such objects as before.
+leaves its document alone: two appliers applying one object fight each other. An
+uncoordinated folder mirrors such objects as before.
**At most one editable document per resource identity.** Two documents claiming the
same identity is not a prune opportunity, it is an invalid repository state: the
@@ -154,7 +154,7 @@ So when something changes Git *out of band* — an external push, a human deleti
files on the branch — **nothing re-runs the path's reconcile to bring Git back in
line.** The drift is detected but not acted on.
-A folder an apply trigger applies is the exception, in the other direction: when
+A coordinated folder is the exception, in the other direction: when
the branch worker learns of a commit it did not push, it asks the applier to apply
it (a `ForeignCommit`), so the cluster follows Git.
diff --git a/docs/spec/reconcile-via-watchlist-mark-and-sweep.md b/docs/spec/reconcile-via-watchlist-mark-and-sweep.md
index 3ada850fd..1d4b85147 100644
--- a/docs/spec/reconcile-via-watchlist-mark-and-sweep.md
+++ b/docs/spec/reconcile-via-watchlist-mark-and-sweep.md
@@ -283,7 +283,7 @@ first committed. The plan above decides two things on that tree: the upserts tha
changed it, and the documents the sweep removed (by identity, under the target's
`prune.mode`). The write then carries only those actions. An object whose upsert
changed nothing is no action, and a resync that decided nothing settles at once
-and is never pushed. A resync into a folder an apply trigger applies that the
+and is never pushed. A resync into a coordinated folder that the
branch does not hold yet also decides the folder's bootstrap files, with or
without objects, so the applier is never asked to apply a missing path.
@@ -303,7 +303,7 @@ an orphan. The write-side mechanics are in
## Objects another applier owns
-In a folder an apply trigger applies (a `GitTarget` with `spec.applyTriggerRef`),
+In a coordinated folder (a `GitTarget` with `spec.applierClaimRef`),
an object another applier owns is outside the resync. Mirrored into the folder,
it would be applied by this folder's applier too, and two appliers applying one
object fight each other.
@@ -313,12 +313,12 @@ object fight each other.
a `Kustomization`), else Argo CD's tracking annotation
(`argocd.argoproj.io/tracking-id`, naming an `Application`). It stamps the owner
on every snapshot entry and live event.
-- A snapshot entry owned by an applier other than the one the folder's trigger
- holds is neither upserted nor treated as missing, so the sweep leaves its
+- A snapshot entry owned by an applier other than the one the folder's applier
+ claim holds is neither upserted nor treated as missing, so the sweep leaves its
document alone, under `prune.mode: Always` too. A live event for it is not
mirrored.
- An object nobody owns, or the folder's own applier owns, is a member as before.
- A folder with no trigger excludes nothing.
+ An uncoordinated folder excludes nothing.
- Each exclusion is counted in `apply_ownership_conflicts_total` and logged once
per object; see
[Interpreting metrics](../interpreting-metrics.md#apply-requests-and-the-push-receiver).
@@ -352,7 +352,7 @@ snapshot is ever parked.
Three rules keep this from holding a resync forever:
-- A branch with no trigger never waits: nothing applies its commits.
+- A branch with no applier claim never waits: nothing applies its commits.
- A collection's initial snapshot is exempt: the first snapshot of each collection
a `GitTarget` watches, until one of them settles (applied, refused or failed; a
discarded or superseded one does not settle). The watch side tracks this per
diff --git a/docs/spec/status-conditions-guide.md b/docs/spec/status-conditions-guide.md
index a36330470..e5a5aa2b4 100644
--- a/docs/spec/status-conditions-guide.md
+++ b/docs/spec/status-conditions-guide.md
@@ -219,7 +219,7 @@ const (
TypeGitPathAccepted = "GitPathAccepted"
TypeRenderMatchesLive = "RenderMatchesLive"
TypeLayoutResolved = "LayoutResolved"
- TypeApplyTriggerReady = "ApplyTriggerReady" // only when spec names an apply trigger
+ TypeApplierClaimReady = "ApplierClaimReady" // only when spec names an applier claim
)
```
@@ -300,29 +300,30 @@ Canonical reads:
existed so a `sourceNamespace: "*"` against a policy admitting nothing could not look healthy while
mirroring nothing; `"*"` is now one cluster-wide watch, which cannot resolve to an empty set.
-### Apply triggers
+### Applier claims
-`FluxTrigger` and `ArgoCDTrigger` use the trio with `Configured` as their gate, and a `FluxTrigger`
-adds `Held` as a second one.
+`FluxClaim` and `ArgoCDClaim` use the trio with `Configured` as their gate, and `Held` as a second
+one.
Canonical reads:
-- **not referenced** (no `GitTarget` names the trigger): `Configured=Unknown` and `Ready=True`, both
+- **not referenced** (no `GitTarget` names the applier claim): `Configured=Unknown` and `Ready=True`, both
reason `NotReferenced`, `Reconciling=False`, `Stalled=False`. As for a `GitTarget` with nothing to
mirror, doing nothing when nothing is asked is converged, and a Flux health check that waits on
- the trigger must not wait for the `GitTarget` that will name it
-- holding its applier: `Configured=True`, for a `FluxTrigger` `Held=True` (`Suspended` or
+ the applier claim must not wait for the `GitTarget` that will name it
+- holding its applier: `Configured=True`, for a `FluxClaim` `Held=True` (`Suspended` or
`Applying`), and `Ready=True`
- applier or `KubeProvider` missing (`ApplierNotFound`, `KubeProviderNotFound`):
`Ready=False`, `Reconciling=True`, `Stalled=False`, because Flux and Argo CD objects are often
- created after the trigger. Every other `Configured=False` reason is `Stalled=True`
+ created after the applier claim. Every other `Configured=False` reason is `Stalled=True`
- an operation past its time (`status.operation.phase: Unresolved`): `Ready=False`,
`Reconciling=True`, reason `OperationUnresolved`. It clears when the operation completes
-- a `FluxTrigger` whose suspend is not handled yet, or that is handing its `Kustomization` back:
+- a `FluxClaim` whose suspend is not handled yet, or that is handing its `Kustomization` back:
`Held=False` (`Suspending`, `Releasing`), `Ready=False`, `Reconciling=True`
-A `GitTarget` naming a trigger gains `ApplyTriggerReady`, a gate on its own trio. `SourceMismatch`,
-or a trigger that is itself `Stalled`, stalls the target; a missing trigger (`ApplyTriggerNotFound`)
+A `GitTarget` naming an applier claim gains `ApplierClaimReady`, a gate on its own trio.
+`SourceMismatch`, or an applier claim that is itself `Stalled`, stalls the target; a missing applier
+claim (`ApplierClaimNotFound`)
or one that is not `Ready` yet keeps it `Reconciling`.
### CommitRequest (one-shot)
diff --git a/hack/crdfields/fields.yaml b/hack/crdfields/fields.yaml
index 0f4574227..988001580 100644
--- a/hack/crdfields/fields.yaml
+++ b/hack/crdfields/fields.yaml
@@ -117,7 +117,7 @@ kinds:
- path: allowCrossNamespace.appliers
default: "`false`"
doc: >-
- Whether triggers may name a `Kustomization` or `Application` in another namespace than
+ Whether applier claims may name a `Kustomization` or `Application` in another namespace than
their own
- path: client.qps
default: "`20`"
@@ -187,12 +187,12 @@ kinds:
doc: >-
Which deletions reach Git. See
[deletion policy](#deletion-policy-specprunemode)
- - path: applyTriggerRef
+ - path: applierClaimRef
default: the applier applies on its own schedule
doc: >-
- The `FluxTrigger` or `ArgoCDTrigger`, in this namespace, holding the applier of this
+ The `FluxClaim` or `ArgoCDClaim`, in this namespace, holding the applier of this
folder, which GitOps Reverser then asks to apply. See
- [who applies this folder](#who-applies-this-folder-specapplytriggerref)
+ [who applies this folder](#who-applies-this-folder-specapplierclaimref)
- path: onRefusal
doc: >-
What happens to an edit with no legal destination. See
@@ -256,35 +256,35 @@ kinds:
Label selector the API server applies to the item's LIST and WATCH. See
[selecting objects by label](#selecting-objects-by-label-rulesobjectselector)
- - kind: FluxTrigger
- title: "`FluxTrigger` (namespaced): the Flux `Kustomization` GitOps Reverser asks to apply"
- intro: "The references are immutable: to hold another `Kustomization`, delete the trigger and create a new one."
+ - kind: FluxClaim
+ title: "`FluxClaim` (namespaced): the Flux `Kustomization` GitOps Reverser asks to apply"
+ intro: "The references are immutable: to hold another `Kustomization`, delete the applier claim and create a new one."
fields:
- path: kubeProviderRef
doc: >-
- The `KubeProvider` whose cluster holds the `Kustomization`: the trigger's own namespace, or
+ The `KubeProvider` whose cluster holds the `Kustomization`: the applier claim's own namespace, or
`namespace` names one whose `accessFrom` admits it
- path: clusterProviderRef
skip: replaced by `kubeProviderRef`, kept in the schema only so admission refuses it, naming `kubeProviderRef`
- path: kustomizationRef
doc: >-
- The `Kustomization` that applies the folders of the `GitTarget`s naming this trigger. See
- [who applies this folder](#who-applies-this-folder-specapplytriggerref)
+ The `Kustomization` that applies the folders of the `GitTarget`s naming this applier claim. See
+ [who applies this folder](#who-applies-this-folder-specapplierclaimref)
- - kind: ArgoCDTrigger
- title: "`ArgoCDTrigger` (namespaced): the Argo CD `Application` GitOps Reverser asks to sync"
- intro: "The references are immutable: to hold another `Application`, delete the trigger and create a new one."
+ - kind: ArgoCDClaim
+ title: "`ArgoCDClaim` (namespaced): the Argo CD `Application` GitOps Reverser asks to sync"
+ intro: "The references are immutable: to hold another `Application`, delete the applier claim and create a new one."
fields:
- path: kubeProviderRef
doc: >-
- The `KubeProvider` whose cluster holds the `Application`: the trigger's own namespace, or
+ The `KubeProvider` whose cluster holds the `Application`: the applier claim's own namespace, or
`namespace` names one whose `accessFrom` admits it
- path: clusterProviderRef
skip: replaced by `kubeProviderRef`, kept in the schema only so admission refuses it, naming `kubeProviderRef`
- path: applicationRef
doc: >-
The `Application`, with automated sync off, that syncs the folders of the `GitTarget`s
- naming this trigger. See [who applies this folder](#who-applies-this-folder-specapplytriggerref)
+ naming this applier claim. See [who applies this folder](#who-applies-this-folder-specapplierclaimref)
- kind: CommitRequest
title: "`CommitRequest` (namespaced): save now"
diff --git a/internal/applier/applier.go b/internal/applier/applier.go
index 77014ad23..f8fd5d00b 100644
--- a/internal/applier/applier.go
+++ b/internal/applier/applier.go
@@ -7,8 +7,8 @@ commit is safe to apply. The worker decides WHEN; this package decides HOW, off
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 held with automated sync off (an ArgoCDTrigger's
+(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".
@@ -40,10 +40,10 @@ import (
)
// 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
+// the applier claim's Configured check (NewClient) pick one by the applier's kind, and use nothing else of
// it.
type Client interface {
- // Validate answers a trigger's Configured condition: the applier exists, applies a source GitOps
+ // 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
@@ -69,13 +69,13 @@ type Ask struct {
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
+ // 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
- // 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
+ // Held reports, from a fresh read, whether the applier claim holds its Flux Kustomization suspended
+ // (Held=True), and errs when it does 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 holds; the Argo CD client never asks it.
Held func(ctx context.Context) error
@@ -140,7 +140,7 @@ func selfSubjectAccessReviewGVR() schema.GroupVersionResource {
// Outcome is what one request did.
type Outcome string
-// Configured and ApplyTriggerReady reasons (docs/configuration.md, "Who applies this folder").
+// 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.
const (
@@ -154,12 +154,12 @@ const (
// 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
+ // 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 trigger writes nothing and is Stalled, so never Ready.
+ // 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"
@@ -168,7 +168,7 @@ const (
ReasonCheckFailed = fluxmeta.FailedReason
)
-// Verdict is the answer Validate gives for a trigger's Configured condition.
+// Verdict is the answer Validate gives for an applier claim's Configured condition.
type Verdict struct {
// OK is Configured=True.
OK bool
@@ -196,7 +196,7 @@ type Source struct {
}
// Started is the operation one Request started, may have started (Unconfirmed), or read back from
-// the trigger's record (Recovered), for Progress to follow.
+// the applier claim's record (Recovered), for Progress to follow.
type Started struct {
// Outcome is what was asked, if anything.
Outcome Outcome
@@ -210,7 +210,7 @@ type Started struct {
// 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
+ // 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
@@ -235,22 +235,22 @@ type Started struct {
}
// 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
+// 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 Binding struct {
RepositoryURL string
Branch 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
+// 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 BindingMismatch(trigger v1alpha3.ApplyTrigger, want Binding) string {
- url, branch, known := trigger.SourceBinding()
+func BindingMismatch(claim v1alpha3.ApplierClaim, want Binding) string {
+ url, branch, known := claim.SourceBinding()
if !known {
return ""
}
- return bindingMismatch(trigger, url, branch, want)
+ return bindingMismatch(claim, url, branch, want)
}
// AccessCheck reports whether the KubeProvider identity may perform attrs.
diff --git a/internal/applier/argocd.go b/internal/applier/argocd.go
index 9b8e403cc..ff10095e5 100644
--- a/internal/applier/argocd.go
+++ b/internal/applier/argocd.go
@@ -24,8 +24,8 @@ import (
// The Argo CD half of the applier. Its standard is full control: the Application has automated
// sync off, so its refresh only compares, and every sync it runs is one GitOps Reverser started,
-// pinned to a commit the branch worker checked. The ArgoCDTrigger holds automated sync off, as a
-// FluxTrigger holds its Kustomization suspended: it owns spec.syncPolicy.automated.enabled: false
+// pinned to a commit the branch worker checked. The ArgoCDClaim holds automated sync off, as a
+// FluxClaim holds its Kustomization suspended: it owns spec.syncPolicy.automated.enabled: false
// with a server-side apply under SuspendFieldManager, switches it off again when somebody switches
// it on, and hands it back when it lets go. The switch is not its to hold when the Application is
// generated by an ApplicationSet, or another field manager applies it (switchOwner): it is then
@@ -62,13 +62,13 @@ const (
kindApplicationSet = "ApplicationSet"
)
-// ReasonOwnedByApplicationSet is Configured=False on an ArgoCDTrigger whose Application has
+// 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
-// trigger leaves it alone rather than fight the ApplicationSet over it.
+// applier claim leaves it alone rather than fight the ApplicationSet over it.
const ReasonOwnedByApplicationSet = "OwnedByApplicationSet"
-// ReasonAutomatedSyncOwnedElsewhere is Configured=False on an ArgoCDTrigger whose Application has
+// 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.
@@ -187,7 +187,7 @@ func (a *ArgoCD) Request(ctx context.Context, ref types.NamespacedName, ask Ask)
return Started{}, errStale
}
if err := record(ctx); err != nil {
- return Started{}, fmt.Errorf("record the operation on its trigger before syncing %s %s: %w",
+ return Started{}, fmt.Errorf("record the operation on its applier claim before syncing %s %s: %w",
kindApplication, ref, err)
}
// Recording can wait on the status writer and the API server, and the branch can move on
@@ -208,8 +208,8 @@ func (a *ArgoCD) Request(ctx context.Context, ref types.NamespacedName, ask Ask)
// readControlled reads the Application, and refuses with a permanent error one that does not apply
// want, or that has automated sync on with the switch somebody else's. One with automated sync on
-// whose switch the trigger holds fails without being refused: somebody switched it on, the
-// trigger's reconciler switches it off again, and the worker asks again after its backoff.
+// whose switch the applier claim holds fails without being refused: somebody switched it on, the
+// applier claim's reconciler switches it off again, and the worker asks again after its backoff.
func (a *ArgoCD) readControlled(
ctx context.Context, ref types.NamespacedName, want Binding,
) (*unstructured.Unstructured, error) {
@@ -224,7 +224,7 @@ func (a *ArgoCD) readControlled(
if owner := switchOwner(app); owner.reason != "" {
return nil, permanentError{errors.New(owner.refusal(ref))}
}
- return nil, fmt.Errorf("%s %s has automated sync on, so it syncs on its own: its ArgoCDTrigger "+
+ return nil, fmt.Errorf("%s %s has automated sync on, so it syncs on its own: its ArgoCDClaim "+
"switches it off again, and is asked again after that", kindApplication, ref)
}
return app, nil
@@ -430,12 +430,12 @@ func (a *ArgoCD) settleUnseen(
return Progress{Outcome: git.ApplyFailed, Message: message}, nil
}
-// Validate answers Configured for an ArgoCDTrigger's Application: that it exists in its cluster and
+// 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
-// trigger may switch it off, and that this identity may update it (a request is an update) and,
-// when the switch is the trigger's to hold, patch it (the hold is a server-side apply). Which
+// 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 trigger to check against what they write.
+// the applier claim to check against what they write.
func (a *ArgoCD) Validate(ctx context.Context, ref types.NamespacedName) Verdict {
app, err := a.client.Resource(applicationGVR()).Namespace(ref.Namespace).Get(ctx, ref.Name, metav1.GetOptions{})
if err != nil {
@@ -545,7 +545,7 @@ type switchHolder struct {
// switchOwner reads who owns the Application's automated sync switch other than GitOps Reverser.
// The ApplicationSet that generated it comes first: it replaces the whole spec, whatever field
// managers say. A manager that wrote the switch with an update (Argo CD's UI, `kubectl edit`, a
-// client-side apply) wrote it once, and the trigger takes it back.
+// client-side apply) wrote it once, and the applier claim takes it back.
func switchOwner(app *unstructured.Unstructured) switchHolder {
for _, ref := range app.GetOwnerReferences() {
group, _, _ := strings.Cut(ref.APIVersion, "/")
@@ -594,7 +594,7 @@ func managedPath(path []string) []string {
}
// AutomatedSync is how an Application's automated sync stands with GitOps Reverser, for its
-// ArgoCDTrigger's reconciler.
+// ArgoCDClaim's reconciler.
type AutomatedSync struct {
// On is automated sync on, by Argo CD's own rule.
On bool
@@ -654,7 +654,7 @@ type Released struct {
// 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 trigger held it (the trigger's
+// - 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
diff --git a/internal/applier/argocd_guide_test.go b/internal/applier/argocd_guide_test.go
index 6066ace83..157e0b88b 100644
--- a/internal/applier/argocd_guide_test.go
+++ b/internal/applier/argocd_guide_test.go
@@ -23,7 +23,7 @@ const argoCDGuide = "../../docs/bi-directional-argocd.md"
// TestArgoCDGuide_TheCompleteExampleIsAccepted reads the guide's complete example and checks it
// against the code that validates and drives an Argo CD applier: the GitOps Reverser objects decode
// into the API types with no unknown field, Configured comes out True for the Application as
-// written, the trigger's source binds the GitTarget, and a request sets a sync pinned to the commit. It also holds the example to the guide's
+// written, the applier claim's source binds the GitTarget, and a request sets a sync pinned to the commit. It also holds the example to the guide's
// own advice: automated sync off, prune kept, and a recursive directory source.
func TestArgoCDGuide_TheCompleteExampleIsAccepted(t *testing.T) {
objects := guideExampleObjects(t, argoCDGuide)
@@ -32,15 +32,15 @@ func TestArgoCDGuide_TheCompleteExampleIsAccepted(t *testing.T) {
var receiver v1alpha3.GitReceiver
var branch v1alpha3.GitBranch
var target v1alpha3.GitTarget
- var trigger v1alpha3.ArgoCDTrigger
+ var claim v1alpha3.ArgoCDClaim
var secret corev1.Secret
decodeStrict(t, objects, "GitProvider", &provider)
decodeStrict(t, objects, "GitReceiver", &receiver)
decodeStrict(t, objects, "GitBranch", &branch)
decodeStrict(t, objects, "GitTarget", &target)
- decodeStrict(t, objects, v1alpha3.ApplyTriggerKindArgoCD, &trigger)
+ decodeStrict(t, objects, v1alpha3.ApplierClaimKindArgoCD, &claim)
decodeStrict(t, objects, "Secret", &secret)
- exampleReachesItsApplier(t, objects, &trigger)
+ exampleReachesItsApplier(t, objects, &claim)
app := one(t, objects, kindApplication)
// The GitOps Reverser objects are wired to each other.
@@ -56,10 +56,10 @@ func TestArgoCDGuide_TheCompleteExampleIsAccepted(t *testing.T) {
assert.Equal(t, secret.Name, receiver.Spec.SecretRef.Name)
assert.Equal(t, receiver.Namespace, secret.Namespace, "a GitReceiver reads its token in its own namespace")
assert.NotEmpty(t, secret.StringData[v1alpha3.ReceiverTokenKey], "the receiver reads the token key")
- require.NotNil(t, target.Spec.ApplyTriggerRef)
- assert.Equal(t, v1alpha3.ApplyTriggerReference{Kind: v1alpha3.ApplyTriggerKindArgoCD, Name: trigger.Name},
- *target.Spec.ApplyTriggerRef)
- assert.Equal(t, target.Namespace, trigger.Namespace, "a GitTarget names a trigger in its own namespace")
+ require.NotNil(t, target.Spec.ApplierClaimRef)
+ assert.Equal(t, v1alpha3.ApplierClaimReference{Kind: v1alpha3.ApplierClaimKindArgoCD, Name: claim.Name},
+ *target.Spec.ApplierClaimRef)
+ assert.Equal(t, target.Namespace, claim.Namespace, "a GitTarget names an applier claim in its own namespace")
assert.Equal(t, v1alpha3.RefusalActionReapply, target.Spec.OnRefusal)
// The Application follows the guide's advice.
@@ -69,7 +69,7 @@ func TestArgoCDGuide_TheCompleteExampleIsAccepted(t *testing.T) {
assert.True(t, recurse, "GitOps Reverser writes nested folders")
// The applier, as written, is accepted, binds the GitTarget, and is driven.
- ref := trigger.Applier()
+ ref := claim.Applier()
assert.Equal(t, types.NamespacedName{Namespace: app.GetNamespace(), Name: app.GetName()}, ref)
want := Binding{RepositoryURL: provider.Spec.URL, Branch: branch.Spec.Branch}
client := newArgoSource(app)
@@ -77,10 +77,10 @@ func TestArgoCDGuide_TheCompleteExampleIsAccepted(t *testing.T) {
verdict := argo.Validate(context.Background(), ref)
assert.True(t, verdict.OK, "Configured for the guide's example: %s: %s", verdict.Reason, verdict.Message)
require.NotNil(t, verdict.Source)
- trigger.Status.Source = &v1alpha3.ArgoCDTriggerSource{
+ claim.Status.Source = &v1alpha3.ArgoCDClaimSource{
RepoURL: verdict.Source.URL, TargetRevision: verdict.Source.Revision,
}
- assert.Empty(t, BindingMismatch(&trigger, want), "the trigger's Application syncs what the GitTarget writes")
+ assert.Empty(t, BindingMismatch(&claim, want), "the applier claim's Application syncs what the GitTarget writes")
started, err := argo.Request(context.Background(), ref, Ask{
Want: want, Commit: testCommit, Reason: v1alpha3.ApplyReasonForeignCommit, Token: requestToken(stampTime),
diff --git a/internal/applier/argocd_test.go b/internal/applier/argocd_test.go
index d58e4873a..459d088c3 100644
--- a/internal/applier/argocd_test.go
+++ b/internal/applier/argocd_test.go
@@ -598,9 +598,9 @@ func TestArgoCDRequest_RefusesWhatIsNotUnderControl(t *testing.T) {
}{
{name: "another branch", app: applicationObj(testRepoURL, "release", nil), commit: testCommit,
wantErr: `targets revision "release", not branch main`, wantPerm: true},
- {name: "automated sync on, which its trigger switches off again", commit: testCommit,
+ {name: "automated sync on, which its applier claim switches off again", commit: testCommit,
app: applicationObj(testRepoURL, testBranch, map[string]any{"automated": map[string]any{}}),
- wantErr: "its ArgoCDTrigger switches it off again"},
+ wantErr: "its ArgoCDClaim switches it off again"},
{name: "automated sync on, generated by an ApplicationSet", commit: testCommit,
app: generated(automatedApp()),
wantErr: "is generated by ApplicationSet argocd/platforms", wantPerm: true},
@@ -693,7 +693,7 @@ func TestArgoCDValidate_Reasons(t *testing.T) {
wantReason: ReasonUnsupportedSource,
wantMessage: "names no spec.source",
},
- {name: "automated sync on, which the trigger switches off",
+ {name: "automated sync on, which the applier claim switches off",
objs: []runtime.Object{applicationObj(testRepoURL, testBranch, map[string]any{
"automated": map[string]any{"enabled": true},
})},
@@ -936,17 +936,17 @@ func TestArgoCD_ASyncWhoseOperationWasRemovedEnds(t *testing.T) {
}
// TestNewClient_PicksTheClientByKind: the dispatcher and the Configured check use the kind of
-// applier the trigger holds.
+// applier the applier claim holds.
func TestNewClient_PicksTheClientByKind(t *testing.T) {
client := newArgoSource()
assert.IsType(t, &ArgoCD{}, NewClient(v1alpha3.ApplierKindApplication, client))
assert.IsType(t, &Flux{}, NewClient(v1alpha3.ApplierKindKustomization, client))
}
-// TestArgoCDTrigger_BindingMismatchReadsTheTargetRevision: an Application binds a GitTarget when its
+// TestArgoCDClaim_BindingMismatchReadsTheTargetRevision: an Application binds a GitTarget when its
// targetRevision names the GitTarget's branch, as its name or as refs/heads/ and the name. HEAD, an
// empty targetRevision (which is HEAD), a tag and another branch never follow it.
-func TestArgoCDTrigger_BindingMismatchReadsTheTargetRevision(t *testing.T) {
+func TestArgoCDClaim_BindingMismatchReadsTheTargetRevision(t *testing.T) {
for revision, want := range map[string]string{
testBranch: "",
"refs/heads/" + testBranch: "",
@@ -955,9 +955,9 @@ func TestArgoCDTrigger_BindingMismatchReadsTheTargetRevision(t *testing.T) {
"": `applies branch "HEAD"`,
"v1.0.0": `applies branch "v1.0.0"`,
} {
- trigger := &v1alpha3.ArgoCDTrigger{ObjectMeta: metav1.ObjectMeta{Name: "platform"}}
- trigger.Status.Source = &v1alpha3.ArgoCDTriggerSource{RepoURL: testRepoURL + ".git", TargetRevision: revision}
- got := BindingMismatch(trigger, testBinding)
+ claim := &v1alpha3.ArgoCDClaim{ObjectMeta: metav1.ObjectMeta{Name: "platform"}}
+ claim.Status.Source = &v1alpha3.ArgoCDClaimSource{RepoURL: testRepoURL + ".git", TargetRevision: revision}
+ got := BindingMismatch(claim, testBinding)
if want == "" {
assert.Empty(t, got, revision)
continue
diff --git a/internal/applier/argosim/argosim.go b/internal/applier/argosim/argosim.go
index 6ea4f4b4d..93001d472 100644
--- a/internal/applier/argosim/argosim.go
+++ b/internal/applier/argosim/argosim.go
@@ -3,7 +3,7 @@
/*
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. The fake dynamic client
-keeps no managedFields, and the ArgoCDTrigger's hold is decided by them.
+keeps no managedFields, and the ArgoCDClaim's hold is decided by them.
*/
package argosim
@@ -35,7 +35,7 @@ func ApplicationGVR() schema.GroupVersionResource {
func switchPath() []string { return []string{"spec", "syncPolicy", "automated", "enabled"} }
// Serve makes client serve the patches of an Application as the API server does for the switch,
-// as measured in envtest (internal/controller/argocdtrigger_apiserver_test.go):
+// as measured in envtest (internal/controller/argocdclaim_apiserver_test.go):
//
// - A write that changes the switch's value takes it from every other owner.
// - An update (a merge patch) that writes the value the switch holds takes nothing.
diff --git a/internal/applier/argosim/argosim_test.go b/internal/applier/argosim/argosim_test.go
index aa210c0a8..f49e0bc91 100644
--- a/internal/applier/argosim/argosim_test.go
+++ b/internal/applier/argosim/argosim_test.go
@@ -18,7 +18,7 @@ import (
dynamicfake "k8s.io/client-go/dynamic/fake"
)
-// The rules Serve applies are the ones internal/controller/argocdtrigger_apiserver_test.go measures
+// The rules Serve applies are the ones internal/controller/argocdclaim_apiserver_test.go measures
// on a real API server; these pin that the fake keeps them.
func application(syncPolicy map[string]any) *unstructured.Unstructured {
diff --git a/internal/applier/dispatcher.go b/internal/applier/dispatcher.go
index dcda4a227..851ea2a85 100644
--- a/internal/applier/dispatcher.go
+++ b/internal/applier/dispatcher.go
@@ -61,7 +61,7 @@ const shortCommitLength = 8
var errNotRunning = errors.New("the apply request dispatcher is not running")
// Clients resolves the dynamic client of the cluster an applier lives in, by KubeProvider
-// name (a trigger's spec.kubeProviderRef). In production it is
+// name (an applier claim's spec.kubeProviderRef). In production it is
// (*watch.Manager).ApplierDynamicClient.
type Clients func(ctx context.Context, clusterID string) (dynamic.Interface, error)
@@ -101,7 +101,7 @@ const pollTimeout = 10 * time.Second
//
// 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
+// 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
@@ -125,7 +125,7 @@ type Dispatcher struct {
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
+ // APIReader reads a FluxClaim 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
@@ -138,16 +138,16 @@ type Dispatcher struct {
stopped bool
waiting []git.ApplyRequest
- // following counts the attempts carrying out or following an operation, by trigger and token
+ // following counts the attempts carrying out or following an operation, by claim and token
// (Following).
followMu sync.Mutex
following map[followKey]int
}
-// followKey is one operation of one trigger, by its request token.
+// followKey is one operation of one applier claim, by its request token.
type followKey struct {
- trigger git.TriggerRef
- token string
+ claim git.ApplierClaimRef
+ token string
}
var (
@@ -239,14 +239,14 @@ func (d *Dispatcher) launch(ctx context.Context, req git.ApplyRequest) {
d.carryOut(ctx, req)
}
-// followRecovered follows an operation recorded on the trigger to its end, asking the applier for
+// followRecovered follows an operation recorded on the applier claim to its end, asking the applier for
// nothing: the request was made by an earlier process, or by somebody else. A cluster that cannot
// be reached is tried again until the worker abandons the attempt, because the record can only be
// completed by following it.
func (d *Dispatcher) followRecovered(ctx context.Context, req git.ApplyRequest) {
log := d.log.WithValues("applier", req.Applier.String(), "branch", req.Branch.String(),
"reason", req.Reason, "commit", req.Commit, "recovered", true)
- defer d.follows(req.Applier.Trigger, req.Recovered.Token)()
+ defer d.follows(req.Applier.ApplierClaim, req.Recovered.Token)()
interval := d.PollInterval
for {
applierClient, err := d.applierClients(ctx, req.Applier.Cluster)
@@ -280,21 +280,21 @@ func recoveredStarted(req git.ApplyRequest) Started {
}
// 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
+// 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 (d *Dispatcher) Following(trigger git.TriggerRef, token string) bool {
+func (d *Dispatcher) Following(claim git.ApplierClaimRef, token string) bool {
d.followMu.Lock()
defer d.followMu.Unlock()
- return d.following[followKey{trigger: trigger, token: token}] > 0
+ return d.following[followKey{claim: claim, token: token}] > 0
}
-// follows registers an attempt as following the operation with token on trigger, and returns the
+// follows registers an attempt as following the operation with token on claim, and returns the
// func that ends it.
-func (d *Dispatcher) follows(trigger git.TriggerRef, token string) func() {
- key := followKey{trigger: trigger, token: token}
+func (d *Dispatcher) follows(claim git.ApplierClaimRef, token string) func() {
+ key := followKey{claim: claim, token: token}
d.followMu.Lock()
defer d.followMu.Unlock()
if d.following == nil {
@@ -316,7 +316,7 @@ func (d *Dispatcher) carryOut(ctx context.Context, req git.ApplyRequest) {
if req.Token == "" {
req.Token = requestToken(d.Now())
}
- defer d.follows(req.Applier.Trigger, req.Token)()
+ defer d.follows(req.Applier.ApplierClaim, req.Token)()
res, op := d.attempt(ctx, req)
req.Report(res)
if op == nil {
@@ -374,7 +374,7 @@ func (d *Dispatcher) attempt(ctx context.Context, req git.ApplyRequest) (git.App
applier := d.client(req.Applier.Kind, applierClient)
started, err := applier.Request(ctx, applierName(req.Applier), Ask{
Want: want, Commit: req.Commit, Reason: req.Reason, Token: req.Token, Current: req.Current, Record: req.Record,
- Held: func(ctx context.Context) error { return d.triggerHolds(ctx, req.Applier.Trigger) },
+ Held: func(ctx context.Context) error { return d.claimHolds(ctx, req.Applier.ApplierClaim) },
})
switch {
case errors.Is(err, errStale):
@@ -433,8 +433,8 @@ func (d *Dispatcher) client(kind string, applierClient dynamic.Interface) Client
// An operation that has not completed within UnresolvedAfter is reported Unresolved, once, and is
// read on at a doubling interval: the apply can still run, and only its completion closes the
// window. The client is told it is overdue, so a Flux Kustomization is suspended at once. The
-// applier object being gone completes it as Failed, and so does its trigger being deleted: the
-// trigger's own finalizer suspends the Kustomization and hands it back, and a suspend from here
+// applier object being gone completes it as Failed, and so does its applier claim being deleted: the
+// applier claim's own finalizer suspends the Kustomization and hands it back, and a suspend from here
// would undo that. It returns false, and reports nothing more, when the worker abandons the
// attempt or the dispatcher stops; a Flux Kustomization this process resumed is suspended first.
func (d *Dispatcher) follow(ctx context.Context, req git.ApplyRequest, op *operation) (git.ApplyResult, bool) {
@@ -490,7 +490,7 @@ func (d *Dispatcher) letGo(ctx context.Context, req git.ApplyRequest, op *operat
// still be resumed, and nothing else follows the operation. It is treated as overdue, so it is
// suspended at once, and read until that suspend's token is handled, within AttemptTimeout and
// whether or not the dispatcher stops meanwhile. It reports nothing: nobody waits for the report.
-// A trigger being deleted completes it without a suspend, since the trigger hands its Kustomization
+// An applier claim being deleted completes it without a suspend, since the applier claim hands its Kustomization
// back itself.
func (d *Dispatcher) suspendAbandoned(ctx context.Context, req git.ApplyRequest, op *operation) {
if op.started.Outcome != OutcomeResumed {
@@ -508,7 +508,7 @@ func (d *Dispatcher) suspendAbandoned(ctx context.Context, req git.ApplyRequest,
}
if !wait(ctx, nil, d.PollInterval) {
op.log.Info("the worker abandoned the operation, and its Kustomization was not seen suspended again "+
- "in time; the trigger's reconciler suspends it", "suspendToken", op.started.SuspendToken)
+ "in time; the applier claim's reconciler suspends it", "suspendToken", op.started.SuspendToken)
return
}
}
@@ -522,7 +522,7 @@ const stopSuspendTimeout = 5 * time.Second
// as the dispatcher stops mid-apply: otherwise it stays resumed until the next process recovers the
// operation, and Flux applies a new artifact, or a drift reconcile reverts live edits, on its own in
// between. It does not wait for the suspend to be handled; the next process recovers a suspend of
-// its own that this one did not report, as it does after a crash. A trigger being deleted hands
+// its own that this one did not report, as it does after a crash. An applier claim being deleted hands
// its Kustomization back itself, and a suspend from here would undo that.
func (d *Dispatcher) suspendOnStop(ctx context.Context, req git.ApplyRequest, op *operation) {
flux, isFlux := op.applier.(*Flux)
@@ -531,7 +531,7 @@ func (d *Dispatcher) suspendOnStop(ctx context.Context, req git.ApplyRequest, op
}
ctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), stopSuspendTimeout)
defer cancel()
- if released, err := d.triggerReleased(ctx, req.Applier.Trigger); err != nil || released {
+ if released, err := d.claimReleased(ctx, req.Applier.ApplierClaim); err != nil || released {
return
}
token, err := flux.Suspend(ctx, op.ref)
@@ -543,19 +543,19 @@ func (d *Dispatcher) suspendOnStop(ctx context.Context, req git.ApplyRequest, op
op.log.Info("stopping mid-apply: suspended the Kustomization", "suspendToken", token)
}
-// progress reads an operation's progress once, within pollTimeout. An applier that is gone, or a
-// trigger that is being deleted, completes it as Failed.
+// progress reads an operation's progress once, within pollTimeout. An applier that is gone, or an
+// applier claim that is being deleted, completes it as Failed.
func (d *Dispatcher) progress(ctx context.Context, req git.ApplyRequest, op *operation) (Progress, error) {
read, cancel := context.WithTimeout(ctx, pollTimeout)
defer cancel()
- released, err := d.triggerReleased(read, req.Applier.Trigger)
+ released, err := d.claimReleased(read, req.Applier.ApplierClaim)
if err != nil {
return Progress{}, err
}
if released {
return Progress{
Outcome: git.ApplyFailed,
- Message: "the trigger is being deleted, and hands its applier back",
+ Message: "the applier claim is being deleted, and hands its applier back",
}, nil
}
progress, err := op.applier.Progress(read, op.ref, &op.started)
@@ -565,66 +565,66 @@ func (d *Dispatcher) progress(ctx context.Context, req git.ApplyRequest, op *ope
return progress, err
}
-// TriggerConditionHeld is a FluxTrigger's Held condition: True (reason Suspended) once its
+// ApplierClaimConditionHeld is a FluxClaim'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.
-const TriggerConditionHeld = "Held"
+// applier claim's reconciler sets it.
+const ApplierClaimConditionHeld = "Held"
-// triggerHolds is Ask.Held: it reads the FluxTrigger uncached, and errs unless it is
+// claimHolds is Ask.Held: it reads the FluxClaim uncached, and errs unless it is
// Held=True and not being deleted. Its reconciler is otherwise suspending the Kustomization,
// or about to (an adoption, an external resume it suspends again, a hand-back), and that suspend would land
// on the resume: Flux would then handle the resume's token on a suspended return, and apply
-// nothing. It also errs when another trigger comes first in line for the Kustomization, read
+// nothing. It also errs when another applier claim comes first in line for the Kustomization, read
// uncached as well: the recheck's cached read can still miss one. The attempt fails, and the worker
// asks again after its backoff.
-func (d *Dispatcher) triggerHolds(ctx context.Context, ref git.TriggerRef) error {
- if ref.Kind != v1alpha3.ApplyTriggerKindFlux {
+func (d *Dispatcher) claimHolds(ctx context.Context, ref git.ApplierClaimRef) error {
+ if ref.Kind != v1alpha3.ApplierClaimKindFlux {
return nil
}
- trigger := &v1alpha3.FluxTrigger{}
- if err := d.APIReader.Get(ctx, client.ObjectKey{Namespace: ref.Namespace, Name: ref.Name}, trigger); err != nil {
+ claim := &v1alpha3.FluxClaim{}
+ if err := d.APIReader.Get(ctx, client.ObjectKey{Namespace: ref.Namespace, Name: ref.Name}, claim); err != nil {
return fmt.Errorf("read %s: %w", ref, err)
}
- held := apimeta.FindStatusCondition(trigger.Status.Conditions, TriggerConditionHeld)
- holder, err := HeldByAnother(ctx, d.APIReader, trigger)
+ held := apimeta.FindStatusCondition(claim.Status.Conditions, ApplierClaimConditionHeld)
+ holder, err := HeldByAnother(ctx, d.APIReader, claim)
switch {
case err != nil:
- return fmt.Errorf("check which trigger holds the Kustomization of %s: %w", ref, err)
+ return fmt.Errorf("check which applier claim holds the Kustomization of %s: %w", ref, err)
case holder != nil:
- return fmt.Errorf("FluxTrigger %s/%s no longer holds its Kustomization: %s", ref.Namespace, ref.Name,
- HeldByAnotherMessage(trigger, holder))
- case !trigger.DeletionTimestamp.IsZero():
- return fmt.Errorf("FluxTrigger %s/%s is being deleted, and hands its Kustomization back",
+ return fmt.Errorf("FluxClaim %s/%s no longer holds its Kustomization: %s", ref.Namespace, ref.Name,
+ HeldByAnotherMessage(claim, holder))
+ case !claim.DeletionTimestamp.IsZero():
+ return fmt.Errorf("FluxClaim %s/%s is being deleted, and hands its Kustomization back",
ref.Namespace, ref.Name)
case held == nil || held.Status != metav1.ConditionTrue:
message := "it reports no Held condition yet"
if held != nil {
message = fmt.Sprintf("it is Held=%s, %s: %s", held.Status, held.Reason, held.Message)
}
- return fmt.Errorf("FluxTrigger %s/%s does not hold its Kustomization suspended: %s",
+ return fmt.Errorf("FluxClaim %s/%s does not hold its Kustomization suspended: %s",
ref.Namespace, ref.Name, message)
}
return nil
}
-// triggerReleased reports whether a trigger is gone or being deleted. It reads through APIReader,
+// claimReleased reports whether an applier claim is gone or being deleted. It reads through APIReader,
// uncached in production, because the step that follows may suspend the Kustomization: after a
-// hand-back's release, a suspend from a cached read that still showed the trigger would hold a
-// Kustomization no trigger holds any more.
-func (d *Dispatcher) triggerReleased(ctx context.Context, ref git.TriggerRef) (bool, error) {
- trigger := v1alpha3.NewApplyTrigger(ref.Kind)
- if trigger == nil {
+// hand-back's release, a suspend from a cached read that still showed the applier claim would hold a
+// Kustomization no applier claim holds any more.
+func (d *Dispatcher) claimReleased(ctx context.Context, ref git.ApplierClaimRef) (bool, error) {
+ claim := v1alpha3.NewApplierClaim(ref.Kind)
+ if claim == nil {
return false, nil
}
- err := d.APIReader.Get(ctx, client.ObjectKey{Namespace: ref.Namespace, Name: ref.Name}, trigger)
+ err := d.APIReader.Get(ctx, client.ObjectKey{Namespace: ref.Namespace, Name: ref.Name}, claim)
switch {
case apierrors.IsNotFound(err):
return true, nil
case err != nil:
return false, fmt.Errorf("read %s: %w", ref, err)
}
- return !trigger.GetDeletionTimestamp().IsZero(), nil
+ return !claim.GetDeletionTimestamp().IsZero(), nil
}
// unresolvedBackoff is the factor an Unresolved operation's poll interval grows by on each read.
@@ -774,7 +774,7 @@ func (d *Dispatcher) recheck(ctx context.Context, req git.ApplyRequest) ([]eligi
//
// A KubeProvider's client is shared by every GitTarget that uses the cluster, so a client being
// available is no permission to use it: a GitTarget its KubeProviders do not admit (its own, and
-// its trigger's) would otherwise drive the applier on the strength of another GitTarget's admission.
+// its applier claim's) would otherwise drive the applier on the strength of another GitTarget's admission.
func (d *Dispatcher) recheckTarget(
ctx context.Context, req git.ApplyRequest, candidate git.ApplyTarget,
) (*v1alpha3.GitTarget, error) {
@@ -791,14 +791,14 @@ func (d *Dispatcher) recheckTarget(
log.V(1).Info("GitTarget is no longer the one that named this applier on this branch; not asking for it")
return nil, nil //nolint:nilnil // as above
}
- trigger, err := holdingTrigger(ctx, d.reader, target, req)
- if err != nil || trigger == nil {
+ claim, err := holdingClaim(ctx, d.reader, target, req)
+ if err != nil || claim == nil {
if err == nil {
- log.V(1).Info("GitTarget no longer names a trigger that holds this applier; not asking for it")
+ log.V(1).Info("GitTarget no longer names an applier claim that holds this applier; not asking for it")
}
return nil, err
}
- decision, err := authz.ApplierAdmitted(ctx, d.reader, target, trigger)
+ decision, err := authz.ApplierAdmitted(ctx, d.reader, target, claim)
if err != nil {
return nil, fmt.Errorf("check GitTarget %s against its KubeProviders: %w", candidate.Ref, err)
}
@@ -814,32 +814,32 @@ func (d *Dispatcher) recheckTarget(
return target, nil
}
-// holdingTrigger is the trigger a GitTarget names when it still holds the request's applier, or nil.
-// A trigger in line behind another naming the same applier (Holder) holds none of it.
-func holdingTrigger(
+// holdingClaim is the applier claim a GitTarget names when it still holds the request's applier, or nil.
+// An applier claim in line behind another naming the same applier (Holder) holds none of it.
+func holdingClaim(
ctx context.Context, reader client.Reader, target *v1alpha3.GitTarget, req git.ApplyRequest,
-) (v1alpha3.ApplyTrigger, error) {
- trigger, err := git.ResolveApplyTrigger(ctx, reader, target)
- if err != nil || trigger == nil {
+) (v1alpha3.ApplierClaim, error) {
+ claim, err := git.ResolveApplierClaim(ctx, reader, target)
+ if err != nil || claim == nil {
return nil, err
}
- if !trigger.GetDeletionTimestamp().IsZero() || git.ApplierOf(trigger) != req.Applier {
- return nil, nil //nolint:nilnil // a trigger that holds another applier holds none of this one
+ if !claim.GetDeletionTimestamp().IsZero() || git.ApplierOf(claim) != req.Applier {
+ return nil, nil //nolint:nilnil // an applier claim that holds another applier holds none of this one
}
- holder, err := HeldByAnother(ctx, reader, trigger)
+ holder, err := HeldByAnother(ctx, reader, claim)
if err != nil || holder != nil {
return nil, err
}
- return trigger, nil
+ return claim, nil
}
-// onBranch reports whether a GitTarget still names a trigger, on the request's branch.
+// onBranch reports whether a GitTarget still names an applier claim, on the request's branch.
func onBranch(target *v1alpha3.GitTarget, req git.ApplyRequest) bool {
- return target.Spec.ApplyTriggerRef != nil && git.WritesThrough(target, req.GitBranch)
+ return target.Spec.ApplierClaimRef != nil && git.WritesThrough(target, req.GitBranch)
}
// delivered records a delivered request as an ApplyRequested Event on every GitTarget it was for,
-// each with its own reason. The operation itself is recorded on the trigger, by the branch worker.
+// each with its own reason. The operation itself is recorded on the applier claim, by the branch worker.
func (d *Dispatcher) delivered(req git.ApplyRequest, eligible []eligibleTarget) {
if d.recorder == nil {
return
diff --git a/internal/applier/dispatcher_test.go b/internal/applier/dispatcher_test.go
index 3810a49bc..29d724fbf 100644
--- a/internal/applier/dispatcher_test.go
+++ b/internal/applier/dispatcher_test.go
@@ -62,19 +62,19 @@ var (
testGitBranch = types.NamespacedName{Namespace: targetNS, Name: "platform-" + testBranch}
)
-// Test-only annotations on a GitTarget, which newDispatcherFixture reads to create the trigger the
+// Test-only annotations on a GitTarget, which newDispatcherFixture reads to create the applier claim the
// GitTarget names: the applier it holds ("namespace/name") and the cluster it is in (a source-cluster
// id, "namespace/name" of its KubeProvider).
const (
- testKustomizationAnnotation = "test.gitops-reverser/kustomization"
- testApplicationAnnotation = "test.gitops-reverser/application"
- testTriggerClusterAnnotation = "test.gitops-reverser/trigger-cluster"
+ testKustomizationAnnotation = "test.gitops-reverser/kustomization"
+ testApplicationAnnotation = "test.gitops-reverser/application"
+ testClaimClusterAnnotation = "test.gitops-reverser/trigger-cluster"
)
// gitTarget builds a GitTarget on testBranchKey, mirroring the KubeProvider cluster (localCluster
// when empty). A
-// kustomizationRef makes it name the FluxTrigger of that Kustomization's name, which holds it in the
-// same cluster; nil names no trigger.
+// kustomizationRef makes it name the FluxClaim of that Kustomization's name, which holds it in the
+// same cluster; nil names no applier claim.
func gitTarget(name string, kustomizationRef *fluxmeta.NamespacedObjectReference, cluster string) *v1alpha3.GitTarget {
target := &v1alpha3.GitTarget{
ObjectMeta: metav1.ObjectMeta{Namespace: targetNS, Name: name, UID: uid(name)},
@@ -89,45 +89,45 @@ func gitTarget(name string, kustomizationRef *fluxmeta.NamespacedObjectReference
target.Spec.KubeProviderRef = kubeProviderRef(cluster)
if kustomizationRef != nil {
namesKustomization(target, kustomizationRef.Namespace, kustomizationRef.Name)
- target.Annotations[testTriggerClusterAnnotation] = target.SourceCluster()
+ target.Annotations[testClaimClusterAnnotation] = target.SourceCluster()
}
return target
}
-// namesKustomization makes a GitTarget name the FluxTrigger called name, which holds the
-// Kustomization namespace/name (the trigger's own namespace when namespace is empty).
+// namesKustomization makes a GitTarget name the FluxClaim called name, which holds the
+// Kustomization namespace/name (the applier claim's own namespace when namespace is empty).
func namesKustomization(target *v1alpha3.GitTarget, namespace, name string) {
if target.Annotations == nil {
target.Annotations = map[string]string{}
}
- target.Spec.ApplyTriggerRef = &v1alpha3.ApplyTriggerReference{Kind: v1alpha3.ApplyTriggerKindFlux, Name: name}
+ target.Spec.ApplierClaimRef = &v1alpha3.ApplierClaimReference{Kind: v1alpha3.ApplierClaimKindFlux, Name: name}
target.Annotations[testKustomizationAnnotation] = namespace + "/" + name
}
-// triggersFor returns the trigger each GitTarget names, as its test annotations describe it.
-func triggersFor(objects []client.Object) []client.Object {
- var triggers []client.Object
+// claimsFor returns the applier claim each GitTarget names, as its test annotations describe it.
+func claimsFor(objects []client.Object) []client.Object {
+ var claims []client.Object
seen := map[string]bool{}
for _, obj := range objects {
target, ok := obj.(*v1alpha3.GitTarget)
- if !ok || target.Spec.ApplyTriggerRef == nil {
+ if !ok || target.Spec.ApplierClaimRef == nil {
continue
}
- key := target.Spec.ApplyTriggerRef.Kind + "/" + target.Spec.ApplyTriggerRef.Name
+ key := target.Spec.ApplierClaimRef.Kind + "/" + target.Spec.ApplierClaimRef.Name
if seen[key] {
continue
}
seen[key] = true
- cluster := target.Annotations[testTriggerClusterAnnotation]
+ cluster := target.Annotations[testClaimClusterAnnotation]
if cluster == "" {
cluster = clusterID(localCluster)
}
- meta := metav1.ObjectMeta{Namespace: target.Namespace, Name: target.Spec.ApplyTriggerRef.Name}
+ meta := metav1.ObjectMeta{Namespace: target.Namespace, Name: target.Spec.ApplierClaimRef.Name}
providerNS, providerName, _ := strings.Cut(cluster, "/")
provider := fluxmeta.NamespacedObjectReference{Namespace: providerNS, Name: providerName}
if app, ok := target.Annotations[testApplicationAnnotation]; ok {
namespace, name, _ := strings.Cut(app, "/")
- triggers = append(triggers, &v1alpha3.ArgoCDTrigger{ObjectMeta: meta, Spec: v1alpha3.ArgoCDTriggerSpec{
+ claims = append(claims, &v1alpha3.ArgoCDClaim{ObjectMeta: meta, Spec: v1alpha3.ArgoCDClaimSpec{
KubeProviderRef: provider,
ApplicationRef: fluxmeta.NamespacedObjectReference{Namespace: namespace, Name: name},
}})
@@ -136,16 +136,16 @@ func triggersFor(objects []client.Object) []client.Object {
if ks, ok := target.Annotations[testKustomizationAnnotation]; ok {
namespace, name, _ := strings.Cut(ks, "/")
// Its reconciler holds the Kustomization (Ask.Held), unless a test says otherwise.
- triggers = append(triggers, &v1alpha3.FluxTrigger{ObjectMeta: meta, Spec: v1alpha3.FluxTriggerSpec{
+ claims = append(claims, &v1alpha3.FluxClaim{ObjectMeta: meta, Spec: v1alpha3.FluxClaimSpec{
KubeProviderRef: provider,
KustomizationRef: fluxmeta.NamespacedObjectReference{Namespace: namespace, Name: name},
- }, Status: v1alpha3.FluxTriggerStatus{Conditions: []metav1.Condition{{
- Type: TriggerConditionHeld, Status: metav1.ConditionTrue, Reason: "Suspended",
+ }, Status: v1alpha3.FluxClaimStatus{Conditions: []metav1.Condition{{
+ Type: ApplierClaimConditionHeld, Status: metav1.ConditionTrue, Reason: "Suspended",
LastTransitionTime: metav1.Now(),
}}}})
}
}
- return triggers
+ return claims
}
// reapplying makes a GitTarget consent to a Refusal request.
@@ -162,7 +162,7 @@ func kubeProvider(name string, admits ...string) *v1alpha3.KubeProvider {
Spec: v1alpha3.KubeProviderSpec{
InCluster: &v1alpha3.KubeProviderInCluster{},
AccessFrom: &v1alpha3.NamespaceMatcher{Names: admits},
- // The test appliers live in flux-system and argocd, not in the triggers' namespace.
+ // The test appliers live in flux-system and argocd, not in the applier claims' namespace.
AllowCrossNamespace: &v1alpha3.KubeProviderCrossNamespace{Appliers: true},
},
}
@@ -198,11 +198,11 @@ func ref(name string) itypes.ResourceReference { return itypes.NewResourceRefere
func uid(name string) types.UID { return types.UID("uid-" + name) }
// kustomizationApplier is a Kustomization applier in the cluster of the test KubeProvider called
-// cluster, held by the FluxTrigger in targetNS of the Kustomization's name.
+// cluster, held by the FluxClaim in targetNS of the Kustomization's name.
func kustomizationApplier(cluster, namespace, name string) git.ApplierRef {
return git.ApplierRef{
- Trigger: git.TriggerRef{Kind: v1alpha3.ApplyTriggerKindFlux, Namespace: targetNS, Name: name},
- Cluster: clusterID(cluster), Kind: v1alpha3.ApplierKindKustomization, Namespace: namespace, Name: name,
+ ApplierClaim: git.ApplierClaimRef{Kind: v1alpha3.ApplierClaimKindFlux, Namespace: targetNS, Name: name},
+ Cluster: clusterID(cluster), Kind: v1alpha3.ApplierKindKustomization, Namespace: namespace, Name: name,
}
}
@@ -259,7 +259,7 @@ func newDispatcherFixture(t *testing.T, targets []client.Object, sourceObjs ...r
require.NoError(t, clientgoscheme.AddToScheme(scheme))
require.NoError(t, v1alpha3.AddToScheme(scheme))
controlClient := fake.NewClientBuilder().WithScheme(scheme).
- WithObjects(append(append(controlPlane(), targets...), triggersFor(targets)...)...).Build()
+ WithObjects(append(append(controlPlane(), targets...), claimsFor(targets)...)...).Build()
source := &sourceStub{client: newSourceClient(sourceObjs...)}
sim := fluxsim.New(t, source.client)
@@ -417,7 +417,7 @@ func TestDispatcher_AsksOnceForEveryTarget(t *testing.T) {
// namespace and so names one in its own.
func TestDispatcher_LooksTheApplierUpInItsCluster(t *testing.T) {
own := kustomizationApplier("prod-eu-1", targetNS, "own")
- // Suspended, as the Kustomization its trigger holds is. A resumed one is reconciled by the
+ // Suspended, as the Kustomization its applier claim holds is. A resumed one is reconciled by the
// simulated kustomize-controller as soon as the fetch lands a new artifact, and that status write
// can move its resourceVersion between the resume's read and its apply: the resume is then
// refused with a Conflict and made again, which is correct, but shows up here as two resumes.
@@ -435,9 +435,9 @@ func TestDispatcher_LooksTheApplierUpInItsCluster(t *testing.T) {
f.patches())
}
-// appliedFrom puts the applier of a GitTarget's trigger in the cluster of another test KubeProvider.
+// appliedFrom puts the applier of a GitTarget's applier claim in the cluster of another test KubeProvider.
func appliedFrom(target *v1alpha3.GitTarget, cluster string) *v1alpha3.GitTarget {
- target.Annotations[testTriggerClusterAnnotation] = clusterID(cluster)
+ target.Annotations[testClaimClusterAnnotation] = clusterID(cluster)
return target
}
@@ -585,14 +585,14 @@ func TestDispatcher_WithdrawnConsentAsksNothing(t *testing.T) {
// third finding is the first case: a recreated GitTarget executed its predecessor's request.
func TestDispatcher_ATargetThatChangedAsksNothing(t *testing.T) {
for name, change := range map[string]func(*v1alpha3.GitTarget){
- "it was deleted and recreated": func(g *v1alpha3.GitTarget) { g.UID = "uid-recreated" },
- "it moved to another branch": func(g *v1alpha3.GitTarget) { g.Spec.GitBranchRef.Name = "platform-release" },
- "it names another trigger": func(g *v1alpha3.GitTarget) { namesKustomization(g, fluxSystemNS, "other") },
- "it names no trigger any more": func(g *v1alpha3.GitTarget) { g.Spec.ApplyTriggerRef = nil },
- "its trigger is gone": func(g *v1alpha3.GitTarget) {
- g.Spec.ApplyTriggerRef.Kind = v1alpha3.ApplyTriggerKindArgoCD // no test annotation creates one
+ "it was deleted and recreated": func(g *v1alpha3.GitTarget) { g.UID = "uid-recreated" },
+ "it moved to another branch": func(g *v1alpha3.GitTarget) { g.Spec.GitBranchRef.Name = "platform-release" },
+ "it names another applier claim": func(g *v1alpha3.GitTarget) { namesKustomization(g, fluxSystemNS, "other") },
+ "it names no applier claim any more": func(g *v1alpha3.GitTarget) { g.Spec.ApplierClaimRef = nil },
+ "its applier claim is gone": func(g *v1alpha3.GitTarget) {
+ g.Spec.ApplierClaimRef.Kind = v1alpha3.ApplierClaimKindArgoCD // no test annotation creates one
},
- "its trigger holds the applier in another cluster": func(g *v1alpha3.GitTarget) {
+ "its applier claim holds the applier in another cluster": func(g *v1alpha3.GitTarget) {
appliedFrom(g, "prod-eu-1")
},
} {
@@ -782,10 +782,10 @@ func TestDispatcher_AFailedApplyIsSuspendedAgain(t *testing.T) {
assert.True(t, suspended(f.sim.Kustomization(checkoutKey)), "no retryInterval retry can fire")
}
-// TestDispatcher_ADeletedTriggerEndsItsOperation: a trigger being deleted hands its Kustomization
+// TestDispatcher_ADeletedClaimEndsItsOperation: an applier claim being deleted hands its Kustomization
// back itself, so its operation completes as Failed, and nothing more is written to the
// Kustomization from here.
-func TestDispatcher_ADeletedTriggerEndsItsOperation(t *testing.T) {
+func TestDispatcher_ADeletedClaimEndsItsOperation(t *testing.T) {
f := newDispatcherFixture(t, []client.Object{gitTarget("a", checkoutRef(), "")}, behindSource()...)
held := f.sim.HoldNextApply()
reports := f.carryOut(context.Background(), request(checkout, v1alpha3.ApplyReasonForeignCommit, "a"))
@@ -793,13 +793,13 @@ func TestDispatcher_ADeletedTriggerEndsItsOperation(t *testing.T) {
<-held
assert.Equal(t, git.ApplyRunning, next(t, reports).Outcome)
- trigger := &v1alpha3.FluxTrigger{}
+ claim := &v1alpha3.FluxClaim{}
require.NoError(t, f.control.Get(context.Background(),
- client.ObjectKey{Namespace: targetNS, Name: "checkout"}, trigger))
- require.NoError(t, f.control.Delete(context.Background(), trigger))
+ client.ObjectKey{Namespace: targetNS, Name: "checkout"}, claim))
+ require.NoError(t, f.control.Delete(context.Background(), claim))
res := next(t, reports)
assert.Equal(t, git.ApplyFailed, res.Outcome)
- require.ErrorContains(t, res.Err, "the trigger is being deleted")
+ require.ErrorContains(t, res.Err, "the applier claim is being deleted")
assert.Equal(t, fetchedAndResumed, f.patches(), "never suspended from here")
}
@@ -819,18 +819,18 @@ func TestDispatcher_AnAbandonedOperationIsSuspendedBeforeItIsLetGo(t *testing.T)
assert.Equal(t, git.ApplyRequested, next(t, reports).Outcome)
<-held
assert.Equal(t, git.ApplyRunning, next(t, reports).Outcome)
- assert.True(t, f.dispatcher.Following(checkout.Trigger, "T1"))
+ assert.True(t, f.dispatcher.Following(checkout.ApplierClaim, "T1"))
close(abandoned)
eventually(t, func() bool { return suspended(f.sim.Kustomization(checkoutKey)) }, "suspended at once")
quiet(t, reports, "still waiting for the suspend to be handled, behind the running apply")
- assert.True(t, f.dispatcher.Following(checkout.Trigger, "T1"), "and it is still followed")
+ assert.True(t, f.dispatcher.Following(checkout.ApplierClaim, "T1"), "and it is still followed")
f.sim.Release()
_, open := <-reports
assert.False(t, open, "nothing more is reported")
assert.True(t, tokenHandled(f.sim.Kustomization(checkoutKey), f.sim.Kustomization(checkoutKey).
GetAnnotations()[fluxmeta.ReconcileRequestAnnotation]), "let go once the suspend is handled")
- assert.False(t, f.dispatcher.Following(checkout.Trigger, "T1"))
+ assert.False(t, f.dispatcher.Following(checkout.ApplierClaim, "T1"))
}
// TestDispatcher_AStopMidApplySuspendsTheKustomization is review 7's F1: an apply is in flight, and
@@ -859,17 +859,17 @@ func TestDispatcher_AStopMidApplySuspendsTheKustomization(t *testing.T) {
f.sim.Release()
}
-// TestDispatcher_AnUnheldTriggerIsNotResumed is review 4's F: the trigger's reconciler suspends the
+// TestDispatcher_AnUnheldClaimIsNotResumed is review 4's F: the applier claim's reconciler suspends the
// Kustomization on its own reads (an adoption, an external resume), and a resume between its read
// and its suspend would be suspended under the apply. So a request resumes only a Kustomization its
-// trigger reports Held=True, read uncached; any other is a failed attempt the worker retries.
-func TestDispatcher_AnUnheldTriggerIsNotResumed(t *testing.T) {
+// applier claim reports Held=True, read uncached; any other is a failed attempt the worker retries.
+func TestDispatcher_AnUnheldClaimIsNotResumed(t *testing.T) {
f := newDispatcherFixture(t, []client.Object{gitTarget("a", checkoutRef(), "")}, behindSource()...)
- trigger := &v1alpha3.FluxTrigger{}
+ claim := &v1alpha3.FluxClaim{}
require.NoError(t, f.control.Get(context.Background(), client.ObjectKey{Namespace: targetNS, Name: "checkout"},
- trigger))
- trigger.Status.Conditions[0].Status, trigger.Status.Conditions[0].Reason = metav1.ConditionFalse, "Suspending"
- require.NoError(t, f.control.Update(context.Background(), trigger))
+ claim))
+ claim.Status.Conditions[0].Status, claim.Status.Conditions[0].Reason = metav1.ConditionFalse, "Suspending"
+ require.NoError(t, f.control.Update(context.Background(), claim))
res := f.attempt(request(checkout, v1alpha3.ApplyReasonForeignCommit, "a"))
@@ -880,20 +880,20 @@ func TestDispatcher_AnUnheldTriggerIsNotResumed(t *testing.T) {
}
}
-// TestDispatcher_ATriggerAnotherCameBeforeIsNotResumed is review 5's P2, the dispatcher's half: the
-// recheck reads the triggers cached, and can miss one that came first in line since (created in the
+// TestDispatcher_AClaimAnotherCameBeforeIsNotResumed is review 5's P2, the dispatcher's half: the
+// recheck reads the applier claims cached, and can miss one that came first in line since (created in the
// same second, after it by name, but with an apply of its own in flight). The hold check before the resume reads them
// uncached, and fails the attempt.
-func TestDispatcher_ATriggerAnotherCameBeforeIsNotResumed(t *testing.T) {
+func TestDispatcher_AClaimAnotherCameBeforeIsNotResumed(t *testing.T) {
f := newDispatcherFixture(t, []client.Object{gitTarget("a", checkoutRef(), "")}, behindSource()...)
- own := &v1alpha3.FluxTrigger{}
+ own := &v1alpha3.FluxClaim{}
require.NoError(t, f.control.Get(context.Background(), client.ObjectKey{Namespace: targetNS, Name: "checkout"},
own))
other := own.DeepCopy()
other.Namespace, other.ResourceVersion = "team-z", ""
other.Spec.KustomizationRef.Namespace = own.Applier().Namespace
other.Status.Operation = &v1alpha3.ApplyOperation{Phase: v1alpha3.ApplyOperationRunning, Token: "T0"}
- other.Finalizers = []string{v1alpha3.FluxTriggerFinalizer} // the holder whose apply this is
+ other.Finalizers = []string{v1alpha3.FluxClaimFinalizer} // the holder whose apply this is
scheme := runtime.NewScheme()
require.NoError(t, v1alpha3.AddToScheme(scheme))
f.dispatcher.APIReader = fake.NewClientBuilder().WithScheme(scheme).WithObjects(own.DeepCopy(), other).Build()
@@ -902,14 +902,14 @@ func TestDispatcher_ATriggerAnotherCameBeforeIsNotResumed(t *testing.T) {
assert.Equal(t, git.ApplyRequestFailed, res.Outcome)
require.ErrorContains(t, res.Err, "no longer holds its Kustomization")
- require.ErrorContains(t, res.Err, "held by FluxTrigger team-z/checkout, whose apply is in flight")
+ require.ErrorContains(t, res.Err, "held by FluxClaim team-z/checkout, whose apply is in flight")
for _, write := range f.patches() {
assert.NotContains(t, write, "suspend=false", "nothing is resumed")
}
}
// TestDispatcher_ARecoveredAttemptBeforeStartWaitsForIt is review 4's B at startup: a worker
-// recovers its trigger's operation once, and one that failed because the dispatcher had not started
+// recovers its applier claim's operation once, and one that failed because the dispatcher had not started
// yet left the Kustomization resumed with nobody following it. Now it is followed once Start runs.
func TestDispatcher_ARecoveredAttemptBeforeStartWaitsForIt(t *testing.T) {
f := newDispatcherFixture(t, []client.Object{gitTarget("a", checkoutRef(), "")}, resumedSource(t, "T1")...)
@@ -971,22 +971,22 @@ func TestDispatcher_AnApplierThatIsGoneFailsItsOperation(t *testing.T) {
require.ErrorContains(t, res.Err, "the applier is gone")
}
-// argoTarget is the GitTarget "a", which names the ArgoCDTrigger "platform", holding the
+// argoTarget is the GitTarget "a", which names the ArgoCDClaim "platform", holding the
// Application argocd/platform.
func argoTarget() *v1alpha3.GitTarget {
target := gitTarget("a", nil, "")
target.Annotations = map[string]string{testApplicationAnnotation: argoNS + "/platform"}
- target.Spec.ApplyTriggerRef = &v1alpha3.ApplyTriggerReference{
- Kind: v1alpha3.ApplyTriggerKindArgoCD,
+ target.Spec.ApplierClaimRef = &v1alpha3.ApplierClaimReference{
+ Kind: v1alpha3.ApplierClaimKindArgoCD,
Name: "platform",
}
return target
}
-// argoPlatform is the applier argoTarget's trigger holds.
+// argoPlatform is the applier argoTarget's applier claim holds.
var argoPlatform = git.ApplierRef{
- Trigger: git.TriggerRef{Kind: v1alpha3.ApplyTriggerKindArgoCD, Namespace: targetNS, Name: "platform"},
- Cluster: clusterID(localCluster), Kind: v1alpha3.ApplierKindApplication,
+ ApplierClaim: git.ApplierClaimRef{Kind: v1alpha3.ApplierClaimKindArgoCD, Namespace: targetNS, Name: "platform"},
+ Cluster: clusterID(localCluster), Kind: v1alpha3.ApplierKindApplication,
Namespace: argoNS, Name: "platform",
}
@@ -1035,7 +1035,7 @@ func TestDispatcher_AsksAnApplicationToSync(t *testing.T) {
// TestDispatcher_AnApplicationWithAutomatedSyncIsNeverSynced: the standard is full control, so an
// Application that syncs on its own is never asked to, whatever its verdict says. One whose switch
-// its trigger holds was switched on by somebody, and its trigger switches it off again: the attempt
+// its applier claim holds was switched on by somebody, and its applier claim switches it off again: the attempt
// fails, and the worker asks again after its backoff. One whose switch an ApplicationSet owns is a
// configuration no retry fixes: Rejected.
func TestDispatcher_AnApplicationWithAutomatedSyncIsNeverSynced(t *testing.T) {
@@ -1044,8 +1044,8 @@ func TestDispatcher_AnApplicationWithAutomatedSyncIsNeverSynced(t *testing.T) {
wantOutcome git.ApplyOutcome
wantErr string
}{
- "held by its trigger": {app: automatedApp(), wantOutcome: git.ApplyRequestFailed,
- wantErr: "its ArgoCDTrigger switches it off again"},
+ "held by its applier claim": {app: automatedApp(), wantOutcome: git.ApplyRequestFailed,
+ wantErr: "its ArgoCDClaim switches it off again"},
"owned by an ApplicationSet": {app: generated(automatedApp()), wantOutcome: git.ApplyRejected,
wantErr: "is generated by ApplicationSet argocd/platforms"},
} {
@@ -1300,7 +1300,7 @@ func TestDispatcher_ARecoveredOperationThatNeverResumedFails(t *testing.T) {
assert.True(t, suspended(f.sim.Kustomization(checkoutKey)))
}
-// TestDispatcher_AnApplyStartedElsewhereIsFollowedByItsSuspend: the trigger's reconciler recorded a
+// TestDispatcher_AnApplyStartedElsewhereIsFollowedByItsSuspend: the applier claim's reconciler recorded a
// `flux resume` it suspended again, with the suspend token and no request token; following it ends
// once that suspend is handled.
func TestDispatcher_AnApplyStartedElsewhereIsFollowedByItsSuspend(t *testing.T) {
diff --git a/internal/applier/flux.go b/internal/applier/flux.go
index 753b65cc5..9fa3fd9c9 100644
--- a/internal/applier/flux.go
+++ b/internal/applier/flux.go
@@ -51,7 +51,7 @@ const OutcomeResumed Outcome = "Resumed"
// applier's managedFields say who holds it.
const SuspendFieldManager = "gitops-reverser"
-// ReasonSuspendOwnedElsewhere is Configured=False on a FluxTrigger whose Kustomization's
+// 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.
const ReasonSuspendOwnedElsewhere = "SuspendOwnedElsewhere"
@@ -100,7 +100,7 @@ const fetchPollInterval = 100 * time.Millisecond
// 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
+// 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
@@ -174,7 +174,7 @@ func (e *sourceElsewhereError) Error() string {
const maxResumeConflicts = 5
// resume reads the Kustomization again and checks it is still the object Request checked (read),
-// rechecks the artifact, the decision and the trigger's hold, records the operation, and resumes
+// rechecks the artifact, the decision and the applier claim's hold, records the operation, and resumes
// the Kustomization with the request's token (Request).
//
// The resume carries the uid and resourceVersion of that last read, which the API server checks
@@ -253,7 +253,7 @@ func (f *Flux) readAgain(
return ks, nil
}
-// mayResume rechecks the artifact, the decision and the trigger's hold, records the operation (the
+// mayResume rechecks the artifact, the decision and the applier claim's hold, records the operation (the
// first time only), and asks the decision again once it is recorded.
func (f *Flux) mayResume(
ctx context.Context, ref, sourceRef types.NamespacedName, ask Ask, record func(context.Context) error,
@@ -271,7 +271,7 @@ func (f *Flux) mayResume(
}
}
if err := record(ctx); err != nil {
- return fmt.Errorf("record the operation on its trigger before resuming %s %s: %w",
+ return fmt.Errorf("record the operation on its applier claim before resuming %s %s: %w",
kindKustomization, ref, err)
}
// Recording can wait on the status writer and the API server, and the branch can move on
@@ -446,12 +446,12 @@ func (f *Flux) verdict(ctx context.Context, ks *unstructured.Unstructured, s *St
// returns false with a Failed outcome when no reconcile that builds is seen to have run since the
// resume, where it would read a Succeeded or (for an operation whose token was never seen) any
// outcome. A token handled is no proof of that: a suspend that lands between the resume and the
-// reconcile that reads it (`flux suspend`, the trigger's own suspend) makes that reconcile a
+// reconcile that reads it (`flux suspend`, the applier claim's own suspend) makes that reconcile a
// suspended return, which marks the token handled and leaves the Ready of an earlier apply.
func (s *Started) judge(ks *unstructured.Unstructured) (Progress, bool) {
read := applyVerdict(ks, s.Commit)
if s.Token == "" {
- // An external resume the trigger's reconciler recorded and suspended again: no resume of ours to
+ // An external resume the applier claim's reconciler recorded and suspended again: no resume of ours to
// have run since. It is judged as the reconciler judges it, on the Kustomization's last
// apply, so the two agree whichever completes the record.
return read, true
@@ -693,7 +693,7 @@ func suspendOwnedMessage(ref types.NamespacedName, owner string) string {
"next apply; remove spec.suspend from the manifest it applies", kindKustomization, ref, owner)
}
-// Suspension is how a Kustomization stands with GitOps Reverser, for its FluxTrigger's reconciler.
+// Suspension is how a Kustomization stands with GitOps Reverser, for its FluxClaim's reconciler.
type Suspension struct {
// Suspended is spec.suspend.
Suspended bool
@@ -735,14 +735,14 @@ func (f *Flux) Suspend(ctx context.Context, ref types.NamespacedName) (string, e
// 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 trigger's status.previousSuspendState) is handed back suspended instead: an
+// (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/fluxtrigger_suspend_apiserver_test.go). Such an update entry is taken back
+// 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 (f *Flux) Release(ctx context.Context, ref types.NamespacedName, wasSuspended bool) (string, error) {
@@ -848,11 +848,11 @@ func splitRevision(revision string) (string, string) {
return named, revision
}
-// Validate answers Configured for a FluxTrigger's Kustomization: that it exists in its cluster and
+// 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 trigger to
+// 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
diff --git a/internal/applier/flux_consumers.go b/internal/applier/flux_consumers.go
index 57aa8a13a..e72fb38fd 100644
--- a/internal/applier/flux_consumers.go
+++ b/internal/applier/flux_consumers.go
@@ -18,7 +18,7 @@ import (
// A GitRepository's consumers (docs/design/apply-trigger.md, "Shared sources"). A suspended
// Kustomization ignores a new artifact; one consumer that is not held applies each new artifact the
-// moment it arrives, so a FluxTrigger lists the ones no trigger holds in its status.
+// moment it arrives, so a FluxClaim lists the ones no applier claim holds in its status.
// A HelmRelease applies a GitRepository through a HelmChart that source-controller builds from it:
// one helm-controller creates from the release's spec.chart, or one the release names in
diff --git a/internal/applier/flux_guide_test.go b/internal/applier/flux_guide_test.go
index b40d5662c..537dd666c 100644
--- a/internal/applier/flux_guide_test.go
+++ b/internal/applier/flux_guide_test.go
@@ -36,7 +36,7 @@ const fluxGuide = "../../docs/bi-directional-flux.md"
// TestFluxGuide_TheCompleteExampleIsAccepted reads the guide's complete example and checks it
// against the code that validates and drives a Flux applier: the GitOps Reverser objects decode
// into the API types with no unknown field, Configured comes out True for the Flux objects as
-// written, the trigger's source binds the GitTarget, and a request fetches and resumes the
+// written, the applier claim's source binds the GitTarget, and a request fetches and resumes the
// Kustomization. It also holds the example to the guide's own
// advice, so the page cannot recommend one thing and show another.
func TestFluxGuide_TheCompleteExampleIsAccepted(t *testing.T) {
@@ -46,15 +46,15 @@ func TestFluxGuide_TheCompleteExampleIsAccepted(t *testing.T) {
var receiver v1alpha3.GitReceiver
var branch v1alpha3.GitBranch
var target v1alpha3.GitTarget
- var trigger v1alpha3.FluxTrigger
+ var claim v1alpha3.FluxClaim
var secret corev1.Secret
decodeStrict(t, objects, "GitProvider", &provider)
decodeStrict(t, objects, "GitReceiver", &receiver)
decodeStrict(t, objects, "GitBranch", &branch)
decodeStrict(t, objects, "GitTarget", &target)
- decodeStrict(t, objects, v1alpha3.ApplyTriggerKindFlux, &trigger)
+ decodeStrict(t, objects, v1alpha3.ApplierClaimKindFlux, &claim)
decodeStrict(t, objects, "Secret", &secret)
- exampleReachesItsApplier(t, objects, &trigger)
+ exampleReachesItsApplier(t, objects, &claim)
repository := one(t, objects, "GitRepository")
kustomization := one(t, objects, "Kustomization")
assert.NotContains(t, objects, "Receiver", "the guide tells the reader to remove the Flux Receiver")
@@ -73,10 +73,10 @@ func TestFluxGuide_TheCompleteExampleIsAccepted(t *testing.T) {
assert.Equal(t, receiver.Namespace, secret.Namespace, "a GitReceiver reads its token in its own namespace")
assert.Equal(t, provider.Namespace, secret.Namespace)
assert.NotEmpty(t, secret.StringData[v1alpha3.ReceiverTokenKey], "the receiver reads the token key")
- require.NotNil(t, target.Spec.ApplyTriggerRef)
- assert.Equal(t, v1alpha3.ApplyTriggerReference{Kind: v1alpha3.ApplyTriggerKindFlux, Name: trigger.Name},
- *target.Spec.ApplyTriggerRef)
- assert.Equal(t, target.Namespace, trigger.Namespace, "a GitTarget names a trigger in its own namespace")
+ require.NotNil(t, target.Spec.ApplierClaimRef)
+ assert.Equal(t, v1alpha3.ApplierClaimReference{Kind: v1alpha3.ApplierClaimKindFlux, Name: claim.Name},
+ *target.Spec.ApplierClaimRef)
+ assert.Equal(t, target.Namespace, claim.Namespace, "a GitTarget names an applier claim in its own namespace")
assert.Equal(t, v1alpha3.RefusalActionReapply, target.Spec.OnRefusal)
// The Flux objects follow the guide's advice.
@@ -89,7 +89,7 @@ func TestFluxGuide_TheCompleteExampleIsAccepted(t *testing.T) {
assert.False(t, setsSuspend, "the manifest omits spec.suspend, which GitOps Reverser holds")
// The applier, as written, is accepted, binds the GitTarget, and is driven.
- ref := trigger.Applier()
+ ref := claim.Applier()
assert.Equal(t, types.NamespacedName{Namespace: kustomization.GetNamespace(), Name: kustomization.GetName()}, ref)
want := Binding{RepositoryURL: provider.Spec.URL, Branch: branch.Spec.Branch}
@@ -99,8 +99,12 @@ func TestFluxGuide_TheCompleteExampleIsAccepted(t *testing.T) {
verdict := flux.Validate(context.Background(), ref)
assert.True(t, verdict.OK, "Configured for the guide's example: %s: %s", verdict.Reason, verdict.Message)
require.NotNil(t, verdict.Source)
- trigger.Status.Source = &v1alpha3.FluxTriggerSource{URL: verdict.Source.URL, Branch: verdict.Source.Revision}
- assert.Empty(t, BindingMismatch(&trigger, want), "the trigger's Kustomization applies what the GitTarget writes")
+ claim.Status.Source = &v1alpha3.FluxClaimSource{URL: verdict.Source.URL, Branch: verdict.Source.Revision}
+ assert.Empty(
+ t,
+ BindingMismatch(&claim, want),
+ "the applier claim's Kustomization applies what the GitTarget writes",
+ )
sim := fluxsim.New(t, client)
sim.Fetches(types.NamespacedName{Namespace: repository.GetNamespace(), Name: repository.GetName()},
@@ -169,21 +173,21 @@ func decodeStrict(t *testing.T, objects map[string][]*unstructured.Unstructured,
require.NoError(t, decoder.Decode(into), "the guide's %s must decode with no unknown field", kind)
}
-// exampleReachesItsApplier checks that the guide's example KubeProvider admits the trigger's
-// namespace and lets it reach its applier, through the admission the trigger reconciler runs.
+// exampleReachesItsApplier checks that the guide's example KubeProvider admits the applier claim's
+// namespace and lets it reach its applier, through the admission the applier claim reconciler runs.
func exampleReachesItsApplier(
- t *testing.T, objects map[string][]*unstructured.Unstructured, trigger v1alpha3.ApplyTrigger,
+ t *testing.T, objects map[string][]*unstructured.Unstructured, claim v1alpha3.ApplierClaim,
) {
t.Helper()
var provider v1alpha3.KubeProvider
decodeStrict(t, objects, "KubeProvider", &provider)
- assert.Equal(t, trigger.KubeProviderKey(), provider.Key(), "the example declares the trigger's KubeProvider")
+ assert.Equal(t, claim.KubeProviderKey(), provider.Key(), "the example declares the applier claim's KubeProvider")
scheme := runtime.NewScheme()
require.NoError(t, clientgoscheme.AddToScheme(scheme))
require.NoError(t, v1alpha3.AddToScheme(scheme))
control := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&provider).Build()
- decision, err := authz.ApplyTriggerAdmitted(context.Background(), control, trigger)
+ decision, err := authz.ApplierClaimAdmitted(context.Background(), control, claim)
require.NoError(t, err)
- assert.True(t, decision.Allowed, "the example's KubeProvider lets the trigger reach its applier: %s: %s",
+ assert.True(t, decision.Allowed, "the example's KubeProvider lets the applier claim reach its applier: %s: %s",
decision.Reason, decision.Message)
}
diff --git a/internal/applier/flux_lifecycle_test.go b/internal/applier/flux_lifecycle_test.go
index ce920c60a..7bcb4cebf 100644
--- a/internal/applier/flux_lifecycle_test.go
+++ b/internal/applier/flux_lifecycle_test.go
@@ -204,7 +204,7 @@ func TestFluxRequest_Outcomes(t *testing.T) {
commit: testCommit, reason: v1alpha3.ApplyReasonCatchUp, want: OutcomeResumed,
},
{
- // Not suspended yet (the trigger has not adopted it): it may apply on its own, so the
+ // Not suspended yet (the applier claim has not adopted it): it may apply on its own, so the
// operation runs, and its suspend after the apply holds it again.
name: "a catch-up applied but not suspended resumes", artifact: "main@sha1:" + testCommit,
applied: "main@sha1:" + testCommit,
@@ -243,7 +243,7 @@ func TestFluxRequest_Outcomes(t *testing.T) {
}
}
-// TestFluxRequest_TheOperationIsRecordedBeforeTheResume: a restart reads the trigger's
+// TestFluxRequest_TheOperationIsRecordedBeforeTheResume: a restart reads the applier claim's
// status.operation to find every apply that may have started, so the record is written before the
// resume, and a record that cannot be written resumes nothing.
func TestFluxRequest_TheOperationIsRecordedBeforeTheResume(t *testing.T) {
@@ -260,7 +260,7 @@ func TestFluxRequest_TheOperationIsRecordedBeforeTheResume(t *testing.T) {
assert.True(t, recorded)
sim = simCluster(t, "main@sha1:"+testCommit, true)
- a.Record = func(context.Context) error { return errors.New("the trigger is being deleted") }
+ a.Record = func(context.Context) error { return errors.New("the applier claim is being deleted") }
_, err = simFlux(sim).Request(context.Background(), checkoutKey, a)
require.ErrorContains(t, err, "record the operation")
assert.False(t, isPermanent(err))
@@ -611,7 +611,7 @@ func TestFluxProgress_ASuspendBeforeTheReconcileIsNoApply(t *testing.T) {
metav1.PatchOptions{FieldManager: "flux"})
require.NoError(t, err)
},
- "the trigger's own suspend": func(t *testing.T, _ *fluxsim.Sim, flux *Flux) {
+ "the applier claim's own suspend": func(t *testing.T, _ *fluxsim.Sim, flux *Flux) {
_, err := flux.Suspend(context.Background(), checkoutKey)
require.NoError(t, err)
},
@@ -799,7 +799,7 @@ func TestReconcileMark_MovesOnAReconcileThatBuilt(t *testing.T) {
assert.False(t, before.since(time.Time{}), "no mark at all is no evidence")
}
-// TestFlux_SuspendAndRelease: the trigger reconciler's half. Suspend suspends, Suspension reports
+// TestFlux_SuspendAndRelease: the applier claim reconciler's half. Suspend suspends, Suspension reports
// held and handled once the token is handled, and Release hands spec.suspend back: the Kustomization
// is resumed, and holds nothing of GitOps Reverser's.
func TestFlux_SuspendAndRelease(t *testing.T) {
@@ -907,7 +907,7 @@ func eventually(t *testing.T, cond func() bool, why string) {
require.Eventually(t, cond, 5*time.Second, time.Millisecond, why)
}
-// TestFluxProgress_AnExternalResumeIsJudgedAsItsReconcilerJudgesIt is review 6: the trigger's
+// TestFluxProgress_AnExternalResumeIsJudgedAsItsReconcilerJudgesIt is review 6: the applier claim's
// reconciler records an external resume when it notices it, which can be after Flux applied it, and
// completes it on the Kustomization's last apply. A worker that recovered the record judged it on a
// reconcile run since the record's requestedAt, found none, and its Failed won over the
diff --git a/internal/applier/flux_test.go b/internal/applier/flux_test.go
index 16105b93a..fd4b24689 100644
--- a/internal/applier/flux_test.go
+++ b/internal/applier/flux_test.go
@@ -377,7 +377,7 @@ func TestFluxValidate_ReportsTheSource(t *testing.T) {
}, got.Source)
}
-// TestBindingMismatch_ComparesRepositoriesAndBranches: a trigger's source and the GitProvider's url
+// TestBindingMismatch_ComparesRepositoriesAndBranches: an applier claim's source and the GitProvider's url
// are compared as repositories, not as strings, and the branch exactly.
func TestBindingMismatch_ComparesRepositoriesAndBranches(t *testing.T) {
tests := []struct {
@@ -405,17 +405,17 @@ func TestBindingMismatch_ComparesRepositoriesAndBranches(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- trigger := &v1alpha3.FluxTrigger{ObjectMeta: metav1.ObjectMeta{Name: "apps"}}
- assert.Empty(t, BindingMismatch(trigger, Binding{RepositoryURL: tt.provider, Branch: testBranch}),
- "a trigger that has read nothing yet is not a mismatch")
- trigger.Status.Source = &v1alpha3.FluxTriggerSource{URL: tt.sourceURL, Branch: tt.branch}
- got := BindingMismatch(trigger, Binding{RepositoryURL: tt.provider, Branch: testBranch})
+ claim := &v1alpha3.FluxClaim{ObjectMeta: metav1.ObjectMeta{Name: "apps"}}
+ assert.Empty(t, BindingMismatch(claim, Binding{RepositoryURL: tt.provider, Branch: testBranch}),
+ "an applier claim that has read nothing yet is not a mismatch")
+ claim.Status.Source = &v1alpha3.FluxClaimSource{URL: tt.sourceURL, Branch: tt.branch}
+ got := BindingMismatch(claim, Binding{RepositoryURL: tt.provider, Branch: testBranch})
if tt.want == "" {
assert.Empty(t, got)
return
}
assert.Contains(t, got, tt.want)
- assert.Contains(t, got, "FluxTrigger apps")
+ assert.Contains(t, got, "FluxClaim apps")
})
}
}
diff --git a/internal/applier/fluxsim/fluxsim.go b/internal/applier/fluxsim/fluxsim.go
index 02ddc6ada..4fa301c35 100644
--- a/internal/applier/fluxsim/fluxsim.go
+++ b/internal/applier/fluxsim/fluxsim.go
@@ -541,7 +541,7 @@ func SetReady(obj *unstructured.Unstructured, status metav1.ConditionStatus, rea
// patchKustomization serves a patch of a Kustomization: a server-side apply (always forced, as the
// client under test applies) or a merge patch, keeping managedFields for spec.suspend and the
// requestedAt annotation, and the generation for a spec change. Ownership follows what a real API
-// server did (measured in envtest, internal/controller/fluxtrigger_suspend_apiserver_test.go): a
+// server did (measured in envtest, internal/controller/fluxclaim_suspend_apiserver_test.go): a
// write that changes a field's value takes it from every other owner; an update that writes the
// value the field holds takes nothing; an apply that does shares the field with its owners.
func (s *Sim) patchKustomization(action k8stesting.Action) (bool, runtime.Object, error) {
diff --git a/internal/applier/fluxsim/fluxsim_test.go b/internal/applier/fluxsim/fluxsim_test.go
index d7ed8be58..37c7b7d1d 100644
--- a/internal/applier/fluxsim/fluxsim_test.go
+++ b/internal/applier/fluxsim/fluxsim_test.go
@@ -169,7 +169,7 @@ func TestSim_FailuresAndRequeues(t *testing.T) {
}
// The ownership a real API server keeps (measured in envtest,
-// internal/controller/fluxtrigger_suspend_apiserver_test.go): a merge patch owns what it changes,
+// internal/controller/fluxclaim_suspend_apiserver_test.go): a merge patch owns what it changes,
// as an update; an apply of the value a field holds shares it, even forced; a write that changes
// the value takes it from every other owner; an apply that leaves a field out gives up its share,
// and removes the field only when nobody else owns it.
diff --git a/internal/applier/holder.go b/internal/applier/holder.go
index 85ac49c40..12ed31ada 100644
--- a/internal/applier/holder.go
+++ b/internal/applier/holder.go
@@ -12,49 +12,49 @@ import (
"github.com/ConfigButler/gitops-reverser/api/v1alpha3"
)
-// One applier, one trigger: two triggers holding the same Kustomization would both suspend it under
+// One applier, one applier claim: two applier claims holding the same Kustomization would both suspend it under
// the same field manager, each would take the other's resume for an external one and suspend it
-// mid-apply, and deleting either would hand back the hold the other still needs. Two triggers on one
-// Application would both start syncs. So exactly one trigger holds an applier, by a rule every party
-// computes alike (precedes, over the triggers in line): the trigger reconciler, which holds and
+// mid-apply, and deleting either would hand back the hold the other still needs. Two applier claims on one
+// Application would both start syncs. So exactly one applier claim holds an applier, by a rule every party
+// computes alike (precedes, over the applier claims in line): the applier claim reconciler, which holds and
// hands back only for the holder, and the dispatcher, which asks the applier only for it. Both
// confirm it uncached before they write to a Kustomization.
//
-// Only a trigger in line can hold: one an eligible GitTarget references (references.go), or one
+// Only an applier claim in line can hold: one an eligible GitTarget references (references.go), or one
// that carries its finalizer, which it does from before its first write to the applier (a suspend,
-// or switching automated sync off) until it has handed the applier back or over. So an older trigger nothing eligible references
+// or switching automated sync off) until it has handed the applier back or over. So an older applier claim nothing eligible references
// keeps no younger one out, and one that is retiring stays the holder until its release is done.
-// ReasonHeldByAnotherTrigger is Configured=False on a trigger whose applier another trigger of its
+// 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 trigger takes the
+// 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.
-const ReasonHeldByAnotherTrigger = "HeldByAnotherTrigger"
+const ReasonHeldByAnotherClaim = "HeldByAnotherClaim"
-// sameApplier reports whether two triggers name one applier: the same kind of object, in the same
+// sameApplier reports whether two applier claims name one applier: the same kind of object, in the same
// KubeProvider's cluster, with the same namespace and name.
-func sameApplier(a, b v1alpha3.ApplyTrigger) bool {
- return a.TriggerKind() == b.TriggerKind() && a.KubeProviderKey() == b.KubeProviderKey() &&
+func sameApplier(a, b v1alpha3.ApplierClaim) bool {
+ return a.ClaimKind() == b.ClaimKind() && a.KubeProviderKey() == b.KubeProviderKey() &&
a.Applier() == b.Applier()
}
-// sameTrigger reports whether a and b are the same trigger, by kind, namespace and name.
-func sameTrigger(a, b v1alpha3.ApplyTrigger) bool {
- return a.TriggerKind() == b.TriggerKind() && a.GetNamespace() == b.GetNamespace() && a.GetName() == b.GetName()
+// sameClaim reports whether a and b are the same applier claim, by kind, namespace and name.
+func sameClaim(a, b v1alpha3.ApplierClaim) bool {
+ return a.ClaimKind() == b.ClaimKind() && a.GetNamespace() == b.GetNamespace() && a.GetName() == b.GetName()
}
-// precedes reports whether a comes before b in line for an applier. A FluxTrigger with an operation
+// precedes reports whether a comes before b in line for an applier. A FluxClaim with an operation
// in flight comes first: its worker follows that operation until the Kustomization is suspended
-// again, and a trigger that took the Kustomization over meanwhile would take that resume for an
+// again, and an applier claim that took the Kustomization over meanwhile would take that resume for an
// external one and suspend it mid-apply. So a Kustomization changes hands only between operations,
-// whatever triggers appear; a trigger created in the same second as the holder, which can come
+// whatever applier claims appear; an applier claim created in the same second as the holder, which can come
// before it by name, waits for the holder's apply to end. An Application has no such hold: a sync
-// somebody else started is never replaced, so an ArgoCDTrigger's operation does not count. Then a
-// trigger that holds its applier already (it carries the finalizer) comes first: a trigger that
+// somebody else started is never replaced, so an ArgoCDClaim's operation does not count. Then an
+// applier claim that holds its applier already (it carries the finalizer) comes first: an applier claim that
// becomes eligible later takes it over only once the holder lets go, so an applier is never taken
-// from a holder still holding or releasing it. Then the trigger created first comes
+// from a holder still holding or releasing it. Then the applier claim created first comes
// first, and then by namespace and name.
-func precedes(a, b v1alpha3.ApplyTrigger) bool {
+func precedes(a, b v1alpha3.ApplierClaim) bool {
if applying, other := holdsAnApply(a), holdsAnApply(b); applying != other {
return applying
}
@@ -64,24 +64,24 @@ func precedes(a, b v1alpha3.ApplyTrigger) bool {
return createdBefore(a, b)
}
-// Holding reports whether trigger carries its kind's finalizer: it holds its applier (a
+// 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 Holding(trigger v1alpha3.ApplyTrigger) bool {
- finalizer := v1alpha3.FluxTriggerFinalizer
- if trigger.TriggerKind() == v1alpha3.ApplyTriggerKindArgoCD {
- finalizer = v1alpha3.ArgoCDTriggerFinalizer
+func Holding(claim v1alpha3.ApplierClaim) bool {
+ finalizer := v1alpha3.FluxClaimFinalizer
+ if claim.ClaimKind() == v1alpha3.ApplierClaimKindArgoCD {
+ finalizer = v1alpha3.ArgoCDClaimFinalizer
}
- return slices.Contains(trigger.GetFinalizers(), finalizer)
+ return slices.Contains(claim.GetFinalizers(), finalizer)
}
-// holdsAnApply reports whether trigger is a FluxTrigger with an operation in flight.
-func holdsAnApply(trigger v1alpha3.ApplyTrigger) bool {
- operation := trigger.Operation()
- return trigger.TriggerKind() == v1alpha3.ApplyTriggerKindFlux && operation != nil && !operation.Phase.Completed()
+// holdsAnApply reports whether claim is a FluxClaim with an operation in flight.
+func holdsAnApply(claim v1alpha3.ApplierClaim) bool {
+ operation := claim.Operation()
+ return claim.ClaimKind() == v1alpha3.ApplierClaimKindFlux && operation != nil && !operation.Phase.Completed()
}
-// createdBefore orders triggers by creation, then by namespace, then by name.
-func createdBefore(a, b v1alpha3.ApplyTrigger) bool {
+// createdBefore orders applier claims by creation, then by namespace, then by name.
+func createdBefore(a, b v1alpha3.ApplierClaim) bool {
at, bt := a.GetCreationTimestamp(), b.GetCreationTimestamp()
switch {
case !at.Equal(&bt):
@@ -93,19 +93,19 @@ func createdBefore(a, b v1alpha3.ApplyTrigger) bool {
}
}
-// firstInLine is the trigger that holds trigger's applier: of the triggers of its kind in line for
-// the same applier, the one first (precedes). trigger itself counts as in line. A trigger being
-// deleted still holds it, until it is gone: its finalizer is handing the applier back. It is trigger
+// firstInLine is the applier claim that holds claim's applier: of the applier claims of its kind in line for
+// the same applier, the one first (precedes). applier claim itself counts as in line. An applier claim being
+// deleted still holds it, until it is gone: its finalizer is handing the applier back. It is claim
// itself when no other comes before it.
func firstInLine(
- ctx context.Context, reader client.Reader, trigger v1alpha3.ApplyTrigger,
-) (v1alpha3.ApplyTrigger, error) {
- others, err := TriggersOfApplier(ctx, reader, trigger)
+ ctx context.Context, reader client.Reader, claim v1alpha3.ApplierClaim,
+) (v1alpha3.ApplierClaim, error) {
+ others, err := ClaimsOfApplier(ctx, reader, claim)
if err != nil {
return nil, err
}
- source := publishedSource(trigger)
- first := trigger
+ source := publishedSource(claim)
+ first := claim
for _, other := range others {
if !precedes(other, first) {
continue
@@ -123,90 +123,90 @@ func firstInLine(
// inLine reports whether other is in line for the applier whose source the caller read: it holds
// it (Holding), or an eligible GitTarget references it.
-func inLine(ctx context.Context, reader client.Reader, other v1alpha3.ApplyTrigger, source *Source) (bool, error) {
+func inLine(ctx context.Context, reader client.Reader, other v1alpha3.ApplierClaim, source *Source) (bool, error) {
if Holding(other) {
return true, nil
}
return eligible(ctx, reader, other, source)
}
-// publishedSource is the source trigger last read, which every trigger naming its applier shares,
+// publishedSource is the source applier claim last read, which every applier claim naming its applier shares,
// or nil when it has read none.
-func publishedSource(trigger v1alpha3.ApplyTrigger) *Source {
- url, branch, known := trigger.SourceBinding()
+func publishedSource(claim v1alpha3.ApplierClaim) *Source {
+ url, branch, known := claim.SourceBinding()
if !known {
return nil
}
return &Source{URL: url, Revision: branch}
}
-// HeldByAnother is the trigger that holds trigger's applier when it is another, or nil when trigger
+// HeldByAnother is the applier claim that holds claim's applier when it is another, or nil when claim
// holds it itself.
func HeldByAnother(
- ctx context.Context, reader client.Reader, trigger v1alpha3.ApplyTrigger,
-) (v1alpha3.ApplyTrigger, error) {
- first, err := firstInLine(ctx, reader, trigger)
- if err != nil || sameTrigger(first, trigger) {
+ ctx context.Context, reader client.Reader, claim v1alpha3.ApplierClaim,
+) (v1alpha3.ApplierClaim, error) {
+ first, err := firstInLine(ctx, reader, claim)
+ if err != nil || sameClaim(first, claim) {
return nil, err
}
return first, nil
}
-// TriggersOfApplier lists the triggers other than trigger that name its applier.
-func TriggersOfApplier(
- ctx context.Context, reader client.Reader, trigger v1alpha3.ApplyTrigger,
-) ([]v1alpha3.ApplyTrigger, error) {
- triggers, err := ListApplyTriggers(ctx, reader, trigger.TriggerKind())
+// ClaimsOfApplier lists the applier claims other than claim that name its applier.
+func ClaimsOfApplier(
+ ctx context.Context, reader client.Reader, claim v1alpha3.ApplierClaim,
+) ([]v1alpha3.ApplierClaim, error) {
+ claims, err := ListApplierClaims(ctx, reader, claim.ClaimKind())
if err != nil {
return nil, err
}
- var others []v1alpha3.ApplyTrigger
- for _, other := range triggers {
- if !sameTrigger(other, trigger) && sameApplier(other, trigger) {
+ var others []v1alpha3.ApplierClaim
+ for _, other := range claims {
+ if !sameClaim(other, claim) && sameApplier(other, claim) {
others = append(others, other)
}
}
return others, nil
}
-// ListApplyTriggers lists the triggers of kind, with opts (everywhere without a namespace option).
-func ListApplyTriggers(
+// ListApplierClaims lists the applier claims of kind, with opts (everywhere without a namespace option).
+func ListApplierClaims(
ctx context.Context, reader client.Reader, kind string, opts ...client.ListOption,
-) ([]v1alpha3.ApplyTrigger, error) {
- var triggers []v1alpha3.ApplyTrigger
+) ([]v1alpha3.ApplierClaim, error) {
+ var claims []v1alpha3.ApplierClaim
switch kind {
- case v1alpha3.ApplyTriggerKindFlux:
- var list v1alpha3.FluxTriggerList
+ case v1alpha3.ApplierClaimKindFlux:
+ var list v1alpha3.FluxClaimList
if err := reader.List(ctx, &list, opts...); err != nil {
return nil, fmt.Errorf("list %ss: %w", kind, err)
}
for i := range list.Items {
- triggers = append(triggers, &list.Items[i])
+ claims = append(claims, &list.Items[i])
}
- case v1alpha3.ApplyTriggerKindArgoCD:
- var list v1alpha3.ArgoCDTriggerList
+ case v1alpha3.ApplierClaimKindArgoCD:
+ var list v1alpha3.ArgoCDClaimList
if err := reader.List(ctx, &list, opts...); err != nil {
return nil, fmt.Errorf("list %ss: %w", kind, err)
}
for i := range list.Items {
- triggers = append(triggers, &list.Items[i])
+ claims = append(claims, &list.Items[i])
}
default:
- return nil, fmt.Errorf("no apply trigger kind %q", kind)
+ return nil, fmt.Errorf("no applier claim kind %q", kind)
}
- return triggers, nil
+ return claims, nil
}
-// HeldByAnotherMessage is the Configured message of a trigger whose applier holder holds.
-func HeldByAnotherMessage(trigger, holder v1alpha3.ApplyTrigger) string {
- held := fmt.Sprintf("%s %s in the cluster of KubeProvider %s is held by %s %s/%s", trigger.ApplierKind(),
- trigger.Applier(), trigger.KubeProviderKey(), holder.TriggerKind(), holder.GetNamespace(), holder.GetName())
+// HeldByAnotherMessage is the Configured message of an applier claim whose applier holder holds.
+func HeldByAnotherMessage(claim, holder v1alpha3.ApplierClaim) string {
+ held := fmt.Sprintf("%s %s in the cluster of KubeProvider %s is held by %s %s/%s", claim.ApplierKind(),
+ claim.Applier(), claim.KubeProviderKey(), holder.ClaimKind(), holder.GetNamespace(), holder.GetName())
switch {
- case holdsAnApply(holder) && !holdsAnApply(trigger):
- return held + ", whose apply is in flight; this trigger takes it over once that apply ends"
- case Holding(holder) && !Holding(trigger):
- return held + ", which holds it now; this trigger takes it over once that one lets go of it"
+ case holdsAnApply(holder) && !holdsAnApply(claim):
+ return held + ", whose apply is in flight; this applier claim takes it over once that apply ends"
+ case Holding(holder) && !Holding(claim):
+ return held + ", which holds it now; this applier claim takes it over once that one lets go of it"
}
- return held + ", which was created first; this trigger takes it over once that one is deleted or no " +
+ return held + ", which was created first; this applier claim takes it over once that one is deleted or no " +
"eligible GitTarget references it"
}
diff --git a/internal/applier/holder_test.go b/internal/applier/holder_test.go
index f66d83ca1..8afd21456 100644
--- a/internal/applier/holder_test.go
+++ b/internal/applier/holder_test.go
@@ -17,11 +17,11 @@ import (
"github.com/ConfigButler/gitops-reverser/internal/git"
)
-// rival is a trigger of kind in namespace team-b naming the same applier as the one the dispatcher
+// rival is an applier claim of kind in namespace team-b naming the same applier as the one the dispatcher
// fixture made for the GitTarget, in the same cluster, created an hour before it (older) or after.
func rival(t *testing.T, f *dispatcherFixture, kind, name string, older bool) {
t.Helper()
- own := v1alpha3.NewApplyTrigger(kind)
+ own := v1alpha3.NewApplierClaim(kind)
require.NoError(t, f.control.Get(context.Background(), client.ObjectKey{Namespace: targetNS, Name: name}, own))
own.SetCreationTimestamp(metav1.NewTime(time.Unix(1_000_000, 0)))
require.NoError(t, f.control.Update(context.Background(), own.(client.Object)))
@@ -29,67 +29,67 @@ func rival(t *testing.T, f *dispatcherFixture, kind, name string, older bool) {
if older {
created = own.GetCreationTimestamp().Add(-time.Hour)
}
- other := own.DeepCopyObject().(v1alpha3.ApplyTrigger)
+ other := own.DeepCopyObject().(v1alpha3.ApplierClaim)
other.SetNamespace("team-b")
other.SetResourceVersion("")
other.SetCreationTimestamp(metav1.NewTime(created))
- // The fixture's own trigger names its applier in its own namespace's terms; spell it out.
+ // The fixture's own applier claim names its applier in its own namespace's terms; spell it out.
applier := own.Applier()
ref := fluxmeta.NamespacedObjectReference{Namespace: applier.Namespace, Name: applier.Name}
switch typed := other.(type) {
- case *v1alpha3.FluxTrigger:
+ case *v1alpha3.FluxClaim:
typed.Spec.KustomizationRef = ref
- case *v1alpha3.ArgoCDTrigger:
+ case *v1alpha3.ArgoCDClaim:
typed.Spec.ApplicationRef = ref
}
require.NoError(t, f.control.Create(context.Background(), other.(client.Object)))
referenceFrom(t, f.control, other)
}
-// referenceFrom puts a GitTarget in trigger's namespace that names it, and admits that namespace
-// on the KubeProviders, so the trigger is in line for its applier.
-func referenceFrom(t *testing.T, c client.Client, trigger v1alpha3.ApplyTrigger) {
+// referenceFrom puts a GitTarget in claim's namespace that names it, and admits that namespace
+// on the KubeProviders, so the applier claim is in line for its applier.
+func referenceFrom(t *testing.T, c client.Client, claim v1alpha3.ApplierClaim) {
t.Helper()
ctx := context.Background()
- target := gitTarget("names-"+trigger.GetName(), nil, "")
- target.Namespace = trigger.GetNamespace()
- target.Spec.ApplyTriggerRef = &v1alpha3.ApplyTriggerReference{Kind: trigger.TriggerKind(), Name: trigger.GetName()}
+ target := gitTarget("names-"+claim.GetName(), nil, "")
+ target.Namespace = claim.GetNamespace()
+ target.Spec.ApplierClaimRef = &v1alpha3.ApplierClaimReference{Kind: claim.ClaimKind(), Name: claim.GetName()}
require.NoError(t, c.Create(ctx, target))
- for _, name := range []string{localCluster, "prod-eu-1", trigger.KubeProviderKey().Name} {
+ for _, name := range []string{localCluster, "prod-eu-1", claim.KubeProviderKey().Name} {
provider := &v1alpha3.KubeProvider{}
if err := c.Get(ctx, client.ObjectKey{Namespace: kubeProviderNS, Name: name}, provider); err != nil {
- require.NoError(t, c.Create(ctx, kubeProvider(name, targetNS, trigger.GetNamespace())))
+ require.NoError(t, c.Create(ctx, kubeProvider(name, targetNS, claim.GetNamespace())))
continue
}
- provider.Spec.AccessFrom.Names = append(provider.Spec.AccessFrom.Names, trigger.GetNamespace())
+ provider.Spec.AccessFrom.Names = append(provider.Spec.AccessFrom.Names, claim.GetNamespace())
require.NoError(t, c.Update(ctx, provider))
}
}
// TestDispatcher_OnlyTheHolderAsksItsApplier is review 5's finding 5, the dispatcher's half: two
-// triggers naming one applier both asked it. Now only the one first in line (created first) does;
+// applier claims naming one applier both asked it. Now only the one first in line (created first) does;
// a GitTarget naming the other asks nothing.
func TestDispatcher_OnlyTheHolderAsksItsApplier(t *testing.T) {
for name, tc := range map[string]struct {
target func() *v1alpha3.GitTarget
kind string
- trigger string
+ claim string
applier git.ApplierRef
}{
- "FluxTrigger": {
- target: func() *v1alpha3.GitTarget { return gitTarget("a", checkoutRef(), "") },
- kind: v1alpha3.ApplyTriggerKindFlux,
- trigger: "checkout", applier: checkout,
+ "FluxClaim": {
+ target: func() *v1alpha3.GitTarget { return gitTarget("a", checkoutRef(), "") },
+ kind: v1alpha3.ApplierClaimKindFlux,
+ claim: "checkout", applier: checkout,
},
- "ArgoCDTrigger": {
- target: argoTarget, kind: v1alpha3.ApplyTriggerKindArgoCD,
- trigger: "platform", applier: argoPlatform,
+ "ArgoCDClaim": {
+ target: argoTarget, kind: v1alpha3.ApplierClaimKindArgoCD,
+ claim: "platform", applier: argoPlatform,
},
} {
t.Run(name, func(t *testing.T) {
- t.Run("another trigger was first", func(t *testing.T) {
+ t.Run("another applier claim was first", func(t *testing.T) {
f := newDispatcherFixture(t, []client.Object{tc.target()}, append(behindSource(), manualApp())...)
- rival(t, f, tc.kind, tc.trigger, true)
+ rival(t, f, tc.kind, tc.claim, true)
res := f.attempt(request(tc.applier, v1alpha3.ApplyReasonForeignCommit, "a"))
@@ -97,9 +97,9 @@ func TestDispatcher_OnlyTheHolderAsksItsApplier(t *testing.T) {
assert.Empty(t, f.patches(), "nothing is resumed")
assert.Empty(t, f.updates(), "no sync is set")
})
- t.Run("this trigger was first", func(t *testing.T) {
+ t.Run("this applier claim was first", func(t *testing.T) {
f := newDispatcherFixture(t, []client.Object{tc.target()}, append(behindSource(), manualApp())...)
- rival(t, f, tc.kind, tc.trigger, false)
+ rival(t, f, tc.kind, tc.claim, false)
res := f.attempt(request(tc.applier, v1alpha3.ApplyReasonForeignCommit, "a"))
@@ -109,33 +109,33 @@ func TestDispatcher_OnlyTheHolderAsksItsApplier(t *testing.T) {
}
}
-// TestHolder_FirstInLine: the holder is the trigger in line created first, then by namespace and
+// TestHolder_FirstInLine: the holder is the applier claim in line created first, then by namespace and
// name; one being deleted still holds until it is gone, one no eligible GitTarget references is not
-// in line, and neither is a trigger naming another applier, or the same one in another cluster.
+// in line, and neither is an applier claim naming another applier, or the same one in another cluster.
func TestHolder_FirstInLine(t *testing.T) {
at := func(minutes int) metav1.Time {
return metav1.NewTime(time.Unix(0, 0).Add(time.Duration(minutes) * time.Minute))
}
- trigger := func(namespace, name, cluster, ks string, created metav1.Time) *v1alpha3.FluxTrigger {
- return &v1alpha3.FluxTrigger{
+ claim := func(namespace, name, cluster, ks string, created metav1.Time) *v1alpha3.FluxClaim {
+ return &v1alpha3.FluxClaim{
ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name, CreationTimestamp: created},
- Spec: v1alpha3.FluxTriggerSpec{
+ Spec: v1alpha3.FluxClaimSpec{
KubeProviderRef: kubeProviderRef(cluster),
KustomizationRef: fluxmeta.NamespacedObjectReference{Namespace: "flux-system", Name: ks},
},
}
}
- self := trigger("team-c", "mine", localCluster, "checkout", at(10))
- deleting := trigger("team-z", "old", localCluster, "checkout", at(5))
- deleting.Finalizers = []string{v1alpha3.FluxTriggerFinalizer}
+ self := claim("team-c", "mine", localCluster, "checkout", at(10))
+ deleting := claim("team-z", "old", localCluster, "checkout", at(5))
+ deleting.Finalizers = []string{v1alpha3.FluxClaimFinalizer}
deleting.DeletionTimestamp = &metav1.Time{Time: time.Unix(100, 0)}
objects := []client.Object{
self,
- trigger("team-a", "elsewhere", "hub", "checkout", at(1)),
- trigger("team-a", "other", localCluster, "infra", at(1)),
- trigger("team-b", "same-second", localCluster, "checkout", at(10)),
+ claim("team-a", "elsewhere", "hub", "checkout", at(1)),
+ claim("team-a", "other", localCluster, "infra", at(1)),
+ claim("team-b", "same-second", localCluster, "checkout", at(10)),
}
- unreferenced := trigger("team-a", "stray", localCluster, "checkout", at(1))
+ unreferenced := claim("team-a", "stray", localCluster, "checkout", at(1))
f := newDispatcherFixture(t, nil)
for _, obj := range append(objects, unreferenced) {
require.NoError(t, f.control.Create(context.Background(), obj))
@@ -144,9 +144,9 @@ func TestHolder_FirstInLine(t *testing.T) {
holder, err := firstInLine(context.Background(), f.control, self)
require.NoError(t, err)
assert.Equal(t, "team-c/mine", holder.GetNamespace()+"/"+holder.GetName(),
- "older triggers nothing eligible references keep no younger one out")
+ "older applier claims nothing eligible references keep no younger one out")
- referenceFrom(t, f.control, objects[3].(*v1alpha3.FluxTrigger))
+ referenceFrom(t, f.control, objects[3].(*v1alpha3.FluxClaim))
holder, err = firstInLine(context.Background(), f.control, self)
require.NoError(t, err)
assert.Equal(t, "team-b/same-second", holder.GetNamespace()+"/"+holder.GetName(),
@@ -156,30 +156,35 @@ func TestHolder_FirstInLine(t *testing.T) {
holder, err = HeldByAnother(context.Background(), f.control, self)
require.NoError(t, err)
require.NotNil(t, holder)
- assert.Equal(t, "team-z/old", holder.GetNamespace()+"/"+holder.GetName(), "a trigger being deleted still holds")
-
- others, err := TriggersOfApplier(context.Background(), f.control, self)
+ assert.Equal(
+ t,
+ "team-z/old",
+ holder.GetNamespace()+"/"+holder.GetName(),
+ "an applier claim being deleted still holds",
+ )
+
+ others, err := ClaimsOfApplier(context.Background(), f.control, self)
require.NoError(t, err)
assert.Len(t, others, 3, "the same Kustomization in another cluster, or another one, is not in line")
}
-// TestHolder_AnApplyInFlightComesFirst is review 5's P2: a FluxTrigger with an operation in flight
-// comes first in line, so a trigger that comes before it by creation and name waits for that apply
-// to end. An ArgoCDTrigger's operation does not count: a sync somebody else started is never
+// TestHolder_AnApplyInFlightComesFirst is review 5's P2: a FluxClaim with an operation in flight
+// comes first in line, so an applier claim that comes before it by creation and name waits for that apply
+// to end. An ArgoCDClaim's operation does not count: a sync somebody else started is never
// replaced, so the Application can change hands mid-sync.
func TestHolder_AnApplyInFlightComesFirst(t *testing.T) {
created := metav1.NewTime(time.Unix(1_000_000, 0))
applying := &v1alpha3.ApplyOperation{Phase: v1alpha3.ApplyOperationRunning, Token: "T1"}
- flux := func(namespace string, operation *v1alpha3.ApplyOperation) *v1alpha3.FluxTrigger {
- return &v1alpha3.FluxTrigger{
+ flux := func(namespace string, operation *v1alpha3.ApplyOperation) *v1alpha3.FluxClaim {
+ return &v1alpha3.FluxClaim{
ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: "checkout", CreationTimestamp: created},
- Status: v1alpha3.FluxTriggerStatus{Operation: operation},
+ Status: v1alpha3.FluxClaimStatus{Operation: operation},
}
}
- argo := func(namespace string, operation *v1alpha3.ApplyOperation) *v1alpha3.ArgoCDTrigger {
- return &v1alpha3.ArgoCDTrigger{
+ argo := func(namespace string, operation *v1alpha3.ApplyOperation) *v1alpha3.ArgoCDClaim {
+ return &v1alpha3.ArgoCDClaim{
ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: "platform", CreationTimestamp: created},
- Status: v1alpha3.ArgoCDTriggerStatus{Operation: operation},
+ Status: v1alpha3.ArgoCDClaimStatus{Operation: operation},
}
}
@@ -191,6 +196,6 @@ func TestHolder_AnApplyInFlightComesFirst(t *testing.T) {
assert.True(t, precedes(flux("team-a", nil), flux("team-b", done)), "a completed one does not count")
assert.True(t, precedes(argo("team-a", nil), argo("team-b", applying)), "nor an Argo CD sync")
assert.Contains(t, HeldByAnotherMessage(flux("team-a", nil), flux("team-b", applying)),
- "is held by FluxTrigger team-b/checkout, whose apply is in flight; this trigger takes it over once that "+
+ "is held by FluxClaim team-b/checkout, whose apply is in flight; this applier claim takes it over once that "+
"apply ends")
}
diff --git a/internal/applier/references.go b/internal/applier/references.go
index 040eda3d8..85617c26f 100644
--- a/internal/applier/references.go
+++ b/internal/applier/references.go
@@ -17,26 +17,26 @@ import (
"github.com/ConfigButler/gitops-reverser/internal/repourl"
)
-// A trigger acts only while an eligible GitTarget references it: one that names it, is admitted by
-// its KubeProviders, and writes the repository and branch the applier fetches. A trigger nothing
+// An applier claim acts only while an eligible GitTarget references it: one that names it, is admitted by
+// its KubeProviders, and writes the repository and branch the applier fetches. An applier claim nothing
// eligible names serves nobody, so it reads and writes nothing in the applier's cluster, and is not
// in line for its applier. Eligibility is judged from those inputs alone, never from the
-// GitTarget's Ready, which reads the trigger's own Ready back (ApplyTriggerReady) and would make
+// GitTarget's Ready, which reads the applier claim's own Ready back (ApplierClaimReady) and would make
// the two circular.
-// ReasonNotReferenced is Configured=Unknown (and Ready=True) on a trigger no GitTarget names: it is
+// ReasonNotReferenced is Configured=Unknown (and Ready=True) on an applier claim no GitTarget names: it is
// asked nothing, so it does nothing.
const ReasonNotReferenced = "NotReferenced"
-// ReasonNotBound is Configured on a trigger whose naming GitTargets are none of them eligible:
+// 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.
const ReasonNotBound = "NotBound"
-// ReferenceState is how one GitTarget naming a trigger stands with it.
+// ReferenceState is how one GitTarget naming an applier claim stands with it.
type ReferenceState int
const (
- // ReferenceEligible names the trigger, is admitted, and writes what the applier fetches.
+ // 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.
@@ -45,7 +45,7 @@ const (
ReferenceRefused
)
-// Reference is one GitTarget naming a trigger, judged.
+// Reference is one GitTarget naming an applier claim, judged.
type Reference struct {
Target types.NamespacedName
State ReferenceState
@@ -56,13 +56,13 @@ type Reference struct {
Message string
}
-// References are the GitTargets naming one trigger, judged.
+// References are the GitTargets naming one applier claim, judged.
type References []Reference
-// Named reports whether any GitTarget names the trigger.
+// Named reports whether any GitTarget names the applier claim.
func (refs References) Named() bool { return len(refs) > 0 }
-// Eligible reports whether at least one GitTarget naming the trigger is eligible.
+// Eligible reports whether at least one GitTarget naming the applier claim is eligible.
func (refs References) Eligible() bool {
for _, ref := range refs {
if ref.State == ReferenceEligible {
@@ -72,7 +72,7 @@ func (refs References) Eligible() bool {
return false
}
-// Refused reports whether every GitTarget naming the trigger is conclusively refused: there is at
+// 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.
func (refs References) Refused() bool {
for _, ref := range refs {
@@ -111,22 +111,22 @@ func (refs References) Message() string {
parts = append(parts, fmt.Sprintf("GitTarget %s: %s", ref.Target, ref.Message))
}
}
- return "no GitTarget naming this trigger is eligible: " + strings.Join(parts, "; ")
+ return "no GitTarget naming this applier claim is eligible: " + strings.Join(parts, "; ")
}
-// NamingTargets lists the GitTargets that name trigger, in its namespace, and are not being
+// NamingTargets lists the GitTargets that name claim, in its namespace, and are not being
// deleted, by name.
func NamingTargets(
- ctx context.Context, reader client.Reader, trigger v1alpha3.ApplyTrigger,
+ ctx context.Context, reader client.Reader, claim v1alpha3.ApplierClaim,
) ([]*v1alpha3.GitTarget, error) {
var list v1alpha3.GitTargetList
- if err := reader.List(ctx, &list, client.InNamespace(trigger.GetNamespace())); err != nil {
+ if err := reader.List(ctx, &list, client.InNamespace(claim.GetNamespace())); err != nil {
return nil, fmt.Errorf("list GitTargets: %w", err)
}
var targets []*v1alpha3.GitTarget
for i := range list.Items {
target := &list.Items[i]
- if target.DeletionTimestamp.IsZero() && Names(target, trigger) {
+ if target.DeletionTimestamp.IsZero() && Names(target, claim) {
targets = append(targets, target)
}
}
@@ -134,23 +134,23 @@ func NamingTargets(
return targets, nil
}
-// Names reports whether target's spec.applyTriggerRef names trigger.
-func Names(target *v1alpha3.GitTarget, trigger v1alpha3.ApplyTrigger) bool {
- ref := target.Spec.ApplyTriggerRef
- return ref != nil && target.Namespace == trigger.GetNamespace() && ref.Kind == trigger.TriggerKind() &&
- ref.Name == trigger.GetName()
+// Names reports whether target's spec.applierClaimRef names claim.
+func Names(target *v1alpha3.GitTarget, claim v1alpha3.ApplierClaim) bool {
+ ref := target.Spec.ApplierClaimRef
+ return ref != nil && target.Namespace == claim.GetNamespace() && ref.Kind == claim.ClaimKind() &&
+ ref.Name == claim.GetName()
}
-// JudgeReferences judges each of targets, which name trigger, against the source its applier
+// 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 JudgeReferences(
- ctx context.Context, reader client.Reader, trigger v1alpha3.ApplyTrigger, targets []*v1alpha3.GitTarget,
+ ctx context.Context, reader client.Reader, claim v1alpha3.ApplierClaim, targets []*v1alpha3.GitTarget,
source *Source,
) (References, error) {
refs := make(References, 0, len(targets))
for _, target := range targets {
- ref, err := judgeReference(ctx, reader, trigger, target, source)
+ ref, err := judgeReference(ctx, reader, claim, target, source)
if err != nil {
return nil, err
}
@@ -160,17 +160,17 @@ func JudgeReferences(
}
func judgeReference(
- ctx context.Context, reader client.Reader, trigger v1alpha3.ApplyTrigger, target *v1alpha3.GitTarget,
+ ctx context.Context, reader client.Reader, claim v1alpha3.ApplierClaim, target *v1alpha3.GitTarget,
source *Source,
) (Reference, error) {
ref := Reference{Target: types.NamespacedName{Namespace: target.Namespace, Name: target.Name}}
- decision, err := authz.ApplierAdmitted(ctx, reader, target, trigger)
+ decision, err := authz.ApplierAdmitted(ctx, reader, target, claim)
switch {
case err != nil:
return ref, fmt.Errorf("check GitTarget %s against its KubeProviders: %w", ref.Target, err)
case decision.Allowed:
case decision.Reason == authz.ReasonKubeProviderNotFound:
- // A provider may be on its way, as the trigger's own KubeProviderNotFound may.
+ // A provider may be on its way, as the applier claim's own KubeProviderNotFound may.
ref.State, ref.Message = ReferenceUndecided, decision.Message
return ref, nil
default:
@@ -194,7 +194,7 @@ func judgeReference(
return ref, nil
}
want := Binding{RepositoryURL: provider.Spec.URL, Branch: branch.Spec.Branch}
- if mismatch := bindingMismatch(trigger, source.URL, source.Revision, want); mismatch != "" {
+ if mismatch := bindingMismatch(claim, source.URL, source.Revision, want); mismatch != "" {
ref.State, ref.Reason, ref.Message = ReferenceRefused, ReasonSourceMismatch, mismatch
return ref, nil
}
@@ -203,8 +203,8 @@ func judgeReference(
// bindingMismatch returns why an applier fetching url and branch does not apply what want writes,
// or "" when it does.
-func bindingMismatch(trigger v1alpha3.ApplyTrigger, url, branch string, want Binding) string {
- name := trigger.TriggerKind() + " " + trigger.GetName()
+func bindingMismatch(claim v1alpha3.ApplierClaim, url, branch string, want Binding) string {
+ name := claim.ClaimKind() + " " + claim.GetName()
if !repourl.Same(url, want.RepositoryURL) {
return fmt.Sprintf("the applier of %s applies %s, not %s", name, url, want.RepositoryURL)
}
@@ -214,21 +214,21 @@ func bindingMismatch(trigger v1alpha3.ApplyTrigger, url, branch string, want Bin
return ""
}
-// eligible reports whether at least one GitTarget naming trigger is eligible, judged against
-// source, the applier's source as the caller read it: triggers in line for one applier share it.
-// With no source read, a reference that is not refused counts: a trigger that may be eligible is
+// eligible reports whether at least one GitTarget naming claim is eligible, judged against
+// source, the applier's source as the caller read it: applier claims in line for one applier share it.
+// With no source read, a reference that is not refused counts: an applier claim that may be eligible is
// taken to be, so a caller that cannot tell waits rather than acts.
func eligible(
- ctx context.Context, reader client.Reader, trigger v1alpha3.ApplyTrigger, source *Source,
+ ctx context.Context, reader client.Reader, claim v1alpha3.ApplierClaim, source *Source,
) (bool, error) {
- if !trigger.GetDeletionTimestamp().IsZero() {
+ if !claim.GetDeletionTimestamp().IsZero() {
return false, nil
}
- targets, err := NamingTargets(ctx, reader, trigger)
+ targets, err := NamingTargets(ctx, reader, claim)
if err != nil || len(targets) == 0 {
return false, err
}
- refs, err := JudgeReferences(ctx, reader, trigger, targets, source)
+ refs, err := JudgeReferences(ctx, reader, claim, targets, source)
if err != nil || source != nil {
return refs.Eligible(), err
}
diff --git a/internal/applier/status_writer.go b/internal/applier/status_writer.go
index 8b7a42214..15103d6c2 100644
--- a/internal/applier/status_writer.go
+++ b/internal/applier/status_writer.go
@@ -19,36 +19,36 @@ import (
"github.com/ConfigButler/gitops-reverser/internal/git"
)
-// DefaultStatusRetryInterval is how soon a trigger status write that failed is tried again.
+// DefaultStatusRetryInterval is how soon an applier claim status write that failed is tried again.
const DefaultStatusRetryInterval = 5 * time.Second
-// maxStatusConflicts bounds how often one write re-reads a trigger after losing a race with its
+// maxStatusConflicts bounds how often one write re-reads an applier claim after losing a race with its
// reconciler, which writes the conditions and the source of the same status.
const maxStatusConflicts = 5
-// 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
+// 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 trigger replaces one not yet written, so the status catches up with the worker's latest view
+// 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 trigger first through RecordApplyOperation, which the
-// dispatcher calls just before the write that starts the apply, so the trigger never shows an
+// 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 trigger's
+// 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 trigger's reconciler. Each
-// write reads the trigger, sets these two fields and updates the status with the resourceVersion it
+// 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.
type StatusWriter struct {
@@ -62,19 +62,19 @@ type StatusWriter struct {
// writing is held for every write, and for the whole of a batch, so writes land in the order
// their publications were made.
writing sync.Mutex
- // recorded is the operation RecordApplyOperation last wrote on each trigger, with what the
+ // recorded is the operation RecordApplyOperation last wrote on each applier claim, with what the
// status held before it. It is read and written under writing.
- recorded map[git.TriggerRef]recordedOperation
+ recorded map[git.ApplierClaimRef]recordedOperation
mu sync.Mutex
- pending map[git.TriggerRef]publication
+ pending map[git.ApplierClaimRef]publication
wake chan struct{}
}
-// publication is what is held for one trigger until it is written: the worker's latest status, and
+// publication is what is held for one applier claim until it is written: the worker's latest status, and
// every operation withdrawn since the last write, which a later publication never takes back.
type publication struct {
- status git.ApplyTriggerStatus
+ status git.ApplierClaimStatus
withdrawn []*v1alpha3.ApplyOperation
}
@@ -95,20 +95,20 @@ type recordedOperation struct {
}
var (
- // errTriggerReleased is a write to a trigger that is gone, or being deleted.
- errTriggerReleased = errors.New("the trigger is gone or being deleted")
- // errOperationInFlight is a record refused because the trigger's status holds another operation
+ // errClaimReleased is a write to an applier claim that is gone, or being deleted.
+ errClaimReleased = errors.New("the applier claim is gone or being deleted")
+ // errOperationInFlight is a record refused because the applier claim's status holds another operation
// that has not completed.
- errOperationInFlight = errors.New("the trigger records another operation that has not completed")
+ errOperationInFlight = errors.New("the applier claim records another operation that has not completed")
)
var (
- _ git.ApplyTriggerStatusWriter = (*StatusWriter)(nil)
+ _ git.ApplierClaimStatusWriter = (*StatusWriter)(nil)
_ manager.Runnable = (*StatusWriter)(nil)
_ manager.LeaderElectionRunnable = (*StatusWriter)(nil)
)
-// NewStatusWriter returns a StatusWriter that reads triggers through reader, uncached so a write
+// 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 NewStatusWriter(reader client.Reader, writer client.StatusClient, log logr.Logger) *StatusWriter {
return &StatusWriter{
@@ -116,8 +116,8 @@ func NewStatusWriter(reader client.Reader, writer client.StatusClient, log logr.
writer: writer,
log: log,
RetryInterval: DefaultStatusRetryInterval,
- recorded: map[git.TriggerRef]recordedOperation{},
- pending: map[git.TriggerRef]publication{},
+ recorded: map[git.ApplierClaimRef]recordedOperation{},
+ pending: map[git.ApplierClaimRef]publication{},
wake: make(chan struct{}, 1),
}
}
@@ -125,9 +125,9 @@ func NewStatusWriter(reader client.Reader, writer client.StatusClient, log logr.
// NeedLeaderElection is true: the branch workers that publish run only on the leader.
func (w *StatusWriter) NeedLeaderElection() bool { return true }
-// PublishApplyTriggerStatus holds status for trigger until it is written, replacing any not yet
+// 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 (w *StatusWriter) PublishApplyTriggerStatus(trigger git.TriggerRef, status git.ApplyTriggerStatus) {
+func (w *StatusWriter) PublishApplierClaimStatus(claim git.ApplierClaimRef, status git.ApplierClaimStatus) {
next := publication{status: status}
if status.Operation != nil {
next.status.Operation = stored(status.Operation)
@@ -136,7 +136,7 @@ func (w *StatusWriter) PublishApplyTriggerStatus(trigger git.TriggerRef, status
next.withdrawn = []*v1alpha3.ApplyOperation{stored(status.Withdrawn)}
}
w.mu.Lock()
- w.pending[trigger] = w.pending[trigger].merged(next)
+ w.pending[claim] = w.pending[claim].merged(next)
w.mu.Unlock()
select {
case w.wake <- struct{}{}:
@@ -173,14 +173,14 @@ func (w *StatusWriter) Start(ctx context.Context) 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
+// 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 trigger's reconciler recorded, or one an earlier process
+// 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 (w *StatusWriter) RecordApplyOperation(
- ctx context.Context, ref git.TriggerRef, operation *v1alpha3.ApplyOperation,
+ ctx context.Context, ref git.ApplierClaimRef, operation *v1alpha3.ApplyOperation,
) error {
w.writing.Lock()
defer w.writing.Unlock()
@@ -190,11 +190,11 @@ func (w *StatusWriter) RecordApplyOperation(
operation = stored(operation)
for range maxStatusConflicts {
- trigger, err := w.read(ctx, ref)
+ claim, err := w.read(ctx, ref)
if err != nil {
return err
}
- current := trigger.Operation()
+ current := claim.Operation()
switch {
case current != nil && sameOperation(current, operation):
return nil
@@ -204,13 +204,13 @@ func (w *StatusWriter) RecordApplyOperation(
}
// Kept before the update: an update that reports an error may still have landed.
w.recorded[ref] = recordedOperation{operation: operation, displaced: current.DeepCopy()}
- trigger.SetOperation(operation, trigger.RepairPending())
- err = w.writer.Status().Update(ctx, trigger)
+ claim.SetOperation(operation, claim.RepairPending())
+ err = w.writer.Status().Update(ctx, claim)
switch {
case err == nil:
return nil
case apierrors.IsNotFound(err):
- return errTriggerReleased
+ return errClaimReleased
case apierrors.IsConflict(err):
continue
default:
@@ -220,9 +220,9 @@ func (w *StatusWriter) RecordApplyOperation(
return fmt.Errorf("record the operation on %s: it kept changing under the write", ref)
}
-// writeHeld writes what is held for one trigger, if anything, and holds it again should the write
+// writeHeld writes what is held for one applier claim, if anything, and holds it again should the write
// fail. The caller holds writing.
-func (w *StatusWriter) writeHeld(ctx context.Context, ref git.TriggerRef) error {
+func (w *StatusWriter) writeHeld(ctx context.Context, ref git.ApplierClaimRef) error {
w.mu.Lock()
held, ok := w.pending[ref]
delete(w.pending, ref)
@@ -231,7 +231,7 @@ func (w *StatusWriter) writeHeld(ctx context.Context, ref git.TriggerRef) error
return nil
}
err := w.write(ctx, ref, held)
- if err != nil && !errors.Is(err, errTriggerReleased) {
+ if err != nil && !errors.Is(err, errClaimReleased) {
w.holdAgain(ref, held)
}
return err
@@ -243,18 +243,18 @@ func (w *StatusWriter) writePending(ctx context.Context) bool {
defer w.writing.Unlock()
w.mu.Lock()
batch := w.pending
- w.pending = map[git.TriggerRef]publication{}
+ w.pending = map[git.ApplierClaimRef]publication{}
w.mu.Unlock()
failed := false
- for trigger, held := range batch {
- err := w.write(ctx, trigger, held)
- if err == nil || errors.Is(err, errTriggerReleased) {
+ for claim, held := range batch {
+ err := w.write(ctx, claim, held)
+ if err == nil || errors.Is(err, errClaimReleased) {
continue
}
- w.log.Info("could not write an apply trigger's status; trying again", "trigger", trigger.String(),
+ w.log.Info("could not write an applier claim's status; trying again", "applier claim", claim.String(),
"error", err.Error())
- if w.holdAgain(trigger, held) {
+ if w.holdAgain(claim, held) {
failed = true
}
}
@@ -263,7 +263,7 @@ func (w *StatusWriter) writePending(ctx context.Context) bool {
// holdAgain holds a publication whose write failed, under any made since, and reports whether none
// was: only then is a retry due, because a newer publication wakes the writer itself.
-func (w *StatusWriter) holdAgain(ref git.TriggerRef, failed publication) bool {
+func (w *StatusWriter) holdAgain(ref git.ApplierClaimRef, failed publication) bool {
w.mu.Lock()
defer w.mu.Unlock()
newer, ok := w.pending[ref]
@@ -275,26 +275,26 @@ func (w *StatusWriter) holdAgain(ref git.TriggerRef, failed publication) bool {
return false
}
-// write sets one trigger's status.operation, as operationToWrite says, and status.repairPending. A
-// trigger that is gone, or being deleted, takes the publication with it: errTriggerReleased.
-func (w *StatusWriter) write(ctx context.Context, ref git.TriggerRef, held publication) error {
+// write sets one applier claim's status.operation, as operationToWrite says, and status.repairPending. An
+// applier claim that is gone, or being deleted, takes the publication with it: errClaimReleased.
+func (w *StatusWriter) write(ctx context.Context, ref git.ApplierClaimRef, held publication) error {
for range maxStatusConflicts {
- trigger, err := w.read(ctx, ref)
+ claim, err := w.read(ctx, ref)
if err != nil {
return err
}
- operation := w.operationToWrite(ref, held, trigger.Operation())
+ operation := w.operationToWrite(ref, held, claim.Operation())
repairPending := held.status.RepairPending
- if trigger.RepairPending() == repairPending && equality.Semantic.DeepEqual(trigger.Operation(), operation) {
+ if claim.RepairPending() == repairPending && equality.Semantic.DeepEqual(claim.Operation(), operation) {
return nil
}
- trigger.SetOperation(operation, repairPending)
- err = w.writer.Status().Update(ctx, trigger)
+ claim.SetOperation(operation, repairPending)
+ err = w.writer.Status().Update(ctx, claim)
switch {
case err == nil:
return nil
case apierrors.IsNotFound(err):
- return errTriggerReleased
+ return errClaimReleased
case apierrors.IsConflict(err):
continue
default:
@@ -304,32 +304,32 @@ func (w *StatusWriter) write(ctx context.Context, ref git.TriggerRef, held publi
return fmt.Errorf("write the status of %s: it kept changing under the write", ref)
}
-// read reads a trigger to write, and errTriggerReleased when it is gone or being deleted.
-func (w *StatusWriter) read(ctx context.Context, ref git.TriggerRef) (v1alpha3.ApplyTrigger, error) {
- trigger := v1alpha3.NewApplyTrigger(ref.Kind)
- if trigger == nil {
- return nil, errTriggerReleased
+// read reads an applier claim to write, and errClaimReleased when it is gone or being deleted.
+func (w *StatusWriter) read(ctx context.Context, ref git.ApplierClaimRef) (v1alpha3.ApplierClaim, error) {
+ claim := v1alpha3.NewApplierClaim(ref.Kind)
+ if claim == nil {
+ return nil, errClaimReleased
}
- err := w.reader.Get(ctx, client.ObjectKey{Namespace: ref.Namespace, Name: ref.Name}, trigger)
+ err := w.reader.Get(ctx, client.ObjectKey{Namespace: ref.Namespace, Name: ref.Name}, claim)
switch {
case apierrors.IsNotFound(err):
- return nil, errTriggerReleased
+ return nil, errClaimReleased
case err != nil:
return nil, fmt.Errorf("read %s: %w", ref, err)
- case !trigger.GetDeletionTimestamp().IsZero():
- return nil, errTriggerReleased
+ case !claim.GetDeletionTimestamp().IsZero():
+ return nil, errClaimReleased
}
- return trigger, nil
+ return claim, nil
}
-// operationToWrite is the operation a write of held leaves on a trigger whose status holds current.
+// operationToWrite is the operation a write of held leaves on an applier claim whose status holds current.
// An operation held withdraws is replaced by what the status held before this writer recorded it,
// or by none. Then the published operation is written over none, or over the same operation at an
// earlier point; never over another, which somebody else recorded, and never in flight over the
// same one completed: a worker that recovered an external resume publishes it as it was listed,
-// and the trigger's reconciler can have completed it since.
+// and the applier claim's reconciler can have completed it since.
func (w *StatusWriter) operationToWrite(
- ref git.TriggerRef, held publication, current *v1alpha3.ApplyOperation,
+ ref git.ApplierClaimRef, held publication, current *v1alpha3.ApplyOperation,
) *v1alpha3.ApplyOperation {
operation := current
if current != nil && holdsOperation(held.withdrawn, current) {
@@ -351,7 +351,7 @@ func (w *StatusWriter) operationToWrite(
// sameOperation reports whether a and b are one operation, perhaps at different points: an
// operation keeps the token and requestedAt it was recorded with. The requestedAt tells apart the
-// operations the trigger's reconciler records, which carry no token.
+// operations the applier claim's reconciler records, which carry no token.
func sameOperation(a, b *v1alpha3.ApplyOperation) bool {
return a.Token == b.Token && a.RequestedAt.Equal(&b.RequestedAt)
}
diff --git a/internal/applier/status_writer_test.go b/internal/applier/status_writer_test.go
index a26c9e5cc..3de41cb2a 100644
--- a/internal/applier/status_writer_test.go
+++ b/internal/applier/status_writer_test.go
@@ -25,37 +25,37 @@ import (
"github.com/ConfigButler/gitops-reverser/internal/git"
)
-var appsTrigger = git.TriggerRef{Kind: v1alpha3.ApplyTriggerKindFlux, Namespace: targetNS, Name: "apps"}
+var appsClaim = git.ApplierClaimRef{Kind: v1alpha3.ApplierClaimKindFlux, Namespace: targetNS, Name: "apps"}
-// statusWriterFixture is a StatusWriter over a fake control cluster holding the FluxTrigger "apps",
+// statusWriterFixture is a StatusWriter over a fake control cluster holding the FluxClaim "apps",
// whose reconciler has already published Configured.
func statusWriterFixture(t *testing.T, funcs interceptor.Funcs) (*StatusWriter, client.Client) {
t.Helper()
scheme := runtime.NewScheme()
require.NoError(t, v1alpha3.AddToScheme(scheme))
- trigger := &v1alpha3.FluxTrigger{
+ claim := &v1alpha3.FluxClaim{
ObjectMeta: metav1.ObjectMeta{Namespace: targetNS, Name: "apps"},
- Spec: v1alpha3.FluxTriggerSpec{
+ Spec: v1alpha3.FluxClaimSpec{
KubeProviderRef: kubeProviderRef(localCluster),
KustomizationRef: fluxmeta.NamespacedObjectReference{Name: "apps"},
},
- Status: v1alpha3.FluxTriggerStatus{Conditions: []metav1.Condition{{
+ Status: v1alpha3.FluxClaimStatus{Conditions: []metav1.Condition{{
Type: "Configured", Status: metav1.ConditionTrue, Reason: "Succeeded",
LastTransitionTime: metav1.Now(),
}}},
}
- c := fake.NewClientBuilder().WithScheme(scheme).WithObjects(trigger).
- WithStatusSubresource(&v1alpha3.FluxTrigger{}).WithInterceptorFuncs(funcs).Build()
+ c := fake.NewClientBuilder().WithScheme(scheme).WithObjects(claim).
+ WithStatusSubresource(&v1alpha3.FluxClaim{}).WithInterceptorFuncs(funcs).Build()
writer := NewStatusWriter(c, c, logr.Discard())
writer.RetryInterval = time.Millisecond
return writer, c
}
-func readTrigger(t *testing.T, c client.Client) *v1alpha3.FluxTrigger {
+func readClaim(t *testing.T, c client.Client) *v1alpha3.FluxClaim {
t.Helper()
- trigger := &v1alpha3.FluxTrigger{}
- require.NoError(t, c.Get(context.Background(), client.ObjectKey{Namespace: targetNS, Name: "apps"}, trigger))
- return trigger
+ claim := &v1alpha3.FluxClaim{}
+ require.NoError(t, c.Get(context.Background(), client.ObjectKey{Namespace: targetNS, Name: "apps"}, claim))
+ return claim
}
func operationAt(phase v1alpha3.ApplyOperationPhase) *v1alpha3.ApplyOperation {
@@ -69,24 +69,24 @@ func operationAt(phase v1alpha3.ApplyOperationPhase) *v1alpha3.ApplyOperation {
// reconciler's conditions stay as they were.
func TestStatusWriter_WritesTheOperationAndKeepsTheRest(t *testing.T) {
writer, c := statusWriterFixture(t, interceptor.Funcs{})
- writer.PublishApplyTriggerStatus(appsTrigger, git.ApplyTriggerStatus{
+ writer.PublishApplierClaimStatus(appsClaim, git.ApplierClaimStatus{
Operation: operationAt(v1alpha3.ApplyOperationRunning), RepairPending: true,
})
assert.False(t, writer.writePending(context.Background()))
- trigger := readTrigger(t, c)
- require.NotNil(t, trigger.Status.Operation)
- assert.Equal(t, v1alpha3.ApplyOperationRunning, trigger.Status.Operation.Phase)
- assert.Equal(t, "T1", trigger.Status.Operation.Token)
- assert.True(t, trigger.Status.RepairPending)
- require.Len(t, trigger.Status.Conditions, 1, "the reconciler's conditions are kept")
+ claim := readClaim(t, c)
+ require.NotNil(t, claim.Status.Operation)
+ assert.Equal(t, v1alpha3.ApplyOperationRunning, claim.Status.Operation.Phase)
+ assert.Equal(t, "T1", claim.Status.Operation.Token)
+ assert.True(t, claim.Status.RepairPending)
+ require.Len(t, claim.Status.Conditions, 1, "the reconciler's conditions are kept")
// No operation in the publication leaves the recorded one.
- writer.PublishApplyTriggerStatus(appsTrigger, git.ApplyTriggerStatus{RepairPending: false})
+ writer.PublishApplierClaimStatus(appsClaim, git.ApplierClaimStatus{RepairPending: false})
assert.False(t, writer.writePending(context.Background()))
- trigger = readTrigger(t, c)
- require.NotNil(t, trigger.Status.Operation, "a worker with nothing to say about it keeps it")
- assert.False(t, trigger.Status.RepairPending)
+ claim = readClaim(t, c)
+ require.NotNil(t, claim.Status.Operation, "a worker with nothing to say about it keeps it")
+ assert.False(t, claim.Status.RepairPending)
}
// TestStatusWriter_TheLatestPublicationWins: publications not yet written are replaced, not
@@ -103,21 +103,21 @@ func TestStatusWriter_TheLatestPublicationWins(t *testing.T) {
for _, phase := range []v1alpha3.ApplyOperationPhase{
v1alpha3.ApplyOperationRequested, v1alpha3.ApplyOperationRunning, v1alpha3.ApplyOperationSucceeded,
} {
- writer.PublishApplyTriggerStatus(appsTrigger, git.ApplyTriggerStatus{Operation: operationAt(phase)})
+ writer.PublishApplierClaimStatus(appsClaim, git.ApplierClaimStatus{Operation: operationAt(phase)})
}
writer.writePending(context.Background())
assert.Equal(t, 1, updates)
- assert.Equal(t, v1alpha3.ApplyOperationSucceeded, readTrigger(t, c).Status.Operation.Phase)
+ assert.Equal(t, v1alpha3.ApplyOperationSucceeded, readClaim(t, c).Status.Operation.Phase)
- writer.PublishApplyTriggerStatus(appsTrigger, git.ApplyTriggerStatus{
+ writer.PublishApplierClaimStatus(appsClaim, git.ApplierClaimStatus{
Operation: operationAt(v1alpha3.ApplyOperationSucceeded),
})
writer.writePending(context.Background())
assert.Equal(t, 1, updates, "a status that already holds it is not written again")
}
-// TestStatusWriter_AConflictReadsAgain: a write that lost to the reconciler reads the trigger again
+// TestStatusWriter_AConflictReadsAgain: a write that lost to the reconciler reads the applier claim again
// and writes over what the reconciler wrote, never over it blind.
func TestStatusWriter_AConflictReadsAgain(t *testing.T) {
conflicts := 1
@@ -126,22 +126,22 @@ func TestStatusWriter_AConflictReadsAgain(t *testing.T) {
opts ...client.SubResourceUpdateOption) error {
if conflicts > 0 {
conflicts--
- return apierrors.NewConflict(schema.GroupResource{Resource: "fluxtriggers"}, "apps",
+ return apierrors.NewConflict(schema.GroupResource{Resource: "fluxclaims"}, "apps",
errors.New("the object has been modified"))
}
return cl.SubResource(sub).Update(ctx, obj, opts...)
},
})
- writer.PublishApplyTriggerStatus(
- appsTrigger,
- git.ApplyTriggerStatus{Operation: operationAt(v1alpha3.ApplyOperationFailed)},
+ writer.PublishApplierClaimStatus(
+ appsClaim,
+ git.ApplierClaimStatus{Operation: operationAt(v1alpha3.ApplyOperationFailed)},
)
assert.False(t, writer.writePending(context.Background()))
- assert.Equal(t, v1alpha3.ApplyOperationFailed, readTrigger(t, c).Status.Operation.Phase)
+ assert.Equal(t, v1alpha3.ApplyOperationFailed, readClaim(t, c).Status.Operation.Phase)
}
// TestStatusWriter_AFailedWriteIsHeldUntilItLands: a write that failed is tried again, unless a newer
-// publication took its place; a trigger that is gone takes its publication with it.
+// publication took its place; an applier claim that is gone takes its publication with it.
func TestStatusWriter_AFailedWriteIsHeldUntilItLands(t *testing.T) {
failing := true
writer, c := statusWriterFixture(t, interceptor.Funcs{
@@ -153,19 +153,19 @@ func TestStatusWriter_AFailedWriteIsHeldUntilItLands(t *testing.T) {
return cl.SubResource(sub).Update(ctx, obj, opts...)
},
})
- writer.PublishApplyTriggerStatus(
- appsTrigger,
- git.ApplyTriggerStatus{Operation: operationAt(v1alpha3.ApplyOperationRunning)},
+ writer.PublishApplierClaimStatus(
+ appsClaim,
+ git.ApplierClaimStatus{Operation: operationAt(v1alpha3.ApplyOperationRunning)},
)
assert.True(t, writer.writePending(context.Background()), "held for a retry")
failing = false
assert.False(t, writer.writePending(context.Background()))
- assert.Equal(t, v1alpha3.ApplyOperationRunning, readTrigger(t, c).Status.Operation.Phase)
+ assert.Equal(t, v1alpha3.ApplyOperationRunning, readClaim(t, c).Status.Operation.Phase)
- gone := git.TriggerRef{Kind: v1alpha3.ApplyTriggerKindFlux, Namespace: targetNS, Name: "gone"}
- writer.PublishApplyTriggerStatus(gone, git.ApplyTriggerStatus{RepairPending: true})
- assert.False(t, writer.writePending(context.Background()), "nothing to hold for a trigger that is gone")
+ gone := git.ApplierClaimRef{Kind: v1alpha3.ApplierClaimKindFlux, Namespace: targetNS, Name: "gone"}
+ writer.PublishApplierClaimStatus(gone, git.ApplierClaimStatus{RepairPending: true})
+ assert.False(t, writer.writePending(context.Background()), "nothing to hold for an applier claim that is gone")
}
// TestStatusWriter_StartWritesWhatIsPublished: the runnable writes a publication without being asked
@@ -176,12 +176,12 @@ func TestStatusWriter_StartWritesWhatIsPublished(t *testing.T) {
done := make(chan error, 1)
go func() { done <- writer.Start(ctx) }()
- writer.PublishApplyTriggerStatus(
- appsTrigger,
- git.ApplyTriggerStatus{Operation: operationAt(v1alpha3.ApplyOperationRequested)},
+ writer.PublishApplierClaimStatus(
+ appsClaim,
+ git.ApplierClaimStatus{Operation: operationAt(v1alpha3.ApplyOperationRequested)},
)
require.Eventually(t, func() bool {
- operation := readTrigger(t, c).Status.Operation
+ operation := readClaim(t, c).Status.Operation
return operation != nil && operation.Phase == v1alpha3.ApplyOperationRequested
}, 5*time.Second, 5*time.Millisecond)
@@ -190,12 +190,12 @@ func TestStatusWriter_StartWritesWhatIsPublished(t *testing.T) {
assert.True(t, writer.NeedLeaderElection())
}
-// setOperation gives the trigger's status an operation, as another writer would.
+// setOperation gives the applier claim's status an operation, as another writer would.
func setOperation(t *testing.T, c client.Client, operation *v1alpha3.ApplyOperation) {
t.Helper()
- trigger := readTrigger(t, c)
- trigger.Status.Operation = operation
- require.NoError(t, c.Status().Update(context.Background(), trigger))
+ claim := readClaim(t, c)
+ claim.Status.Operation = operation
+ require.NoError(t, c.Status().Update(context.Background(), claim))
}
// operationRequested is a worker's operation with its own token, requested after operationAt's.
@@ -229,17 +229,17 @@ func countingUpdates(updates *int) interceptor.Funcs {
}
// TestStatusWriter_ARecordIsWrittenThroughBeforeItReturns: the dispatcher records the operation
-// just before it starts the apply. It is on the trigger when RecordApplyOperation returns, over the
+// just before it starts the apply. It is on the applier claim when RecordApplyOperation returns, over the
// completed operation before it, and the reconciler's conditions and the recorded repairPending stay.
func TestStatusWriter_ARecordIsWrittenThroughBeforeItReturns(t *testing.T) {
writer, c := statusWriterFixture(t, interceptor.Funcs{})
setOperation(t, c, operationAt(v1alpha3.ApplyOperationSucceeded))
- writer.PublishApplyTriggerStatus(appsTrigger,
- git.ApplyTriggerStatus{Operation: operationAt(v1alpha3.ApplyOperationSucceeded), RepairPending: true})
+ writer.PublishApplierClaimStatus(appsClaim,
+ git.ApplierClaimStatus{Operation: operationAt(v1alpha3.ApplyOperationSucceeded), RepairPending: true})
requested := operationRequested(v1alpha3.ApplyOperationRequested, "T2", time.Minute)
- require.NoError(t, writer.RecordApplyOperation(context.Background(), appsTrigger, requested))
- recorded := readTrigger(t, c)
+ require.NoError(t, writer.RecordApplyOperation(context.Background(), appsClaim, requested))
+ recorded := readClaim(t, c)
require.NotNil(t, recorded.Status.Operation)
assert.Equal(t, "T2", recorded.Status.Operation.Token)
assert.Equal(t, v1alpha3.ApplyOperationRequested, recorded.Status.Operation.Phase)
@@ -247,8 +247,8 @@ func TestStatusWriter_ARecordIsWrittenThroughBeforeItReturns(t *testing.T) {
require.Len(t, recorded.Status.Conditions, 1, "the reconciler's conditions are kept")
assert.False(t, writer.writePending(context.Background()))
- assert.Equal(t, recorded.ResourceVersion, readTrigger(t, c).ResourceVersion, "nothing left to write")
- require.NoError(t, writer.RecordApplyOperation(context.Background(), appsTrigger, requested),
+ assert.Equal(t, recorded.ResourceVersion, readClaim(t, c).ResourceVersion, "nothing left to write")
+ require.NoError(t, writer.RecordApplyOperation(context.Background(), appsClaim, requested),
"an operation recorded already is recorded")
}
@@ -258,16 +258,16 @@ func TestStatusWriter_ARecordIsWrittenThroughBeforeItReturns(t *testing.T) {
func TestStatusWriter_ARecordWritesTheLatestViewFirst(t *testing.T) {
writer, c := statusWriterFixture(t, interceptor.Funcs{})
setOperation(t, c, operationAt(v1alpha3.ApplyOperationRunning))
- writer.PublishApplyTriggerStatus(appsTrigger,
- git.ApplyTriggerStatus{Operation: operationAt(v1alpha3.ApplyOperationSucceeded)})
+ writer.PublishApplierClaimStatus(appsClaim,
+ git.ApplierClaimStatus{Operation: operationAt(v1alpha3.ApplyOperationSucceeded)})
requested := operationRequested(v1alpha3.ApplyOperationRequested, "T2", time.Minute)
- require.NoError(t, writer.RecordApplyOperation(context.Background(), appsTrigger, requested))
- assert.Equal(t, "T2", readTrigger(t, c).Status.Operation.Token)
+ require.NoError(t, writer.RecordApplyOperation(context.Background(), appsClaim, requested))
+ assert.Equal(t, "T2", readClaim(t, c).Status.Operation.Token)
}
// TestStatusWriter_ARecordNeverReplacesAnotherOperationInFlight is the fourth review's first
-// finding. The trigger's reconciler recorded an external resume (no token, requested after the
+// finding. The applier claim's reconciler recorded an external resume (no token, requested after the
// worker sent its attempt), and the record used to keep it, write nothing, and return nil, so the
// resume went ahead with no record of its own. The record fails, and leaves the status as it is.
func TestStatusWriter_ARecordNeverReplacesAnotherOperationInFlight(t *testing.T) {
@@ -279,36 +279,36 @@ func TestStatusWriter_ARecordNeverReplacesAnotherOperationInFlight(t *testing.T)
setOperation(t, c, external)
requested := operationRequested(v1alpha3.ApplyOperationRequested, "T2", time.Minute)
- err := writer.RecordApplyOperation(context.Background(), appsTrigger, requested)
+ err := writer.RecordApplyOperation(context.Background(), appsClaim, requested)
require.ErrorIs(t, err, errOperationInFlight)
- assertOperation(t, external, readTrigger(t, c).Status.Operation)
+ assertOperation(t, external, readClaim(t, c).Status.Operation)
// An operation an earlier process left in flight is refused the same way: the worker follows it
// before it asks for anything.
setOperation(t, c, operationRequested(v1alpha3.ApplyOperationRunning, "T0", -time.Hour))
- require.ErrorIs(t, writer.RecordApplyOperation(context.Background(), appsTrigger, requested),
+ require.ErrorIs(t, writer.RecordApplyOperation(context.Background(), appsClaim, requested),
errOperationInFlight)
}
-// TestStatusWriter_ARecordForAReleasedTriggerFails: a trigger that is being deleted is an error, so
+// TestStatusWriter_ARecordForAReleasedClaimFails: an applier claim that is being deleted is an error, so
// nothing is started for it, also when the background writer already dropped what was held for it.
-func TestStatusWriter_ARecordForAReleasedTriggerFails(t *testing.T) {
+func TestStatusWriter_ARecordForAReleasedClaimFails(t *testing.T) {
writer, c := statusWriterFixture(t, interceptor.Funcs{})
- trigger := readTrigger(t, c)
- trigger.Finalizers = []string{v1alpha3.FluxTriggerFinalizer}
- require.NoError(t, c.Update(context.Background(), trigger))
- require.NoError(t, c.Delete(context.Background(), trigger))
+ claim := readClaim(t, c)
+ claim.Finalizers = []string{v1alpha3.FluxClaimFinalizer}
+ require.NoError(t, c.Update(context.Background(), claim))
+ require.NoError(t, c.Delete(context.Background(), claim))
requested := operationRequested(v1alpha3.ApplyOperationRequested, "T2", time.Minute)
- writer.PublishApplyTriggerStatus(appsTrigger, git.ApplyTriggerStatus{RepairPending: true})
- require.ErrorIs(t, writer.RecordApplyOperation(context.Background(), appsTrigger, requested),
- errTriggerReleased, "with a publication held")
+ writer.PublishApplierClaimStatus(appsClaim, git.ApplierClaimStatus{RepairPending: true})
+ require.ErrorIs(t, writer.RecordApplyOperation(context.Background(), appsClaim, requested),
+ errClaimReleased, "with a publication held")
- writer.PublishApplyTriggerStatus(appsTrigger, git.ApplyTriggerStatus{RepairPending: true})
+ writer.PublishApplierClaimStatus(appsClaim, git.ApplierClaimStatus{RepairPending: true})
assert.False(t, writer.writePending(context.Background()), "the background writer drops it")
- require.ErrorIs(t, writer.RecordApplyOperation(context.Background(), appsTrigger, requested),
- errTriggerReleased, "with nothing held")
- assert.Nil(t, readTrigger(t, c).Status.Operation)
+ require.ErrorIs(t, writer.RecordApplyOperation(context.Background(), appsClaim, requested),
+ errClaimReleased, "with nothing held")
+ assert.Nil(t, readClaim(t, c).Status.Operation)
}
// TestStatusWriter_AWithdrawnRecordIsPutBack: the operation was recorded, and the apply then never
@@ -325,16 +325,16 @@ func TestStatusWriter_AWithdrawnRecordIsPutBack(t *testing.T) {
setOperation(t, c, before)
}
requested := operationRequested(v1alpha3.ApplyOperationRequested, "T2", time.Minute)
- require.NoError(t, writer.RecordApplyOperation(context.Background(), appsTrigger, requested))
- require.Equal(t, "T2", readTrigger(t, c).Status.Operation.Token)
+ require.NoError(t, writer.RecordApplyOperation(context.Background(), appsClaim, requested))
+ require.Equal(t, "T2", readClaim(t, c).Status.Operation.Token)
// The worker knows nothing older than the record: it withdraws it with no operation.
- writer.PublishApplyTriggerStatus(appsTrigger, git.ApplyTriggerStatus{Withdrawn: requested})
+ writer.PublishApplierClaimStatus(appsClaim, git.ApplierClaimStatus{Withdrawn: requested})
// A later publication that withdraws nothing does not take the withdrawal back.
- writer.PublishApplyTriggerStatus(appsTrigger, git.ApplyTriggerStatus{RepairPending: true})
+ writer.PublishApplierClaimStatus(appsClaim, git.ApplierClaimStatus{RepairPending: true})
assert.False(t, writer.writePending(context.Background()))
- written := readTrigger(t, c)
+ written := readClaim(t, c)
assertOperation(t, before, written.Status.Operation)
assert.True(t, written.Status.RepairPending)
})
@@ -349,13 +349,13 @@ func TestStatusWriter_AWithdrawalOfAnOperationNotHeldChangesNothing(t *testing.T
setOperation(t, c, operationAt(v1alpha3.ApplyOperationSucceeded))
updates = 0
- writer.PublishApplyTriggerStatus(appsTrigger, git.ApplyTriggerStatus{
+ writer.PublishApplierClaimStatus(appsClaim, git.ApplierClaimStatus{
Operation: operationAt(v1alpha3.ApplyOperationSucceeded),
Withdrawn: operationRequested(v1alpha3.ApplyOperationRequested, "T2", time.Minute),
})
assert.False(t, writer.writePending(context.Background()))
assert.Zero(t, updates)
- assert.Equal(t, "T1", readTrigger(t, c).Status.Operation.Token)
+ assert.Equal(t, "T1", readClaim(t, c).Status.Operation.Token)
}
// TestStatusWriter_NeverWritesOverAnotherOperation: the reconciler records an external resume as an
@@ -370,12 +370,12 @@ func TestStatusWriter_NeverWritesOverAnotherOperation(t *testing.T) {
}
setOperation(t, c, external)
- writer.PublishApplyTriggerStatus(appsTrigger, git.ApplyTriggerStatus{
+ writer.PublishApplierClaimStatus(appsClaim, git.ApplierClaimStatus{
Operation: operationRequested(v1alpha3.ApplyOperationSucceeded, "T2", requested),
RepairPending: true,
})
assert.False(t, writer.writePending(context.Background()))
- written := readTrigger(t, c)
+ written := readClaim(t, c)
assertOperation(t, external, written.Status.Operation)
assert.True(t, written.Status.RepairPending, "the rest of the publication still lands")
})
@@ -394,13 +394,13 @@ func TestStatusWriter_NeverReopensACompletedOperation(t *testing.T) {
completed.Phase = v1alpha3.ApplyOperationSucceeded
setOperation(t, c, completed)
- writer.PublishApplyTriggerStatus(appsTrigger, git.ApplyTriggerStatus{Operation: listed})
+ writer.PublishApplierClaimStatus(appsClaim, git.ApplierClaimStatus{Operation: listed})
assert.False(t, writer.writePending(context.Background()))
- assertOperation(t, completed, readTrigger(t, c).Status.Operation)
+ assertOperation(t, completed, readClaim(t, c).Status.Operation)
failed := listed.DeepCopy()
failed.Phase = v1alpha3.ApplyOperationFailed
- writer.PublishApplyTriggerStatus(appsTrigger, git.ApplyTriggerStatus{Operation: failed})
+ writer.PublishApplierClaimStatus(appsClaim, git.ApplierClaimStatus{Operation: failed})
assert.False(t, writer.writePending(context.Background()))
- assertOperation(t, failed, readTrigger(t, c).Status.Operation) // a completion still replaces a completion
+ assertOperation(t, failed, readClaim(t, c).Status.Operation) // a completion still replaces a completion
}
diff --git a/internal/authz/kubeprovider_admission.go b/internal/authz/kubeprovider_admission.go
index a7192d43b..bea6959f3 100644
--- a/internal/authz/kubeprovider_admission.go
+++ b/internal/authz/kubeprovider_admission.go
@@ -37,7 +37,7 @@ const (
// carries a selector the apiserver accepted but that does not convert to a selector.
ReasonNamespaceNotAuthorized = "NamespaceNotAuthorized"
- // ReasonApplierNamespaceNotAllowed is the denial reason when a trigger names an applier in
+ // ReasonApplierNamespaceNotAllowed is the denial reason when an applier claim names an applier in
// another namespace than its own and its KubeProvider does not set
// spec.allowCrossNamespace.appliers.
ReasonApplierNamespaceNotAllowed = "ApplierNamespaceNotAllowed"
@@ -139,56 +139,56 @@ func gitBranchAdmitted(
return Decision{Allowed: true}, nil
}
-// ApplyTriggerAdmitted reports whether a trigger's namespace is admitted by the KubeProvider it
+// ApplierClaimAdmitted reports whether an applier claim's namespace is admitted by the KubeProvider it
// names (spec.kubeProviderRef), and whether it may reach its applier there. The applier is
// reached with that provider's credential, so the provider's accessFrom decides who may wield it,
// exactly as for mirroring, and its allowCrossNamespace.appliers whether a namespace may suspend
-// and apply another namespace's applier. The trigger's own namespace is always allowed; in a remote
+// and apply another namespace's applier. The applier claim's own namespace is always allowed; in a remote
// cluster that is the namespace of the same name, as it is for mirroring.
-func ApplyTriggerAdmitted(
+func ApplierClaimAdmitted(
ctx context.Context,
reader client.Reader,
- trigger configv1alpha3.ApplyTrigger,
+ claim configv1alpha3.ApplierClaim,
) (Decision, error) {
- key := trigger.KubeProviderKey()
- decision, provider, err := namespaceAdmitted(ctx, reader, trigger.GetNamespace(), key, fmt.Sprintf(
+ key := claim.KubeProviderKey()
+ decision, provider, err := namespaceAdmitted(ctx, reader, claim.GetNamespace(), key, fmt.Sprintf(
"KubeProvider %s named by %s %s/%s spec.kubeProviderRef was not found",
- key, trigger.TriggerKind(), trigger.GetNamespace(), trigger.GetName()))
+ key, claim.ClaimKind(), claim.GetNamespace(), claim.GetName()))
if err != nil || !decision.Allowed {
return decision, err
}
- applier := trigger.Applier()
- if applier.Namespace != trigger.GetNamespace() && !provider.AllowsCrossNamespaceAppliers() {
+ applier := claim.Applier()
+ if applier.Namespace != claim.GetNamespace() && !provider.AllowsCrossNamespaceAppliers() {
return Decision{
Reason: ReasonApplierNamespaceNotAllowed,
Message: fmt.Sprintf(
- "%s %s is in another namespace than the trigger's own %q, and KubeProvider %s does not "+
- "set spec.allowCrossNamespace.appliers", trigger.ApplierKind(), applier, trigger.GetNamespace(),
+ "%s %s is in another namespace than the applier claim's own %q, and KubeProvider %s does not "+
+ "set spec.allowCrossNamespace.appliers", claim.ApplierKind(), applier, claim.GetNamespace(),
key),
}, nil
}
return decision, nil
}
-// ApplierAdmitted reports whether target may ask the applier its trigger holds to apply: its
-// namespace is admitted by its own KubeProvider (GitTargetAdmitted) and by the trigger's
-// (ApplyTriggerAdmitted). A GitTarget names a trigger in its own namespace only, so the second
+// ApplierAdmitted reports whether target may ask the applier its applier claim holds to apply: its
+// namespace is admitted by its own KubeProvider (GitTargetAdmitted) and by the applier claim's
+// (ApplierClaimAdmitted). A GitTarget names an applier claim in its own namespace only, so the second
// answers for the GitTarget's namespace too. A KubeProvider's client is shared by every object
// that uses the cluster, so a client being available is no permission to use it.
func ApplierAdmitted(
ctx context.Context,
reader client.Reader,
target *configv1alpha3.GitTarget,
- trigger configv1alpha3.ApplyTrigger,
+ claim configv1alpha3.ApplierClaim,
) (Decision, error) {
decision, err := GitTargetAdmitted(ctx, reader, target)
if err != nil || !decision.Allowed {
return decision, err
}
- if trigger.KubeProviderKey() == target.KubeProviderKey() && trigger.GetNamespace() == target.Namespace {
+ if claim.KubeProviderKey() == target.KubeProviderKey() && claim.GetNamespace() == target.Namespace {
return decision, nil
}
- return ApplyTriggerAdmitted(ctx, reader, trigger)
+ return ApplierClaimAdmitted(ctx, reader, claim)
}
// namespaceAdmitted is the check both admissions share: the provider exists, it admits namespace
diff --git a/internal/authz/kubeprovider_admission_test.go b/internal/authz/kubeprovider_admission_test.go
index 71e216daa..c326ce666 100644
--- a/internal/authz/kubeprovider_admission_test.go
+++ b/internal/authz/kubeprovider_admission_test.go
@@ -312,16 +312,16 @@ func TestGitTargetAdmitted_ReadErrorsSurfaceAsError(t *testing.T) {
}
}
-// TestApplierAdmitted: an applier is reached with its trigger's KubeProvider's credential, so
+// TestApplierAdmitted: an applier is reached with its applier claim's KubeProvider's credential, so
// that provider must admit the namespace too, and the source's denial comes first.
func TestApplierAdmitted(t *testing.T) {
admits := func(name string, namespaces ...string) *configv1alpha3.KubeProvider {
return providerNamed(name, &configv1alpha3.NamespaceMatcher{Names: namespaces})
}
- triggerOn := func(cluster string) configv1alpha3.ApplyTrigger {
- return &configv1alpha3.FluxTrigger{
+ claimOn := func(cluster string) configv1alpha3.ApplierClaim {
+ return &configv1alpha3.FluxClaim{
ObjectMeta: metav1.ObjectMeta{Namespace: testNS, Name: "apps"},
- Spec: configv1alpha3.FluxTriggerSpec{
+ Spec: configv1alpha3.FluxClaimSpec{
KubeProviderRef: meta.NamespacedObjectReference{Name: cluster, Namespace: platformNS},
},
}
@@ -329,41 +329,41 @@ func TestApplierAdmitted(t *testing.T) {
tests := []struct {
name string
objects []client.Object
- trigger configv1alpha3.ApplyTrigger
+ claim configv1alpha3.ApplierClaim
wantAllowed bool
wantReason string
wantMessage string
}{
{
- name: "the trigger's provider is the source's",
+ name: "the applier claim's provider is the source's",
objects: []client.Object{admits("prod-eu-1", testNS)},
- trigger: triggerOn("prod-eu-1"),
+ claim: claimOn("prod-eu-1"),
wantAllowed: true,
},
{
name: "another provider that admits the namespace",
objects: []client.Object{admits("prod-eu-1", testNS), admits("hub", testNS)},
- trigger: triggerOn("hub"),
+ claim: claimOn("hub"),
wantAllowed: true,
},
{
name: "another provider that does not admit the namespace",
objects: []client.Object{admits("prod-eu-1", testNS), admits("hub", "elsewhere")},
- trigger: triggerOn("hub"),
+ claim: claimOn("hub"),
wantReason: ReasonNamespaceNotAuthorized,
wantMessage: "KubeProvider platform/hub",
},
{
name: "another provider that does not exist",
objects: []client.Object{admits("prod-eu-1", testNS)},
- trigger: triggerOn("hub"),
+ claim: claimOn("hub"),
wantReason: ReasonKubeProviderNotFound,
- wantMessage: "FluxTrigger " + testNS + "/apps spec.kubeProviderRef",
+ wantMessage: "FluxClaim " + testNS + "/apps spec.kubeProviderRef",
},
{
name: "the source's denial comes first",
objects: []client.Object{admits("prod-eu-1", "elsewhere"), admits("hub", testNS)},
- trigger: triggerOn("hub"),
+ claim: claimOn("hub"),
wantReason: ReasonNamespaceNotAuthorized,
wantMessage: "KubeProvider platform/prod-eu-1",
},
@@ -371,7 +371,7 @@ func TestApplierAdmitted(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
reader := fake.NewClientBuilder().WithScheme(admissionScheme(t)).WithObjects(tc.objects...).Build()
- decision, err := ApplierAdmitted(context.Background(), reader, targetIn("prod-eu-1"), tc.trigger)
+ decision, err := ApplierAdmitted(context.Background(), reader, targetIn("prod-eu-1"), tc.claim)
require.NoError(t, err)
assert.Equal(t, tc.wantAllowed, decision.Allowed, decision.Message)
assert.Equal(t, tc.wantReason, decision.Reason)
diff --git a/internal/controller/applytrigger_controller.go b/internal/controller/applierclaim_controller.go
similarity index 51%
rename from internal/controller/applytrigger_controller.go
rename to internal/controller/applierclaim_controller.go
index 7efaf5249..da32c3bd8 100644
--- a/internal/controller/applytrigger_controller.go
+++ b/internal/controller/applierclaim_controller.go
@@ -32,25 +32,25 @@ import (
"github.com/ConfigButler/gitops-reverser/internal/telemetry"
)
-// ApplyTriggerConditionConfigured reports whether a trigger's applier is one GitOps Reverser can
+// ApplierClaimConditionConfigured reports whether an applier claim's applier is one GitOps Reverser can
// drive: it exists in its cluster, applies a source that follows a branch, may be written, and (an
-// Argo CD Application) has automated sync off or a switch the trigger may hold. The trigger's KubeProvider must admit its
-// namespace first, and no trigger in line before it may name the same applier
-// (applier.ReasonHeldByAnotherTrigger).
-const ApplyTriggerConditionConfigured = "Configured"
+// Argo CD Application) has automated sync off or a switch the applier claim may hold. The applier claim's KubeProvider must admit its
+// namespace first, and no applier claim in line before it may name the same applier
+// (applier.ReasonHeldByAnotherClaim).
+const ApplierClaimConditionConfigured = "Configured"
-// ReasonOperationUnresolved is Ready=False on a trigger whose operation has not completed in the
+// ReasonOperationUnresolved is Ready=False on an applier claim whose operation has not completed in the
// time it was given. It clears when the operation completes.
const ReasonOperationUnresolved = "OperationUnresolved"
-// ApplyTriggerConditionHeld reports whether a trigger holds its applier. A FluxTrigger holds its
+// ApplierClaimConditionHeld reports whether an applier claim holds its applier. A FluxClaim holds its
// Kustomization suspended under GitOps Reverser's field manager, with the last suspend's token
// handled, so nothing that started before the suspend is still running, or resumed for an apply of
-// GitOps Reverser's own. An ArgoCDTrigger holds its Application's automated sync off
-// (argocdtrigger_hold.go). A trigger is Ready only once it holds its applier.
-const ApplyTriggerConditionHeld = applier.TriggerConditionHeld
+// GitOps Reverser's own. An ArgoCDClaim holds its Application's automated sync off
+// (argocdclaim_hold.go). An applier claim is Ready only once it holds its applier.
+const ApplierClaimConditionHeld = applier.ApplierClaimConditionHeld
-// Held reasons, and the Events of the hold lifecycle on a trigger.
+// Held reasons, and the Events of the hold lifecycle on an applier claim.
const (
// ReasonSuspended is Held=True: the Kustomization is suspended, the last suspend handled, and
// nothing applies.
@@ -60,77 +60,77 @@ const (
ReasonApplying = "Applying"
// ReasonSuspending is Held=False while the token of the suspend is not handled yet.
ReasonSuspending = "Suspending"
- // ReasonNotConfigured is Held=Unknown on a trigger that is not Configured: nothing is held, or
- // the Kustomization is left suspended as the trigger last held it (the message says which).
+ // ReasonNotConfigured is Held=Unknown on an applier claim that is not Configured: nothing is held, or
+ // the Kustomization is left suspended as the applier claim last held it (the message says which).
ReasonNotConfigured = "NotConfigured"
- // ReasonReleasing is Held=False on a trigger that held its Kustomization and is handing it back:
+ // ReasonReleasing is Held=False on an applier claim that held its Kustomization and is handing it back:
// it is being deleted, or no eligible GitTarget references it any more. Held=False with
- // applier.ReasonNotReferenced is a trigger with nothing to hold.
+ // applier.ReasonNotReferenced is an applier claim with nothing to hold.
ReasonReleasing = "Releasing"
// EventReasonAdopted is a Kustomization suspended, or an Application's automated sync switched
// off, for the first time.
EventReasonAdopted = "Adopted"
// EventReasonClaimedResourceReconciled is a held applier found changed by somebody else, and put
- // back as the trigger holds it: a Kustomization resumed (`flux resume`) and suspended again, or an
+ // back as the applier claim holds it: a Kustomization resumed (`flux resume`) and suspended again, or an
// Application's automated sync switched on and off again.
EventReasonClaimedResourceReconciled = "ClaimedResourceReconciled"
- // EventReasonReleased is an applier handed back to Flux or Argo CD, or left held for the trigger
- // next in line, when its trigger was deleted or no eligible GitTarget references it any more.
+ // EventReasonReleased is an applier handed back to Flux or Argo CD, or left held for the applier claim
+ // next in line, when its applier claim was deleted or no eligible GitTarget references it any more.
EventReasonReleased = "Released"
- // EventReasonReleaseFailed is a retiring trigger that could not hand its applier back.
+ // EventReasonReleaseFailed is a retiring applier claim that could not hand its applier back.
EventReasonReleaseFailed = "ReleaseFailed"
)
-// heldTriggerSteadyInterval is how often a trigger that is Ready reads its applier again: that read
+// heldClaimSteadyInterval is how often an applier claim that is Ready reads its applier again: that read
// is what finds a `flux resume`, or automated sync switched on in Argo CD, so it is more often than
// other kinds settle.
-const heldTriggerSteadyInterval = time.Minute
+const heldClaimSteadyInterval = time.Minute
-// applyTriggerSuspendingInterval is how soon a trigger waiting on its Kustomization's suspend is
+// applierClaimSuspendingInterval is how soon an applier claim waiting on its Kustomization's suspend is
// read again: a suspended reconcile handles the token in tens of milliseconds.
-const applyTriggerSuspendingInterval = 2 * time.Second
+const applierClaimSuspendingInterval = 2 * time.Second
// applierCheckTimeout bounds the Configured check: at most two GETs and two access reviews
// against the applier's cluster. Its client has no request timeout, because it also carries the
// watches, and a cluster that accepts the connection and never answers would otherwise hold this
-// controller's worker, not only this trigger.
+// controller's worker, not only this applier claim.
const applierCheckTimeout = 10 * time.Second
-// Why the trigger reconciler reads the applier on every reconcile rather than watching it: Flux or
+// Why the applier claim reconciler reads the applier on every reconcile rather than watching it: Flux or
// Argo CD may not be installed in the applier's cluster at all, and an informer on a kind that is
// not served would be a failure to report rather than a check that answers "not found". The
-// cadence is its own: heldTriggerSteadyInterval,
-// applyTriggerSuspendingInterval while a Kustomization has not handled its suspend yet, or
-// applyTriggerSettleInterval while something is expected to clear on its own, such as an applier
-// created after its trigger.
+// cadence is its own: heldClaimSteadyInterval,
+// applierClaimSuspendingInterval while a Kustomization has not handled its suspend yet, or
+// applierClaimSettleInterval while something is expected to clear on its own, such as an applier
+// created after its applier claim.
-// applyTriggerSettleInterval is how soon a trigger that is not Ready, and not stalled, is read again.
-const applyTriggerSettleInterval = 15 * time.Second
+// applierClaimSettleInterval is how soon an applier claim that is not Ready, and not stalled, is read again.
+const applierClaimSettleInterval = 15 * time.Second
-// ApplyTriggerReconciler reconciles one apply trigger kind: it checks that the applier the trigger
+// ApplierClaimReconciler reconciles one applier claim kind: it checks that the applier the applier claim
// holds is one GitOps Reverser can drive, and publishes what it applies (status.source) for the
-// GitTargets naming the trigger. It also holds the applier (a Kustomization suspended,
-// fluxtrigger_hold.go; an Application's automated sync off, argocdtrigger_hold.go), and hands it
-// back once the trigger is deleted. It never starts an apply: the
+// GitTargets naming the applier claim. It also holds the applier (a Kustomization suspended,
+// fluxclaim_hold.go; an Application's automated sync off, argocdclaim_hold.go), and hands it
+// back once the applier claim is deleted. It never starts an apply: the
// branch workers decide when, and write status.operation and status.repairPending. It writes
// status.operation only for what no worker follows: an external resume, and an operation nobody
// follows any more.
-type ApplyTriggerReconciler struct {
+type ApplierClaimReconciler struct {
client.Client
- // Kind is the trigger kind this reconciler serves (v1alpha3.ApplyTriggerKind*).
+ // Kind is the applier claim kind this reconciler serves (v1alpha3.ApplierClaimKind*).
Kind string
Recorder record.EventRecorder
- // ApplierClients resolves the dynamic client of a trigger's cluster. Nil leaves Configured
+ // ApplierClients resolves the dynamic client of an applier claim's cluster. Nil leaves Configured
// Unknown (tests, and the CLI).
ApplierClients applier.Clients
- // APIReader reads a trigger uncached before its Kustomization is suspended, so an operation a
+ // APIReader reads an applier claim uncached before its Kustomization is suspended, so an operation a
// branch worker recorded a moment ago is never taken for an external resume. Nil reads through
// Client.
APIReader client.Reader
// Follower says which branch-worker operations this process follows (the apply dispatcher). A
- // FluxTrigger's Kustomization is left resumed for an operation in flight only while it is
+ // FluxClaim's Kustomization is left resumed for an operation in flight only while it is
// followed, or for UnfollowedGrace (DefaultUnfollowedGrace when zero) after it was first seen
// followed by nobody. Nil follows nothing, so only the grace applies.
Follower OperationFollower
@@ -141,23 +141,23 @@ type ApplyTriggerReconciler struct {
newApplier func(kind string, client dynamic.Interface) applier.Client
}
-// +kubebuilder:rbac:groups=configbutler.ai,resources=fluxtriggers;argocdtriggers,verbs=get;list;watch
-// +kubebuilder:rbac:groups=configbutler.ai,resources=fluxtriggers;argocdtriggers,verbs=update;patch
-// +kubebuilder:rbac:groups=configbutler.ai,resources=fluxtriggers/finalizers;argocdtriggers/finalizers,verbs=update
-// +kubebuilder:rbac:groups=configbutler.ai,resources=fluxtriggers/status;argocdtriggers/status,verbs=get;update;patch
+// +kubebuilder:rbac:groups=configbutler.ai,resources=fluxclaims;argocdclaims,verbs=get;list;watch
+// +kubebuilder:rbac:groups=configbutler.ai,resources=fluxclaims;argocdclaims,verbs=update;patch
+// +kubebuilder:rbac:groups=configbutler.ai,resources=fluxclaims/finalizers;argocdclaims/finalizers,verbs=update
+// +kubebuilder:rbac:groups=configbutler.ai,resources=fluxclaims/status;argocdclaims/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=configbutler.ai,resources=kubeproviders,verbs=get;list;watch
// +kubebuilder:rbac:groups=configbutler.ai,resources=gittargets;gitproviders,verbs=get;list;watch
// +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch
// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch
-// Reconcile publishes one trigger's Configured and Held conditions, its source and the readiness
+// Reconcile publishes one applier claim's Configured and Held conditions, its source and the readiness
// trio.
-func (r *ApplyTriggerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
- trigger := configbutleraiv1alpha3.NewApplyTrigger(r.Kind)
- if trigger == nil {
- return ctrl.Result{}, fmt.Errorf("no apply trigger kind %q", r.Kind)
+func (r *ApplierClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+ claim := configbutleraiv1alpha3.NewApplierClaim(r.Kind)
+ if claim == nil {
+ return ctrl.Result{}, fmt.Errorf("no applier claim kind %q", r.Kind)
}
- if err := r.Get(ctx, req.NamespacedName, trigger); err != nil {
+ if err := r.Get(ctx, req.NamespacedName, claim); err != nil {
if apierrors.IsNotFound(err) {
telemetry.ForgetResourceConditions(r.Kind, req.Namespace, req.Name)
return ctrl.Result{}, nil
@@ -165,53 +165,53 @@ func (r *ApplyTriggerReconciler) Reconcile(ctx context.Context, req ctrl.Request
return ctrl.Result{}, err
}
- st := beginStatus(r.Client, r.Recorder, trigger)
- switch t := trigger.(type) {
- case *configbutleraiv1alpha3.FluxTrigger:
+ st := beginStatus(r.Client, r.Recorder, claim)
+ switch t := claim.(type) {
+ case *configbutleraiv1alpha3.FluxClaim:
return r.reconcileFlux(ctx, st, t)
- case *configbutleraiv1alpha3.ArgoCDTrigger:
+ case *configbutleraiv1alpha3.ArgoCDClaim:
return r.reconcileArgoCD(ctx, st, t)
}
- return ctrl.Result{}, fmt.Errorf("no apply trigger kind %q", r.Kind)
+ return ctrl.Result{}, fmt.Errorf("no applier claim kind %q", r.Kind)
}
-// reconcileFlux is Reconcile for a FluxTrigger: one being deleted, or that no eligible GitTarget
+// reconcileFlux is Reconcile for a FluxClaim: one being deleted, or that no eligible GitTarget
// references any more, hands its Kustomization back; any other holds it while it is Configured.
-func (r *ApplyTriggerReconciler) reconcileFlux(
- ctx context.Context, st *reconcileStatus, trigger *configbutleraiv1alpha3.FluxTrigger,
+func (r *ApplierClaimReconciler) reconcileFlux(
+ ctx context.Context, st *reconcileStatus, claim *configbutleraiv1alpha3.FluxClaim,
) (ctrl.Result, error) {
- if !trigger.DeletionTimestamp.IsZero() {
- if !controllerutil.ContainsFinalizer(trigger, configbutleraiv1alpha3.FluxTriggerFinalizer) {
+ if !claim.DeletionTimestamp.IsZero() {
+ if !controllerutil.ContainsFinalizer(claim, configbutleraiv1alpha3.FluxClaimFinalizer) {
return ctrl.Result{}, nil
}
- return r.retire(ctx, st, trigger, nil)
+ return r.retire(ctx, st, claim, nil)
}
- check := r.configuredVerdict(ctx, trigger)
- publishCheck(st, trigger, check)
- if check.retiring(trigger) {
- return r.retire(ctx, st, trigger, &check)
+ check := r.configuredVerdict(ctx, claim)
+ publishCheck(st, claim, check)
+ if check.retiring(claim) {
+ return r.retire(ctx, st, claim, &check)
}
- held, err := r.holdFlux(ctx, st, trigger, check)
+ held, err := r.holdFlux(ctx, st, claim, check)
if err != nil {
return ctrl.Result{}, err
}
- st.setValue(ApplyTriggerConditionHeld, held)
- return r.commitTrigger(ctx, st, r.triggerReadiness(trigger, check.configured, &held), &held,
- heldTriggerSteadyInterval)
+ st.setValue(ApplierClaimConditionHeld, held)
+ return r.commitClaim(ctx, st, r.claimReadiness(claim, check.configured, &held), &held,
+ heldClaimSteadyInterval)
}
// publishCheck sets Configured, and the source when the check was made.
-func publishCheck(st *reconcileStatus, trigger configbutleraiv1alpha3.ApplyTrigger, check triggerCheck) {
- st.setValue(ApplyTriggerConditionConfigured, check.configured)
+func publishCheck(st *reconcileStatus, claim configbutleraiv1alpha3.ApplierClaim, check claimCheck) {
+ st.setValue(ApplierClaimConditionConfigured, check.configured)
if check.checked {
- setTriggerSource(trigger, check.source)
+ setClaimSource(claim, check.source)
}
}
-// commitTrigger writes the trio from rd and the status, and returns the cadence to read the
-// trigger again on: steady, or sooner while a suspend is waited for or something may clear on its
+// commitClaim writes the trio from rd and the status, and returns the cadence to read the
+// applier claim again on: steady, or sooner while a suspend is waited for or something may clear on its
// own.
-func (r *ApplyTriggerReconciler) commitTrigger(
+func (r *ApplierClaimReconciler) commitClaim(
ctx context.Context, st *reconcileStatus, rd *readiness, held *conditionValue, steady time.Duration,
) (ctrl.Result, error) {
st.applyReadiness(rd)
@@ -221,100 +221,102 @@ func (r *ApplyTriggerReconciler) commitTrigger(
requeue := steady
switch {
case held != nil && held.Reason == ReasonSuspending:
- requeue = applyTriggerSuspendingInterval
+ requeue = applierClaimSuspendingInterval
case rd.converging():
- requeue = applyTriggerSettleInterval
+ requeue = applierClaimSettleInterval
}
return ctrl.Result{RequeueAfter: st.requeueAfter(requeue)}, nil
}
-// holdFlux returns a FluxTrigger's Held condition. A Configured trigger holds its Kustomization,
+// holdFlux returns a FluxClaim's Held condition. A Configured applier claim holds its Kustomization,
// with its finalizer put in place before the first adoption write, and then publishes its source
// consumers. One that is not Configured holds nothing,
// and one in line behind another holder lets go of a finalizer it carries, handing nothing back.
-func (r *ApplyTriggerReconciler) holdFlux(
- ctx context.Context, st *reconcileStatus, trigger *configbutleraiv1alpha3.FluxTrigger, check triggerCheck,
+func (r *ApplierClaimReconciler) holdFlux(
+ ctx context.Context, st *reconcileStatus, claim *configbutleraiv1alpha3.FluxClaim, check claimCheck,
) (conditionValue, error) {
configured := check.configured
- ref := trigger.Applier()
+ ref := claim.Applier()
switch {
case configured.Status == metav1.ConditionTrue:
- if err := r.setFinalizer(ctx, st, trigger, true); err != nil {
+ if err := r.setFinalizer(ctx, st, claim, true); err != nil {
return conditionValue{}, err
}
- held := r.holdKustomization(ctx, st, trigger)
+ held := r.holdKustomization(ctx, st, claim)
if check.checked {
- r.publishConsumers(ctx, trigger)
+ r.publishConsumers(ctx, claim)
}
return held, nil
case configured.Reason == applier.ReasonNotReferenced:
return conditionValue{Status: metav1.ConditionFalse, Reason: applier.ReasonNotReferenced,
- Message: "nothing to hold: no GitTarget names this trigger"}, nil
- case configured.Reason == applier.ReasonHeldByAnotherTrigger && applier.Holding(trigger):
+ Message: "nothing to hold: no GitTarget names this applier claim"}, nil
+ case configured.Reason == applier.ReasonHeldByAnotherClaim && applier.Holding(claim):
// The holder took it, or two adopted at once: spec.suspend is held under the same field
- // manager either way, so there is nothing of this trigger's to hand back.
- if err := r.setFinalizer(ctx, st, trigger, false); err != nil {
+ // manager either way, so there is nothing of this applier claim's to hand back.
+ if err := r.setFinalizer(ctx, st, claim, false); err != nil {
return conditionValue{}, err
}
- case applier.Holding(trigger):
+ case applier.Holding(claim):
return conditionValue{Status: metav1.ConditionUnknown, Reason: ReasonNotConfigured, Message: fmt.Sprintf(
- "Kustomization %s is left suspended under GitOps Reverser's field manager, as this trigger last held "+
- "it: it is held again once the trigger is Configured, and handed back once no eligible GitTarget "+
- "references the trigger or the trigger is deleted", ref)}, nil
+ "Kustomization %s is left suspended under GitOps Reverser's field manager, as this applier claim last held "+
+ "it: it is held again once the applier claim is Configured, and handed back once no eligible GitTarget "+
+ "references the applier claim or the applier claim is deleted",
+ ref,
+ )}, nil
}
return conditionValue{Status: metav1.ConditionUnknown, Reason: ReasonNotConfigured,
- Message: "the Kustomization is held once the trigger is Configured"}, nil
+ Message: "the Kustomization is held once the applier claim is Configured"}, nil
}
-// triggerCheck is what configuredVerdict found.
-type triggerCheck struct {
+// claimCheck is what configuredVerdict found.
+type claimCheck struct {
// configured is the Configured condition.
configured conditionValue
// source is what the applier applies, published only while the binding is satisfied.
source *applier.Source
// checked is false when the check could not be made: the published verdict and source stay.
checked bool
- // refs are the GitTargets naming the trigger, judged; judged is false when they could not be.
+ // refs are the GitTargets naming the applier claim, judged; judged is false when they could not be.
refs applier.References
judged bool
}
-// retiring reports whether trigger holds its applier and no eligible GitTarget references it any
+// retiring reports whether claim holds its applier and no eligible GitTarget references it any
// more, conclusively: none names it, or each is refused. A reference that cannot be judged yet
// keeps the hold, since a release is not undone: Flux applies at once, and Argo CD syncs.
-func (c triggerCheck) retiring(trigger configbutleraiv1alpha3.ApplyTrigger) bool {
- return applier.Holding(trigger) && c.judged && (!c.refs.Named() || c.refs.Refused())
+func (c claimCheck) retiring(claim configbutleraiv1alpha3.ApplierClaim) bool {
+ return applier.Holding(claim) && c.judged && (!c.refs.Named() || c.refs.Refused())
}
-// configuredVerdict judges the GitTargets naming the trigger, and, while one may be eligible,
-// checks the trigger's applier in its cluster and the binding against what it read. A trigger no
+// configuredVerdict judges the GitTargets naming the applier claim, and, while one may be eligible,
+// checks the applier claim's applier in its cluster and the binding against what it read. An applier claim no
// GitTarget names is NotReferenced, and reads nothing in the applier's cluster. A check that could
// not be made keeps the verdict and the source already published, since a blip reading that cluster
// says nothing about the applier; with nothing published it is Unknown.
-func (r *ApplyTriggerReconciler) configuredVerdict(
- ctx context.Context, trigger configbutleraiv1alpha3.ApplyTrigger,
-) triggerCheck {
- cluster := trigger.KubeProviderKey()
+func (r *ApplierClaimReconciler) configuredVerdict(
+ ctx context.Context, claim configbutleraiv1alpha3.ApplierClaim,
+) claimCheck {
+ cluster := claim.KubeProviderKey()
ctx, cancel := context.WithTimeout(ctx, applierCheckTimeout)
defer cancel()
- kept := func(message string) triggerCheck { return keptCheck(trigger, message) }
+ kept := func(message string) claimCheck { return keptCheck(claim, message) }
- targets, err := applier.NamingTargets(ctx, r.Client, trigger)
+ targets, err := applier.NamingTargets(ctx, r.Client, claim)
if err != nil {
- return kept(fmt.Sprintf("Cannot list the GitTargets naming the trigger: %v", err))
+ return kept(fmt.Sprintf("Cannot list the GitTargets naming the applier claim: %v", err))
}
if len(targets) == 0 {
- return triggerCheck{configured: conditionValue{
+ return claimCheck{configured: conditionValue{
Status: metav1.ConditionUnknown, Reason: applier.ReasonNotReferenced,
- Message: "no GitTarget names this trigger; it reads and writes nothing until one does",
+ Message: "no GitTarget names this applier claim; it reads and writes nothing until one does",
}, checked: true, judged: true}
}
- refs, err := applier.JudgeReferences(ctx, r.Client, trigger, targets, nil)
+ refs, err := applier.JudgeReferences(ctx, r.Client, claim, targets, nil)
if err != nil {
return kept(err.Error())
}
- decision, err := authz.ApplyTriggerAdmitted(ctx, r.Client, trigger)
+ decision, err := authz.ApplierClaimAdmitted(ctx, r.Client, claim)
switch {
case err != nil:
return kept(fmt.Sprintf("Cannot check KubeProvider %s: %v", cluster, err))
@@ -323,18 +325,18 @@ func (r *ApplyTriggerReconciler) configuredVerdict(
case refs.Refused():
return falseCheck(refs.Reason(), refs.Message(), refs)
}
- return r.applierVerdict(ctx, trigger, targets, refs)
+ return r.applierVerdict(ctx, claim, targets, refs)
}
-// applierVerdict checks the trigger's applier in its cluster, and judges the GitTargets naming the
-// trigger against the source it read: refs is their judgement on admission alone.
-func (r *ApplyTriggerReconciler) applierVerdict(
- ctx context.Context, trigger configbutleraiv1alpha3.ApplyTrigger, targets []*configbutleraiv1alpha3.GitTarget,
+// applierVerdict checks the applier claim's applier in its cluster, and judges the GitTargets naming the
+// applier claim against the source it read: refs is their judgement on admission alone.
+func (r *ApplierClaimReconciler) applierVerdict(
+ ctx context.Context, claim configbutleraiv1alpha3.ApplierClaim, targets []*configbutleraiv1alpha3.GitTarget,
refs applier.References,
-) triggerCheck {
- kind, ref, cluster := trigger.ApplierKind(), trigger.Applier(), trigger.KubeProviderKey().String()
+) claimCheck {
+ kind, ref, cluster := claim.ApplierKind(), claim.Applier(), claim.KubeProviderKey().String()
if r.ApplierClients == nil {
- return triggerCheck{configured: conditionValue{
+ return claimCheck{configured: conditionValue{
Status: metav1.ConditionUnknown,
Reason: GitTargetReasonNotChecked,
Message: fmt.Sprintf("%s %s has not been checked: no cluster client is wired", kind, ref),
@@ -342,7 +344,7 @@ func (r *ApplyTriggerReconciler) applierVerdict(
}
dynamicClient, err := r.ApplierClients(ctx, cluster)
if err != nil {
- return keptCheck(trigger, fmt.Sprintf("Cannot reach cluster %q to check %s %s: %v", cluster, kind, ref, err))
+ return keptCheck(claim, fmt.Sprintf("Cannot reach cluster %q to check %s %s: %v", cluster, kind, ref, err))
}
newApplier := r.newApplier
if newApplier == nil {
@@ -351,72 +353,72 @@ func (r *ApplyTriggerReconciler) applierVerdict(
verdict := newApplier(kind, dynamicClient).Validate(ctx, ref)
switch {
case verdict.Err != nil:
- return keptCheck(trigger, verdict.Message)
+ return keptCheck(claim, verdict.Message)
case !verdict.OK:
return falseCheck(verdict.Reason, verdict.Message, refs)
}
- refs, err = applier.JudgeReferences(ctx, r.Client, trigger, targets, verdict.Source)
+ refs, err = applier.JudgeReferences(ctx, r.Client, claim, targets, verdict.Source)
switch {
case err != nil:
- return keptCheck(trigger, err.Error())
+ return keptCheck(claim, err.Error())
case refs.Eligible():
- return r.heldVerdict(ctx, trigger, verdict, refs)
+ return r.heldVerdict(ctx, claim, verdict, refs)
case refs.Refused():
return falseCheck(refs.Reason(), refs.Message(), refs)
}
- return triggerCheck{configured: conditionValue{
+ return claimCheck{configured: conditionValue{
Status: metav1.ConditionUnknown, Reason: applier.ReasonNotBound, Message: refs.Message(),
}, checked: true, refs: refs, judged: true}
}
// keptCheck is a check that could not be made: the published verdict and source stay.
-func keptCheck(trigger configbutleraiv1alpha3.ApplyTrigger, message string) triggerCheck {
- return triggerCheck{configured: keptCondition(*trigger.StatusConditions(), ApplyTriggerConditionConfigured,
+func keptCheck(claim configbutleraiv1alpha3.ApplierClaim, message string) claimCheck {
+ return claimCheck{configured: keptCondition(*claim.StatusConditions(), ApplierClaimConditionConfigured,
message)}
}
-// falseCheck is Configured=False for reason, with the GitTargets naming the trigger as judged.
-func falseCheck(reason, message string, refs applier.References) triggerCheck {
- return triggerCheck{configured: conditionValue{Status: metav1.ConditionFalse, Reason: reason, Message: message},
+// falseCheck is Configured=False for reason, with the GitTargets naming the applier claim as judged.
+func falseCheck(reason, message string, refs applier.References) claimCheck {
+ return claimCheck{configured: conditionValue{Status: metav1.ConditionFalse, Reason: reason, Message: message},
checked: true, refs: refs, judged: true}
}
-// heldVerdict is Configured for a trigger whose applier passed every other check and an eligible
-// GitTarget references: True when this trigger holds it, False (HeldByAnotherTrigger) when another
-// trigger in line comes first. Checked last, so a trigger in line for an applier is one that can
+// heldVerdict is Configured for an applier claim whose applier passed every other check and an eligible
+// GitTarget references: True when this applier claim holds it, False (HeldByAnotherClaim) when another
+// applier claim in line comes first. Checked last, so an applier claim in line for an applier is one that can
// take it over as it is.
-func (r *ApplyTriggerReconciler) heldVerdict(
- ctx context.Context, trigger configbutleraiv1alpha3.ApplyTrigger, verdict applier.Verdict,
+func (r *ApplierClaimReconciler) heldVerdict(
+ ctx context.Context, claim configbutleraiv1alpha3.ApplierClaim, verdict applier.Verdict,
refs applier.References,
-) triggerCheck {
- // The triggers in line are judged against the source just read, which they share.
- setTriggerSource(trigger, verdict.Source)
- holder, err := applier.HeldByAnother(ctx, r.Client, trigger)
+) claimCheck {
+ // The applier claims in line are judged against the source just read, which they share.
+ setClaimSource(claim, verdict.Source)
+ holder, err := applier.HeldByAnother(ctx, r.Client, claim)
switch {
case err != nil:
- return triggerCheck{configured: keptCondition(*trigger.StatusConditions(), ApplyTriggerConditionConfigured,
- fmt.Sprintf("Cannot check which trigger holds %s %s: %v", trigger.ApplierKind(), trigger.Applier(), err)),
+ return claimCheck{configured: keptCondition(*claim.StatusConditions(), ApplierClaimConditionConfigured,
+ fmt.Sprintf("Cannot check which applier claim holds %s %s: %v", claim.ApplierKind(), claim.Applier(), err)),
source: verdict.Source, checked: true, refs: refs, judged: true}
case holder != nil:
- return triggerCheck{configured: conditionValue{
- Status: metav1.ConditionFalse, Reason: applier.ReasonHeldByAnotherTrigger,
- Message: applier.HeldByAnotherMessage(trigger, holder),
+ return claimCheck{configured: conditionValue{
+ Status: metav1.ConditionFalse, Reason: applier.ReasonHeldByAnotherClaim,
+ Message: applier.HeldByAnotherMessage(claim, holder),
}, source: verdict.Source, checked: true, refs: refs, judged: true}
}
- return triggerCheck{
+ return claimCheck{
configured: conditionValue{Status: metav1.ConditionTrue, Reason: ReasonSucceeded, Message: verdict.Message},
source: verdict.Source, checked: true, refs: refs, judged: true,
}
}
-// setTriggerSource publishes what the applier applies, or clears it when the check read nothing.
-func setTriggerSource(trigger configbutleraiv1alpha3.ApplyTrigger, source *applier.Source) {
- switch t := trigger.(type) {
- case *configbutleraiv1alpha3.FluxTrigger:
+// setClaimSource publishes what the applier applies, or clears it when the check read nothing.
+func setClaimSource(claim configbutleraiv1alpha3.ApplierClaim, source *applier.Source) {
+ switch t := claim.(type) {
+ case *configbutleraiv1alpha3.FluxClaim:
t.Status.Source = nil
if source != nil {
- t.Status.Source = &configbutleraiv1alpha3.FluxTriggerSource{
+ t.Status.Source = &configbutleraiv1alpha3.FluxClaimSource{
GitRepository: meta.NamespacedObjectReference{
Namespace: source.GitRepository.Namespace, Name: source.GitRepository.Name,
},
@@ -424,49 +426,49 @@ func setTriggerSource(trigger configbutleraiv1alpha3.ApplyTrigger, source *appli
Branch: source.Revision,
}
}
- case *configbutleraiv1alpha3.ArgoCDTrigger:
+ case *configbutleraiv1alpha3.ArgoCDClaim:
t.Status.Source = nil
if source != nil {
- t.Status.Source = &configbutleraiv1alpha3.ArgoCDTriggerSource{
+ t.Status.Source = &configbutleraiv1alpha3.ArgoCDClaimSource{
RepoURL: source.URL, TargetRevision: source.Revision,
}
}
}
}
-// triggerSourceSummary names what an applier applies, as the trigger publishes it, for the Ready
+// claimSourceSummary names what an applier applies, as the applier claim publishes it, for the Ready
// message.
-func triggerSourceSummary(trigger configbutleraiv1alpha3.ApplyTrigger) string {
- url, revision, known := trigger.SourceBinding()
+func claimSourceSummary(claim configbutleraiv1alpha3.ApplierClaim) string {
+ url, revision, known := claim.SourceBinding()
if !known {
return ""
}
return fmt.Sprintf("(revision %q of %s)", revision, url)
}
-// triggerReadiness is the trio's accumulator for a trigger, its gates contributed.
-func (r *ApplyTriggerReconciler) triggerReadiness(
- trigger configbutleraiv1alpha3.ApplyTrigger, configured conditionValue, held *conditionValue,
+// claimReadiness is the trio's accumulator for an applier claim, its gates contributed.
+func (r *ApplierClaimReconciler) claimReadiness(
+ claim configbutleraiv1alpha3.ApplierClaim, configured conditionValue, held *conditionValue,
) *readiness {
- rd := newReadiness(fmt.Sprintf("%s %s %s can be asked to apply", trigger.ApplierKind(), trigger.Applier(),
- triggerSourceSummary(trigger)), trigger.TriggerKind()+" is not stalled")
- applyTriggerReadinessGates(rd, configured, held, trigger.Operation())
+ rd := newReadiness(fmt.Sprintf("%s %s %s can be asked to apply", claim.ApplierKind(), claim.Applier(),
+ claimSourceSummary(claim)), claim.ClaimKind()+" is not stalled")
+ applierClaimReadinessGates(rd, configured, held, claim.Operation())
return rd
}
-// applyTriggerReadinessGates contributes Configured, the operation, and Held to the trio, in that
+// applierClaimReadinessGates contributes Configured, the operation, and Held to the trio, in that
// order.
// An applier that is missing, or a KubeProvider that is, may be on its way (Flux and Argo CD
-// objects are often created after the trigger); anything else Configured=False needs a human. An
-// operation that has not completed in time clears on its own once it does. A trigger no GitTarget
+// objects are often created after the applier claim); anything else Configured=False needs a human. An
+// operation that has not completed in time clears on its own once it does. An applier claim no GitTarget
// names is Ready (NotReferenced): it does all it is asked to, which is nothing, and a Flux health
// check that waits on it must not wait for the GitTarget that will name it.
-func applyTriggerReadinessGates(
+func applierClaimReadinessGates(
rd *readiness, configured conditionValue, held *conditionValue, operation *configbutleraiv1alpha3.ApplyOperation,
) {
if configured.Reason == applier.ReasonNotReferenced {
rd.convergesAs(conditionValue{Status: metav1.ConditionTrue, Reason: applier.ReasonNotReferenced,
- Message: "no GitTarget names this trigger, so it does nothing"})
+ Message: "no GitTarget names this applier claim, so it does nothing"})
}
waits := configured.Reason == applier.ReasonApplierNotFound ||
configured.Reason == authz.ReasonKubeProviderNotFound
@@ -491,58 +493,58 @@ func applyTriggerReadinessGates(
}
// SetupWithManager sets up the controller for its kind with the Manager.
-func (r *ApplyTriggerReconciler) SetupWithManager(mgr ctrl.Manager) error {
- trigger := configbutleraiv1alpha3.NewApplyTrigger(r.Kind)
- if trigger == nil {
- return fmt.Errorf("no apply trigger kind %q", r.Kind)
+func (r *ApplierClaimReconciler) SetupWithManager(mgr ctrl.Manager) error {
+ claim := configbutleraiv1alpha3.NewApplierClaim(r.Kind)
+ if claim == nil {
+ return fmt.Errorf("no applier claim kind %q", r.Kind)
}
return ctrl.NewControllerManagedBy(mgr).
// The status is written by two parties: this reconciler (conditions, source) and the branch
// workers (operation, repairPending). A status-only update is filtered, as everywhere, except
// one that moves the operation's phase, which Ready reads.
- For(trigger, builder.WithPredicates(applyTriggerSpecOrOperationChanged())).
- // What makes a GitTarget eligible re-runs the check of the trigger it names, and of the one it
+ For(claim, builder.WithPredicates(applierClaimSpecOrOperationChanged())).
+ // What makes a GitTarget eligible re-runs the check of the applier claim it names, and of the one it
// named before: the GitTarget appearing, going, or changing what it names or writes.
- Watches(&configbutleraiv1alpha3.GitTarget{}, r.gitTargetToTriggers()).
+ Watches(&configbutleraiv1alpha3.GitTarget{}, r.gitTargetToClaims()).
// A GitBranch and its GitProvider's URL are what its GitTargets write.
Watches(
&configbutleraiv1alpha3.GitBranch{},
- handler.EnqueueRequestsFromMapFunc(r.gitDestinationToTriggers),
+ handler.EnqueueRequestsFromMapFunc(r.gitDestinationToClaims),
builder.WithPredicates(predicate.GenerationChangedPredicate{}),
).
Watches(
&configbutleraiv1alpha3.GitProvider{},
- handler.EnqueueRequestsFromMapFunc(r.gitDestinationToTriggers),
+ handler.EnqueueRequestsFromMapFunc(r.gitDestinationToClaims),
builder.WithPredicates(predicate.GenerationChangedPredicate{}),
).
- // A KubeProvider admits the trigger's namespace, and the namespaces of the GitTargets naming
- // triggers, so a provider created, deleted, or with its spec changed re-runs every check.
+ // A KubeProvider admits the applier claim's namespace, and the namespaces of the GitTargets naming
+ // applier claims, so a provider created, deleted, or with its spec changed re-runs every check.
Watches(
&configbutleraiv1alpha3.KubeProvider{},
- handler.EnqueueRequestsFromMapFunc(r.kubeProviderToTriggers),
+ handler.EnqueueRequestsFromMapFunc(r.kubeProviderToClaims),
builder.WithPredicates(predicate.GenerationChangedPredicate{}),
).
// An accessFrom selector reads namespace labels.
Watches(
&corev1.Namespace{},
- handler.EnqueueRequestsFromMapFunc(r.namespaceToTriggers),
+ handler.EnqueueRequestsFromMapFunc(r.namespaceToClaims),
builder.WithPredicates(predicate.LabelChangedPredicate{}),
).
- // One trigger holds an applier; the others naming it wait in line. A holder that is gone, or
- // lets go, and a trigger that joins or leaves the line, re-run the others' check: nothing else
+ // One applier claim holds an applier; the others naming it wait in line. A holder that is gone, or
+ // lets go, and an applier claim that joins or leaves the line, re-run the others' check: nothing else
// changes on them to reconcile them.
Watches(
- trigger,
- handler.EnqueueRequestsFromMapFunc(r.triggersInLine),
- builder.WithPredicates(triggerLineChanged()),
+ claim,
+ handler.EnqueueRequestsFromMapFunc(r.claimsInLine),
+ builder.WithPredicates(claimLineChanged()),
).
Named(r.Kind).
Complete(r)
}
-// applyTriggerSpecOrOperationChanged fires on create and delete, on a spec change, on a change of
+// applierClaimSpecOrOperationChanged fires on create and delete, on a spec change, on a change of
// the reconcile-request annotation, and on a change of status.operation.phase.
-func applyTriggerSpecOrOperationChanged() predicate.Predicate {
+func applierClaimSpecOrOperationChanged() predicate.Predicate {
requested := reconcileRequestedOrSpecChanged()
return predicate.Funcs{
CreateFunc: requested.Create,
@@ -552,40 +554,40 @@ func applyTriggerSpecOrOperationChanged() predicate.Predicate {
if requested.Update(e) {
return true
}
- before, ok1 := e.ObjectOld.(configbutleraiv1alpha3.ApplyTrigger)
- after, ok2 := e.ObjectNew.(configbutleraiv1alpha3.ApplyTrigger)
+ before, ok1 := e.ObjectOld.(configbutleraiv1alpha3.ApplierClaim)
+ after, ok2 := e.ObjectNew.(configbutleraiv1alpha3.ApplierClaim)
return !ok1 || !ok2 || operationPhase(before) != operationPhase(after)
},
}
}
-// operationPhase is a trigger's status.operation.phase, or "" without one.
-func operationPhase(trigger configbutleraiv1alpha3.ApplyTrigger) configbutleraiv1alpha3.ApplyOperationPhase {
- if operation := trigger.Operation(); operation != nil {
+// operationPhase is an applier claim's status.operation.phase, or "" without one.
+func operationPhase(claim configbutleraiv1alpha3.ApplierClaim) configbutleraiv1alpha3.ApplyOperationPhase {
+ if operation := claim.Operation(); operation != nil {
return operation.Phase
}
return ""
}
-// listTriggers lists this reconciler's kind, in namespace or everywhere when it is empty.
-func (r *ApplyTriggerReconciler) listTriggers(
+// listClaims lists this reconciler's kind, in namespace or everywhere when it is empty.
+func (r *ApplierClaimReconciler) listClaims(
ctx context.Context, namespace string,
-) ([]configbutleraiv1alpha3.ApplyTrigger, error) {
+) ([]configbutleraiv1alpha3.ApplierClaim, error) {
var opts []client.ListOption
if namespace != "" {
opts = append(opts, client.InNamespace(namespace))
}
- return applier.ListApplyTriggers(ctx, r.Client, r.Kind, opts...)
+ return applier.ListApplierClaims(ctx, r.Client, r.Kind, opts...)
}
-// triggerLineChanged passes the event of a trigger that is gone, and an update that can move it in
+// claimLineChanged passes the event of an applier claim that is gone, and an update that can move it in
// or out of line for its applier: its finalizer, its Configured condition, or its Held one.
-func triggerLineChanged() predicate.Predicate {
+func claimLineChanged() predicate.Predicate {
return predicate.Funcs{
CreateFunc: func(event.CreateEvent) bool { return false },
UpdateFunc: func(e event.UpdateEvent) bool {
- before, ok1 := e.ObjectOld.(configbutleraiv1alpha3.ApplyTrigger)
- after, ok2 := e.ObjectNew.(configbutleraiv1alpha3.ApplyTrigger)
+ before, ok1 := e.ObjectOld.(configbutleraiv1alpha3.ApplierClaim)
+ after, ok2 := e.ObjectNew.(configbutleraiv1alpha3.ApplierClaim)
return !ok1 || !ok2 || lineSummary(before) != lineSummary(after)
},
DeleteFunc: func(event.DeleteEvent) bool { return true },
@@ -593,28 +595,28 @@ func triggerLineChanged() predicate.Predicate {
}
}
-// lineSummary is what of a trigger decides its place in line for its applier.
-func lineSummary(trigger configbutleraiv1alpha3.ApplyTrigger) string {
+// lineSummary is what of an applier claim decides its place in line for its applier.
+func lineSummary(claim configbutleraiv1alpha3.ApplierClaim) string {
var summary strings.Builder
- summary.WriteString(strconv.FormatBool(applier.Holding(trigger)))
- for _, conditionType := range []string{ApplyTriggerConditionConfigured, ApplyTriggerConditionHeld} {
- if cond := findCondition(*trigger.StatusConditions(), conditionType); cond != nil {
+ summary.WriteString(strconv.FormatBool(applier.Holding(claim)))
+ for _, conditionType := range []string{ApplierClaimConditionConfigured, ApplierClaimConditionHeld} {
+ if cond := findCondition(*claim.StatusConditions(), conditionType); cond != nil {
summary.WriteString("|" + string(cond.Status) + "/" + cond.Reason)
}
}
return summary.String()
}
-// gitTargetToTriggers enqueues the triggers of this kind a GitTarget names, before and after an
+// gitTargetToClaims enqueues the applier claims of this kind a GitTarget names, before and after an
// update, when it appears, goes, or changes anything eligibility reads: its spec, or its deletion.
-func (r *ApplyTriggerReconciler) gitTargetToTriggers() handler.EventHandler {
+func (r *ApplierClaimReconciler) gitTargetToClaims() handler.EventHandler {
named := func(obj client.Object) []ctrlreconcile.Request {
target, ok := obj.(*configbutleraiv1alpha3.GitTarget)
- if !ok || target.Spec.ApplyTriggerRef == nil || target.Spec.ApplyTriggerRef.Kind != r.Kind {
+ if !ok || target.Spec.ApplierClaimRef == nil || target.Spec.ApplierClaimRef.Kind != r.Kind {
return nil
}
return []ctrlreconcile.Request{{NamespacedName: k8stypes.NamespacedName{
- Namespace: target.Namespace, Name: target.Spec.ApplyTriggerRef.Name,
+ Namespace: target.Namespace, Name: target.Spec.ApplierClaimRef.Name,
}}}
}
add := func(q workqueue.TypedRateLimitingInterface[ctrlreconcile.Request], objs ...client.Object) {
@@ -644,9 +646,9 @@ func (r *ApplyTriggerReconciler) gitTargetToTriggers() handler.EventHandler {
}
}
-// gitDestinationToTriggers maps a GitBranch or GitProvider event to the triggers of this kind named
+// gitDestinationToClaims maps a GitBranch or GitProvider event to the applier claims of this kind named
// by the GitTargets that write through it.
-func (r *ApplyTriggerReconciler) gitDestinationToTriggers(
+func (r *ApplierClaimReconciler) gitDestinationToClaims(
ctx context.Context,
obj client.Object,
) []ctrlreconcile.Request {
@@ -659,7 +661,7 @@ func (r *ApplyTriggerReconciler) gitDestinationToTriggers(
var requests []ctrlreconcile.Request
for i := range targets {
target := &targets[i]
- ref := target.Spec.ApplyTriggerRef
+ ref := target.Spec.ApplierClaimRef
if ref == nil || ref.Kind != r.Kind {
continue
}
@@ -672,59 +674,59 @@ func (r *ApplyTriggerReconciler) gitDestinationToTriggers(
return requests
}
-// triggersInLine maps a trigger that is gone, or that moved in line, to the other triggers of this
+// claimsInLine maps an applier claim that is gone, or that moved in line, to the other applier claims of this
// kind naming its applier, so the next in line takes it over.
-func (r *ApplyTriggerReconciler) triggersInLine(ctx context.Context, obj client.Object) []ctrlreconcile.Request {
- gone, ok := obj.(configbutleraiv1alpha3.ApplyTrigger)
- if !ok || gone.TriggerKind() != r.Kind {
+func (r *ApplierClaimReconciler) claimsInLine(ctx context.Context, obj client.Object) []ctrlreconcile.Request {
+ gone, ok := obj.(configbutleraiv1alpha3.ApplierClaim)
+ if !ok || gone.ClaimKind() != r.Kind {
return nil
}
- others, err := applier.TriggersOfApplier(ctx, r.Client, gone)
+ others, err := applier.ClaimsOfApplier(ctx, r.Client, gone)
if err != nil {
logDependencyListError(ctx, err, r.Kind+"s", obj)
return nil
}
requests := make([]ctrlreconcile.Request, 0, len(others))
for _, other := range others {
- requests = append(requests, triggerRequest(other))
+ requests = append(requests, claimRequest(other))
}
return requests
}
-// kubeProviderToTriggers maps a KubeProvider event to every trigger of this kind: a provider
-// admits the triggers that name it, and the GitTargets that mirror through it, which may name any
-// trigger in their namespace.
-func (r *ApplyTriggerReconciler) kubeProviderToTriggers(
+// kubeProviderToClaims maps a KubeProvider event to every applier claim of this kind: a provider
+// admits the applier claims that name it, and the GitTargets that mirror through it, which may name any
+// applier claim in their namespace.
+func (r *ApplierClaimReconciler) kubeProviderToClaims(
ctx context.Context, obj client.Object,
) []ctrlreconcile.Request {
- triggers, err := r.listTriggers(ctx, "")
+ claims, err := r.listClaims(ctx, "")
if err != nil {
logDependencyListError(ctx, err, r.Kind+"s", obj)
return nil
}
- requests := make([]ctrlreconcile.Request, 0, len(triggers))
- for _, trigger := range triggers {
- requests = append(requests, triggerRequest(trigger))
+ requests := make([]ctrlreconcile.Request, 0, len(claims))
+ for _, claim := range claims {
+ requests = append(requests, claimRequest(claim))
}
return requests
}
-// namespaceToTriggers maps a Namespace label change to every trigger of this kind in it.
-func (r *ApplyTriggerReconciler) namespaceToTriggers(ctx context.Context, obj client.Object) []ctrlreconcile.Request {
- triggers, err := r.listTriggers(ctx, obj.GetName())
+// namespaceToClaims maps a Namespace label change to every applier claim of this kind in it.
+func (r *ApplierClaimReconciler) namespaceToClaims(ctx context.Context, obj client.Object) []ctrlreconcile.Request {
+ claims, err := r.listClaims(ctx, obj.GetName())
if err != nil {
logDependencyListError(ctx, err, r.Kind+"s", obj)
return nil
}
- requests := make([]ctrlreconcile.Request, 0, len(triggers))
- for _, trigger := range triggers {
- requests = append(requests, triggerRequest(trigger))
+ requests := make([]ctrlreconcile.Request, 0, len(claims))
+ for _, claim := range claims {
+ requests = append(requests, claimRequest(claim))
}
return requests
}
-func triggerRequest(trigger configbutleraiv1alpha3.ApplyTrigger) ctrlreconcile.Request {
+func claimRequest(claim configbutleraiv1alpha3.ApplierClaim) ctrlreconcile.Request {
return ctrlreconcile.Request{NamespacedName: k8stypes.NamespacedName{
- Namespace: trigger.GetNamespace(), Name: trigger.GetName(),
+ Namespace: claim.GetNamespace(), Name: claim.GetName(),
}}
}
diff --git a/internal/controller/applytrigger_controller_test.go b/internal/controller/applierclaim_controller_test.go
similarity index 62%
rename from internal/controller/applytrigger_controller_test.go
rename to internal/controller/applierclaim_controller_test.go
index 8f8af6e6e..b51150b85 100644
--- a/internal/controller/applytrigger_controller_test.go
+++ b/internal/controller/applierclaim_controller_test.go
@@ -100,31 +100,31 @@ func applierSim(t *testing.T, objs ...runtime.Object) *fluxsim.Sim {
return fluxsim.New(t, client)
}
-// The trigger tests reach their appliers through KubeProviders in triggerProviderNamespace, a
+// The applier claim tests reach their appliers through KubeProviders in claimProviderNamespace, a
// namespace of their own, so a provider admits team-a only through spec.accessFrom: the namespace
-// policy is what those tests exercise. triggerLocalProvider is the operator's own cluster there.
+// policy is what those tests exercise. claimLocalProvider is the operator's own cluster there.
const (
- triggerProviderNamespace = "clusters"
- triggerLocalProvider = "local"
+ claimProviderNamespace = "clusters"
+ claimLocalProvider = "local"
)
-// triggerProviderRef names the KubeProvider name in triggerProviderNamespace.
-func triggerProviderRef(name string) meta.NamespacedObjectReference {
- return meta.NamespacedObjectReference{Namespace: triggerProviderNamespace, Name: name}
+// claimProviderRef names the KubeProvider name in claimProviderNamespace.
+func claimProviderRef(name string) meta.NamespacedObjectReference {
+ return meta.NamespacedObjectReference{Namespace: claimProviderNamespace, Name: name}
}
-// triggerProviderKey is the key of the KubeProvider name in triggerProviderNamespace, as
+// claimProviderKey is the key of the KubeProvider name in claimProviderNamespace, as
// ApplierClients is asked for it.
-func triggerProviderKey(name string) string {
- return triggerProviderNamespace + "/" + name
+func claimProviderKey(name string) string {
+ return claimProviderNamespace + "/" + name
}
-// admitting is a KubeProvider in triggerProviderNamespace whose accessFrom admits namespaces, and
-// which lets a trigger name an applier in another namespace (the test appliers live in flux-system
+// admitting is a KubeProvider in claimProviderNamespace whose accessFrom admits namespaces, and
+// which lets an applier claim name an applier in another namespace (the test appliers live in flux-system
// and argocd).
func admitting(name string, namespaces ...string) *configbutleraiv1alpha3.KubeProvider {
return &configbutleraiv1alpha3.KubeProvider{
- ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: triggerProviderNamespace},
+ ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: claimProviderNamespace},
Spec: configbutleraiv1alpha3.KubeProviderSpec{
InCluster: &configbutleraiv1alpha3.KubeProviderInCluster{},
AccessFrom: &configbutleraiv1alpha3.NamespaceMatcher{Names: namespaces},
@@ -133,41 +133,41 @@ func admitting(name string, namespaces ...string) *configbutleraiv1alpha3.KubePr
}
}
-// testFluxTrigger is the FluxTrigger team-a/checkout holding flux-system/checkout in the cluster of
-// the KubeProvider cluster in triggerProviderNamespace.
-func testFluxTrigger(cluster string) *configbutleraiv1alpha3.FluxTrigger {
- return &configbutleraiv1alpha3.FluxTrigger{
+// testFluxClaim is the FluxClaim team-a/checkout holding flux-system/checkout in the cluster of
+// the KubeProvider cluster in claimProviderNamespace.
+func testFluxClaim(cluster string) *configbutleraiv1alpha3.FluxClaim {
+ return &configbutleraiv1alpha3.FluxClaim{
ObjectMeta: metav1.ObjectMeta{Name: "checkout", Namespace: "team-a", Generation: 1},
- Spec: configbutleraiv1alpha3.FluxTriggerSpec{
- KubeProviderRef: triggerProviderRef(cluster),
+ Spec: configbutleraiv1alpha3.FluxClaimSpec{
+ KubeProviderRef: claimProviderRef(cluster),
KustomizationRef: meta.NamespacedObjectReference{Namespace: "flux-system", Name: "checkout"},
},
}
}
-// testArgoCDTrigger is the ArgoCDTrigger team-a/checkout holding argocd/checkout in the cluster of
-// the KubeProvider cluster in triggerProviderNamespace.
-func testArgoCDTrigger(cluster string) *configbutleraiv1alpha3.ArgoCDTrigger {
- return &configbutleraiv1alpha3.ArgoCDTrigger{
+// testArgoCDClaim is the ArgoCDClaim team-a/checkout holding argocd/checkout in the cluster of
+// the KubeProvider cluster in claimProviderNamespace.
+func testArgoCDClaim(cluster string) *configbutleraiv1alpha3.ArgoCDClaim {
+ return &configbutleraiv1alpha3.ArgoCDClaim{
ObjectMeta: metav1.ObjectMeta{Name: "checkout", Namespace: "team-a", Generation: 1},
- Spec: configbutleraiv1alpha3.ArgoCDTriggerSpec{
- KubeProviderRef: triggerProviderRef(cluster),
+ Spec: configbutleraiv1alpha3.ArgoCDClaimSpec{
+ KubeProviderRef: claimProviderRef(cluster),
ApplicationRef: meta.NamespacedObjectReference{Namespace: "argocd", Name: "checkout"},
},
}
}
// referencingTarget is a GitTarget team-a/name mirroring through the KubeProvider
-// triggerLocalProvider and writing main of the test repository through the GitProvider platform,
-// naming the trigger kind/checkout.
+// claimLocalProvider and writing main of the test repository through the GitProvider platform,
+// naming the applier claim kind/checkout.
func referencingTarget(name, kind string) *configbutleraiv1alpha3.GitTarget {
return &configbutleraiv1alpha3.GitTarget{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "team-a"},
Spec: configbutleraiv1alpha3.GitTargetSpec{
- KubeProviderRef: triggerProviderRef(triggerLocalProvider),
+ KubeProviderRef: claimProviderRef(claimLocalProvider),
GitBranchRef: meta.NamespacedObjectReference{Name: gitBranchNameForTest("platform", "main")},
Path: name,
- ApplyTriggerRef: &configbutleraiv1alpha3.ApplyTriggerReference{Kind: kind, Name: "checkout"},
+ ApplierClaimRef: &configbutleraiv1alpha3.ApplierClaimReference{Kind: kind, Name: "checkout"},
},
}
}
@@ -180,8 +180,8 @@ func platformProvider() *configbutleraiv1alpha3.GitProvider {
}
}
-// referencedIn is a GitProvider platform and a GitTarget checkout- naming the trigger
-// kind/checkout, both in namespace: what makes a trigger there eligible.
+// referencedIn is a GitProvider platform and a GitTarget checkout- naming the applier claim
+// kind/checkout, both in namespace: what makes an applier claim there eligible.
func referencedIn(namespace, kind string) []ctrlclient.Object {
provider := platformProvider()
provider.Namespace = namespace
@@ -190,14 +190,14 @@ func referencedIn(namespace, kind string) []ctrlclient.Object {
return []ctrlclient.Object{provider, target}
}
-// unreferenced marks a triggerTestClient that holds no GitTarget naming the triggers.
+// unreferenced marks a claimTestClient that holds no GitTarget naming the applier claims.
type unreferenced struct{ ctrlclient.Object }
-// triggerTestClient is a control cluster holding objects, the KubeProviders triggerLocalProvider and
+// claimTestClient is a control cluster holding objects, the KubeProviders claimLocalProvider and
// "hub" admitting team-a unless objects carries its own, the GitProvider platform unless objects carries a
-// GitProvider, and a GitTarget naming each trigger kind's checkout unless objects carries a
+// GitProvider, and a GitTarget naming each applier claim kind's checkout unless objects carries a
// GitTarget or unreferenced{}.
-func triggerTestClient(t *testing.T, objects ...ctrlclient.Object) ctrlclient.Client {
+func claimTestClient(t *testing.T, objects ...ctrlclient.Object) ctrlclient.Client {
t.Helper()
scheme := runtime.NewScheme()
require.NoError(t, clientgoscheme.AddToScheme(scheme))
@@ -221,7 +221,7 @@ func triggerTestClient(t *testing.T, objects ...ctrlclient.Object) ctrlclient.Cl
}
}
objects = kept
- for _, name := range []string{triggerLocalProvider, "hub"} {
+ for _, name := range []string{claimLocalProvider, "hub"} {
if !held[name] {
objects = append(objects, admitting(name, "team-a"))
}
@@ -231,19 +231,19 @@ func triggerTestClient(t *testing.T, objects ...ctrlclient.Object) ctrlclient.Cl
}
if !hasTarget {
objects = append(objects,
- referencingTarget("checkout-flux", configbutleraiv1alpha3.ApplyTriggerKindFlux),
- referencingTarget("checkout-argo", configbutleraiv1alpha3.ApplyTriggerKindArgoCD))
+ referencingTarget("checkout-flux", configbutleraiv1alpha3.ApplierClaimKindFlux),
+ referencingTarget("checkout-argo", configbutleraiv1alpha3.ApplierClaimKindArgoCD))
}
return fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).
- WithStatusSubresource(&configbutleraiv1alpha3.FluxTrigger{}, &configbutleraiv1alpha3.ArgoCDTrigger{}).
+ WithStatusSubresource(&configbutleraiv1alpha3.FluxClaim{}, &configbutleraiv1alpha3.ArgoCDClaim{}).
Build()
}
-// triggerReconciler checks appliers against a fake cluster whose access review answers allowed.
-func triggerReconciler(
+// claimReconciler checks appliers against a fake cluster whose access review answers allowed.
+func claimReconciler(
c ctrlclient.Client, kind string, source dynamic.Interface, sourceErr error, allowed bool,
-) *ApplyTriggerReconciler {
- return &ApplyTriggerReconciler{
+) *ApplierClaimReconciler {
+ return &ApplierClaimReconciler{
Client: c,
Kind: kind,
ApplierClients: func(context.Context, string) (dynamic.Interface, error) { return source, sourceErr },
@@ -261,47 +261,47 @@ func triggerReconciler(
}
}
-// triggerKey is the trigger every test reconciles.
-var triggerKey = k8stypes.NamespacedName{Namespace: "team-a", Name: "checkout"}
+// claimKey is the applier claim every test reconciles.
+var claimKey = k8stypes.NamespacedName{Namespace: "team-a", Name: "checkout"}
-// reconcileTrigger reconciles team-a/checkout until it settles and returns the trigger as it is
-// then. While its Kustomization's suspend is being handled, a FluxTrigger is read again at the
+// reconcileClaim reconciles team-a/checkout until it settles and returns the applier claim as it is
+// then. While its Kustomization's suspend is being handled, a FluxClaim is read again at the
// suspending interval. Every pass publishes its conditions: the one that adopts too, which also adds
-// the finalizer, since nothing re-enqueues a trigger for a metadata-only update. A FluxTrigger
+// the finalizer, since nothing re-enqueues an applier claim for a metadata-only update. A FluxClaim
// carries the finalizer exactly while it is Configured: it holds its Kustomization. An
-// ArgoCDTrigger does too, unless the switch it holds is somebody else's.
-func reconcileTrigger(t *testing.T, r *ApplyTriggerReconciler) configbutleraiv1alpha3.ApplyTrigger {
+// ArgoCDClaim does too, unless the switch it holds is somebody else's.
+func reconcileClaim(t *testing.T, r *ApplierClaimReconciler) configbutleraiv1alpha3.ApplierClaim {
t.Helper()
- var trigger configbutleraiv1alpha3.ApplyTrigger
+ var claim configbutleraiv1alpha3.ApplierClaim
require.Eventually(t, func() bool {
- result, err := r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ result, err := r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
- trigger = configbutleraiv1alpha3.NewApplyTrigger(r.Kind)
- require.NoError(t, r.Get(context.Background(), triggerKey, trigger))
- require.NotNil(t, findCondition(*trigger.StatusConditions(), ConditionTypeReady),
+ claim = configbutleraiv1alpha3.NewApplierClaim(r.Kind)
+ require.NoError(t, r.Get(context.Background(), claimKey, claim))
+ require.NotNil(t, findCondition(*claim.StatusConditions(), ConditionTypeReady),
"every pass publishes Ready")
- held := findCondition(*trigger.StatusConditions(), ApplyTriggerConditionHeld)
- if conditionIsTrue(*trigger.StatusConditions(), ApplyTriggerConditionConfigured) && held != nil &&
+ held := findCondition(*claim.StatusConditions(), ApplierClaimConditionHeld)
+ if conditionIsTrue(*claim.StatusConditions(), ApplierClaimConditionConfigured) && held != nil &&
!strings.Contains(held.Message, "which owns the switch") {
- require.True(t, controllerutil.ContainsFinalizer(trigger, triggerFinalizer(trigger)),
- "a trigger that holds its applier carries the finalizer")
+ require.True(t, controllerutil.ContainsFinalizer(claim, claimFinalizer(claim)),
+ "an applier claim that holds its applier carries the finalizer")
}
if held != nil && held.Reason == ReasonSuspending {
- assert.Equal(t, applyTriggerSuspendingInterval, result.RequeueAfter, "a suspend is waited for closely")
+ assert.Equal(t, applierClaimSuspendingInterval, result.RequeueAfter, "a suspend is waited for closely")
return false
}
- want := heldTriggerSteadyInterval
- if !conditionIsTrue(*trigger.StatusConditions(), ConditionTypeReady) &&
- !conditionIsTrue(*trigger.StatusConditions(), ConditionTypeStalled) {
- want = applyTriggerSettleInterval
+ want := heldClaimSteadyInterval
+ if !conditionIsTrue(*claim.StatusConditions(), ConditionTypeReady) &&
+ !conditionIsTrue(*claim.StatusConditions(), ConditionTypeStalled) {
+ want = applierClaimSettleInterval
}
- assert.Equal(t, want, result.RequeueAfter, "a trigger that may clear on its own is read again soon")
+ assert.Equal(t, want, result.RequeueAfter, "an applier claim that may clear on its own is read again soon")
return true
}, 5*time.Second, 5*time.Millisecond)
- return trigger
+ return claim
}
-func TestApplyTriggerReconcile_FluxVerdicts(t *testing.T) {
+func TestApplierClaimReconcile_FluxVerdicts(t *testing.T) {
cases := []struct {
name string
objects []runtime.Object
@@ -309,11 +309,11 @@ func TestApplyTriggerReconcile_FluxVerdicts(t *testing.T) {
wantStatus metav1.ConditionStatus
wantReason string
wantStalled bool
- wantSource *configbutleraiv1alpha3.FluxTriggerSource
+ wantSource *configbutleraiv1alpha3.FluxClaimSource
}{
{name: "a Kustomization applying a GitRepository on a branch", objects: applierFluxObjects(),
allowed: true, wantStatus: metav1.ConditionTrue, wantReason: ReasonSucceeded,
- wantSource: &configbutleraiv1alpha3.FluxTriggerSource{
+ wantSource: &configbutleraiv1alpha3.FluxClaimSource{
GitRepository: meta.NamespacedObjectReference{Namespace: "flux-system", Name: "platform"},
URL: "https://github.com/acme/platform", Branch: "main",
}},
@@ -324,29 +324,29 @@ func TestApplyTriggerReconcile_FluxVerdicts(t *testing.T) {
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
- c := triggerTestClient(t, testFluxTrigger(triggerLocalProvider))
- r := triggerReconciler(c, configbutleraiv1alpha3.ApplyTriggerKindFlux,
+ c := claimTestClient(t, testFluxClaim(claimLocalProvider))
+ r := claimReconciler(c, configbutleraiv1alpha3.ApplierClaimKindFlux,
applierFakeSource(t, tc.objects...), nil, tc.allowed)
- trigger := reconcileTrigger(t, r).(*configbutleraiv1alpha3.FluxTrigger)
+ claim := reconcileClaim(t, r).(*configbutleraiv1alpha3.FluxClaim)
- configured := findCondition(trigger.Status.Conditions, ApplyTriggerConditionConfigured)
+ configured := findCondition(claim.Status.Conditions, ApplierClaimConditionConfigured)
require.NotNil(t, configured)
assert.Equal(t, tc.wantStatus, configured.Status, configured.Message)
assert.Equal(t, tc.wantReason, configured.Reason)
- ready := findCondition(trigger.Status.Conditions, ConditionTypeReady)
+ ready := findCondition(claim.Status.Conditions, ConditionTypeReady)
require.NotNil(t, ready)
assert.Equal(t, tc.wantStatus, ready.Status)
- assert.Equal(t, tc.wantStalled, conditionIsTrue(trigger.Status.Conditions, ConditionTypeStalled))
- assert.Equal(t, tc.wantSource, trigger.Status.Source)
- assert.Equal(t, int64(1), trigger.Status.ObservedGeneration)
+ assert.Equal(t, tc.wantStalled, conditionIsTrue(claim.Status.Conditions, ConditionTypeStalled))
+ assert.Equal(t, tc.wantSource, claim.Status.Source)
+ assert.Equal(t, int64(1), claim.Status.ObservedGeneration)
})
}
}
-// TestApplyTriggerReconcile_ArgoCDVerdicts: an ArgoCDTrigger checks an Application, with automated
+// TestApplierClaimReconcile_ArgoCDVerdicts: an ArgoCDClaim checks an Application, with automated
// sync on as a verdict of its own.
-func TestApplyTriggerReconcile_ArgoCDVerdicts(t *testing.T) {
+func TestApplierClaimReconcile_ArgoCDVerdicts(t *testing.T) {
cases := []struct {
name string
objects []runtime.Object
@@ -374,48 +374,48 @@ func TestApplyTriggerReconcile_ArgoCDVerdicts(t *testing.T) {
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
- c := triggerTestClient(t, testArgoCDTrigger(triggerLocalProvider))
- r := triggerReconciler(c, configbutleraiv1alpha3.ApplyTriggerKindArgoCD,
+ c := claimTestClient(t, testArgoCDClaim(claimLocalProvider))
+ r := claimReconciler(c, configbutleraiv1alpha3.ApplierClaimKindArgoCD,
applierFakeSource(t, tc.objects...), nil, tc.allowed)
- trigger := reconcileTrigger(t, r).(*configbutleraiv1alpha3.ArgoCDTrigger)
+ claim := reconcileClaim(t, r).(*configbutleraiv1alpha3.ArgoCDClaim)
- configured := findCondition(trigger.Status.Conditions, ApplyTriggerConditionConfigured)
+ configured := findCondition(claim.Status.Conditions, ApplierClaimConditionConfigured)
require.NotNil(t, configured)
assert.Equal(t, tc.wantStatus, configured.Status, configured.Message)
assert.Equal(t, tc.wantReason, configured.Reason)
- assert.Equal(t, tc.wantStalled, conditionIsTrue(trigger.Status.Conditions, ConditionTypeStalled))
+ assert.Equal(t, tc.wantStalled, conditionIsTrue(claim.Status.Conditions, ConditionTypeStalled))
if tc.wantStatus == metav1.ConditionTrue {
- assert.Equal(t, &configbutleraiv1alpha3.ArgoCDTriggerSource{
+ assert.Equal(t, &configbutleraiv1alpha3.ArgoCDClaimSource{
RepoURL: "https://github.com/acme/platform", TargetRevision: "main",
- }, trigger.Status.Source)
+ }, claim.Status.Source)
}
})
}
}
-// TestApplyTriggerReconcile_AnUnreachableClusterKeepsThePublishedVerdict: a blip reading the
+// TestApplierClaimReconcile_AnUnreachableClusterKeepsThePublishedVerdict: a blip reading the
// applier's cluster says nothing about the applier, so it must not flip a True to Unknown and back on
// every hiccup, nor drop the source the GitTargets bind against.
-func TestApplyTriggerReconcile_AnUnreachableClusterKeepsThePublishedVerdict(t *testing.T) {
- trigger := testFluxTrigger(triggerLocalProvider)
- c := triggerTestClient(t, trigger)
- r := triggerReconciler(c, configbutleraiv1alpha3.ApplyTriggerKindFlux, nil, errors.New("connection refused"), true)
- got := reconcileTrigger(t, r)
- configured := findCondition(*got.StatusConditions(), ApplyTriggerConditionConfigured)
+func TestApplierClaimReconcile_AnUnreachableClusterKeepsThePublishedVerdict(t *testing.T) {
+ claim := testFluxClaim(claimLocalProvider)
+ c := claimTestClient(t, claim)
+ r := claimReconciler(c, configbutleraiv1alpha3.ApplierClaimKindFlux, nil, errors.New("connection refused"), true)
+ got := reconcileClaim(t, r)
+ configured := findCondition(*got.StatusConditions(), ApplierClaimConditionConfigured)
require.NotNil(t, configured)
assert.Equal(t, metav1.ConditionUnknown, configured.Status, "nothing published yet")
- published := testFluxTrigger(triggerLocalProvider)
+ published := testFluxClaim(claimLocalProvider)
published.Status.Conditions = []metav1.Condition{{
- Type: ApplyTriggerConditionConfigured, Status: metav1.ConditionTrue, Reason: ReasonSucceeded,
+ Type: ApplierClaimConditionConfigured, Status: metav1.ConditionTrue, Reason: ReasonSucceeded,
LastTransitionTime: metav1.Now(),
}}
- published.Status.Source = &configbutleraiv1alpha3.FluxTriggerSource{URL: applierTestRepo, Branch: "main"}
- c = triggerTestClient(t, published)
- r = triggerReconciler(c, configbutleraiv1alpha3.ApplyTriggerKindFlux, nil, errors.New("connection refused"), true)
- got = reconcileTrigger(t, r)
- configured = findCondition(*got.StatusConditions(), ApplyTriggerConditionConfigured)
+ published.Status.Source = &configbutleraiv1alpha3.FluxClaimSource{URL: applierTestRepo, Branch: "main"}
+ c = claimTestClient(t, published)
+ r = claimReconciler(c, configbutleraiv1alpha3.ApplierClaimKindFlux, nil, errors.New("connection refused"), true)
+ got = reconcileClaim(t, r)
+ configured = findCondition(*got.StatusConditions(), ApplierClaimConditionConfigured)
require.NotNil(t, configured)
assert.Equal(t, metav1.ConditionTrue, configured.Status, "the published verdict stands")
url, branch, known := got.SourceBinding()
@@ -424,20 +424,20 @@ func TestApplyTriggerReconcile_AnUnreachableClusterKeepsThePublishedVerdict(t *t
assert.Equal(t, "main", branch)
}
-// TestApplyTriggerReconcile_AHangingClusterIsBounded: the applier's client has no request timeout,
+// TestApplierClaimReconcile_AHangingClusterIsBounded: the applier's client has no request timeout,
// so a cluster that accepts the connection and never answers would hold the reconcile for as long as
// it liked. The check gives up, and a check that could not be made keeps the published verdict.
-func TestApplyTriggerReconcile_AHangingClusterIsBounded(t *testing.T) {
+func TestApplierClaimReconcile_AHangingClusterIsBounded(t *testing.T) {
var sawDeadline bool
source := applierFakeSource(t, applierFluxObjects()...)
- published := testFluxTrigger(triggerLocalProvider)
+ published := testFluxClaim(claimLocalProvider)
published.Status.Conditions = []metav1.Condition{{
- Type: ApplyTriggerConditionConfigured, Status: metav1.ConditionTrue, Reason: ReasonSucceeded,
+ Type: ApplierClaimConditionConfigured, Status: metav1.ConditionTrue, Reason: ReasonSucceeded,
LastTransitionTime: metav1.Now(),
}}
- r := &ApplyTriggerReconciler{
- Client: triggerTestClient(t, published),
- Kind: configbutleraiv1alpha3.ApplyTriggerKindFlux,
+ r := &ApplierClaimReconciler{
+ Client: claimTestClient(t, published),
+ Kind: configbutleraiv1alpha3.ApplierClaimKindFlux,
ApplierClients: func(ctx context.Context, _ string) (dynamic.Interface, error) {
_, sawDeadline = ctx.Deadline()
return source, nil
@@ -454,18 +454,18 @@ func TestApplyTriggerReconcile_AHangingClusterIsBounded(t *testing.T) {
},
}
- got := reconcileTrigger(t, r)
+ got := reconcileClaim(t, r)
assert.True(t, sawDeadline, "the client lookup must run under a deadline")
- configured := findCondition(*got.StatusConditions(), ApplyTriggerConditionConfigured)
+ configured := findCondition(*got.StatusConditions(), ApplierClaimConditionConfigured)
require.NotNil(t, configured)
assert.Equal(t, metav1.ConditionTrue, configured.Status, "a timed-out check keeps the published verdict")
}
-// TestApplyTriggerReconcile_TheTriggersKubeProviderAdmitsIt: the applier is reached with the
-// trigger's KubeProvider's credential, so that provider's accessFrom gates the check before the
+// TestApplierClaimReconcile_TheClaimsKubeProviderAdmitsIt: the applier is reached with the
+// applier claim's KubeProvider's credential, so that provider's accessFrom gates the check before the
// cluster is ever asked.
-func TestApplyTriggerReconcile_TheTriggersKubeProviderAdmitsIt(t *testing.T) {
+func TestApplierClaimReconcile_TheClaimsKubeProviderAdmitsIt(t *testing.T) {
cases := []struct {
name string
cluster string
@@ -481,7 +481,7 @@ func TestApplyTriggerReconcile_TheTriggersKubeProviderAdmitsIt(t *testing.T) {
hub: admitting("hub", "team-a"),
wantStatus: metav1.ConditionTrue,
wantReason: ReasonSucceeded,
- wantClients: []string{triggerProviderKey("hub")},
+ wantClients: []string{claimProviderKey("hub")},
},
{name: "a provider that does not admit it is never asked", cluster: "hub", hub: admitting("hub", "team-b"),
wantStatus: metav1.ConditionFalse, wantReason: authz.ReasonNamespaceNotAuthorized, wantStalled: true},
@@ -490,12 +490,12 @@ func TestApplyTriggerReconcile_TheTriggersKubeProviderAdmitsIt(t *testing.T) {
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
- objects := []ctrlclient.Object{testFluxTrigger(tc.cluster)}
+ objects := []ctrlclient.Object{testFluxClaim(tc.cluster)}
if tc.hub != nil {
objects = append(objects, tc.hub)
}
var asked []string
- r := triggerReconciler(triggerTestClient(t, objects...), configbutleraiv1alpha3.ApplyTriggerKindFlux,
+ r := claimReconciler(claimTestClient(t, objects...), configbutleraiv1alpha3.ApplierClaimKindFlux,
applierFakeSource(t, applierFluxObjects()...), nil, true)
source := r.ApplierClients
r.ApplierClients = func(ctx context.Context, cluster string) (dynamic.Interface, error) {
@@ -503,32 +503,32 @@ func TestApplyTriggerReconcile_TheTriggersKubeProviderAdmitsIt(t *testing.T) {
return source(ctx, cluster)
}
- got := reconcileTrigger(t, r)
+ got := reconcileClaim(t, r)
- configured := findCondition(*got.StatusConditions(), ApplyTriggerConditionConfigured)
+ configured := findCondition(*got.StatusConditions(), ApplierClaimConditionConfigured)
require.NotNil(t, configured)
assert.Equal(t, tc.wantStatus, configured.Status, configured.Message)
assert.Equal(t, tc.wantReason, configured.Reason)
assert.Equal(t, tc.wantStalled, conditionIsTrue(*got.StatusConditions(), ConditionTypeStalled))
- assert.Equal(t, tc.wantClients, slices.Compact(asked), "only the trigger's own cluster is asked")
+ assert.Equal(t, tc.wantClients, slices.Compact(asked), "only the applier claim's own cluster is asked")
})
}
}
-// TestApplyTriggerReconcile_AnUnresolvedOperationIsNotReady is the third review's fourth finding,
-// seen from the trigger: an operation past its budget keeps its window open, and the trigger says so
+// TestApplierClaimReconcile_AnUnresolvedOperationIsNotReady is the third review's fourth finding,
+// seen from the applier claim: an operation past its budget keeps its window open, and the applier claim says so
// in Ready until the operation completes. It clears on its own, so it is never Stalled.
-func TestApplyTriggerReconcile_AnUnresolvedOperationIsNotReady(t *testing.T) {
- trigger := testFluxTrigger(triggerLocalProvider)
- trigger.Status.Operation = &configbutleraiv1alpha3.ApplyOperation{
+func TestApplierClaimReconcile_AnUnresolvedOperationIsNotReady(t *testing.T) {
+ claim := testFluxClaim(claimLocalProvider)
+ claim.Status.Operation = &configbutleraiv1alpha3.ApplyOperation{
Phase: configbutleraiv1alpha3.ApplyOperationUnresolved, Reason: configbutleraiv1alpha3.ApplyReasonCatchUp,
Commit: "4f2c1ab9d00d", Token: "T1", RequestedAt: metav1.Now(), Message: "still applying",
}
- c := triggerTestClient(t, trigger)
- r := triggerReconciler(c, configbutleraiv1alpha3.ApplyTriggerKindFlux,
+ c := claimTestClient(t, claim)
+ r := claimReconciler(c, configbutleraiv1alpha3.ApplierClaimKindFlux,
applierFakeSource(t, applierFluxObjects()...), nil, true)
- got := reconcileTrigger(t, r)
+ got := reconcileClaim(t, r)
ready := findCondition(*got.StatusConditions(), ConditionTypeReady)
require.NotNil(t, ready)
@@ -540,17 +540,17 @@ func TestApplyTriggerReconcile_AnUnresolvedOperationIsNotReady(t *testing.T) {
assert.Equal(t, configbutleraiv1alpha3.ApplyOperationUnresolved, got.Operation().Phase)
}
-// TestApplyTriggerSpecOrOperationChanged_ReadsTheOperationPhase: the worker's status writes reach the
+// TestApplierClaimSpecOrOperationChanged_ReadsTheOperationPhase: the worker's status writes reach the
// reconciler only when they move the phase Ready reads.
-func TestApplyTriggerSpecOrOperationChanged_ReadsTheOperationPhase(t *testing.T) {
- p := applyTriggerSpecOrOperationChanged()
+func TestApplierClaimSpecOrOperationChanged_ReadsTheOperationPhase(t *testing.T) {
+ p := applierClaimSpecOrOperationChanged()
at := func(phase configbutleraiv1alpha3.ApplyOperationPhase, generation int64) ctrlclient.Object {
- trigger := testFluxTrigger(triggerLocalProvider)
- trigger.Generation = generation
+ claim := testFluxClaim(claimLocalProvider)
+ claim.Generation = generation
if phase != "" {
- trigger.Status.Operation = &configbutleraiv1alpha3.ApplyOperation{Phase: phase}
+ claim.Status.Operation = &configbutleraiv1alpha3.ApplyOperation{Phase: phase}
}
- return trigger
+ return claim
}
assert.True(t, p.Update(event.UpdateEvent{
ObjectOld: at(configbutleraiv1alpha3.ApplyOperationRunning, 1),
@@ -563,37 +563,37 @@ func TestApplyTriggerSpecOrOperationChanged_ReadsTheOperationPhase(t *testing.T)
}), "a status write that moves nothing Ready reads")
}
-// TestApplyTriggerReconcile_KubeProviderAndNamespaceEventsFindTheirTriggers: a provider event
-// re-runs every trigger, since it admits the GitTargets naming triggers too, and a namespace label
-// change the triggers in it.
-func TestApplyTriggerReconcile_KubeProviderAndNamespaceEventsFindTheirTriggers(t *testing.T) {
- other := testFluxTrigger("other")
+// TestApplierClaimReconcile_KubeProviderAndNamespaceEventsFindTheirClaims: a provider event
+// re-runs every applier claim, since it admits the GitTargets naming applier claims too, and a namespace label
+// change the applier claims in it.
+func TestApplierClaimReconcile_KubeProviderAndNamespaceEventsFindTheirClaims(t *testing.T) {
+ other := testFluxClaim("other")
other.Name = "elsewhere"
- c := triggerTestClient(t, testFluxTrigger("hub"), other)
- r := &ApplyTriggerReconciler{Client: c, Kind: configbutleraiv1alpha3.ApplyTriggerKindFlux}
+ c := claimTestClient(t, testFluxClaim("hub"), other)
+ r := &ApplierClaimReconciler{Client: c, Kind: configbutleraiv1alpha3.ApplierClaimKindFlux}
- assert.Len(t, r.kubeProviderToTriggers(context.Background(), admitting("hub")), 2)
+ assert.Len(t, r.kubeProviderToClaims(context.Background(), admitting("hub")), 2)
ns := &metav1.PartialObjectMetadata{ObjectMeta: metav1.ObjectMeta{Name: "team-a"}}
- assert.Len(t, r.namespaceToTriggers(context.Background(), ns), 2)
+ assert.Len(t, r.namespaceToClaims(context.Background(), ns), 2)
}
-// TestApplyTriggerReconcile_ArgoCDEventsFindTheirTriggers: the Argo CD kind lists its own.
-func TestApplyTriggerReconcile_ArgoCDEventsFindTheirTriggers(t *testing.T) {
- c := triggerTestClient(t, testArgoCDTrigger("hub"), testFluxTrigger("hub"))
- r := &ApplyTriggerReconciler{Client: c, Kind: configbutleraiv1alpha3.ApplyTriggerKindArgoCD}
- requests := r.kubeProviderToTriggers(context.Background(), admitting("hub"))
- assert.Len(t, requests, 1, "a FluxTrigger of the same name is another kind's")
+// TestApplierClaimReconcile_ArgoCDEventsFindTheirClaims: the Argo CD kind lists its own.
+func TestApplierClaimReconcile_ArgoCDEventsFindTheirClaims(t *testing.T) {
+ c := claimTestClient(t, testArgoCDClaim("hub"), testFluxClaim("hub"))
+ r := &ApplierClaimReconciler{Client: c, Kind: configbutleraiv1alpha3.ApplierClaimKindArgoCD}
+ requests := r.kubeProviderToClaims(context.Background(), admitting("hub"))
+ assert.Len(t, requests, 1, "a FluxClaim of the same name is another kind's")
}
-// TestApplyTriggerReconcile_AGoneTriggerIsForgotten: a trigger that is gone has nothing to publish.
-func TestApplyTriggerReconcile_AGoneTriggerIsForgotten(t *testing.T) {
- r := &ApplyTriggerReconciler{Client: triggerTestClient(t), Kind: configbutleraiv1alpha3.ApplyTriggerKindFlux}
+// TestApplierClaimReconcile_AGoneClaimIsForgotten: an applier claim that is gone has nothing to publish.
+func TestApplierClaimReconcile_AGoneClaimIsForgotten(t *testing.T) {
+ r := &ApplierClaimReconciler{Client: claimTestClient(t), Kind: configbutleraiv1alpha3.ApplierClaimKindFlux}
result, err := r.Reconcile(context.Background(),
ctrl.Request{NamespacedName: k8stypes.NamespacedName{Namespace: "team-a", Name: "gone"}})
require.NoError(t, err)
assert.Zero(t, result.RequeueAfter)
- _, err = (&ApplyTriggerReconciler{Kind: "WebhookTrigger"}).Reconcile(context.Background(), ctrl.Request{})
+ _, err = (&ApplierClaimReconciler{Kind: "WebhookClaim"}).Reconcile(context.Background(), ctrl.Request{})
require.Error(t, err, "a kind this release does not serve")
}
diff --git a/internal/controller/applytrigger_holder_test.go b/internal/controller/applierclaim_holder_test.go
similarity index 56%
rename from internal/controller/applytrigger_holder_test.go
rename to internal/controller/applierclaim_holder_test.go
index 9d295e951..a6b9f0206 100644
--- a/internal/controller/applytrigger_holder_test.go
+++ b/internal/controller/applierclaim_holder_test.go
@@ -23,92 +23,92 @@ import (
"github.com/ConfigButler/gitops-reverser/internal/applier/fluxsim"
)
-// The second FluxTrigger on flux-system/checkout, in another namespace and created after
+// The second FluxClaim on flux-system/checkout, in another namespace and created after
// team-a/checkout.
var inLineKey = k8stypes.NamespacedName{Namespace: "team-b", Name: "checkout"}
-// duplicateFixture is the hold fixture with two FluxTriggers naming flux-system/checkout in the
-// cluster of triggerLocalProvider: team-a/checkout, created first, and team-b/checkout, an hour
+// duplicateFixture is the hold fixture with two FluxClaims naming flux-system/checkout in the
+// cluster of claimLocalProvider: team-a/checkout, created first, and team-b/checkout, an hour
// later. That KubeProvider admits both namespaces.
func duplicateFixture(t *testing.T) *holdFixture {
t.Helper()
- first := testFluxTrigger(triggerLocalProvider)
+ first := testFluxClaim(claimLocalProvider)
first.CreationTimestamp = metav1.NewTime(time.Unix(1_000_000, 0))
- second := testFluxTrigger(triggerLocalProvider)
+ second := testFluxClaim(claimLocalProvider)
second.Namespace = inLineKey.Namespace
second.CreationTimestamp = metav1.NewTime(first.CreationTimestamp.Add(time.Hour))
sim := applierSim(t, applierFluxObjects()...)
- c := inLineClient(t, configbutleraiv1alpha3.ApplyTriggerKindFlux, first, second)
- r := triggerReconciler(c, configbutleraiv1alpha3.ApplyTriggerKindFlux, sim.Client, nil, true)
+ c := inLineClient(t, configbutleraiv1alpha3.ApplierClaimKindFlux, first, second)
+ r := claimReconciler(c, configbutleraiv1alpha3.ApplierClaimKindFlux, sim.Client, nil, true)
recorder := record.NewFakeRecorder(64)
r.Recorder = recorder
return &holdFixture{r: r, sim: sim, recorder: recorder}
}
-// inLineClient is a control cluster holding triggers, in team-a and team-b, each referenced by an
-// eligible GitTarget of its namespace, with the KubeProvider triggerLocalProvider admitting both.
-func inLineClient(t *testing.T, kind string, triggers ...ctrlclient.Object) ctrlclient.Client {
+// inLineClient is a control cluster holding applier claims, in team-a and team-b, each referenced by an
+// eligible GitTarget of its namespace, with the KubeProvider claimLocalProvider admitting both.
+func inLineClient(t *testing.T, kind string, claims ...ctrlclient.Object) ctrlclient.Client {
t.Helper()
- objects := append([]ctrlclient.Object{admitting(triggerLocalProvider, "team-a", "team-b")}, triggers...)
+ objects := append([]ctrlclient.Object{admitting(claimLocalProvider, "team-a", "team-b")}, claims...)
objects = append(objects, referencedIn("team-a", kind)...)
objects = append(objects, referencedIn("team-b", kind)...)
- return triggerTestClient(t, objects...)
+ return claimTestClient(t, objects...)
}
-// reconcileKey reconciles one trigger a few times, enough to settle a suspend, and returns it.
-func (f *holdFixture) reconcileKey(t *testing.T, key k8stypes.NamespacedName) *configbutleraiv1alpha3.FluxTrigger {
+// reconcileKey reconciles one applier claim a few times, enough to settle a suspend, and returns it.
+func (f *holdFixture) reconcileKey(t *testing.T, key k8stypes.NamespacedName) *configbutleraiv1alpha3.FluxClaim {
t.Helper()
- trigger := &configbutleraiv1alpha3.FluxTrigger{}
+ claim := &configbutleraiv1alpha3.FluxClaim{}
require.Eventually(t, func() bool {
_, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: key})
require.NoError(t, err)
- require.NoError(t, f.r.Get(context.Background(), key, trigger))
- held := findCondition(trigger.Status.Conditions, ApplyTriggerConditionHeld)
+ require.NoError(t, f.r.Get(context.Background(), key, claim))
+ held := findCondition(claim.Status.Conditions, ApplierClaimConditionHeld)
return held == nil || held.Reason != ReasonSuspending
}, 5*time.Second, 5*time.Millisecond)
- return trigger
+ return claim
}
-// deleteUntilGone deletes a trigger and reconciles it until its finalizer is gone.
+// deleteUntilGone deletes an applier claim and reconciles it until its finalizer is gone.
func (f *holdFixture) deleteUntilGone(t *testing.T, key k8stypes.NamespacedName) {
t.Helper()
- trigger := &configbutleraiv1alpha3.FluxTrigger{}
- require.NoError(t, f.r.Get(context.Background(), key, trigger))
- require.NoError(t, f.r.Delete(context.Background(), trigger))
+ claim := &configbutleraiv1alpha3.FluxClaim{}
+ require.NoError(t, f.r.Get(context.Background(), key, claim))
+ require.NoError(t, f.r.Delete(context.Background(), claim))
require.Eventually(t, func() bool {
_, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: key})
require.NoError(t, err)
- return apierrors.IsNotFound(f.r.Get(context.Background(), key, &configbutleraiv1alpha3.FluxTrigger{}))
+ return apierrors.IsNotFound(f.r.Get(context.Background(), key, &configbutleraiv1alpha3.FluxClaim{}))
}, 5*time.Second, 5*time.Millisecond, "the finalizer goes")
}
-// TestFluxTrigger_OneTriggerHoldsAKustomization is review 5's finding 5: two FluxTriggers naming one
+// TestFluxClaim_OneClaimHoldsAKustomization is review 5's finding 5: two FluxClaims naming one
// Kustomization both held it, under the same field manager. Each took the other's resume for an
// external one and suspended it mid-apply, and deleting either handed back the hold the other still
-// needed. Now the one created first holds it; the other is Configured=False (HeldByAnotherTrigger),
+// needed. Now the one created first holds it; the other is Configured=False (HeldByAnotherClaim),
// naming the holder, and does nothing to the Kustomization, ever.
-func TestFluxTrigger_OneTriggerHoldsAKustomization(t *testing.T) {
+func TestFluxClaim_OneClaimHoldsAKustomization(t *testing.T) {
f := duplicateFixture(t)
second := f.reconcileKey(t, inLineKey)
- first := f.reconcileKey(t, triggerKey)
+ first := f.reconcileKey(t, claimKey)
- assert.True(t, conditionIsTrue(first.Status.Conditions, ApplyTriggerConditionHeld), "the first holds it")
+ assert.True(t, conditionIsTrue(first.Status.Conditions, ApplierClaimConditionHeld), "the first holds it")
assert.True(t, conditionIsTrue(first.Status.Conditions, ConditionTypeReady))
- configured := findCondition(second.Status.Conditions, ApplyTriggerConditionConfigured)
+ configured := findCondition(second.Status.Conditions, ApplierClaimConditionConfigured)
require.NotNil(t, configured)
assert.Equal(t, metav1.ConditionFalse, configured.Status)
- assert.Equal(t, applier.ReasonHeldByAnotherTrigger, configured.Reason)
+ assert.Equal(t, applier.ReasonHeldByAnotherClaim, configured.Reason)
assert.Equal(t, "Kustomization flux-system/checkout in the cluster of KubeProvider clusters/local is held by "+
- "FluxTrigger team-a/checkout, which was created first; this trigger takes it over once that one is deleted "+
+ "FluxClaim team-a/checkout, which was created first; this applier claim takes it over once that one is deleted "+
"or no eligible GitTarget references it", configured.Message)
ready := findCondition(second.Status.Conditions, ConditionTypeReady)
require.NotNil(t, ready)
assert.Equal(t, metav1.ConditionFalse, ready.Status)
- assert.Equal(t, applier.ReasonHeldByAnotherTrigger, ready.Reason)
+ assert.Equal(t, applier.ReasonHeldByAnotherClaim, ready.Reason)
assert.True(t, conditionIsTrue(second.Status.Conditions, ConditionTypeStalled), "a human removes one of them")
- held := findCondition(second.Status.Conditions, ApplyTriggerConditionHeld)
+ held := findCondition(second.Status.Conditions, ApplierClaimConditionHeld)
require.NotNil(t, held)
assert.Equal(t, metav1.ConditionUnknown, held.Status)
assert.Equal(t, ReasonNotConfigured, held.Reason)
@@ -117,30 +117,30 @@ func TestFluxTrigger_OneTriggerHoldsAKustomization(t *testing.T) {
require.Len(t, events, 1, "one adoption, by the holder: %v", events)
assert.True(t, strings.HasPrefix(events[0], "Normal Adopted"), events[0])
- // A GitTarget naming the trigger in line says why it is not Ready.
- target := triggerTarget(true)
+ // A GitTarget naming the applier claim in line says why it is not Ready.
+ target := claimTarget(true)
target.Namespace = inLineKey.Namespace
gt := &GitTargetReconciler{Client: f.r.Client}
- verdict, _ := gt.observeApplyTrigger(context.Background(), beginStatus(nil, nil, target), target,
- triggerDestination())
+ verdict, _ := gt.observeApplierClaim(context.Background(), beginStatus(nil, nil, target), target,
+ claimDestination())
assert.Equal(t, metav1.ConditionFalse, verdict.value.Status)
- assert.Equal(t, applier.ReasonHeldByAnotherTrigger, verdict.value.Reason)
- assert.Contains(t, verdict.value.Message, "held by FluxTrigger team-a/checkout")
+ assert.Equal(t, applier.ReasonHeldByAnotherClaim, verdict.value.Reason)
+ assert.Contains(t, verdict.value.Message, "held by FluxClaim team-a/checkout")
assert.True(t, verdict.stalled)
}
-// TestFluxTrigger_TheHoldersResumeIsNoExternalResumeToTheOther: a branch worker's apply through the
-// holder resumes the Kustomization; the trigger in line does not take that for `flux resume`, and
+// TestFluxClaim_TheHoldersResumeIsNoExternalResumeToTheOther: a branch worker's apply through the
+// holder resumes the Kustomization; the applier claim in line does not take that for `flux resume`, and
// leaves it running.
-func TestFluxTrigger_TheHoldersResumeIsNoExternalResumeToTheOther(t *testing.T) {
+func TestFluxClaim_TheHoldersResumeIsNoExternalResumeToTheOther(t *testing.T) {
f := duplicateFixture(t)
- f.reconcileKey(t, triggerKey)
+ f.reconcileKey(t, claimKey)
f.reconcileKey(t, inLineKey)
f.events()
// The worker records its operation on the holder, and resumes the Kustomization.
- holder := &configbutleraiv1alpha3.FluxTrigger{}
- require.NoError(t, f.r.Get(context.Background(), triggerKey, holder))
+ holder := &configbutleraiv1alpha3.FluxClaim{}
+ require.NoError(t, f.r.Get(context.Background(), claimKey, holder))
holder.Status.Operation = &configbutleraiv1alpha3.ApplyOperation{
Phase: configbutleraiv1alpha3.ApplyOperationRunning, Reason: configbutleraiv1alpha3.ApplyReasonRefusal,
Token: "T1", RequestedAt: metav1.Now(),
@@ -155,15 +155,15 @@ func TestFluxTrigger_TheHoldersResumeIsNoExternalResumeToTheOther(t *testing.T)
assert.False(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)), "left running for the holder")
assert.Empty(t, f.events(), "no external resume")
- assert.Nil(t, second.Status.Operation, "nothing recorded on the trigger in line")
+ assert.Nil(t, second.Status.Operation, "nothing recorded on the applier claim in line")
}
-// TestFluxTrigger_DeletingTheTriggerInLineHandsNothingBack: the trigger in line never held the
+// TestFluxClaim_DeletingTheClaimInLineHandsNothingBack: the applier claim in line never held the
// Kustomization, so its finalizer goes without touching it: it stays suspended under GitOps
// Reverser's field manager, for the holder.
-func TestFluxTrigger_DeletingTheTriggerInLineHandsNothingBack(t *testing.T) {
+func TestFluxClaim_DeletingTheClaimInLineHandsNothingBack(t *testing.T) {
f := duplicateFixture(t)
- f.reconcileKey(t, triggerKey)
+ f.reconcileKey(t, claimKey)
f.reconcileKey(t, inLineKey)
f.events()
@@ -173,59 +173,59 @@ func TestFluxTrigger_DeletingTheTriggerInLineHandsNothingBack(t *testing.T) {
assert.True(t, suspendedSpec(ks), "still held")
assert.Contains(t, kustomizationManagers(ks), applier.SuspendFieldManager)
assert.Empty(t, f.events(), "nothing released")
- holder := f.reconcileKey(t, triggerKey)
- assert.True(t, conditionIsTrue(holder.Status.Conditions, ApplyTriggerConditionHeld))
+ holder := f.reconcileKey(t, claimKey)
+ assert.True(t, conditionIsTrue(holder.Status.Conditions, ApplierClaimConditionHeld))
}
-// TestFluxTrigger_DeletingTheHolderHandsOverToTheNextInLine: the holder suspends the Kustomization as
-// it goes, and leaves it suspended for the trigger in line instead of releasing it: released, Flux
-// would apply it at once, unchecked against the branch, before the next trigger could suspend it
-// again (review 6). Its going enqueues the trigger in line (a deletion maps to the others naming the
+// TestFluxClaim_DeletingTheHolderHandsOverToTheNextInLine: the holder suspends the Kustomization as
+// it goes, and leaves it suspended for the applier claim in line instead of releasing it: released, Flux
+// would apply it at once, unchecked against the branch, before the next applier claim could suspend it
+// again (review 6). Its going enqueues the applier claim in line (a deletion maps to the others naming the
// applier), which then holds the Kustomization as it was left, with nothing to adopt.
-func TestFluxTrigger_DeletingTheHolderHandsOverToTheNextInLine(t *testing.T) {
+func TestFluxClaim_DeletingTheHolderHandsOverToTheNextInLine(t *testing.T) {
f := duplicateFixture(t)
- f.reconcileKey(t, triggerKey)
+ f.reconcileKey(t, claimKey)
f.reconcileKey(t, inLineKey)
f.events()
- gone := &configbutleraiv1alpha3.FluxTrigger{}
- require.NoError(t, f.r.Get(context.Background(), triggerKey, gone))
+ gone := &configbutleraiv1alpha3.FluxClaim{}
+ require.NoError(t, f.r.Get(context.Background(), claimKey, gone))
applied := len(f.sim.Applied())
- f.deleteUntilGone(t, triggerKey)
+ f.deleteUntilGone(t, claimKey)
ks := f.sim.Kustomization(checkoutKustomization)
assert.True(t, suspendedSpec(ks), "never released between the two")
assert.Contains(t, kustomizationManagers(ks), applier.SuspendFieldManager)
assert.Len(t, f.sim.Applied(), applied, "so Flux applied nothing on its own")
- requests := f.r.triggersInLine(context.Background(), gone)
+ requests := f.r.claimsInLine(context.Background(), gone)
require.Len(t, requests, 1)
assert.Equal(t, inLineKey, requests[0].NamespacedName)
- assert.True(t, triggerLineChanged().Delete(event.DeleteEvent{Object: gone}))
- assert.False(t, triggerLineChanged().Update(event.UpdateEvent{ObjectOld: gone, ObjectNew: gone}))
+ assert.True(t, claimLineChanged().Delete(event.DeleteEvent{Object: gone}))
+ assert.False(t, claimLineChanged().Update(event.UpdateEvent{ObjectOld: gone, ObjectNew: gone}))
second := f.reconcileKey(t, inLineKey)
- assert.True(t, conditionIsTrue(second.Status.Conditions, ApplyTriggerConditionConfigured))
- assert.True(t, conditionIsTrue(second.Status.Conditions, ApplyTriggerConditionHeld))
+ assert.True(t, conditionIsTrue(second.Status.Conditions, ApplierClaimConditionConfigured))
+ assert.True(t, conditionIsTrue(second.Status.Conditions, ApplierClaimConditionHeld))
assert.True(t, conditionIsTrue(second.Status.Conditions, ConditionTypeReady))
assert.True(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)))
- assert.Equal(t, []string{"Normal Released left Kustomization flux-system/checkout suspended for FluxTrigger " +
+ assert.Equal(t, []string{"Normal Released left Kustomization flux-system/checkout suspended for FluxClaim " +
"team-b/checkout, which holds it next"}, f.events())
}
-// TestFluxTrigger_AHolderWithAnApplyInFlightKeepsIt is review 5's second finding (P2): a trigger
+// TestFluxClaim_AHolderWithAnApplyInFlightKeepsIt is review 5's second finding (P2): an applier claim
// created in the same second as the holder, which comes before it by name, appears while the
// holder's apply is in flight. It waits: it is Configured=False, leaves the resumed Kustomization to
-// the holder's worker, and records nothing. Once that apply ends, the holder still keeps it: a
-// trigger that holds its Kustomization comes before any that does not, so the Kustomization changes
+// the holder's worker, and records nothing. Once that apply ends, the holder still keeps it: an
+// applier claim that holds its Kustomization comes before any that does not, so the Kustomization changes
// hands only once the holder lets go of it.
-func TestFluxTrigger_AHolderWithAnApplyInFlightKeepsIt(t *testing.T) {
- holderTrigger := testFluxTrigger(triggerLocalProvider)
- holderTrigger.Namespace = inLineKey.Namespace // team-b: after team-a by name
- holderTrigger.CreationTimestamp = metav1.NewTime(time.Unix(1_000_000, 0))
- holderKey := k8stypes.NamespacedName{Namespace: holderTrigger.Namespace, Name: holderTrigger.Name}
+func TestFluxClaim_AHolderWithAnApplyInFlightKeepsIt(t *testing.T) {
+ holderClaim := testFluxClaim(claimLocalProvider)
+ holderClaim.Namespace = inLineKey.Namespace // team-b: after team-a by name
+ holderClaim.CreationTimestamp = metav1.NewTime(time.Unix(1_000_000, 0))
+ holderKey := k8stypes.NamespacedName{Namespace: holderClaim.Namespace, Name: holderClaim.Name}
sim := applierSim(t, applierFluxObjects()...)
- c := inLineClient(t, configbutleraiv1alpha3.ApplyTriggerKindFlux, holderTrigger)
- r := triggerReconciler(c, configbutleraiv1alpha3.ApplyTriggerKindFlux, sim.Client, nil, true)
+ c := inLineClient(t, configbutleraiv1alpha3.ApplierClaimKindFlux, holderClaim)
+ r := claimReconciler(c, configbutleraiv1alpha3.ApplierClaimKindFlux, sim.Client, nil, true)
recorder := record.NewFakeRecorder(64)
r.Recorder = recorder
f := &holdFixture{r: r, sim: sim, recorder: recorder}
@@ -233,7 +233,7 @@ func TestFluxTrigger_AHolderWithAnApplyInFlightKeepsIt(t *testing.T) {
f.events()
// The holder's worker records its operation, and resumes the Kustomization.
- held := &configbutleraiv1alpha3.FluxTrigger{}
+ held := &configbutleraiv1alpha3.FluxClaim{}
require.NoError(t, f.r.Get(context.Background(), holderKey, held))
held.Status.Operation = &configbutleraiv1alpha3.ApplyOperation{
Phase: configbutleraiv1alpha3.ApplyOperationRunning, Reason: configbutleraiv1alpha3.ApplyReasonRefusal,
@@ -245,20 +245,24 @@ func TestFluxTrigger_AHolderWithAnApplyInFlightKeepsIt(t *testing.T) {
metav1.PatchOptions{FieldManager: applier.SuspendFieldManager})
require.NoError(t, err)
// The newcomer: same second, team-a.
- newcomer := testFluxTrigger(triggerLocalProvider)
- newcomer.CreationTimestamp = holderTrigger.CreationTimestamp
+ newcomer := testFluxClaim(claimLocalProvider)
+ newcomer.CreationTimestamp = holderClaim.CreationTimestamp
require.NoError(t, f.r.Create(context.Background(), newcomer))
- waiting := f.reconcileKey(t, triggerKey)
+ waiting := f.reconcileKey(t, claimKey)
- configured := findCondition(waiting.Status.Conditions, ApplyTriggerConditionConfigured)
+ configured := findCondition(waiting.Status.Conditions, ApplierClaimConditionConfigured)
require.NotNil(t, configured)
- assert.Equal(t, applier.ReasonHeldByAnotherTrigger, configured.Reason)
- assert.Contains(t, configured.Message, "whose apply is in flight; this trigger takes it over once that apply ends")
+ assert.Equal(t, applier.ReasonHeldByAnotherClaim, configured.Reason)
+ assert.Contains(
+ t,
+ configured.Message,
+ "whose apply is in flight; this applier claim takes it over once that apply ends",
+ )
assert.False(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)), "left running for the holder")
assert.Nil(t, waiting.Status.Operation, "no external resume recorded")
assert.Empty(t, f.events())
- assert.True(t, conditionIsTrue(f.reconcileKey(t, holderKey).Status.Conditions, ApplyTriggerConditionConfigured),
+ assert.True(t, conditionIsTrue(f.reconcileKey(t, holderKey).Status.Conditions, ApplierClaimConditionConfigured),
"the holder keeps it")
// The worker suspends the Kustomization again and completes its operation.
@@ -272,40 +276,40 @@ func TestFluxTrigger_AHolderWithAnApplyInFlightKeepsIt(t *testing.T) {
&finished
require.NoError(t, f.r.Status().Update(context.Background(), held))
- still := findCondition(f.reconcileKey(t, triggerKey).Status.Conditions, ApplyTriggerConditionConfigured)
+ still := findCondition(f.reconcileKey(t, claimKey).Status.Conditions, ApplierClaimConditionConfigured)
require.NotNil(t, still)
- assert.Equal(t, applier.ReasonHeldByAnotherTrigger, still.Reason)
+ assert.Equal(t, applier.ReasonHeldByAnotherClaim, still.Reason)
assert.Contains(t, still.Message, "which holds it now")
- assert.True(t, conditionIsTrue(f.reconcileKey(t, holderKey).Status.Conditions, ApplyTriggerConditionConfigured),
+ assert.True(t, conditionIsTrue(f.reconcileKey(t, holderKey).Status.Conditions, ApplierClaimConditionConfigured),
"the holder keeps it between applies too")
assert.Empty(t, f.events(), "and nothing is adopted twice")
}
-// TestArgoCDTrigger_OneTriggerHoldsAnApplication: two ArgoCDTriggers on one Application would both
-// start syncs; the one created later is Configured=False (HeldByAnotherTrigger).
-func TestArgoCDTrigger_OneTriggerHoldsAnApplication(t *testing.T) {
- first := testArgoCDTrigger(triggerLocalProvider)
+// TestArgoCDClaim_OneClaimHoldsAnApplication: two ArgoCDClaims on one Application would both
+// start syncs; the one created later is Configured=False (HeldByAnotherClaim).
+func TestArgoCDClaim_OneClaimHoldsAnApplication(t *testing.T) {
+ first := testArgoCDClaim(claimLocalProvider)
first.CreationTimestamp = metav1.NewTime(time.Unix(1_000_000, 0))
- second := testArgoCDTrigger(triggerLocalProvider)
+ second := testArgoCDClaim(claimLocalProvider)
second.Namespace = inLineKey.Namespace
second.CreationTimestamp = first.CreationTimestamp
- c := inLineClient(t, configbutleraiv1alpha3.ApplyTriggerKindArgoCD, first, second)
- r := triggerReconciler(c, configbutleraiv1alpha3.ApplyTriggerKindArgoCD,
+ c := inLineClient(t, configbutleraiv1alpha3.ApplierClaimKindArgoCD, first, second)
+ r := claimReconciler(c, configbutleraiv1alpha3.ApplierClaimKindArgoCD,
applierFakeSource(t, applierArgoApplication(nil)), nil, true)
- for _, key := range []k8stypes.NamespacedName{triggerKey, inLineKey} {
+ for _, key := range []k8stypes.NamespacedName{claimKey, inLineKey} {
_, err := r.Reconcile(context.Background(), ctrl.Request{NamespacedName: key})
require.NoError(t, err)
}
- got := &configbutleraiv1alpha3.ArgoCDTrigger{}
- require.NoError(t, c.Get(context.Background(), triggerKey, got))
- assert.True(t, conditionIsTrue(got.Status.Conditions, ApplyTriggerConditionConfigured),
+ got := &configbutleraiv1alpha3.ArgoCDClaim{}
+ require.NoError(t, c.Get(context.Background(), claimKey, got))
+ assert.True(t, conditionIsTrue(got.Status.Conditions, ApplierClaimConditionConfigured),
"created in the same second: the namespace decides")
require.NoError(t, c.Get(context.Background(), inLineKey, got))
- configured := findCondition(got.Status.Conditions, ApplyTriggerConditionConfigured)
+ configured := findCondition(got.Status.Conditions, ApplierClaimConditionConfigured)
require.NotNil(t, configured)
- assert.Equal(t, applier.ReasonHeldByAnotherTrigger, configured.Reason)
+ assert.Equal(t, applier.ReasonHeldByAnotherClaim, configured.Reason)
assert.Contains(t, configured.Message, "Application argocd/checkout")
- assert.Contains(t, configured.Message, "held by ArgoCDTrigger team-a/checkout")
+ assert.Contains(t, configured.Message, "held by ArgoCDClaim team-a/checkout")
}
diff --git a/internal/controller/applytrigger_rule_test.go b/internal/controller/applierclaim_rule_test.go
similarity index 67%
rename from internal/controller/applytrigger_rule_test.go
rename to internal/controller/applierclaim_rule_test.go
index 7c62c5d86..d143bb521 100644
--- a/internal/controller/applytrigger_rule_test.go
+++ b/internal/controller/applierclaim_rule_test.go
@@ -26,71 +26,71 @@ import (
"github.com/ConfigButler/gitops-reverser/internal/authz"
)
-// The rule (review 7): a trigger acts only while an eligible GitTarget references it. These tests
+// The rule (review 7): an applier claim acts only while an eligible GitTarget references it. These tests
// pin its three cases (never referenced or released, referenced, retiring), the binding verdict, and
-// the line of triggers for one applier.
+// the line of applier claims for one applier.
// ruleFixture is the hold fixture over a control cluster holding objects, the KubeProviders and
// the GitProvider platform, and no GitTarget unless objects carries one.
func ruleFixture(t *testing.T, objects ...ctrlclient.Object) *holdFixture {
t.Helper()
sim := applierSim(t, applierFluxObjects()...)
- c := triggerTestClient(t, append(objects, unreferenced{})...)
- r := triggerReconciler(c, configbutleraiv1alpha3.ApplyTriggerKindFlux, sim.Client, nil, true)
+ c := claimTestClient(t, append(objects, unreferenced{})...)
+ r := claimReconciler(c, configbutleraiv1alpha3.ApplierClaimKindFlux, sim.Client, nil, true)
recorder := record.NewFakeRecorder(64)
r.Recorder = recorder
return &holdFixture{r: r, sim: sim, recorder: recorder}
}
-// fluxTarget is the GitTarget team-a/checkout-flux naming the FluxTrigger checkout, writing main.
+// fluxTarget is the GitTarget team-a/checkout-flux naming the FluxClaim checkout, writing main.
func fluxTarget() *configbutleraiv1alpha3.GitTarget {
- return referencingTarget("checkout-flux", configbutleraiv1alpha3.ApplyTriggerKindFlux)
+ return referencingTarget("checkout-flux", configbutleraiv1alpha3.ApplierClaimKindFlux)
}
-// settle reconciles the trigger at key until its Held condition is neither Suspending nor
+// settle reconciles the applier claim at key until its Held condition is neither Suspending nor
// Releasing, and returns it.
-func (f *holdFixture) settle(t *testing.T, key k8stypes.NamespacedName) *configbutleraiv1alpha3.FluxTrigger {
+func (f *holdFixture) settle(t *testing.T, key k8stypes.NamespacedName) *configbutleraiv1alpha3.FluxClaim {
t.Helper()
- trigger := &configbutleraiv1alpha3.FluxTrigger{}
+ claim := &configbutleraiv1alpha3.FluxClaim{}
require.Eventually(t, func() bool {
_, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: key})
require.NoError(t, err)
- require.NoError(t, f.r.Get(context.Background(), key, trigger))
- held := findCondition(trigger.Status.Conditions, ApplyTriggerConditionHeld)
+ require.NoError(t, f.r.Get(context.Background(), key, claim))
+ held := findCondition(claim.Status.Conditions, ApplierClaimConditionHeld)
return held != nil && held.Reason != ReasonSuspending && held.Reason != ReasonReleasing
}, 5*time.Second, 5*time.Millisecond)
- return trigger
+ return claim
}
-// heldReason is the trigger's Held reason.
-func heldReason(t *testing.T, trigger *configbutleraiv1alpha3.FluxTrigger) string {
+// heldReason is the applier claim's Held reason.
+func heldReason(t *testing.T, claim *configbutleraiv1alpha3.FluxClaim) string {
t.Helper()
- held := findCondition(trigger.Status.Conditions, ApplyTriggerConditionHeld)
+ held := findCondition(claim.Status.Conditions, ApplierClaimConditionHeld)
require.NotNil(t, held)
return held.Reason
}
-// TestApplyTriggerRule_AnUnreferencedTriggerDoesNothingAndIsReady: no call reaches the applier's
-// cluster, nothing is held, no finalizer is added, and the trigger is Ready, so a Flux health check
+// TestApplierClaimRule_AnUnreferencedClaimDoesNothingAndIsReady: no call reaches the applier's
+// cluster, nothing is held, no finalizer is added, and the applier claim is Ready, so a Flux health check
// that waits on it does not wait for the GitTarget that will name it.
-func TestApplyTriggerRule_AnUnreferencedTriggerDoesNothingAndIsReady(t *testing.T) {
- for _, kind := range []string{configbutleraiv1alpha3.ApplyTriggerKindFlux, configbutleraiv1alpha3.ApplyTriggerKindArgoCD} {
+func TestApplierClaimRule_AnUnreferencedClaimDoesNothingAndIsReady(t *testing.T) {
+ for _, kind := range []string{configbutleraiv1alpha3.ApplierClaimKindFlux, configbutleraiv1alpha3.ApplierClaimKindArgoCD} {
t.Run(kind, func(t *testing.T) {
- var trigger ctrlclient.Object = testFluxTrigger(triggerLocalProvider)
- if kind == configbutleraiv1alpha3.ApplyTriggerKindArgoCD {
- trigger = testArgoCDTrigger(triggerLocalProvider)
+ var claim ctrlclient.Object = testFluxClaim(claimLocalProvider)
+ if kind == configbutleraiv1alpha3.ApplierClaimKindArgoCD {
+ claim = testArgoCDClaim(claimLocalProvider)
}
- r := triggerReconciler(triggerTestClient(t, trigger, unreferenced{}), kind, nil, nil, true)
+ r := claimReconciler(claimTestClient(t, claim, unreferenced{}), kind, nil, nil, true)
calls := 0
r.ApplierClients = func(context.Context, string) (dynamic.Interface, error) {
calls++
return nil, errors.New("the applier's cluster must not be asked")
}
- got := reconcileTrigger(t, r)
+ got := reconcileClaim(t, r)
assert.Zero(t, calls, "nothing is read in the applier's cluster")
- configured := findCondition(*got.StatusConditions(), ApplyTriggerConditionConfigured)
+ configured := findCondition(*got.StatusConditions(), ApplierClaimConditionConfigured)
require.NotNil(t, configured)
assert.Equal(t, metav1.ConditionUnknown, configured.Status)
assert.Equal(t, applier.ReasonNotReferenced, configured.Reason)
@@ -102,40 +102,40 @@ func TestApplyTriggerRule_AnUnreferencedTriggerDoesNothingAndIsReady(t *testing.
assert.Empty(t, got.GetFinalizers())
url, _, known := got.SourceBinding()
assert.False(t, known, "no source is published: %s", url)
- if flux, ok := got.(*configbutleraiv1alpha3.FluxTrigger); ok {
+ if flux, ok := got.(*configbutleraiv1alpha3.FluxClaim); ok {
assert.Equal(t, applier.ReasonNotReferenced, heldReason(t, flux))
}
})
}
}
-// TestApplyTriggerRule_TheFirstEligibleReferenceAdoptsAndTheLastReleases: naming the trigger is the
-// adoption moment; the GitTarget going hands the Kustomization back, and the trigger is idle again.
-func TestApplyTriggerRule_TheFirstEligibleReferenceAdoptsAndTheLastReleases(t *testing.T) {
- f := ruleFixture(t, testFluxTrigger(triggerLocalProvider))
- idle := f.settle(t, triggerKey)
+// TestApplierClaimRule_TheFirstEligibleReferenceAdoptsAndTheLastReleases: naming the applier claim is the
+// adoption moment; the GitTarget going hands the Kustomization back, and the applier claim is idle again.
+func TestApplierClaimRule_TheFirstEligibleReferenceAdoptsAndTheLastReleases(t *testing.T) {
+ f := ruleFixture(t, testFluxClaim(claimLocalProvider))
+ idle := f.settle(t, claimKey)
assert.Equal(t, applier.ReasonNotReferenced, heldReason(t, idle))
assert.False(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)), "Flux applies it on its own")
target := fluxTarget()
require.NoError(t, f.r.Create(context.Background(), target))
- held := f.settle(t, triggerKey)
+ held := f.settle(t, claimKey)
assert.Equal(t, ReasonSuspended, heldReason(t, held))
assert.True(t, conditionIsTrue(held.Status.Conditions, ConditionTypeReady))
- assert.True(t, controllerutil.ContainsFinalizer(held, configbutleraiv1alpha3.FluxTriggerFinalizer))
+ assert.True(t, controllerutil.ContainsFinalizer(held, configbutleraiv1alpha3.FluxClaimFinalizer))
assert.True(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)))
events := f.events()
require.Len(t, events, 1)
assert.True(t, strings.HasPrefix(events[0], "Normal Adopted"), events[0])
require.NoError(t, f.r.Delete(context.Background(), target))
- released := f.settle(t, triggerKey)
+ released := f.settle(t, claimKey)
assert.Equal(t, applier.ReasonNotReferenced, heldReason(t, released))
ready := findCondition(released.Status.Conditions, ConditionTypeReady)
require.NotNil(t, ready)
assert.Equal(t, metav1.ConditionTrue, ready.Status)
assert.Equal(t, applier.ReasonNotReferenced, ready.Reason)
- assert.Empty(t, released.Finalizers, "a trigger that holds nothing deletes at once")
+ assert.Empty(t, released.Finalizers, "an applier claim that holds nothing deletes at once")
assert.Nil(t, released.Status.Source)
assert.False(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)), "handed back to Flux")
events = f.events()
@@ -144,21 +144,21 @@ func TestApplyTriggerRule_TheFirstEligibleReferenceAdoptsAndTheLastReleases(t *t
events[0])
}
-// TestApplyTriggerRule_ARecreatedGitTargetReleasesAndReadopts is the trade-off the rule accepts:
+// TestApplierClaimRule_ARecreatedGitTargetReleasesAndReadopts is the trade-off the rule accepts:
// deleting and re-creating the GitTarget hands the Kustomization back, and adopts it again.
-func TestApplyTriggerRule_ARecreatedGitTargetReleasesAndReadopts(t *testing.T) {
- f := ruleFixture(t, testFluxTrigger(triggerLocalProvider), fluxTarget())
- f.settle(t, triggerKey)
+func TestApplierClaimRule_ARecreatedGitTargetReleasesAndReadopts(t *testing.T) {
+ f := ruleFixture(t, testFluxClaim(claimLocalProvider), fluxTarget())
+ f.settle(t, claimKey)
f.events()
target := &configbutleraiv1alpha3.GitTarget{}
require.NoError(t, f.r.Get(context.Background(),
k8stypes.NamespacedName{Namespace: "team-a", Name: "checkout-flux"}, target))
require.NoError(t, f.r.Delete(context.Background(), target))
- f.settle(t, triggerKey)
+ f.settle(t, claimKey)
assert.False(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)))
require.NoError(t, f.r.Create(context.Background(), fluxTarget()))
- again := f.settle(t, triggerKey)
+ again := f.settle(t, claimKey)
assert.Equal(t, ReasonSuspended, heldReason(t, again))
assert.True(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)))
@@ -169,17 +169,17 @@ func TestApplyTriggerRule_ARecreatedGitTargetReleasesAndReadopts(t *testing.T) {
assert.Equal(t, []string{EventReasonReleased, EventReasonAdopted}, reasons)
}
-// TestApplyTriggerRule_TheBindingAggregatesItsGitTargets: one eligible GitTarget among mismatched
-// ones binds the trigger; none that can be judged yet is progressing; every one refused is stalled,
+// TestApplierClaimRule_TheBindingAggregatesItsGitTargets: one eligible GitTarget among mismatched
+// ones binds the applier claim; none that can be judged yet is progressing; every one refused is stalled,
// with the reason they share, or NotBound for mixed ones, and each GitTarget in the message.
-func TestApplyTriggerRule_TheBindingAggregatesItsGitTargets(t *testing.T) {
+func TestApplierClaimRule_TheBindingAggregatesItsGitTargets(t *testing.T) {
onBranch := func(name, branch string) *configbutleraiv1alpha3.GitTarget {
- target := referencingTarget(name, configbutleraiv1alpha3.ApplyTriggerKindFlux)
+ target := referencingTarget(name, configbutleraiv1alpha3.ApplierClaimKindFlux)
target.Spec.GitBranchRef.Name = gitBranchNameForTest("platform", branch)
return target
}
elsewhere := onBranch("elsewhere", "main")
- elsewhere.Spec.KubeProviderRef = triggerProviderRef("closed")
+ elsewhere.Spec.KubeProviderRef = claimProviderRef("closed")
cases := []struct {
name string
objects []ctrlclient.Object
@@ -209,14 +209,14 @@ func TestApplyTriggerRule_TheBindingAggregatesItsGitTargets(t *testing.T) {
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
- objects := append([]ctrlclient.Object{testFluxTrigger(triggerLocalProvider),
+ objects := append([]ctrlclient.Object{testFluxClaim(claimLocalProvider),
gitBranchOn("platform", "team-a", "release"), gitBranchOn("platform", "team-a", "staging")},
tc.objects...)
f := ruleFixture(t, objects...)
- got := f.settle(t, triggerKey)
+ got := f.settle(t, claimKey)
- configured := findCondition(got.Status.Conditions, ApplyTriggerConditionConfigured)
+ configured := findCondition(got.Status.Conditions, ApplierClaimConditionConfigured)
require.NotNil(t, configured)
assert.Equal(t, tc.wantStatus, configured.Status, configured.Message)
assert.Equal(t, tc.wantReason, configured.Reason)
@@ -232,64 +232,64 @@ func TestApplyTriggerRule_TheBindingAggregatesItsGitTargets(t *testing.T) {
}
}
-// TestApplyTriggerRule_AnOlderTriggerNotInLineKeepsNoneOut: an older trigger whose GitTarget writes
+// TestApplierClaimRule_AnOlderClaimNotInLineKeepsNoneOut: an older applier claim whose GitTarget writes
// another branch, or whose namespace is not admitted, no longer keeps a younger eligible one from
// holding the Kustomization.
-func TestApplyTriggerRule_AnOlderTriggerNotInLineKeepsNoneOut(t *testing.T) {
+func TestApplierClaimRule_AnOlderClaimNotInLineKeepsNoneOut(t *testing.T) {
for name, older := range map[string]func(*configbutleraiv1alpha3.GitTarget, *[]ctrlclient.Object){
"bound to another branch": func(target *configbutleraiv1alpha3.GitTarget, objects *[]ctrlclient.Object) {
target.Spec.GitBranchRef.Name = gitBranchNameForTest("platform", "release")
*objects = append(*objects, gitBranchOn("platform", target.Namespace, "release"))
},
"not admitted": func(target *configbutleraiv1alpha3.GitTarget, objects *[]ctrlclient.Object) {
- target.Spec.KubeProviderRef = triggerProviderRef("closed")
+ target.Spec.KubeProviderRef = claimProviderRef("closed")
*objects = append(*objects, admitting("closed", "team-a"))
},
} {
t.Run(name, func(t *testing.T) {
- stray := testFluxTrigger(triggerLocalProvider)
+ stray := testFluxClaim(claimLocalProvider)
stray.Namespace = inLineKey.Namespace
stray.CreationTimestamp = metav1.NewTime(time.Unix(1_000_000, 0))
- mine := testFluxTrigger(triggerLocalProvider)
+ mine := testFluxClaim(claimLocalProvider)
mine.CreationTimestamp = metav1.NewTime(stray.CreationTimestamp.Add(time.Hour))
objects := []ctrlclient.Object{stray, mine}
- strayRefs := referencedIn(inLineKey.Namespace, configbutleraiv1alpha3.ApplyTriggerKindFlux)
+ strayRefs := referencedIn(inLineKey.Namespace, configbutleraiv1alpha3.ApplierClaimKindFlux)
older(strayRefs[1].(*configbutleraiv1alpha3.GitTarget), &objects)
objects = append(objects, strayRefs...)
- objects = append(objects, referencedIn("team-a", configbutleraiv1alpha3.ApplyTriggerKindFlux)...)
- objects = append(objects, admitting(triggerLocalProvider, "team-a", "team-b"))
+ objects = append(objects, referencedIn("team-a", configbutleraiv1alpha3.ApplierClaimKindFlux)...)
+ objects = append(objects, admitting(claimLocalProvider, "team-a", "team-b"))
f := ruleFixture(t, objects...)
- got := f.settle(t, triggerKey)
+ got := f.settle(t, claimKey)
- assert.True(t, conditionIsTrue(got.Status.Conditions, ApplyTriggerConditionConfigured),
- findCondition(got.Status.Conditions, ApplyTriggerConditionConfigured).Message)
+ assert.True(t, conditionIsTrue(got.Status.Conditions, ApplierClaimConditionConfigured),
+ findCondition(got.Status.Conditions, ApplierClaimConditionConfigured).Message)
assert.Equal(t, ReasonSuspended, heldReason(t, got))
- strayConfigured := findCondition(f.settle(t, inLineKey).Status.Conditions, ApplyTriggerConditionConfigured)
+ strayConfigured := findCondition(f.settle(t, inLineKey).Status.Conditions, ApplierClaimConditionConfigured)
require.NotNil(t, strayConfigured)
assert.NotEqual(t, metav1.ConditionTrue, strayConfigured.Status)
- assert.NotEqual(t, applier.ReasonHeldByAnotherTrigger, strayConfigured.Reason,
+ assert.NotEqual(t, applier.ReasonHeldByAnotherClaim, strayConfigured.Reason,
"it is refused for its own binding, before the line is asked")
})
}
}
-// TestApplyTriggerRule_ARetiringHolderStaysInLineUntilItsReleaseIsDone: the holder's GitTarget goes
+// TestApplierClaimRule_ARetiringHolderStaysInLineUntilItsReleaseIsDone: the holder's GitTarget goes
// while a successor waits. Until the holder has suspended the Kustomization and seen that handled,
// it stays first in line, Held=False (Releasing), and the successor does nothing; then the holder
// leaves the Kustomization suspended for it, and the successor takes it over.
-func TestApplyTriggerRule_ARetiringHolderStaysInLineUntilItsReleaseIsDone(t *testing.T) {
- holder := testFluxTrigger(triggerLocalProvider)
+func TestApplierClaimRule_ARetiringHolderStaysInLineUntilItsReleaseIsDone(t *testing.T) {
+ holder := testFluxClaim(claimLocalProvider)
holder.CreationTimestamp = metav1.NewTime(time.Unix(1_000_000, 0))
- successor := testFluxTrigger(triggerLocalProvider)
+ successor := testFluxClaim(claimLocalProvider)
successor.Namespace = inLineKey.Namespace
successor.CreationTimestamp = metav1.NewTime(holder.CreationTimestamp.Add(time.Hour))
objects := []ctrlclient.Object{holder, successor,
- admitting(triggerLocalProvider, "team-a", "team-b")}
- objects = append(objects, referencedIn("team-a", configbutleraiv1alpha3.ApplyTriggerKindFlux)...)
- objects = append(objects, referencedIn("team-b", configbutleraiv1alpha3.ApplyTriggerKindFlux)...)
+ admitting(claimLocalProvider, "team-a", "team-b")}
+ objects = append(objects, referencedIn("team-a", configbutleraiv1alpha3.ApplierClaimKindFlux)...)
+ objects = append(objects, referencedIn("team-b", configbutleraiv1alpha3.ApplierClaimKindFlux)...)
f := ruleFixture(t, objects...)
- f.settle(t, triggerKey)
+ f.settle(t, claimKey)
f.settle(t, inLineKey)
f.events()
@@ -298,37 +298,37 @@ func TestApplyTriggerRule_ARetiringHolderStaysInLineUntilItsReleaseIsDone(t *tes
unpause := f.sim.PauseKustomizations()
target := &configbutleraiv1alpha3.GitTarget{}
require.NoError(t, f.r.Get(context.Background(),
- k8stypes.NamespacedName{Namespace: "team-a", Name: "checkout-fluxtrigger"}, target))
+ k8stypes.NamespacedName{Namespace: "team-a", Name: "checkout-fluxclaim"}, target))
require.NoError(t, f.r.Delete(context.Background(), target))
- _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
- retiring := f.trigger(t)
+ retiring := f.claim(t)
assert.Equal(t, ReasonReleasing, heldReason(t, retiring))
- assert.True(t, controllerutil.ContainsFinalizer(retiring, configbutleraiv1alpha3.FluxTriggerFinalizer))
+ assert.True(t, controllerutil.ContainsFinalizer(retiring, configbutleraiv1alpha3.FluxClaimFinalizer))
assert.True(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)), "suspended first")
_, err = f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: inLineKey})
require.NoError(t, err)
- waiting := &configbutleraiv1alpha3.FluxTrigger{}
+ waiting := &configbutleraiv1alpha3.FluxClaim{}
require.NoError(t, f.r.Get(context.Background(), inLineKey, waiting))
- configured := findCondition(waiting.Status.Conditions, ApplyTriggerConditionConfigured)
+ configured := findCondition(waiting.Status.Conditions, ApplierClaimConditionConfigured)
require.NotNil(t, configured)
- assert.Equal(t, applier.ReasonHeldByAnotherTrigger, configured.Reason, "the retiring holder is still first")
+ assert.Equal(t, applier.ReasonHeldByAnotherClaim, configured.Reason, "the retiring holder is still first")
assert.Contains(t, configured.Message, "which holds it now")
unpause()
- done := f.settle(t, triggerKey)
+ done := f.settle(t, claimKey)
assert.Empty(t, done.Finalizers, "handed over")
assert.True(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)), "left suspended for the successor")
taken := f.settle(t, inLineKey)
- assert.True(t, conditionIsTrue(taken.Status.Conditions, ApplyTriggerConditionConfigured))
+ assert.True(t, conditionIsTrue(taken.Status.Conditions, ApplierClaimConditionConfigured))
assert.Equal(t, ReasonSuspended, heldReason(t, taken))
assert.Equal(t, configbutleraiv1alpha3.SuspendStateNotSuspended, taken.Status.PreviousSuspendState,
"passed on by the holder: the successor took the Kustomization over suspended already")
events := f.events()
require.NotEmpty(t, events)
assert.True(t, strings.HasPrefix(events[0], "Normal Released left Kustomization flux-system/checkout suspended "+
- "for FluxTrigger team-b/checkout"), events[0])
+ "for FluxClaim team-b/checkout"), events[0])
}
// resume resumes the Kustomization as a branch worker's apply does, under GitOps Reverser's field
@@ -340,12 +340,12 @@ func resume(t *testing.T, f *holdFixture) {
})
}
-// TestApplyTriggerRule_AReferenceReturningDuringTheReleaseHoldsAgain: the GitTarget is gone, and
-// back, before the release finished: the trigger holds the Kustomization again, as it is, and
+// TestApplierClaimRule_AReferenceReturningDuringTheReleaseHoldsAgain: the GitTarget is gone, and
+// back, before the release finished: the applier claim holds the Kustomization again, as it is, and
// hands nothing back.
-func TestApplyTriggerRule_AReferenceReturningDuringTheReleaseHoldsAgain(t *testing.T) {
- f := ruleFixture(t, testFluxTrigger(triggerLocalProvider), fluxTarget())
- f.settle(t, triggerKey)
+func TestApplierClaimRule_AReferenceReturningDuringTheReleaseHoldsAgain(t *testing.T) {
+ f := ruleFixture(t, testFluxClaim(claimLocalProvider), fluxTarget())
+ f.settle(t, claimKey)
f.events()
resume(t, f)
unpause := f.sim.PauseKustomizations()
@@ -353,35 +353,35 @@ func TestApplyTriggerRule_AReferenceReturningDuringTheReleaseHoldsAgain(t *testi
require.NoError(t, f.r.Get(context.Background(),
k8stypes.NamespacedName{Namespace: "team-a", Name: "checkout-flux"}, target))
require.NoError(t, f.r.Delete(context.Background(), target))
- _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
- assert.Equal(t, ReasonReleasing, heldReason(t, f.trigger(t)))
+ assert.Equal(t, ReasonReleasing, heldReason(t, f.claim(t)))
require.NoError(t, f.r.Create(context.Background(), fluxTarget()))
unpause()
- back := f.settle(t, triggerKey)
+ back := f.settle(t, claimKey)
assert.Equal(t, ReasonSuspended, heldReason(t, back))
- assert.True(t, controllerutil.ContainsFinalizer(back, configbutleraiv1alpha3.FluxTriggerFinalizer))
+ assert.True(t, controllerutil.ContainsFinalizer(back, configbutleraiv1alpha3.FluxClaimFinalizer))
assert.True(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)), "never handed back")
for _, event := range f.events() {
assert.NotContains(t, event, EventReasonReleased)
}
}
-// TestApplyTriggerRule_ReleasingWithAnApplyInFlightSuspendsFirst: the last GitTarget goes while an
+// TestApplierClaimRule_ReleasingWithAnApplyInFlightSuspendsFirst: the last GitTarget goes while an
// apply has the Kustomization resumed, followed by nobody here. The release suspends it, waits until
// that is handled, then hands it back, and completes the operation nobody would complete.
-func TestApplyTriggerRule_ReleasingWithAnApplyInFlightSuspendsFirst(t *testing.T) {
- f := ruleFixture(t, testFluxTrigger(triggerLocalProvider), fluxTarget())
- f.settle(t, triggerKey)
+func TestApplierClaimRule_ReleasingWithAnApplyInFlightSuspendsFirst(t *testing.T) {
+ f := ruleFixture(t, testFluxClaim(claimLocalProvider), fluxTarget())
+ f.settle(t, claimKey)
f.events()
- trigger := f.trigger(t)
- trigger.Status.Operation = &configbutleraiv1alpha3.ApplyOperation{
+ claim := f.claim(t)
+ claim.Status.Operation = &configbutleraiv1alpha3.ApplyOperation{
Phase: configbutleraiv1alpha3.ApplyOperationRunning, Reason: configbutleraiv1alpha3.ApplyReasonCatchUp,
Token: "T1", RequestedAt: metav1.Now(),
}
- require.NoError(t, f.r.Status().Update(context.Background(), trigger))
+ require.NoError(t, f.r.Status().Update(context.Background(), claim))
resume(t, f)
before := f.sim.Reconciles(checkoutKustomization)
target := &configbutleraiv1alpha3.GitTarget{}
@@ -389,7 +389,7 @@ func TestApplyTriggerRule_ReleasingWithAnApplyInFlightSuspendsFirst(t *testing.T
k8stypes.NamespacedName{Namespace: "team-a", Name: "checkout-flux"}, target))
require.NoError(t, f.r.Delete(context.Background(), target))
- released := f.settle(t, triggerKey)
+ released := f.settle(t, claimKey)
assert.Greater(t, f.sim.Reconciles(checkoutKustomization), before, "the suspend was handled before the release")
assert.False(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)), "then handed back")
@@ -398,51 +398,51 @@ func TestApplyTriggerRule_ReleasingWithAnApplyInFlightSuspendsFirst(t *testing.T
assert.Contains(t, released.Status.Operation.Message, "handed its Kustomization back")
}
-// TestApplyTriggerRule_AHolderLeftUnconfiguredSaysTheKustomizationIsSuspended is C3: a holder whose
+// TestApplierClaimRule_AHolderLeftUnconfiguredSaysTheKustomizationIsSuspended is C3: a holder whose
// configuration turns invalid keeps its finalizer and the Kustomization suspended, and its Held
// message says so.
-func TestApplyTriggerRule_AHolderLeftUnconfiguredSaysTheKustomizationIsSuspended(t *testing.T) {
- f := ruleFixture(t, testFluxTrigger(triggerLocalProvider), fluxTarget())
- f.settle(t, triggerKey)
- f.r.newApplier = triggerReconciler(nil, configbutleraiv1alpha3.ApplyTriggerKindFlux, nil, nil, false).newApplier
+func TestApplierClaimRule_AHolderLeftUnconfiguredSaysTheKustomizationIsSuspended(t *testing.T) {
+ f := ruleFixture(t, testFluxClaim(claimLocalProvider), fluxTarget())
+ f.settle(t, claimKey)
+ f.r.newApplier = claimReconciler(nil, configbutleraiv1alpha3.ApplierClaimKindFlux, nil, nil, false).newApplier
- _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
- got := f.trigger(t)
+ got := f.claim(t)
- configured := findCondition(got.Status.Conditions, ApplyTriggerConditionConfigured)
+ configured := findCondition(got.Status.Conditions, ApplierClaimConditionConfigured)
require.NotNil(t, configured)
assert.Equal(t, applier.ReasonForbidden, configured.Reason)
- held := findCondition(got.Status.Conditions, ApplyTriggerConditionHeld)
+ held := findCondition(got.Status.Conditions, ApplierClaimConditionHeld)
require.NotNil(t, held)
assert.Equal(t, metav1.ConditionUnknown, held.Status)
assert.Equal(t, ReasonNotConfigured, held.Reason)
assert.Contains(t, held.Message, "is left suspended under GitOps Reverser's field manager")
- assert.True(t, controllerutil.ContainsFinalizer(got, configbutleraiv1alpha3.FluxTriggerFinalizer))
+ assert.True(t, controllerutil.ContainsFinalizer(got, configbutleraiv1alpha3.FluxClaimFinalizer))
assert.True(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)))
}
-// TestApplyTriggerRule_AGitTargetEventRerunsTheTriggersItNames: a GitTarget naming a trigger, or
-// that named one before an update, re-runs its check; a GitProvider re-runs the triggers of the
+// TestApplierClaimRule_AGitTargetEventRerunsTheClaimsItNames: a GitTarget naming an applier claim, or
+// that named one before an update, re-runs its check; a GitProvider re-runs the applier claims of the
// GitTargets writing through it.
-func TestApplyTriggerRule_AGitTargetEventRerunsTheTriggersItNames(t *testing.T) {
- c := triggerTestClient(t, fluxTarget(), platformProvider())
- r := &ApplyTriggerReconciler{Client: c, Kind: configbutleraiv1alpha3.ApplyTriggerKindFlux}
+func TestApplierClaimRule_AGitTargetEventRerunsTheClaimsItNames(t *testing.T) {
+ c := claimTestClient(t, fluxTarget(), platformProvider())
+ r := &ApplierClaimReconciler{Client: c, Kind: configbutleraiv1alpha3.ApplierClaimKindFlux}
- requests := r.gitDestinationToTriggers(context.Background(), platformProvider())
+ requests := r.gitDestinationToClaims(context.Background(), platformProvider())
require.Len(t, requests, 1)
- assert.Equal(t, triggerKey, requests[0].NamespacedName)
+ assert.Equal(t, claimKey, requests[0].NamespacedName)
- argo := &ApplyTriggerReconciler{Client: c, Kind: configbutleraiv1alpha3.ApplyTriggerKindArgoCD}
- assert.Empty(t, argo.gitDestinationToTriggers(context.Background(), platformProvider()), "another kind's")
+ argo := &ApplierClaimReconciler{Client: c, Kind: configbutleraiv1alpha3.ApplierClaimKindArgoCD}
+ assert.Empty(t, argo.gitDestinationToClaims(context.Background(), platformProvider()), "another kind's")
}
-// TestApplyTriggerRule_AnApplierInAnotherNamespaceNeedsTheOptIn is B1: a namespace a KubeProvider
+// TestApplierClaimRule_AnApplierInAnotherNamespaceNeedsTheOptIn is B1: a namespace a KubeProvider
// admits may suspend and apply an applier in another namespace only when the provider sets
-// spec.allowCrossNamespace.appliers; one in the trigger's own namespace needs nothing. Refused, the
-// trigger never asks the applier's cluster, and holds nothing.
-func TestApplyTriggerRule_AnApplierInAnotherNamespaceNeedsTheOptIn(t *testing.T) {
- for _, kind := range []string{configbutleraiv1alpha3.ApplyTriggerKindFlux, configbutleraiv1alpha3.ApplyTriggerKindArgoCD} {
+// spec.allowCrossNamespace.appliers; one in the applier claim's own namespace needs nothing. Refused, the
+// applier claim never asks the applier's cluster, and holds nothing.
+func TestApplierClaimRule_AnApplierInAnotherNamespaceNeedsTheOptIn(t *testing.T) {
+ for _, kind := range []string{configbutleraiv1alpha3.ApplierClaimKindFlux, configbutleraiv1alpha3.ApplierClaimKindArgoCD} {
for name, tc := range map[string]struct {
sameNamespace, optIn bool
wantStatus metav1.ConditionStatus
@@ -459,9 +459,9 @@ func TestApplyTriggerRule_AnApplierInAnotherNamespaceNeedsTheOptIn(t *testing.T)
t.Run(kind+"/"+name, func(t *testing.T) {
r, calls := applierNamespaceReconciler(t, kind, tc.sameNamespace, tc.optIn)
- got := reconcileTrigger(t, r)
+ got := reconcileClaim(t, r)
- configured := findCondition(*got.StatusConditions(), ApplyTriggerConditionConfigured)
+ configured := findCondition(*got.StatusConditions(), ApplierClaimConditionConfigured)
require.NotNil(t, configured)
assert.Equal(t, tc.wantStatus, configured.Status, configured.Message)
assert.Equal(t, tc.wantReason, configured.Reason)
@@ -476,36 +476,36 @@ func TestApplyTriggerRule_AnApplierInAnotherNamespaceNeedsTheOptIn(t *testing.T)
}
}
-// applierNamespaceReconciler is a reconciler of kind over the trigger team-a/checkout, whose
+// applierNamespaceReconciler is a reconciler of kind over the applier claim team-a/checkout, whose
// applier is in team-a when sameNamespace is set and in flux-system or argocd otherwise, with the
-// KubeProvider triggerLocalProvider setting allowCrossNamespace.appliers when optIn is set. It counts the
+// KubeProvider claimLocalProvider setting allowCrossNamespace.appliers when optIn is set. It counts the
// lookups of the applier's cluster.
func applierNamespaceReconciler(
t *testing.T, kind string, sameNamespace, optIn bool,
-) (*ApplyTriggerReconciler, *int) {
+) (*ApplierClaimReconciler, *int) {
t.Helper()
- trigger := ctrlclient.Object(testFluxTrigger(triggerLocalProvider))
+ claim := ctrlclient.Object(testFluxClaim(claimLocalProvider))
appliers := applierFluxObjects()
- if kind == configbutleraiv1alpha3.ApplyTriggerKindArgoCD {
- trigger = testArgoCDTrigger(triggerLocalProvider)
+ if kind == configbutleraiv1alpha3.ApplierClaimKindArgoCD {
+ claim = testArgoCDClaim(claimLocalProvider)
appliers = []runtime.Object{applierArgoApplication(nil)}
}
if sameNamespace {
- switch typed := trigger.(type) {
- case *configbutleraiv1alpha3.FluxTrigger:
+ switch typed := claim.(type) {
+ case *configbutleraiv1alpha3.FluxClaim:
typed.Spec.KustomizationRef.Namespace = ""
- case *configbutleraiv1alpha3.ArgoCDTrigger:
+ case *configbutleraiv1alpha3.ArgoCDClaim:
typed.Spec.ApplicationRef.Namespace = ""
}
for _, obj := range appliers {
obj.(*unstructured.Unstructured).SetNamespace("team-a")
}
}
- provider := admitting(triggerLocalProvider, "team-a")
+ provider := admitting(claimLocalProvider, "team-a")
if !optIn {
provider.Spec.AllowCrossNamespace = nil
}
- r := triggerReconciler(triggerTestClient(t, trigger, provider), kind, applierFakeSource(t, appliers...), nil, true)
+ r := claimReconciler(claimTestClient(t, claim, provider), kind, applierFakeSource(t, appliers...), nil, true)
calls := new(int)
clients := r.ApplierClients
r.ApplierClients = func(ctx context.Context, cluster string) (dynamic.Interface, error) {
diff --git a/internal/controller/applytrigger_schema_test.go b/internal/controller/applierclaim_schema_test.go
similarity index 62%
rename from internal/controller/applytrigger_schema_test.go
rename to internal/controller/applierclaim_schema_test.go
index 40ff2545b..014b50a0a 100644
--- a/internal/controller/applytrigger_schema_test.go
+++ b/internal/controller/applierclaim_schema_test.go
@@ -17,10 +17,10 @@ import (
"github.com/ConfigButler/gitops-reverser/internal/applier"
)
-// The API server holds the trigger kinds to their contract: each names one applier and its
+// The API server holds the applier claim kinds to their contract: each names one applier and its
// cluster, neither can be pointed elsewhere once created, and
-// a GitTarget names a trigger by a kind this release serves.
-var _ = Describe("Apply trigger schema", func() {
+// a GitTarget names an applier claim by a kind this release serves.
+var _ = Describe("Applier claim schema", func() {
ctx := context.Background()
create := func(obj client.Object) error {
err := k8sClient.Create(ctx, obj)
@@ -29,19 +29,19 @@ var _ = Describe("Apply trigger schema", func() {
}
return err
}
- flux := func(name string) *configbutleraiv1alpha3.FluxTrigger {
- return &configbutleraiv1alpha3.FluxTrigger{
+ flux := func(name string) *configbutleraiv1alpha3.FluxClaim {
+ return &configbutleraiv1alpha3.FluxClaim{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "default"},
- Spec: configbutleraiv1alpha3.FluxTriggerSpec{
+ Spec: configbutleraiv1alpha3.FluxClaimSpec{
KubeProviderRef: meta.NamespacedObjectReference{Name: "hub"},
KustomizationRef: meta.NamespacedObjectReference{Namespace: "flux-system", Name: "apps"},
},
}
}
- argo := func(name string) *configbutleraiv1alpha3.ArgoCDTrigger {
- return &configbutleraiv1alpha3.ArgoCDTrigger{
+ argo := func(name string) *configbutleraiv1alpha3.ArgoCDClaim {
+ return &configbutleraiv1alpha3.ArgoCDClaim{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "default"},
- Spec: configbutleraiv1alpha3.ArgoCDTriggerSpec{
+ Spec: configbutleraiv1alpha3.ArgoCDClaimSpec{
KubeProviderRef: meta.NamespacedObjectReference{Name: "hub"},
ApplicationRef: meta.NamespacedObjectReference{Namespace: "argocd", Name: "apps"},
},
@@ -64,26 +64,26 @@ var _ = Describe("Apply trigger schema", func() {
Expect(create(noKustomization)).To(MatchError(ContainSubstring("spec.kustomizationRef.name must not be empty")))
})
- // A trigger no GitTarget names adopts nothing, so it carries no finalizer and deletes at once;
+ // An applier claim no GitTarget names adopts nothing, so it carries no finalizer and deletes at once;
// it still publishes its conditions, Ready because it does all it is asked to.
- It("publishes a new FluxTrigger's conditions, and holds nothing while no GitTarget names it", func() {
- trigger := flux("flux-first-pass")
- Expect(create(trigger)).To(Succeed())
+ It("publishes a new FluxClaim's conditions, and holds nothing while no GitTarget names it", func() {
+ claim := flux("flux-first-pass")
+ Expect(create(claim)).To(Succeed())
Eventually(func(g Gomega) {
- g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(trigger), trigger)).To(Succeed())
- ready := findCondition(trigger.Status.Conditions, ConditionTypeReady)
+ g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(claim), claim)).To(Succeed())
+ ready := findCondition(claim.Status.Conditions, ConditionTypeReady)
g.Expect(ready).NotTo(BeNil())
g.Expect(ready.Status).To(Equal(metav1.ConditionTrue))
g.Expect(ready.Reason).To(Equal(applier.ReasonNotReferenced))
}, 20*time.Second, 100*time.Millisecond).Should(Succeed())
- Expect(trigger.Finalizers).NotTo(ContainElement(configbutleraiv1alpha3.FluxTriggerFinalizer))
- held := findCondition(trigger.Status.Conditions, ApplyTriggerConditionHeld)
+ Expect(claim.Finalizers).NotTo(ContainElement(configbutleraiv1alpha3.FluxClaimFinalizer))
+ held := findCondition(claim.Status.Conditions, ApplierClaimConditionHeld)
Expect(held).NotTo(BeNil())
Expect(held.Reason).To(Equal(applier.ReasonNotReferenced))
})
- It("never points a trigger at another applier or cluster", func() {
- // The suite's FluxTrigger reconciler writes the trigger too (its finalizer), so each update
+ It("never points an applier claim at another applier or cluster", func() {
+ // The suite's FluxClaim reconciler writes the applier claim too (its finalizer), so each update
// is made from a fresh read, and one that loses that race is made again.
update := func(obj client.Object, mutate func()) error {
var err error
@@ -95,69 +95,69 @@ var _ = Describe("Apply trigger schema", func() {
}).Should(BeTrue())
return err
}
- fluxTrigger := flux("flux-immutable")
- Expect(create(fluxTrigger)).To(Succeed())
- Expect(update(fluxTrigger, func() { fluxTrigger.Spec.KustomizationRef.Name = "other" })).
+ fluxClaim := flux("flux-immutable")
+ Expect(create(fluxClaim)).To(Succeed())
+ Expect(update(fluxClaim, func() { fluxClaim.Spec.KustomizationRef.Name = "other" })).
To(MatchError(ContainSubstring("spec.kustomizationRef is immutable")))
- Expect(update(fluxTrigger, func() { fluxTrigger.Spec.KubeProviderRef.Name = "other" })).
+ Expect(update(fluxClaim, func() { fluxClaim.Spec.KubeProviderRef.Name = "other" })).
To(MatchError(ContainSubstring("spec.kubeProviderRef is immutable")))
- argoTrigger := argo("argo-immutable")
- Expect(create(argoTrigger)).To(Succeed())
- Expect(update(argoTrigger, func() { argoTrigger.Spec.ApplicationRef.Namespace = "elsewhere" })).
+ argoClaim := argo("argo-immutable")
+ Expect(create(argoClaim)).To(Succeed())
+ Expect(update(argoClaim, func() { argoClaim.Spec.ApplicationRef.Namespace = "elsewhere" })).
To(MatchError(ContainSubstring("spec.applicationRef is immutable")))
})
- It("lets a GitTarget name a trigger of a kind it serves, and requires one for Reapply", func() {
- target := func(name string, ref *configbutleraiv1alpha3.ApplyTriggerReference) *configbutleraiv1alpha3.GitTarget {
+ It("lets a GitTarget name an applier claim of a kind it serves, and requires one for Reapply", func() {
+ target := func(name string, ref *configbutleraiv1alpha3.ApplierClaimReference) *configbutleraiv1alpha3.GitTarget {
return &configbutleraiv1alpha3.GitTarget{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "default"},
Spec: configbutleraiv1alpha3.GitTargetSpec{
KubeProviderRef: testKubeProviderRef(),
GitBranchRef: meta.NamespacedObjectReference{Name: gitBranchNameForTest("prov", "main")},
Path: "apps/" + name,
- ApplyTriggerRef: ref,
+ ApplierClaimRef: ref,
},
}
}
- Expect(create(target("names-flux", &configbutleraiv1alpha3.ApplyTriggerReference{
- Kind: configbutleraiv1alpha3.ApplyTriggerKindFlux, Name: "apps",
+ Expect(create(target("names-flux", &configbutleraiv1alpha3.ApplierClaimReference{
+ Kind: configbutleraiv1alpha3.ApplierClaimKindFlux, Name: "apps",
}))).To(Succeed())
- Expect(create(target("names-argo", &configbutleraiv1alpha3.ApplyTriggerReference{
- Kind: configbutleraiv1alpha3.ApplyTriggerKindArgoCD, Name: "apps",
+ Expect(create(target("names-argo", &configbutleraiv1alpha3.ApplierClaimReference{
+ Kind: configbutleraiv1alpha3.ApplierClaimKindArgoCD, Name: "apps",
}))).To(Succeed())
- Expect(create(target("names-webhook", &configbutleraiv1alpha3.ApplyTriggerReference{
- Kind: "WebhookTrigger", Name: "apps",
+ Expect(create(target("names-webhook", &configbutleraiv1alpha3.ApplierClaimReference{
+ Kind: "WebhookClaim", Name: "apps",
}))).To(MatchError(ContainSubstring("Unsupported value")))
reapply := target("reapply-alone", nil)
reapply.Spec.OnRefusal = configbutleraiv1alpha3.RefusalActionReapply
- Expect(create(reapply)).To(MatchError(ContainSubstring("spec.onRefusal: Reapply needs spec.applyTriggerRef")))
+ Expect(create(reapply)).To(MatchError(ContainSubstring("spec.onRefusal: Reapply needs spec.applierClaimRef")))
})
})
-// The trigger reconciler runs under the manager: a trigger created gets its trio, Ready while no
+// The applier claim reconciler runs under the manager: an applier claim created gets its trio, Ready while no
// GitTarget names it, and a GitTarget naming it re-runs the check, though nothing else changed on
-// the trigger. The suite reaches no applier cluster, so that check goes no further.
-var _ = Describe("Apply trigger reconcile", func() {
+// the applier claim. The suite reaches no applier cluster, so that check goes no further.
+var _ = Describe("Applier claim reconcile", func() {
ctx := context.Background()
- It("publishes the trigger's verdict", func() {
- trigger := &configbutleraiv1alpha3.ArgoCDTrigger{
+ It("publishes the applier claim's verdict", func() {
+ claim := &configbutleraiv1alpha3.ArgoCDClaim{
ObjectMeta: metav1.ObjectMeta{Name: "reconciled", Namespace: "default"},
- Spec: configbutleraiv1alpha3.ArgoCDTriggerSpec{
+ Spec: configbutleraiv1alpha3.ArgoCDClaimSpec{
KubeProviderRef: testKubeProviderRef(),
ApplicationRef: meta.NamespacedObjectReference{Namespace: "argocd", Name: "apps"},
},
}
- Expect(k8sClient.Create(ctx, trigger)).To(Succeed())
- DeferCleanup(func() { _ = k8sClient.Delete(ctx, trigger) })
+ Expect(k8sClient.Create(ctx, claim)).To(Succeed())
+ DeferCleanup(func() { _ = k8sClient.Delete(ctx, claim) })
configuredReason := func(g Gomega) string {
- got := &configbutleraiv1alpha3.ArgoCDTrigger{}
- g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(trigger), got)).To(Succeed())
+ got := &configbutleraiv1alpha3.ArgoCDClaim{}
+ g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(claim), got)).To(Succeed())
g.Expect(got.Status.ObservedGeneration).To(Equal(got.Generation))
- configured := findCondition(got.Status.Conditions, ApplyTriggerConditionConfigured)
+ configured := findCondition(got.Status.Conditions, ApplierClaimConditionConfigured)
g.Expect(configured).NotTo(BeNil())
return configured.Reason
}
@@ -173,8 +173,8 @@ var _ = Describe("Apply trigger reconcile", func() {
Name: gitBranchNameForTest("prov", "main"),
},
Path: "apps/reconciled",
- ApplyTriggerRef: &configbutleraiv1alpha3.ApplyTriggerReference{
- Kind: configbutleraiv1alpha3.ApplyTriggerKindArgoCD, Name: "reconciled",
+ ApplierClaimRef: &configbutleraiv1alpha3.ApplierClaimReference{
+ Kind: configbutleraiv1alpha3.ApplierClaimKindArgoCD, Name: "reconciled",
},
},
}
diff --git a/internal/controller/argocdtrigger_apiserver_test.go b/internal/controller/argocdclaim_apiserver_test.go
similarity index 100%
rename from internal/controller/argocdtrigger_apiserver_test.go
rename to internal/controller/argocdclaim_apiserver_test.go
diff --git a/internal/controller/argocdclaim_hold.go b/internal/controller/argocdclaim_hold.go
new file mode 100644
index 000000000..a173097a5
--- /dev/null
+++ b/internal/controller/argocdclaim_hold.go
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: Apache-2.0
+
+package controller
+
+import (
+ "context"
+ "fmt"
+
+ corev1 "k8s.io/api/core/v1"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ apimeta "k8s.io/apimachinery/pkg/api/meta"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ k8stypes "k8s.io/apimachinery/pkg/types"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+
+ configbutleraiv1alpha3 "github.com/ConfigButler/gitops-reverser/api/v1alpha3"
+ "github.com/ConfigButler/gitops-reverser/internal/applier"
+)
+
+// An ArgoCDClaim holds its Application's automated sync off, as a FluxClaim holds its
+// Kustomization suspended (fluxclaim_hold.go). The branch workers only start syncs
+// (internal/applier/argocd.go); the reconciler does the lifecycle:
+//
+// - Adoption: a Configured applier claim applies spec.syncPolicy.automated.enabled: false under GitOps
+// Reverser's field manager, with a Normal Event. Nothing else of automated changes, so prune
+// keeps applying to the syncs GitOps Reverser starts.
+// - Switched on elsewhere: a held Application found with automated sync on (Argo CD's UI,
+// `kubectl edit`) is switched off again, every time, with a Normal ClaimedResourceReconciled
+// Event. A sync Argo CD started meanwhile is not stopped: a request waits for it to end, and
+// never replaces it.
+// - Hand-back: the finalizer gives the switch up, so the Application is as its own spec says
+// again.
+//
+// The switch of an Application an ApplicationSet generated, or whose switch another field manager
+// applies, is not the applier claim's (applier.AutomatedSync.Owner): the applier claim never writes it and
+// carries no finalizer for it. Configured refuses such an Application while its automated sync is
+// on, and the applier claim is Held while the owner keeps it off.
+
+// ReasonAutomatedSyncOff is Held=True on an ArgoCDClaim whose Application has automated sync off:
+// held off by GitOps Reverser, or kept off by the switch's owner (the message says which).
+const ReasonAutomatedSyncOff = "AutomatedSyncOff"
+
+// reconcileArgoCD is Reconcile for an ArgoCDClaim: one being deleted, or that no eligible
+// GitTarget references any more, hands its Application's automated sync back; any other holds it
+// off while it is Configured.
+func (r *ApplierClaimReconciler) reconcileArgoCD(
+ ctx context.Context, st *reconcileStatus, claim *configbutleraiv1alpha3.ArgoCDClaim,
+) (ctrl.Result, error) {
+ if !claim.DeletionTimestamp.IsZero() {
+ if !controllerutil.ContainsFinalizer(claim, configbutleraiv1alpha3.ArgoCDClaimFinalizer) {
+ return ctrl.Result{}, nil
+ }
+ return r.retire(ctx, st, claim, nil)
+ }
+ check := r.configuredVerdict(ctx, claim)
+ publishCheck(st, claim, check)
+ if check.retiring(claim) {
+ return r.retire(ctx, st, claim, &check)
+ }
+ held, err := r.holdArgoCD(ctx, st, claim, check)
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ st.setValue(ApplierClaimConditionHeld, held)
+ return r.commitClaim(ctx, st, r.claimReadiness(claim, check.configured, &held), &held,
+ heldClaimSteadyInterval)
+}
+
+// holdArgoCD returns an ArgoCDClaim's Held condition. A Configured applier claim holds its
+// Application's automated sync off. One that is not Configured holds nothing, and one in line
+// behind another holder lets go of a finalizer it carries, handing nothing back.
+func (r *ApplierClaimReconciler) holdArgoCD(
+ ctx context.Context, st *reconcileStatus, claim *configbutleraiv1alpha3.ArgoCDClaim, check claimCheck,
+) (conditionValue, error) {
+ configured := check.configured
+ switch {
+ case configured.Status == metav1.ConditionTrue:
+ return r.holdAutomatedSync(ctx, st, claim)
+ case configured.Reason == applier.ReasonNotReferenced:
+ return conditionValue{Status: metav1.ConditionFalse, Reason: applier.ReasonNotReferenced,
+ Message: "nothing to hold: no GitTarget names this applier claim"}, nil
+ case configured.Reason == applier.ReasonHeldByAnotherClaim && applier.Holding(claim):
+ // The holder took it, or two adopted at once: the switch is held under the same field
+ // manager either way, so there is nothing of this applier claim's to hand back.
+ if err := r.setFinalizer(ctx, st, claim, false); err != nil {
+ return conditionValue{}, err
+ }
+ case applier.Holding(claim):
+ return conditionValue{Status: metav1.ConditionUnknown, Reason: ReasonNotConfigured, Message: fmt.Sprintf(
+ "Application %s is left with its automated sync held off under GitOps Reverser's field manager, as "+
+ "this applier claim last held it: it is held again once the applier claim is Configured, and handed back "+
+ "once no eligible GitTarget references the applier claim or the applier claim is deleted", claim.Applier())}, nil
+ }
+ return conditionValue{Status: metav1.ConditionUnknown, Reason: ReasonNotConfigured,
+ Message: "the Application's automated sync is held off once the applier claim is Configured"}, nil
+}
+
+// holdAutomatedSync holds a Configured ArgoCDClaim's Application's automated sync off, with the
+// applier claim's finalizer put in place before the first write, and returns the Held condition. A switch
+// somebody else owns is left to them: Configured is True only while they keep it off.
+func (r *ApplierClaimReconciler) holdAutomatedSync(
+ ctx context.Context, st *reconcileStatus, claim *configbutleraiv1alpha3.ArgoCDClaim,
+) (conditionValue, error) {
+ ctx, cancel := context.WithTimeout(ctx, applierCheckTimeout)
+ defer cancel()
+ ref := claim.Applier()
+ argo, err := r.argoClient(ctx, claim)
+ if err != nil {
+ return keptCondition(claim.Status.Conditions, ApplierClaimConditionHeld,
+ fmt.Sprintf("cannot reach the cluster of Application %s: %v", ref, err)), nil
+ }
+ state, err := argo.AutomatedSync(ctx, ref)
+ if err != nil {
+ return keptCondition(claim.Status.Conditions, ApplierClaimConditionHeld,
+ fmt.Sprintf("cannot read Application %s: %v", ref, err)), nil
+ }
+ if state.Owner != "" {
+ return ownedSwitchCondition(ref, state), nil
+ }
+ // A switch held already, by an applier claim that adopts, is one a hand-back kept off (adopting).
+ adopt := adopting(claim, claim.Status.Operation)
+ if adopt && state.Held {
+ if other, ok := r.heldElsewhere(ctx, claim); !ok {
+ return other, nil
+ }
+ }
+ if err := r.setFinalizer(ctx, st, claim, true); err != nil {
+ return conditionValue{}, err
+ }
+ if !state.Held || state.On || adopt {
+ if failed, ok := r.switchOff(ctx, st, claim, argo, state, adopt); !ok {
+ return failed, nil
+ }
+ }
+ return conditionValue{Status: metav1.ConditionTrue, Reason: ReasonAutomatedSyncOff, Message: fmt.Sprintf(
+ "Application %s has automated sync held off by GitOps Reverser, which starts every sync", ref)}, nil
+}
+
+// switchOff switches an ArgoCDClaim's Application's automated sync off, having recorded how it stood
+// when the applier claim adopts it, and reports false with the Held condition when it could not.
+func (r *ApplierClaimReconciler) switchOff(
+ ctx context.Context, st *reconcileStatus, claim *configbutleraiv1alpha3.ArgoCDClaim, argo *applier.ArgoCD,
+ state applier.AutomatedSync, adopt bool,
+) (conditionValue, bool) {
+ ref := claim.Applier()
+ if err := r.recordPreviousSuspendState(ctx, st, claim, state.Held && !adopt, !state.On); err != nil {
+ return keptCondition(claim.Status.Conditions, ApplierClaimConditionHeld, fmt.Sprintf(
+ "cannot record how Application %s stood before it is held: %v", ref, err)), false
+ }
+ written, err := argo.SwitchOff(ctx, ref)
+ switch {
+ case err != nil:
+ return keptCondition(claim.Status.Conditions, ApplierClaimConditionHeld, err.Error()), false
+ case !written:
+ // Configured checks this first; an Argo CD downgraded since is caught here, before a write.
+ return conditionValue{Status: metav1.ConditionFalse, Reason: applier.ReasonArgoCDVersionNotSupported,
+ Message: fmt.Sprintf("Application %s is served without spec.syncPolicy.automated.enabled (Argo CD "+
+ "before 3.1), so GitOps Reverser writes nothing", ref)}, false
+ }
+ switch {
+ case !heldBefore(claim):
+ r.event(claim, corev1.EventTypeNormal, EventReasonAdopted,
+ "switched automated sync of Application %s off: from now on GitOps Reverser starts every sync", ref)
+ case state.On:
+ r.event(claim, corev1.EventTypeNormal, EventReasonClaimedResourceReconciled,
+ "automated sync of Application %s was switched on outside GitOps Reverser, which switched it off "+
+ "again; a sync Argo CD started meanwhile was not checked against the branch", ref)
+ }
+ return conditionValue{}, true
+}
+
+// ownedSwitchCondition is the Held condition of an applier claim whose Application's switch somebody else
+// owns. Nothing is written: a share of the switch the applier claim took before the owner did is handed
+// back by the finalizer it carries.
+func ownedSwitchCondition(ref k8stypes.NamespacedName, state applier.AutomatedSync) conditionValue {
+ if state.On {
+ // Switched on since Configured read it: the next reconcile refuses it.
+ return conditionValue{Status: metav1.ConditionFalse, Reason: ReasonNotConfigured, Message: fmt.Sprintf(
+ "Application %s has automated sync on, switched by %s", ref, state.Owner)}
+ }
+ return conditionValue{Status: metav1.ConditionTrue, Reason: ReasonAutomatedSyncOff, Message: fmt.Sprintf(
+ "Application %s has automated sync kept off by %s, which owns the switch; GitOps Reverser starts "+
+ "every sync", ref, state.Owner)}
+}
+
+// releaseAutomatedSync takes an ArgoCDClaim's hand-back one step: it hands the Application's
+// automated sync switch back as status.previousSuspendState recorded it, or as the ApplicationSet or field
+// manager that owns the switch by then set it (applier.ArgoCD.ReleaseAutomatedSync). A successor
+// (nextInLine) takes it over instead, and it is left held off for it: released, Argo CD would sync on
+// its own at once, before the successor could switch it off again.
+func (r *ApplierClaimReconciler) releaseAutomatedSync(
+ ctx context.Context, claim *configbutleraiv1alpha3.ArgoCDClaim, successor configbutleraiv1alpha3.ApplierClaim,
+) *pendingRelease {
+ ref := claim.Applier()
+ if successor != nil {
+ r.event(claim, corev1.EventTypeNormal, EventReasonReleased,
+ "left automated sync of Application %s held off for %s %s/%s, which holds it next", ref,
+ successor.ClaimKind(), successor.GetNamespace(), successor.GetName())
+ return nil
+ }
+ dynamicClient, err := r.releaseClient(ctx, claim)
+ if err != nil {
+ return &pendingRelease{
+ err: err, left: fmt.Sprintf("its cluster cannot be reached (%v), so it is left as it was", err),
+ }
+ }
+ if dynamicClient == nil {
+ return nil
+ }
+ var wasOn *bool
+ if previous := claim.Status.PreviousSuspendState; previous != "" {
+ on := previous == configbutleraiv1alpha3.SuspendStateNotSuspended
+ wasOn = &on
+ }
+ released, err := applier.NewArgoCD(dynamicClient).ReleaseAutomatedSync(ctx, ref, wasOn)
+ switch {
+ case apierrors.IsNotFound(err) || apimeta.IsNoMatchError(err):
+ return nil
+ case err != nil:
+ return r.releaseFailed(claim, err, fmt.Sprintf("giving its automated sync switch up failed (%v); %s",
+ err, leftHeld(claim)))
+ }
+ state := "its automated sync is off"
+ if released.On {
+ state = "its automated sync is on, and it syncs on its own"
+ }
+ if released.Kept != "" {
+ state += ", " + released.Kept + "; GitOps Reverser's field manager keeps that value until its owner changes it"
+ }
+ r.event(claim, corev1.EventTypeNormal, EventReasonReleased, "handed Application %s back to Argo CD: %s",
+ ref, state)
+ return nil
+}
+
+// argoClient is the Argo CD client of the applier claim's cluster.
+func (r *ApplierClaimReconciler) argoClient(
+ ctx context.Context, claim *configbutleraiv1alpha3.ArgoCDClaim,
+) (*applier.ArgoCD, error) {
+ dynamicClient, err := r.ApplierClients(ctx, claim.KubeProviderKey().String())
+ if err != nil {
+ return nil, err
+ }
+ return applier.NewArgoCD(dynamicClient), nil
+}
diff --git a/internal/controller/argocdtrigger_hold_test.go b/internal/controller/argocdclaim_hold_test.go
similarity index 70%
rename from internal/controller/argocdtrigger_hold_test.go
rename to internal/controller/argocdclaim_hold_test.go
index c3e41eab8..885e25b67 100644
--- a/internal/controller/argocdtrigger_hold_test.go
+++ b/internal/controller/argocdclaim_hold_test.go
@@ -28,10 +28,10 @@ import (
var checkoutApplication = k8stypes.NamespacedName{Namespace: "argocd", Name: "checkout"}
-// argoHoldFixture is an ArgoCDTrigger team-a/checkout over an applier cluster holding objs, with
+// argoHoldFixture is an ArgoCDClaim team-a/checkout over an applier cluster holding objs, with
// Events recorded.
type argoHoldFixture struct {
- r *ApplyTriggerReconciler
+ r *ApplierClaimReconciler
sim *fluxsim.Sim
recorder *record.FakeRecorder
}
@@ -39,10 +39,10 @@ type argoHoldFixture struct {
func newArgoHoldFixture(t *testing.T, objects []ctrlclient.Object, objs ...runtime.Object) *argoHoldFixture {
t.Helper()
if len(objects) == 0 {
- objects = []ctrlclient.Object{testArgoCDTrigger(triggerLocalProvider)}
+ objects = []ctrlclient.Object{testArgoCDClaim(claimLocalProvider)}
}
sim := applierSim(t, objs...)
- r := triggerReconciler(triggerTestClient(t, objects...), configbutleraiv1alpha3.ApplyTriggerKindArgoCD,
+ r := claimReconciler(claimTestClient(t, objects...), configbutleraiv1alpha3.ApplierClaimKindArgoCD,
sim.Client, nil, true)
recorder := record.NewFakeRecorder(32)
r.Recorder = recorder
@@ -68,11 +68,11 @@ func (f *argoHoldFixture) events() []string {
}
}
-func (f *argoHoldFixture) trigger(t *testing.T) *configbutleraiv1alpha3.ArgoCDTrigger {
+func (f *argoHoldFixture) claim(t *testing.T) *configbutleraiv1alpha3.ArgoCDClaim {
t.Helper()
- trigger := &configbutleraiv1alpha3.ArgoCDTrigger{}
- require.NoError(t, f.r.Get(context.Background(), triggerKey, trigger))
- return trigger
+ claim := &configbutleraiv1alpha3.ArgoCDClaim{}
+ require.NoError(t, f.r.Get(context.Background(), claimKey, claim))
+ return claim
}
func (f *argoHoldFixture) application(t *testing.T) *unstructured.Unstructured {
@@ -114,23 +114,23 @@ func generatedApplication(syncPolicy map[string]any) runtime.Object {
return app
}
-// TestArgoCDTrigger_SwitchesAutomatedSyncOff: a Configured trigger holds its Application's
+// TestArgoCDClaim_SwitchesAutomatedSyncOff: a Configured applier claim holds its Application's
// automated sync off under GitOps Reverser's field manager, keeping prune, with its finalizer in
// place first and a Normal Adopted Event.
-func TestArgoCDTrigger_SwitchesAutomatedSyncOff(t *testing.T) {
+func TestArgoCDClaim_SwitchesAutomatedSyncOff(t *testing.T) {
f := newArgoHoldFixture(t, nil, applierArgoApplication(map[string]any{
"automated": map[string]any{"prune": true, "selfHeal": true},
}))
- got := reconcileTrigger(t, f.r).(*configbutleraiv1alpha3.ArgoCDTrigger)
+ got := reconcileClaim(t, f.r).(*configbutleraiv1alpha3.ArgoCDClaim)
- held := findCondition(got.Status.Conditions, ApplyTriggerConditionHeld)
+ held := findCondition(got.Status.Conditions, ApplierClaimConditionHeld)
require.NotNil(t, held)
assert.Equal(t, metav1.ConditionTrue, held.Status, held.Message)
assert.Equal(t, ReasonAutomatedSyncOff, held.Reason)
assert.Contains(t, held.Message, "held off by GitOps Reverser")
assert.True(t, conditionIsTrue(got.Status.Conditions, ConditionTypeReady))
- assert.True(t, controllerutil.ContainsFinalizer(got, configbutleraiv1alpha3.ArgoCDTriggerFinalizer))
+ assert.True(t, controllerutil.ContainsFinalizer(got, configbutleraiv1alpha3.ArgoCDClaimFinalizer))
automated, _ := f.automated(t)
assert.Equal(t, map[string]any{"enabled": false, "prune": true, "selfHeal": true}, automated,
"only the switch is written")
@@ -139,22 +139,22 @@ func TestArgoCDTrigger_SwitchesAutomatedSyncOff(t *testing.T) {
assert.True(t, strings.HasPrefix(events[0],
"Normal Adopted switched automated sync of Application argocd/checkout off"), events[0])
- reconcileTrigger(t, f.r)
+ reconcileClaim(t, f.r)
assert.Empty(t, f.events(), "a held switch is not written again")
}
-// TestArgoCDTrigger_AutomatedSyncSwitchedOnIsSwitchedOffAgain: the control loop. Automated sync
+// TestArgoCDClaim_AutomatedSyncSwitchedOnIsSwitchedOffAgain: the control loop. Automated sync
// switched on outside GitOps Reverser is switched off again, every time, with a Normal
// ClaimedResourceReconciled Event each time.
-func TestArgoCDTrigger_AutomatedSyncSwitchedOnIsSwitchedOffAgain(t *testing.T) {
+func TestArgoCDClaim_AutomatedSyncSwitchedOnIsSwitchedOffAgain(t *testing.T) {
f := newArgoHoldFixture(t, nil, applierArgoApplication(nil))
- reconcileTrigger(t, f.r)
+ reconcileClaim(t, f.r)
f.events()
for round := range 2 {
f.switchOn(t)
- got := reconcileTrigger(t, f.r)
+ got := reconcileClaim(t, f.r)
assert.True(t, conditionIsTrue(*got.StatusConditions(), ConditionTypeReady), "round %d", round)
automated, _ := f.automated(t)
@@ -166,9 +166,9 @@ func TestArgoCDTrigger_AutomatedSyncSwitchedOnIsSwitchedOffAgain(t *testing.T) {
}
}
-// TestArgoCDTrigger_DeletionHandsTheSwitchBack: the finalizer gives the switch up, so the Application
+// TestArgoCDClaim_DeletionHandsTheSwitchBack: the finalizer gives the switch up, so the Application
// is as its own spec says again: syncing on its own when it did before, manual when it was.
-func TestArgoCDTrigger_DeletionHandsTheSwitchBack(t *testing.T) {
+func TestArgoCDClaim_DeletionHandsTheSwitchBack(t *testing.T) {
for name, tc := range map[string]struct {
syncPolicy map[string]any
wantOn bool
@@ -182,14 +182,14 @@ func TestArgoCDTrigger_DeletionHandsTheSwitchBack(t *testing.T) {
} {
t.Run(name, func(t *testing.T) {
f := newArgoHoldFixture(t, nil, applierArgoApplication(tc.syncPolicy))
- reconcileTrigger(t, f.r)
+ reconcileClaim(t, f.r)
f.events()
- require.NoError(t, f.r.Delete(context.Background(), f.trigger(t)))
- _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ require.NoError(t, f.r.Delete(context.Background(), f.claim(t)))
+ _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
- err = f.r.Get(context.Background(), triggerKey, &configbutleraiv1alpha3.ArgoCDTrigger{})
+ err = f.r.Get(context.Background(), claimKey, &configbutleraiv1alpha3.ArgoCDClaim{})
assert.True(t, apierrors.IsNotFound(err), "the finalizer is gone: %v", err)
automated, found := f.automated(t)
assert.Equal(t, tc.wantOn, found)
@@ -207,21 +207,21 @@ func TestArgoCDTrigger_DeletionHandsTheSwitchBack(t *testing.T) {
}
}
-// TestArgoCDTrigger_NoLongerReferencedHandsTheSwitchBack: a trigger no eligible GitTarget references
+// TestArgoCDClaim_NoLongerReferencedHandsTheSwitchBack: an applier claim no eligible GitTarget references
// any more hands the switch back, keeps nothing, and is held again once one does.
-func TestArgoCDTrigger_NoLongerReferencedHandsTheSwitchBack(t *testing.T) {
+func TestArgoCDClaim_NoLongerReferencedHandsTheSwitchBack(t *testing.T) {
f := newArgoHoldFixture(t, nil, applierArgoApplication(map[string]any{"automated": map[string]any{}}))
- reconcileTrigger(t, f.r)
+ reconcileClaim(t, f.r)
f.events()
target := &configbutleraiv1alpha3.GitTarget{}
require.NoError(t, f.r.Get(context.Background(),
ctrlclient.ObjectKey{Namespace: "team-a", Name: "checkout-argo"}, target))
require.NoError(t, f.r.Delete(context.Background(), target))
- got := reconcileTrigger(t, f.r)
+ got := reconcileClaim(t, f.r)
- assert.False(t, controllerutil.ContainsFinalizer(got, configbutleraiv1alpha3.ArgoCDTriggerFinalizer))
- held := findCondition(*got.StatusConditions(), ApplyTriggerConditionHeld)
+ assert.False(t, controllerutil.ContainsFinalizer(got, configbutleraiv1alpha3.ArgoCDClaimFinalizer))
+ held := findCondition(*got.StatusConditions(), ApplierClaimConditionHeld)
require.NotNil(t, held)
assert.Equal(t, applier.ReasonNotReferenced, held.Reason)
automated, _ := f.automated(t)
@@ -231,58 +231,58 @@ func TestArgoCDTrigger_NoLongerReferencedHandsTheSwitchBack(t *testing.T) {
assert.Contains(t, events[0], "Normal Released handed Application argocd/checkout back to Argo CD")
}
-// TestArgoCDTrigger_ASuccessorInLineKeepsTheSwitchOff: a holder that goes with another trigger in
+// TestArgoCDClaim_ASuccessorInLineKeepsTheSwitchOff: a holder that goes with another applier claim in
// line leaves the switch held off for it: released, Argo CD would sync on its own at once. It passes
// on how the Application stood, which the successor cannot read from a switch held already.
-func TestArgoCDTrigger_ASuccessorInLineKeepsTheSwitchOff(t *testing.T) {
- successor := testArgoCDTrigger(triggerLocalProvider)
+func TestArgoCDClaim_ASuccessorInLineKeepsTheSwitchOff(t *testing.T) {
+ successor := testArgoCDClaim(claimLocalProvider)
successor.Name = "checkout-next"
successor.CreationTimestamp = metav1.NewTime(time.Now().Add(time.Hour))
successor.Status.Conditions = []metav1.Condition{{
- Type: ApplyTriggerConditionConfigured, Status: metav1.ConditionFalse,
- Reason: applier.ReasonHeldByAnotherTrigger, LastTransitionTime: metav1.Now(),
+ Type: ApplierClaimConditionConfigured, Status: metav1.ConditionFalse,
+ Reason: applier.ReasonHeldByAnotherClaim, LastTransitionTime: metav1.Now(),
}}
- holder := testArgoCDTrigger(triggerLocalProvider)
+ holder := testArgoCDClaim(claimLocalProvider)
holder.CreationTimestamp = metav1.NewTime(time.Now())
f := newArgoHoldFixture(t, []ctrlclient.Object{holder, successor},
applierArgoApplication(map[string]any{"automated": map[string]any{}}))
- reconcileTrigger(t, f.r)
+ reconcileClaim(t, f.r)
f.events()
- require.NoError(t, f.r.Delete(context.Background(), f.trigger(t)))
- _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ require.NoError(t, f.r.Delete(context.Background(), f.claim(t)))
+ _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
automated, _ := f.automated(t)
assert.Equal(t, false, automated["enabled"], "left held off for the successor")
- next := &configbutleraiv1alpha3.ArgoCDTrigger{}
+ next := &configbutleraiv1alpha3.ArgoCDClaim{}
require.NoError(t, f.r.Get(context.Background(), ctrlclient.ObjectKeyFromObject(successor), next))
assert.Equal(t, configbutleraiv1alpha3.SuspendStateNotSuspended, next.Status.PreviousSuspendState,
"passed on: it synced on its own")
events := f.events()
require.Len(t, events, 1)
assert.True(t, strings.HasPrefix(events[0], "Normal Released left automated sync of Application argocd/checkout "+
- "held off for ArgoCDTrigger team-a/checkout-next"), events[0])
+ "held off for ArgoCDClaim team-a/checkout-next"), events[0])
}
-// TestArgoCDTrigger_AnApplicationSetOwnsTheSwitch: the switch of an Application an ApplicationSet
-// generated is the ApplicationSet's template's. With automated sync off the trigger is Held and
+// TestArgoCDClaim_AnApplicationSetOwnsTheSwitch: the switch of an Application an ApplicationSet
+// generated is the ApplicationSet's template's. With automated sync off the applier claim is Held and
// writes nothing; with it on, it is refused with a reason and message that say what to change.
-func TestArgoCDTrigger_AnApplicationSetOwnsTheSwitch(t *testing.T) {
+func TestArgoCDClaim_AnApplicationSetOwnsTheSwitch(t *testing.T) {
t.Run("kept off by the ApplicationSet", func(t *testing.T) {
f := newArgoHoldFixture(t, nil, generatedApplication(map[string]any{
"automated": map[string]any{"enabled": false, "prune": true},
}))
- got := reconcileTrigger(t, f.r).(*configbutleraiv1alpha3.ArgoCDTrigger)
+ got := reconcileClaim(t, f.r).(*configbutleraiv1alpha3.ArgoCDClaim)
assert.True(t, conditionIsTrue(got.Status.Conditions, ConditionTypeReady))
- held := findCondition(got.Status.Conditions, ApplyTriggerConditionHeld)
+ held := findCondition(got.Status.Conditions, ApplierClaimConditionHeld)
require.NotNil(t, held)
assert.Equal(t, ReasonAutomatedSyncOff, held.Reason)
assert.Contains(t, held.Message, "kept off by ApplicationSet argocd/checkouts, which owns the switch")
- assert.False(t, controllerutil.ContainsFinalizer(got, configbutleraiv1alpha3.ArgoCDTriggerFinalizer),
- "nothing of the trigger's to hand back")
+ assert.False(t, controllerutil.ContainsFinalizer(got, configbutleraiv1alpha3.ArgoCDClaimFinalizer),
+ "nothing of the applier claim's to hand back")
assert.Empty(t, f.application(t).GetManagedFields(), "the switch is never written")
assert.Empty(t, f.events())
})
@@ -291,9 +291,9 @@ func TestArgoCDTrigger_AnApplicationSetOwnsTheSwitch(t *testing.T) {
"automated": map[string]any{"prune": true},
}))
- got := reconcileTrigger(t, f.r).(*configbutleraiv1alpha3.ArgoCDTrigger)
+ got := reconcileClaim(t, f.r).(*configbutleraiv1alpha3.ArgoCDClaim)
- configured := findCondition(got.Status.Conditions, ApplyTriggerConditionConfigured)
+ configured := findCondition(got.Status.Conditions, ApplierClaimConditionConfigured)
require.NotNil(t, configured)
assert.Equal(t, metav1.ConditionFalse, configured.Status)
assert.Equal(t, applier.ReasonOwnedByApplicationSet, configured.Reason)
@@ -303,14 +303,14 @@ func TestArgoCDTrigger_AnApplicationSetOwnsTheSwitch(t *testing.T) {
assert.True(t, conditionIsTrue(got.Status.Conditions, ConditionTypeStalled), "a human has to change it")
automated, _ := f.automated(t)
assert.NotContains(t, automated, "enabled", "the switch is never written")
- assert.False(t, controllerutil.ContainsFinalizer(got, configbutleraiv1alpha3.ArgoCDTriggerFinalizer))
+ assert.False(t, controllerutil.ContainsFinalizer(got, configbutleraiv1alpha3.ArgoCDClaimFinalizer))
})
}
-// TestArgoCDTrigger_AManagerThatAppliesTheSwitchOwnsIt: whatever applies the Application's manifest
-// with the switch in it sets it on every apply, so the trigger leaves it alone: Held while it is
+// TestArgoCDClaim_AManagerThatAppliesTheSwitchOwnsIt: whatever applies the Application's manifest
+// with the switch in it sets it on every apply, so the applier claim leaves it alone: Held while it is
// off, refused while it is on.
-func TestArgoCDTrigger_AManagerThatAppliesTheSwitchOwnsIt(t *testing.T) {
+func TestArgoCDClaim_AManagerThatAppliesTheSwitchOwnsIt(t *testing.T) {
for name, tc := range map[string]struct {
enabled bool
wantReason string
@@ -323,9 +323,9 @@ func TestArgoCDTrigger_AManagerThatAppliesTheSwitchOwnsIt(t *testing.T) {
"kustomize-controller", metav1.ManagedFieldsOperationApply)
f := newArgoHoldFixture(t, nil, app)
- got := reconcileTrigger(t, f.r)
+ got := reconcileClaim(t, f.r)
- configured := findCondition(*got.StatusConditions(), ApplyTriggerConditionConfigured)
+ configured := findCondition(*got.StatusConditions(), ApplierClaimConditionConfigured)
require.NotNil(t, configured)
assert.Equal(t, tc.wantReason, configured.Reason, configured.Message)
automated, _ := f.automated(t)
@@ -335,11 +335,11 @@ func TestArgoCDTrigger_AManagerThatAppliesTheSwitchOwnsIt(t *testing.T) {
}
}
-// TestArgoCDTrigger_AnArgoCDThatDropsTheSwitchIsNeverWritten: Argo CD before 3.1 has no
+// TestArgoCDClaim_AnArgoCDThatDropsTheSwitchIsNeverWritten: Argo CD before 3.1 has no
// spec.syncPolicy.automated.enabled in its schema, and its API server prunes it, so switching a
-// manual Application off would leave automated: {}, which is automated sync ON. The trigger finds
+// manual Application off would leave automated: {}, which is automated sync ON. The applier claim finds
// that with a dry run, refuses the Application, and writes nothing.
-func TestArgoCDTrigger_AnArgoCDThatDropsTheSwitchIsNeverWritten(t *testing.T) {
+func TestArgoCDClaim_AnArgoCDThatDropsTheSwitchIsNeverWritten(t *testing.T) {
for name, syncPolicy := range map[string]map[string]any{
"a manual Application": nil,
"automated sync on": {"automated": map[string]any{"prune": true}},
@@ -350,9 +350,9 @@ func TestArgoCDTrigger_AnArgoCDThatDropsTheSwitchIsNeverWritten(t *testing.T) {
argosim.Serve(f.sim.Client, argosim.WithoutSwitch())
before, _ := f.automated(t)
- got := reconcileTrigger(t, f.r)
+ got := reconcileClaim(t, f.r)
- configured := findCondition(*got.StatusConditions(), ApplyTriggerConditionConfigured)
+ configured := findCondition(*got.StatusConditions(), ApplierClaimConditionConfigured)
require.NotNil(t, configured)
assert.Equal(t, metav1.ConditionFalse, configured.Status)
assert.Equal(t, applier.ReasonArgoCDVersionNotSupported, configured.Reason)
@@ -365,18 +365,18 @@ func TestArgoCDTrigger_AnArgoCDThatDropsTheSwitchIsNeverWritten(t *testing.T) {
after, _ := f.automated(t)
assert.Equal(t, before, after, "nothing is written")
assert.Empty(t, f.application(t).GetManagedFields())
- assert.False(t, controllerutil.ContainsFinalizer(got, configbutleraiv1alpha3.ArgoCDTriggerFinalizer))
+ assert.False(t, controllerutil.ContainsFinalizer(got, configbutleraiv1alpha3.ArgoCDClaimFinalizer))
assert.Empty(t, f.events())
})
}
}
-// TestArgoCDTrigger_RecordsAndRestoresHowItStood: adoption records whether the Application synced on
+// TestArgoCDClaim_RecordsAndRestoresHowItStood: adoption records whether the Application synced on
// its own in status.previousSuspendState, the hand-back puts exactly that back, and then forgets it.
// Giving the switch up alone would not: here the owner's own enabled: false was taken by a click in
// the UI, so giving the switch up would remove it, and an Application that was manual would sync on
// its own.
-func TestArgoCDTrigger_RecordsAndRestoresHowItStood(t *testing.T) {
+func TestArgoCDClaim_RecordsAndRestoresHowItStood(t *testing.T) {
app := argosim.OwnSwitch(
t,
applierArgoApplication(map[string]any{"automated": map[string]any{"prune": true}}).(*unstructured.Unstructured),
@@ -385,11 +385,11 @@ func TestArgoCDTrigger_RecordsAndRestoresHowItStood(t *testing.T) {
metav1.ManagedFieldsOperationUpdate,
)
f := newArgoHoldFixture(t, nil, app)
- got := reconcileTrigger(t, f.r).(*configbutleraiv1alpha3.ArgoCDTrigger)
+ got := reconcileClaim(t, f.r).(*configbutleraiv1alpha3.ArgoCDClaim)
require.Equal(t, configbutleraiv1alpha3.SuspendStateSuspended, got.Status.PreviousSuspendState)
f.switchOn(t)
- reconcileTrigger(t, f.r)
- assert.Equal(t, configbutleraiv1alpha3.SuspendStateSuspended, f.trigger(t).Status.PreviousSuspendState,
+ reconcileClaim(t, f.r)
+ assert.Equal(t, configbutleraiv1alpha3.SuspendStateSuspended, f.claim(t).Status.PreviousSuspendState,
"a switch-on is not how it stood")
f.events()
@@ -397,7 +397,7 @@ func TestArgoCDTrigger_RecordsAndRestoresHowItStood(t *testing.T) {
require.NoError(t, f.r.Get(context.Background(),
ctrlclient.ObjectKey{Namespace: "team-a", Name: "checkout-argo"}, target))
require.NoError(t, f.r.Delete(context.Background(), target))
- got = reconcileTrigger(t, f.r).(*configbutleraiv1alpha3.ArgoCDTrigger)
+ got = reconcileClaim(t, f.r).(*configbutleraiv1alpha3.ArgoCDClaim)
automated, _ := f.automated(t)
assert.Equal(t, false, automated["enabled"], "manual, as it was")
@@ -407,14 +407,14 @@ func TestArgoCDTrigger_RecordsAndRestoresHowItStood(t *testing.T) {
assert.Contains(t, events[0], "its automated sync is off, as it was before GitOps Reverser held it")
}
-// TestArgoCDTrigger_AnApplicationSetThatAdoptsAHeldApplicationKeepsItsValue is the review's P2. An
+// TestArgoCDClaim_AnApplicationSetThatAdoptsAHeldApplicationKeepsItsValue is the review's P2. An
// ApplicationSet that takes over a held Application with enabled: false in its template writes the
// switch with an update, which takes no share of a value that does not change: the switch stays
// GitOps Reverser's alone. Giving it up would then switch automated sync on against the template, so
// the hand-back keeps the value the ApplicationSet set.
-func TestArgoCDTrigger_AnApplicationSetThatAdoptsAHeldApplicationKeepsItsValue(t *testing.T) {
+func TestArgoCDClaim_AnApplicationSetThatAdoptsAHeldApplicationKeepsItsValue(t *testing.T) {
f := newArgoHoldFixture(t, nil, applierArgoApplication(map[string]any{"automated": map[string]any{"prune": true}}))
- reconcileTrigger(t, f.r)
+ reconcileClaim(t, f.r)
f.events()
adopted := f.application(t)
controller := true
@@ -426,8 +426,8 @@ func TestArgoCDTrigger_AnApplicationSetThatAdoptsAHeldApplicationKeepsItsValue(t
Update(context.Background(), adopted, metav1.UpdateOptions{FieldManager: "argocd-applicationset-controller"})
require.NoError(t, err)
- require.NoError(t, f.r.Delete(context.Background(), f.trigger(t)))
- _, err = f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ require.NoError(t, f.r.Delete(context.Background(), f.claim(t)))
+ _, err = f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
automated, _ := f.automated(t)
@@ -437,10 +437,10 @@ func TestArgoCDTrigger_AnApplicationSetThatAdoptsAHeldApplicationKeepsItsValue(t
assert.Contains(t, events[0], "its automated sync is off, as ApplicationSet argocd/checkouts set it")
}
-// TestArgoCDTrigger_RecordsHowItStoodBeforeTheSwitchOff: status.previousSuspendState is written before
+// TestArgoCDClaim_RecordsHowItStoodBeforeTheSwitchOff: status.previousSuspendState is written before
// the switch-off that hides it. With the commit after it lost, the next reconcile finds the switch
// held and cannot read how it stood, yet the hand-back still leaves a manual Application manual.
-func TestArgoCDTrigger_RecordsHowItStoodBeforeTheSwitchOff(t *testing.T) {
+func TestArgoCDClaim_RecordsHowItStoodBeforeTheSwitchOff(t *testing.T) {
app := argosim.OwnSwitch(
t,
applierArgoApplication(map[string]any{"automated": map[string]any{"prune": true}}).(*unstructured.Unstructured),
@@ -451,18 +451,57 @@ func TestArgoCDTrigger_RecordsHowItStoodBeforeTheSwitchOff(t *testing.T) {
f := newArgoHoldFixture(t, nil, app)
f.r.Client = &commitFailingClient{Client: f.r.Client, failures: 1}
- _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.Error(t, err, "the commit after the switch-off is lost")
- assert.Equal(t, configbutleraiv1alpha3.SuspendStateSuspended, f.trigger(t).Status.PreviousSuspendState,
+ assert.Equal(t, configbutleraiv1alpha3.SuspendStateSuspended, f.claim(t).Status.PreviousSuspendState,
"recorded before the switch-off")
- reconcileTrigger(t, f.r)
+ reconcileClaim(t, f.r)
target := &configbutleraiv1alpha3.GitTarget{}
require.NoError(t, f.r.Get(context.Background(),
ctrlclient.ObjectKey{Namespace: "team-a", Name: "checkout-argo"}, target))
require.NoError(t, f.r.Delete(context.Background(), target))
- reconcileTrigger(t, f.r)
+ reconcileClaim(t, f.r)
automated, _ := f.automated(t)
assert.Equal(t, false, automated["enabled"], "manual, as it was")
}
+
+// TestArgoCDClaim_ReadoptsAnApplicationItHandedBackManual is the review's reuse finding. A manual
+// Application whose switch was taken by a click while it was held is handed back manual by keeping
+// enabled: false under GitOps Reverser's field manager, and its record is forgotten. Referenced
+// again, the applier claim adopts it anew and records it manual: a switch it kept is not a hold in
+// place. Otherwise the next hand-back would give the switch up, and the Application would sync on
+// its own.
+func TestArgoCDClaim_ReadoptsAnApplicationItHandedBackManual(t *testing.T) {
+ app := argosim.OwnSwitch(
+ t,
+ applierArgoApplication(map[string]any{"automated": map[string]any{"prune": true}}).(*unstructured.Unstructured),
+ false,
+ "kubectl-client-side-apply",
+ metav1.ManagedFieldsOperationUpdate,
+ )
+ f := newArgoHoldFixture(t, nil, app)
+ reconcileClaim(t, f.r)
+ f.switchOn(t)
+ reconcileClaim(t, f.r)
+ setReferenced(t, f.r.Client, "checkout-argo", configbutleraiv1alpha3.ApplierClaimKindArgoCD, false)
+ got := reconcileClaim(t, f.r).(*configbutleraiv1alpha3.ArgoCDClaim)
+ require.Empty(t, got.Status.PreviousSuspendState, "forgotten once handed back")
+ automated, _ := f.automated(t)
+ require.Equal(t, false, automated["enabled"], "handed back manual, the switch kept under GitOps Reverser")
+ f.events()
+
+ setReferenced(t, f.r.Client, "checkout-argo", configbutleraiv1alpha3.ApplierClaimKindArgoCD, true)
+ got = reconcileClaim(t, f.r).(*configbutleraiv1alpha3.ArgoCDClaim)
+ assert.Equal(t, configbutleraiv1alpha3.SuspendStateSuspended, got.Status.PreviousSuspendState,
+ "adopted anew: manual before the applier claim held it")
+ if events := f.events(); assert.NotEmpty(t, events) {
+ assert.Contains(t, events[0], "Normal Adopted switched automated sync of Application argocd/checkout off")
+ }
+
+ setReferenced(t, f.r.Client, "checkout-argo", configbutleraiv1alpha3.ApplierClaimKindArgoCD, false)
+ reconcileClaim(t, f.r)
+ automated, _ = f.automated(t)
+ assert.Equal(t, false, automated["enabled"], "manual, as it was, again")
+}
diff --git a/internal/controller/argocdtrigger_hold.go b/internal/controller/argocdtrigger_hold.go
deleted file mode 100644
index c4c6b84a2..000000000
--- a/internal/controller/argocdtrigger_hold.go
+++ /dev/null
@@ -1,225 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-
-package controller
-
-import (
- "context"
- "fmt"
-
- corev1 "k8s.io/api/core/v1"
- apierrors "k8s.io/apimachinery/pkg/api/errors"
- apimeta "k8s.io/apimachinery/pkg/api/meta"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- k8stypes "k8s.io/apimachinery/pkg/types"
- ctrl "sigs.k8s.io/controller-runtime"
- "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
-
- configbutleraiv1alpha3 "github.com/ConfigButler/gitops-reverser/api/v1alpha3"
- "github.com/ConfigButler/gitops-reverser/internal/applier"
-)
-
-// An ArgoCDTrigger holds its Application's automated sync off, as a FluxTrigger holds its
-// Kustomization suspended (fluxtrigger_hold.go). The branch workers only start syncs
-// (internal/applier/argocd.go); the reconciler does the lifecycle:
-//
-// - Adoption: a Configured trigger applies spec.syncPolicy.automated.enabled: false under GitOps
-// Reverser's field manager, with a Normal Event. Nothing else of automated changes, so prune
-// keeps applying to the syncs GitOps Reverser starts.
-// - Switched on elsewhere: a held Application found with automated sync on (Argo CD's UI,
-// `kubectl edit`) is switched off again, every time, with a Normal ClaimedResourceReconciled
-// Event. A sync Argo CD started meanwhile is not stopped: a request waits for it to end, and
-// never replaces it.
-// - Hand-back: the finalizer gives the switch up, so the Application is as its own spec says
-// again.
-//
-// The switch of an Application an ApplicationSet generated, or whose switch another field manager
-// applies, is not the trigger's (applier.AutomatedSync.Owner): the trigger never writes it and
-// carries no finalizer for it. Configured refuses such an Application while its automated sync is
-// on, and the trigger is Held while the owner keeps it off.
-
-// ReasonAutomatedSyncOff is Held=True on an ArgoCDTrigger whose Application has automated sync off:
-// held off by GitOps Reverser, or kept off by the switch's owner (the message says which).
-const ReasonAutomatedSyncOff = "AutomatedSyncOff"
-
-// reconcileArgoCD is Reconcile for an ArgoCDTrigger: one being deleted, or that no eligible
-// GitTarget references any more, hands its Application's automated sync back; any other holds it
-// off while it is Configured.
-func (r *ApplyTriggerReconciler) reconcileArgoCD(
- ctx context.Context, st *reconcileStatus, trigger *configbutleraiv1alpha3.ArgoCDTrigger,
-) (ctrl.Result, error) {
- if !trigger.DeletionTimestamp.IsZero() {
- if !controllerutil.ContainsFinalizer(trigger, configbutleraiv1alpha3.ArgoCDTriggerFinalizer) {
- return ctrl.Result{}, nil
- }
- return r.retire(ctx, st, trigger, nil)
- }
- check := r.configuredVerdict(ctx, trigger)
- publishCheck(st, trigger, check)
- if check.retiring(trigger) {
- return r.retire(ctx, st, trigger, &check)
- }
- held, err := r.holdArgoCD(ctx, st, trigger, check)
- if err != nil {
- return ctrl.Result{}, err
- }
- st.setValue(ApplyTriggerConditionHeld, held)
- return r.commitTrigger(ctx, st, r.triggerReadiness(trigger, check.configured, &held), &held,
- heldTriggerSteadyInterval)
-}
-
-// holdArgoCD returns an ArgoCDTrigger's Held condition. A Configured trigger holds its
-// Application's automated sync off. One that is not Configured holds nothing, and one in line
-// behind another holder lets go of a finalizer it carries, handing nothing back.
-func (r *ApplyTriggerReconciler) holdArgoCD(
- ctx context.Context, st *reconcileStatus, trigger *configbutleraiv1alpha3.ArgoCDTrigger, check triggerCheck,
-) (conditionValue, error) {
- configured := check.configured
- switch {
- case configured.Status == metav1.ConditionTrue:
- return r.holdAutomatedSync(ctx, st, trigger)
- case configured.Reason == applier.ReasonNotReferenced:
- return conditionValue{Status: metav1.ConditionFalse, Reason: applier.ReasonNotReferenced,
- Message: "nothing to hold: no GitTarget names this trigger"}, nil
- case configured.Reason == applier.ReasonHeldByAnotherTrigger && applier.Holding(trigger):
- // The holder took it, or two adopted at once: the switch is held under the same field
- // manager either way, so there is nothing of this trigger's to hand back.
- if err := r.setFinalizer(ctx, st, trigger, false); err != nil {
- return conditionValue{}, err
- }
- case applier.Holding(trigger):
- return conditionValue{Status: metav1.ConditionUnknown, Reason: ReasonNotConfigured, Message: fmt.Sprintf(
- "Application %s is left with its automated sync held off under GitOps Reverser's field manager, as "+
- "this trigger last held it: it is held again once the trigger is Configured, and handed back "+
- "once no eligible GitTarget references the trigger or the trigger is deleted", trigger.Applier())}, nil
- }
- return conditionValue{Status: metav1.ConditionUnknown, Reason: ReasonNotConfigured,
- Message: "the Application's automated sync is held off once the trigger is Configured"}, nil
-}
-
-// holdAutomatedSync holds a Configured ArgoCDTrigger's Application's automated sync off, with the
-// trigger's finalizer put in place before the first write, and returns the Held condition. A switch
-// somebody else owns is left to them: Configured is True only while they keep it off.
-func (r *ApplyTriggerReconciler) holdAutomatedSync(
- ctx context.Context, st *reconcileStatus, trigger *configbutleraiv1alpha3.ArgoCDTrigger,
-) (conditionValue, error) {
- ctx, cancel := context.WithTimeout(ctx, applierCheckTimeout)
- defer cancel()
- ref := trigger.Applier()
- argo, err := r.argoClient(ctx, trigger)
- if err != nil {
- return keptCondition(trigger.Status.Conditions, ApplyTriggerConditionHeld,
- fmt.Sprintf("cannot reach the cluster of Application %s: %v", ref, err)), nil
- }
- state, err := argo.AutomatedSync(ctx, ref)
- if err != nil {
- return keptCondition(trigger.Status.Conditions, ApplyTriggerConditionHeld,
- fmt.Sprintf("cannot read Application %s: %v", ref, err)), nil
- }
- if state.Owner != "" {
- return ownedSwitchCondition(ref, state), nil
- }
- if err := r.setFinalizer(ctx, st, trigger, true); err != nil {
- return conditionValue{}, err
- }
- if !state.Held || state.On {
- if err := r.recordPreviousSuspendState(ctx, st, trigger, state.Held, !state.On); err != nil {
- return keptCondition(trigger.Status.Conditions, ApplyTriggerConditionHeld, fmt.Sprintf(
- "cannot record how Application %s stood before it is held: %v", ref, err)), nil
- }
- written, err := argo.SwitchOff(ctx, ref)
- switch {
- case err != nil:
- return keptCondition(trigger.Status.Conditions, ApplyTriggerConditionHeld, err.Error()), nil
- case !written:
- // Configured checks this first; an Argo CD downgraded since is caught here, before a write.
- return conditionValue{Status: metav1.ConditionFalse, Reason: applier.ReasonArgoCDVersionNotSupported,
- Message: fmt.Sprintf("Application %s is served without spec.syncPolicy.automated.enabled (Argo CD "+
- "before 3.1), so GitOps Reverser writes nothing", ref)}, nil
- }
- switch {
- case !heldBefore(trigger):
- r.event(trigger, corev1.EventTypeNormal, EventReasonAdopted,
- "switched automated sync of Application %s off: from now on GitOps Reverser starts every sync", ref)
- case state.On:
- r.event(trigger, corev1.EventTypeNormal, EventReasonClaimedResourceReconciled,
- "automated sync of Application %s was switched on outside GitOps Reverser, which switched it off "+
- "again; a sync Argo CD started meanwhile was not checked against the branch", ref)
- }
- }
- return conditionValue{Status: metav1.ConditionTrue, Reason: ReasonAutomatedSyncOff, Message: fmt.Sprintf(
- "Application %s has automated sync held off by GitOps Reverser, which starts every sync", ref)}, nil
-}
-
-// ownedSwitchCondition is the Held condition of a trigger whose Application's switch somebody else
-// owns. Nothing is written: a share of the switch the trigger took before the owner did is handed
-// back by the finalizer it carries.
-func ownedSwitchCondition(ref k8stypes.NamespacedName, state applier.AutomatedSync) conditionValue {
- if state.On {
- // Switched on since Configured read it: the next reconcile refuses it.
- return conditionValue{Status: metav1.ConditionFalse, Reason: ReasonNotConfigured, Message: fmt.Sprintf(
- "Application %s has automated sync on, switched by %s", ref, state.Owner)}
- }
- return conditionValue{Status: metav1.ConditionTrue, Reason: ReasonAutomatedSyncOff, Message: fmt.Sprintf(
- "Application %s has automated sync kept off by %s, which owns the switch; GitOps Reverser starts "+
- "every sync", ref, state.Owner)}
-}
-
-// releaseAutomatedSync takes an ArgoCDTrigger's hand-back one step: it hands the Application's
-// automated sync switch back as status.previousSuspendState recorded it, or as the ApplicationSet or field
-// manager that owns the switch by then set it (applier.ArgoCD.ReleaseAutomatedSync). A successor
-// (nextInLine) takes it over instead, and it is left held off for it: released, Argo CD would sync on
-// its own at once, before the successor could switch it off again.
-func (r *ApplyTriggerReconciler) releaseAutomatedSync(
- ctx context.Context, trigger *configbutleraiv1alpha3.ArgoCDTrigger, successor configbutleraiv1alpha3.ApplyTrigger,
-) *pendingRelease {
- ref := trigger.Applier()
- if successor != nil {
- r.event(trigger, corev1.EventTypeNormal, EventReasonReleased,
- "left automated sync of Application %s held off for %s %s/%s, which holds it next", ref,
- successor.TriggerKind(), successor.GetNamespace(), successor.GetName())
- return nil
- }
- dynamicClient, err := r.releaseClient(ctx, trigger)
- if err != nil {
- return &pendingRelease{
- err: err, left: fmt.Sprintf("its cluster cannot be reached (%v), so it is left as it was", err),
- }
- }
- if dynamicClient == nil {
- return nil
- }
- var wasOn *bool
- if previous := trigger.Status.PreviousSuspendState; previous != "" {
- on := previous == configbutleraiv1alpha3.SuspendStateNotSuspended
- wasOn = &on
- }
- released, err := applier.NewArgoCD(dynamicClient).ReleaseAutomatedSync(ctx, ref, wasOn)
- switch {
- case apierrors.IsNotFound(err) || apimeta.IsNoMatchError(err):
- return nil
- case err != nil:
- return r.releaseFailed(trigger, err, fmt.Sprintf("giving its automated sync switch up failed (%v); %s",
- err, leftHeld(trigger)))
- }
- state := "its automated sync is off"
- if released.On {
- state = "its automated sync is on, and it syncs on its own"
- }
- if released.Kept != "" {
- state += ", " + released.Kept + "; GitOps Reverser's field manager keeps that value until its owner changes it"
- }
- r.event(trigger, corev1.EventTypeNormal, EventReasonReleased, "handed Application %s back to Argo CD: %s",
- ref, state)
- return nil
-}
-
-// argoClient is the Argo CD client of the trigger's cluster.
-func (r *ApplyTriggerReconciler) argoClient(
- ctx context.Context, trigger *configbutleraiv1alpha3.ArgoCDTrigger,
-) (*applier.ArgoCD, error) {
- dynamicClient, err := r.ApplierClients(ctx, trigger.KubeProviderKey().String())
- if err != nil {
- return nil, err
- }
- return applier.NewArgoCD(dynamicClient), nil
-}
diff --git a/internal/controller/fluxtrigger_hold.go b/internal/controller/fluxclaim_hold.go
similarity index 52%
rename from internal/controller/fluxtrigger_hold.go
rename to internal/controller/fluxclaim_hold.go
index 5a41a11c2..2feb4fddd 100644
--- a/internal/controller/fluxtrigger_hold.go
+++ b/internal/controller/fluxclaim_hold.go
@@ -24,11 +24,11 @@ import (
"github.com/ConfigButler/gitops-reverser/internal/git"
)
-// A FluxTrigger holds its Kustomization suspended while idle; the branch workers resume it for each
+// A FluxClaim holds its Kustomization suspended while idle; the branch workers resume it for each
// apply and suspend it again (internal/applier/flux.go). The reconciler does the rest of the
// lifecycle, from docs/design/apply-trigger.md ("Who owns spec.suspend"):
//
-// - Adoption: a Configured trigger suspends its Kustomization, under GitOps Reverser's field
+// - Adoption: a Configured applier claim suspends its Kustomization, under GitOps Reverser's field
// manager, with a token; Held (and so Ready) waits until the Kustomization handled it.
// - External resume: a held Kustomization found resumed with no operation of a branch worker in
// flight was resumed by somebody else (`flux resume`). It is suspended again with a token, every
@@ -37,35 +37,35 @@ import (
// - Hand-back: the finalizer suspends once more, waits until that token is handled, and releases
// spec.suspend, so Flux owns the Kustomization's applies again.
//
-// All of it is the holder's alone (applier.HeldByAnother): another trigger naming the same Kustomization
-// is Configured=False (HeldByAnotherTrigger), so it never suspends or records an external
+// All of it is the holder's alone (applier.HeldByAnother): another applier claim naming the same Kustomization
+// is Configured=False (HeldByAnotherClaim), so it never suspends or records an external
// resume, and its deletion hands nothing back. It takes the Kustomization over, with an adoption,
// once the holder is gone.
-// holdKustomization holds a Configured FluxTrigger's Kustomization suspended, and returns the
-// trigger's Held condition. It writes nothing while a branch worker's operation is in flight
+// holdKustomization holds a Configured FluxClaim's Kustomization suspended, and returns the
+// applier claim's Held condition. It writes nothing while a branch worker's operation is in flight
// and followed: the worker resumes the Kustomization and suspends it again itself. One nobody
// follows any more is suspended here (suspendUnfollowed), so the Kustomization is never left resumed
// with nothing guarding it.
-func (r *ApplyTriggerReconciler) holdKustomization(
- ctx context.Context, st *reconcileStatus, trigger *configbutleraiv1alpha3.FluxTrigger,
+func (r *ApplierClaimReconciler) holdKustomization(
+ ctx context.Context, st *reconcileStatus, claim *configbutleraiv1alpha3.FluxClaim,
) conditionValue {
ctx, cancel := context.WithTimeout(ctx, applierCheckTimeout)
defer cancel()
- held, err := r.readHold(ctx, trigger)
+ held, err := r.readHold(ctx, claim)
if err != nil {
- return keptCondition(trigger.Status.Conditions, ApplyTriggerConditionHeld, err.Error())
+ return keptCondition(claim.Status.Conditions, ApplierClaimConditionHeld, err.Error())
}
if !held.inFlight() || held.operation.Token == "" {
- r.unfollowed.forget(trigger, "")
+ r.unfollowed.forget(claim, "")
}
if held.inFlight() && held.operation.Token != "" {
- if r.followed(trigger, held.operation) {
+ if r.followed(claim, held.operation) {
return applyingCondition(held.operation)
}
- return r.suspendUnfollowed(ctx, trigger, held)
+ return r.suspendUnfollowed(ctx, claim, held)
}
- return r.hold(ctx, st, trigger, held)
+ return r.hold(ctx, st, claim, held)
}
// applyingCondition is Held=True (Applying) for a branch worker's operation in flight that is
@@ -79,7 +79,7 @@ func applyingCondition(operation *configbutleraiv1alpha3.ApplyOperation) conditi
// OperationFollower answers whether this process carries out or follows a branch worker's
// operation: (*applier.Dispatcher).Following, which runs beside this reconciler on the leader.
type OperationFollower interface {
- Following(trigger git.TriggerRef, token string) bool
+ Following(claim git.ApplierClaimRef, token string) bool
}
// DefaultUnfollowedGrace is how long a branch worker's operation in flight may go unfollowed
@@ -92,45 +92,45 @@ const DefaultUnfollowedGrace = time.Minute
// Kustomization the reconciler suspended and whose operation it completed.
const EventReasonOperationUnfollowed = "OperationUnfollowed"
-// followed reports whether the reconciler leaves the trigger's in-flight operation to its worker:
+// followed reports whether the reconciler leaves the applier claim's in-flight operation to its worker:
// this process follows it, or has seen it unfollowed for less than the grace.
-func (r *ApplyTriggerReconciler) followed(
- trigger *configbutleraiv1alpha3.FluxTrigger, operation *configbutleraiv1alpha3.ApplyOperation,
+func (r *ApplierClaimReconciler) followed(
+ claim *configbutleraiv1alpha3.FluxClaim, operation *configbutleraiv1alpha3.ApplyOperation,
) bool {
- ref := git.TriggerRef{Kind: trigger.TriggerKind(), Namespace: trigger.Namespace, Name: trigger.Name}
+ ref := git.ApplierClaimRef{Kind: claim.ClaimKind(), Namespace: claim.Namespace, Name: claim.Name}
if r.Follower != nil && r.Follower.Following(ref, operation.Token) {
- r.unfollowed.forget(trigger, operation.Token)
+ r.unfollowed.forget(claim, operation.Token)
return true
}
grace := r.UnfollowedGrace
if grace <= 0 {
grace = DefaultUnfollowedGrace
}
- return time.Since(r.unfollowed.since(trigger, operation.Token)) < grace
+ return time.Since(r.unfollowed.since(claim, operation.Token)) < grace
}
// suspendUnfollowed sees a branch worker's operation that nobody follows through, as the worker's own
// follow would have: it suspends the Kustomization with a token, waits until that is handled, and
// completes the operation Failed, since whether its apply ran cannot be told from here. The worker,
-// should it still name the trigger, asks again. The suspend token is recorded on the operation, so a
+// should it still name the applier claim, asks again. The suspend token is recorded on the operation, so a
// worker that recovers it meanwhile follows the same suspend.
-func (r *ApplyTriggerReconciler) suspendUnfollowed(
- ctx context.Context, trigger *configbutleraiv1alpha3.FluxTrigger, held heldState,
+func (r *ApplierClaimReconciler) suspendUnfollowed(
+ ctx context.Context, claim *configbutleraiv1alpha3.FluxClaim, held heldState,
) conditionValue {
- ref := trigger.Applier()
+ ref := claim.Applier()
operation := held.operation.DeepCopy()
switch {
case !held.suspension.Suspended || !held.suspension.Held:
token, err := held.flux.Suspend(ctx, ref)
if err != nil {
- return keptCondition(trigger.Status.Conditions, ApplyTriggerConditionHeld, err.Error())
+ return keptCondition(claim.Status.Conditions, ApplierClaimConditionHeld, err.Error())
}
- r.event(trigger, corev1.EventTypeWarning, EventReasonOperationUnfollowed,
+ r.event(claim, corev1.EventTypeWarning, EventReasonOperationUnfollowed,
"the %s apply of %s is followed by nobody; suspended Kustomization %s again", operation.Reason,
applier.DescribeCommit(operation.Commit), ref)
operation.SuspendToken = token
operation.Message = "followed by nobody; suspended again, and complete once nothing it started is running"
- trigger.Status.Operation = operation
+ claim.Status.Operation = operation
return conditionValue{Status: metav1.ConditionFalse, Reason: ReasonSuspending,
Message: fmt.Sprintf("Kustomization %s is being suspended: its apply is followed by nobody", ref)}
case !held.suspension.Handled:
@@ -142,14 +142,14 @@ func (r *ApplyTriggerReconciler) suspendUnfollowed(
operation.Phase = configbutleraiv1alpha3.ApplyOperationFailed
operation.Message = "followed by nobody to its end; the Kustomization is suspended again, and whether the apply " +
"ran cannot be told"
- trigger.Status.Operation = operation
- r.unfollowed.forget(trigger, operation.Token)
+ claim.Status.Operation = operation
+ r.unfollowed.forget(claim, operation.Token)
return conditionValue{Status: metav1.ConditionTrue, Reason: ReasonSuspended,
Message: fmt.Sprintf("Kustomization %s is suspended; GitOps Reverser starts every apply it makes", ref)}
}
-// unfollowedOperations remembers when the reconciler first saw each trigger's in-flight operation
-// followed by nobody, by trigger and token. It is in memory: a process that starts gives every
+// unfollowedOperations remembers when the reconciler first saw each applier claim's in-flight operation
+// followed by nobody, by claim and token. It is in memory: a process that starts gives every
// operation the whole grace, which is when restart recovery picks it up.
type unfollowedOperations struct {
mu sync.Mutex
@@ -157,16 +157,16 @@ type unfollowedOperations struct {
}
type unfollowedKey struct {
- trigger k8stypes.NamespacedName
- token string
+ claim k8stypes.NamespacedName
+ token string
}
// since is when the operation was first seen unfollowed, now if this is the first time.
-func (u *unfollowedOperations) since(trigger configbutleraiv1alpha3.ApplyTrigger, token string) time.Time {
+func (u *unfollowedOperations) since(claim configbutleraiv1alpha3.ApplierClaim, token string) time.Time {
u.mu.Lock()
defer u.mu.Unlock()
key := unfollowedKey{
- trigger: k8stypes.NamespacedName{Namespace: trigger.GetNamespace(), Name: trigger.GetName()}, token: token,
+ claim: k8stypes.NamespacedName{Namespace: claim.GetNamespace(), Name: claim.GetName()}, token: token,
}
if u.first == nil {
u.first = map[unfollowedKey]time.Time{}
@@ -179,14 +179,14 @@ func (u *unfollowedOperations) since(trigger configbutleraiv1alpha3.ApplyTrigger
return first
}
-// forget drops what is remembered of the trigger's operation with token, or of all its operations
+// forget drops what is remembered of the applier claim's operation with token, or of all its operations
// when token is empty.
-func (u *unfollowedOperations) forget(trigger configbutleraiv1alpha3.ApplyTrigger, token string) {
+func (u *unfollowedOperations) forget(claim configbutleraiv1alpha3.ApplierClaim, token string) {
u.mu.Lock()
defer u.mu.Unlock()
- name := k8stypes.NamespacedName{Namespace: trigger.GetNamespace(), Name: trigger.GetName()}
+ name := k8stypes.NamespacedName{Namespace: claim.GetNamespace(), Name: claim.GetName()}
for key := range u.first {
- if key.trigger == name && (token == "" || key.token == token) {
+ if key.claim == name && (token == "" || key.token == token) {
delete(u.first, key)
}
}
@@ -199,43 +199,43 @@ type heldState struct {
operation *configbutleraiv1alpha3.ApplyOperation
}
-// inFlight reports whether the trigger records an operation that has not completed.
+// inFlight reports whether the applier claim records an operation that has not completed.
func (h heldState) inFlight() bool { return h.operation != nil && !h.operation.Phase.Completed() }
-// readHold reads the Kustomization's suspension and the trigger's operation, uncached. The
+// readHold reads the Kustomization's suspension and the applier claim's operation, uncached. The
// operation is read before and after the Kustomization, and the two must be the same operation in
// the same phase, or the read is an error and the next reconcile reads again. A worker writes its
// operation before it resumes and completes it after it suspended again, so with both reads equal,
// a Kustomization read resumed with no operation in flight was resumed by somebody else. With one
// read, an operation that completed between the two GETs would be taken for an external resume.
-func (r *ApplyTriggerReconciler) readHold(
- ctx context.Context, trigger *configbutleraiv1alpha3.FluxTrigger,
+func (r *ApplierClaimReconciler) readHold(
+ ctx context.Context, claim *configbutleraiv1alpha3.FluxClaim,
) (heldState, error) {
- ref := trigger.Applier()
- flux, err := r.fluxClient(ctx, trigger)
+ ref := claim.Applier()
+ flux, err := r.fluxClient(ctx, claim)
if err != nil {
return heldState{}, fmt.Errorf("cannot reach the cluster of Kustomization %s: %w", ref, err)
}
- before, err := r.liveOperation(ctx, trigger)
+ before, err := r.liveOperation(ctx, claim)
if err != nil {
- return heldState{}, fmt.Errorf("cannot read the operation of %s: %w", trigger.Name, err)
+ return heldState{}, fmt.Errorf("cannot read the operation of %s: %w", claim.Name, err)
}
suspension, err := flux.Suspension(ctx, ref)
if err != nil {
return heldState{}, fmt.Errorf("cannot read Kustomization %s: %w", ref, err)
}
- operation, err := r.liveOperation(ctx, trigger)
+ operation, err := r.liveOperation(ctx, claim)
if err != nil {
- return heldState{}, fmt.Errorf("cannot read the operation of %s: %w", trigger.Name, err)
+ return heldState{}, fmt.Errorf("cannot read the operation of %s: %w", claim.Name, err)
}
if !sameOperationPhase(before, operation) {
return heldState{}, fmt.Errorf("the operation of %s moved while Kustomization %s was read; reading again",
- trigger.Name, ref)
+ claim.Name, ref)
}
return heldState{flux: flux, suspension: suspension, operation: operation}, nil
}
-// sameOperationPhase reports whether two reads of a trigger's operation show the same operation, by
+// sameOperationPhase reports whether two reads of an applier claim's operation show the same operation, by
// its tokens, in the same phase.
func sameOperationPhase(a, b *configbutleraiv1alpha3.ApplyOperation) bool {
if a == nil || b == nil {
@@ -247,113 +247,157 @@ func sameOperationPhase(a, b *configbutleraiv1alpha3.ApplyOperation) bool {
// hold suspends the Kustomization when it is not held with its suspend handled, and completes the operation an
// external resume was recorded as once it is.
-func (r *ApplyTriggerReconciler) hold(
- ctx context.Context, st *reconcileStatus, trigger *configbutleraiv1alpha3.FluxTrigger, held heldState,
+func (r *ApplierClaimReconciler) hold(
+ ctx context.Context, st *reconcileStatus, claim *configbutleraiv1alpha3.FluxClaim, held heldState,
) conditionValue {
- ref := trigger.Applier()
+ ref := claim.Applier()
suspending := func(message string) conditionValue {
return conditionValue{Status: metav1.ConditionFalse, Reason: ReasonSuspending, Message: message}
}
unrecorded := func(err error) conditionValue {
- return keptCondition(trigger.Status.Conditions, ApplyTriggerConditionHeld, fmt.Sprintf(
+ return keptCondition(claim.Status.Conditions, ApplierClaimConditionHeld, fmt.Sprintf(
"cannot record how Kustomization %s stood before it is held: %v", ref, err))
}
+ adopt := adopting(claim, held.operation)
switch {
case !held.suspension.Suspended:
- // Read resumed: the resume can be another trigger's, one that came first in line since the
- // cached read this reconcile decided on. That trigger recorded its operation before it
+ // Read resumed: the resume can be another applier claim's, one that came first in line since the
+ // cached read this reconcile decided on. That applier claim recorded its operation before it
// resumed, so a read made now sees it.
- if other, err := applier.HeldByAnother(ctx, r.uncachedReader(), trigger); err != nil || other != nil {
- message := fmt.Sprintf("cannot check which trigger holds Kustomization %s: %v", ref, err)
- if other != nil {
- message = applier.HeldByAnotherMessage(trigger, other)
- }
- return conditionValue{Status: metav1.ConditionUnknown, Reason: ReasonNotConfigured, Message: message}
+ if other, ok := r.heldElsewhere(ctx, claim); !ok {
+ return other
}
- if err := r.recordPreviousSuspendState(ctx, st, trigger, held.suspension.Held, false); err != nil {
+ if err := r.recordPreviousSuspendState(ctx, st, claim, held.suspension.Held, false); err != nil {
return unrecorded(err)
}
token, err := held.flux.Suspend(ctx, ref)
if err != nil {
- return keptCondition(trigger.Status.Conditions, ApplyTriggerConditionHeld, err.Error())
+ return keptCondition(claim.Status.Conditions, ApplierClaimConditionHeld, err.Error())
}
- r.suspended(trigger, token, heldBefore(trigger) || held.inFlight())
+ r.suspended(claim, token, heldBefore(claim) || held.inFlight())
return suspending(fmt.Sprintf("Kustomization %s is being suspended", ref))
- case !held.suspension.Held:
- // Suspended by somebody else's write (`flux suspend`): take the field over, with a token, so
- // nothing that write let run is taken for idle.
- if err := r.recordPreviousSuspendState(ctx, st, trigger, false, true); err != nil {
- return unrecorded(err)
- }
- if _, err := held.flux.Suspend(ctx, ref); err != nil {
- return keptCondition(trigger.Status.Conditions, ApplyTriggerConditionHeld, err.Error())
- }
- return suspending(fmt.Sprintf("Kustomization %s is being suspended under GitOps Reverser", ref))
+ case !held.suspension.Held || adopt:
+ return r.takeOverSuspend(ctx, st, claim, held)
case !held.suspension.Handled:
return suspending(fmt.Sprintf("Kustomization %s is suspended; waiting for it to handle the suspend", ref))
}
if held.inFlight() {
- completeExternalOperation(trigger, held.operation, held.suspension)
+ completeExternalOperation(claim, held.operation, held.suspension)
}
return conditionValue{Status: metav1.ConditionTrue, Reason: ReasonSuspended,
Message: fmt.Sprintf("Kustomization %s is suspended; GitOps Reverser starts every apply it makes", ref)}
}
+// takeOverSuspend takes over a Kustomization suspended by somebody else's write (`flux suspend`), or
+// kept suspended under GitOps Reverser's field manager by a hand-back with no applier claim holding it
+// now: it suspends it with a token, so nothing that write let run is taken for idle. A kept suspend
+// needs the token most: the hand-back gave its requestedAt up, and Flux still reports the last one
+// handled, so without a new one the suspend would never read as handled.
+func (r *ApplierClaimReconciler) takeOverSuspend(
+ ctx context.Context, st *reconcileStatus, claim *configbutleraiv1alpha3.FluxClaim, held heldState,
+) conditionValue {
+ ref := claim.Applier()
+ if held.suspension.Held {
+ if other, ok := r.heldElsewhere(ctx, claim); !ok {
+ return other
+ }
+ }
+ if err := r.recordPreviousSuspendState(ctx, st, claim, false, true); err != nil {
+ return keptCondition(claim.Status.Conditions, ApplierClaimConditionHeld, fmt.Sprintf(
+ "cannot record how Kustomization %s stood before it is held: %v", ref, err))
+ }
+ if _, err := held.flux.Suspend(ctx, ref); err != nil {
+ return keptCondition(claim.Status.Conditions, ApplierClaimConditionHeld, err.Error())
+ }
+ return conditionValue{Status: metav1.ConditionFalse, Reason: ReasonSuspending,
+ Message: fmt.Sprintf("Kustomization %s is being suspended under GitOps Reverser", ref)}
+}
+
+// heldElsewhere reports, from an uncached read, whether the applier claim's applier is free of any
+// other applier claim's hold, and when it is not (or cannot be told) the Held condition that says so.
+// The cached read Configured was judged on can predate another applier claim coming first in line, and
+// that one's hold looks like a hand-back's or an apply's from the applier alone.
+func (r *ApplierClaimReconciler) heldElsewhere(
+ ctx context.Context, claim configbutleraiv1alpha3.ApplierClaim,
+) (conditionValue, bool) {
+ other, err := applier.HeldByAnother(ctx, r.uncachedReader(), claim)
+ if err == nil && other == nil {
+ return conditionValue{}, true
+ }
+ message := fmt.Sprintf("cannot check which applier claim holds %s %s: %v", claim.ApplierKind(),
+ claim.Applier(), err)
+ if other != nil {
+ message = applier.HeldByAnotherMessage(claim, other)
+ }
+ return conditionValue{Status: metav1.ConditionUnknown, Reason: ReasonNotConfigured, Message: message}, false
+}
+
+// adopting reports whether the applier claim takes its applier anew: it records nothing of how the
+// applier stood, did not last say it held it, and has no operation in flight (operation, read
+// uncached where it matters). A hand-back forgets the record and ends the operation, and a successor
+// is passed its predecessor's record. So a field GitOps Reverser's field manager holds when an
+// applier claim adopts is no hold in place but one a hand-back kept, set to how the applier stood:
+// it is recorded as such, and held anew.
+func adopting(claim configbutleraiv1alpha3.ApplierClaim, operation *configbutleraiv1alpha3.ApplyOperation) bool {
+ return claim.PreviousSuspendState() == "" && !heldBefore(claim) &&
+ (operation == nil || operation.Phase.Completed())
+}
+
// recordPreviousSuspendState records in status.previousSuspendState, on adoption, whether the applier
-// was suspended before the trigger first held it (held: whether GitOps Reverser's field manager holds
+// was suspended before the applier claim first held it (held: whether GitOps Reverser's field manager holds
// it already), which the hand-back puts back. Only on adoption, so the value is neither GitOps
// Reverser's own nor a later resume or switch-on, and only where the adoption goes ahead. It is
// written before the write that holds the applier: after that write the applier no longer shows how
// it stood, so a status write lost after it would lose the value for good.
-func (r *ApplyTriggerReconciler) recordPreviousSuspendState(
- ctx context.Context, st *reconcileStatus, trigger configbutleraiv1alpha3.ApplyTrigger, held, suspended bool,
+func (r *ApplierClaimReconciler) recordPreviousSuspendState(
+ ctx context.Context, st *reconcileStatus, claim configbutleraiv1alpha3.ApplierClaim, held, suspended bool,
) error {
- if held || trigger.PreviousSuspendState() != "" || heldBefore(trigger) {
+ if held || claim.PreviousSuspendState() != "" || heldBefore(claim) {
return nil
}
state := suspendState(suspended)
- resourceVersion, err := r.patchPreviousSuspendState(ctx, trigger, state)
+ resourceVersion, err := r.patchPreviousSuspendState(ctx, claim, state)
if err != nil {
return err
}
- trigger.SetPreviousSuspendState(state)
- trigger.SetResourceVersion(resourceVersion)
- if before, ok := st.before.(configbutleraiv1alpha3.ApplyTrigger); ok {
+ claim.SetPreviousSuspendState(state)
+ claim.SetResourceVersion(resourceVersion)
+ if before, ok := st.before.(configbutleraiv1alpha3.ApplierClaim); ok {
before.SetPreviousSuspendState(state)
}
st.metadataWritten()
return nil
}
-// patchPreviousSuspendState writes state alone to trigger's status.previousSuspendState, guarded by
-// its resourceVersion, and returns the resourceVersion the write left. trigger itself is not changed.
-func (r *ApplyTriggerReconciler) patchPreviousSuspendState(
- ctx context.Context, trigger configbutleraiv1alpha3.ApplyTrigger, state configbutleraiv1alpha3.SuspendState,
+// patchPreviousSuspendState writes state alone to claim's status.previousSuspendState, guarded by
+// its resourceVersion, and returns the resourceVersion the write left. claim itself is not changed.
+func (r *ApplierClaimReconciler) patchPreviousSuspendState(
+ ctx context.Context, claim configbutleraiv1alpha3.ApplierClaim, state configbutleraiv1alpha3.SuspendState,
) (string, error) {
- base, ok := trigger.DeepCopyObject().(configbutleraiv1alpha3.ApplyTrigger)
+ base, ok := claim.DeepCopyObject().(configbutleraiv1alpha3.ApplierClaim)
if !ok {
- return "", fmt.Errorf("a copy of %s %s/%s is no trigger", trigger.TriggerKind(), trigger.GetNamespace(),
- trigger.GetName())
+ return "", fmt.Errorf("a copy of %s %s/%s is no applier claim", claim.ClaimKind(), claim.GetNamespace(),
+ claim.GetName())
}
- patched, _ := base.DeepCopyObject().(configbutleraiv1alpha3.ApplyTrigger)
+ patched, _ := base.DeepCopyObject().(configbutleraiv1alpha3.ApplierClaim)
patched.SetPreviousSuspendState(state)
if err := r.Status().Patch(ctx, patched,
client.MergeFromWithOptions(base, client.MergeFromWithOptimisticLock{})); err != nil {
- return "", fmt.Errorf("write status.previousSuspendState of %s %s/%s: %w", trigger.TriggerKind(),
- trigger.GetNamespace(), trigger.GetName(), err)
+ return "", fmt.Errorf("write status.previousSuspendState of %s %s/%s: %w", claim.ClaimKind(),
+ claim.GetNamespace(), claim.GetName(), err)
}
return patched.GetResourceVersion(), nil
}
-// passPreviousSuspendState gives a successor in line what trigger recorded of how the applier stood
-// before it was held, before trigger lets go: the successor takes over an applier GitOps Reverser
+// passPreviousSuspendState gives a successor in line what claim recorded of how the applier stood
+// before it was held, before claim lets go: the successor takes over an applier GitOps Reverser
// holds already, so it cannot read that for itself, and without it its hand-back would resume a
// Kustomization that was suspended, or switch on an Application that was manual. A successor that
// recorded a value of its own keeps it.
-func (r *ApplyTriggerReconciler) passPreviousSuspendState(
- ctx context.Context, trigger, successor configbutleraiv1alpha3.ApplyTrigger,
+func (r *ApplierClaimReconciler) passPreviousSuspendState(
+ ctx context.Context, claim, successor configbutleraiv1alpha3.ApplierClaim,
) error {
- state := trigger.PreviousSuspendState()
+ state := claim.PreviousSuspendState()
if state == "" || successor.PreviousSuspendState() != "" {
return nil
}
@@ -371,17 +415,17 @@ func suspendState(suspended bool) configbutleraiv1alpha3.SuspendState {
// suspended records a Kustomization just suspended: an adoption, or, for one held before, an
// external resume, recorded as an Unresolved operation that completes once token is handled.
-func (r *ApplyTriggerReconciler) suspended(trigger *configbutleraiv1alpha3.FluxTrigger, token string, resumed bool) {
- ref := trigger.Applier()
+func (r *ApplierClaimReconciler) suspended(claim *configbutleraiv1alpha3.FluxClaim, token string, resumed bool) {
+ ref := claim.Applier()
if !resumed {
- r.event(trigger, corev1.EventTypeNormal, EventReasonAdopted,
+ r.event(claim, corev1.EventTypeNormal, EventReasonAdopted,
"suspended Kustomization %s: from now on GitOps Reverser starts every apply it makes", ref)
return
}
- r.event(trigger, corev1.EventTypeNormal, EventReasonClaimedResourceReconciled,
+ r.event(claim, corev1.EventTypeNormal, EventReasonClaimedResourceReconciled,
"Kustomization %s was resumed outside GitOps Reverser, which suspended it again; the apply "+
"that resume started was not checked against the branch", ref)
- trigger.Status.Operation = &configbutleraiv1alpha3.ApplyOperation{
+ claim.Status.Operation = &configbutleraiv1alpha3.ApplyOperation{
Phase: configbutleraiv1alpha3.ApplyOperationUnresolved, SuspendToken: token,
RequestedAt: metav1.NewTime(time.Now()),
Message: "the Kustomization was resumed outside GitOps Reverser; suspended again, and " +
@@ -391,10 +435,10 @@ func (r *ApplyTriggerReconciler) suspended(trigger *configbutleraiv1alpha3.FluxT
// completeExternalOperation completes recorded, the operation an external resume was recorded as,
// once the suspend after it was handled, with what the Kustomization's last apply reported.
-// recorded is the operation read uncached, which the hold decided on: the cached trigger can still
+// recorded is the operation read uncached, which the hold decided on: the cached applier claim can still
// lack it.
func completeExternalOperation(
- trigger *configbutleraiv1alpha3.FluxTrigger, recorded *configbutleraiv1alpha3.ApplyOperation,
+ claim *configbutleraiv1alpha3.FluxClaim, recorded *configbutleraiv1alpha3.ApplyOperation,
suspension applier.Suspension,
) {
operation := recorded.DeepCopy()
@@ -406,222 +450,222 @@ func completeExternalOperation(
operation.Phase = configbutleraiv1alpha3.ApplyOperationFailed
operation.Message += ": " + suspension.Applied.Message
}
- trigger.Status.Operation = operation
+ claim.Status.Operation = operation
}
-// heldBefore reports whether the trigger last said it held its applier.
-func heldBefore(trigger configbutleraiv1alpha3.ApplyTrigger) bool {
- return apimeta.IsStatusConditionTrue(*trigger.StatusConditions(), ApplyTriggerConditionHeld)
+// heldBefore reports whether the applier claim last said it held its applier.
+func heldBefore(claim configbutleraiv1alpha3.ApplierClaim) bool {
+ return apimeta.IsStatusConditionTrue(*claim.StatusConditions(), ApplierClaimConditionHeld)
}
-// liveOperation reads the trigger's status.operation uncached: a branch worker writes it just before
+// liveOperation reads the applier claim's status.operation uncached: a branch worker writes it just before
// it resumes the Kustomization, and a cached read could still show the operation before it.
-func (r *ApplyTriggerReconciler) liveOperation(
- ctx context.Context, trigger *configbutleraiv1alpha3.FluxTrigger,
+func (r *ApplierClaimReconciler) liveOperation(
+ ctx context.Context, claim *configbutleraiv1alpha3.FluxClaim,
) (*configbutleraiv1alpha3.ApplyOperation, error) {
- live := &configbutleraiv1alpha3.FluxTrigger{}
- if err := r.uncachedReader().Get(ctx, client.ObjectKeyFromObject(trigger), live); err != nil {
+ live := &configbutleraiv1alpha3.FluxClaim{}
+ if err := r.uncachedReader().Get(ctx, client.ObjectKeyFromObject(claim), live); err != nil {
return nil, err
}
return live.Status.Operation, nil
}
// uncachedReader is APIReader, or the client when none is set.
-func (r *ApplyTriggerReconciler) uncachedReader() client.Reader {
+func (r *ApplierClaimReconciler) uncachedReader() client.Reader {
if r.APIReader != nil {
return r.APIReader
}
return r.Client
}
-// fluxClient is the Flux client of the trigger's cluster.
-func (r *ApplyTriggerReconciler) fluxClient(
- ctx context.Context, trigger *configbutleraiv1alpha3.FluxTrigger,
+// fluxClient is the Flux client of the applier claim's cluster.
+func (r *ApplierClaimReconciler) fluxClient(
+ ctx context.Context, claim *configbutleraiv1alpha3.FluxClaim,
) (*applier.Flux, error) {
- dynamicClient, err := r.ApplierClients(ctx, trigger.KubeProviderKey().String())
+ dynamicClient, err := r.ApplierClients(ctx, claim.KubeProviderKey().String())
if err != nil {
return nil, err
}
return applier.NewFlux(dynamicClient), nil
}
-// releaseDeadline is how long a retiring trigger tries to hand its applier back, counted from when
+// releaseDeadline is how long a retiring applier claim tries to hand its applier back, counted from when
// its Held condition turned Releasing, before it lets go anyway with a Warning Event that says what
// the applier is left as. A Kustomization's suspend is handled once the apply running then ends, so
// the deadline is longer than an apply usually takes; a kustomize-controller that is down never
// handles it, and the deletion must not wait on that forever.
const releaseDeadline = 10 * time.Minute
-// retire hands a trigger's applier back, and then lets go of it: a trigger being deleted (check
+// retire hands an applier claim's applier back, and then lets go of it: an applier claim being deleted (check
// nil), or one no eligible GitTarget references any more (check is what the reconcile found). A
-// FluxTrigger suspends its Kustomization once more if it is resumed (a branch worker's apply may
+// FluxClaim suspends its Kustomization once more if it is resumed (a branch worker's apply may
// still be running), waits until that suspend's token is handled, and releases spec.suspend; an
-// ArgoCDTrigger gives its Application's automated sync switch up (releaseStep). Neither hands back
-// what a trigger in line takes over. Until that is done the trigger stays the holder, Held=False
+// ArgoCDClaim gives its Application's automated sync switch up (releaseStep). Neither hands back
+// what an applier claim in line takes over. Until that is done the applier claim stays the holder, Held=False
// (Releasing), so no successor acts on the applier meanwhile. An applier that is gone has nothing
// to hand back; a cluster no client reaches any more since its KubeProvider went (releaseClient),
// and an applier the identity may not write, are let go with a Warning Event: none should hold the
-// trigger forever. Nor does anything else past releaseDeadline. A trigger behind another in line
+// applier claim forever. Nor does anything else past releaseDeadline. An applier claim behind another in line
// for the applier hands nothing back: the applier is that one's. Letting go removes the finalizer;
-// a live trigger then holds nothing until an eligible GitTarget references it again, which adopts
+// a live applier claim then holds nothing until an eligible GitTarget references it again, which adopts
// the applier anew.
-func (r *ApplyTriggerReconciler) retire(
- ctx context.Context, st *reconcileStatus, trigger configbutleraiv1alpha3.ApplyTrigger, check *triggerCheck,
+func (r *ApplierClaimReconciler) retire(
+ ctx context.Context, st *reconcileStatus, claim configbutleraiv1alpha3.ApplierClaim, check *claimCheck,
) (ctrl.Result, error) {
- holder, err := applier.HeldByAnother(ctx, r.Client, trigger)
+ holder, err := applier.HeldByAnother(ctx, r.Client, claim)
if err != nil {
return ctrl.Result{}, err
}
var pending *pendingRelease
if holder != nil {
- // A trigger in line never held the applier: what it holds is the holder's, under the same
- // field manager, so there is nothing of this trigger's to hand back.
- logf.FromContext(ctx).Info("the applier is held by another trigger; handing nothing back",
- "applier", trigger.ApplierKind()+" "+trigger.Applier().String(),
+ // An applier claim in line never held the applier: what it holds is the holder's, under the same
+ // field manager, so there is nothing of this applier claim's to hand back.
+ logf.FromContext(ctx).Info("the applier is held by another applier claim; handing nothing back",
+ "applier", claim.ApplierKind()+" "+claim.Applier().String(),
"holder", holder.GetNamespace()+"/"+holder.GetName())
} else {
- successor, err := r.nextInLine(ctx, trigger)
+ successor, err := r.nextInLine(ctx, claim)
if err != nil {
return ctrl.Result{}, err
}
step, cancel := context.WithTimeout(ctx, applierCheckTimeout)
- pending = r.releaseStep(step, trigger, successor)
+ pending = r.releaseStep(step, claim, successor)
cancel()
}
if pending != nil {
- since := releasingSince(trigger)
+ since := releasingSince(claim)
if time.Since(since) < releaseDeadline {
- r.publishReleasing(st, trigger, check, since)
+ r.publishReleasing(st, claim, check, since)
if err := st.commit(ctx); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{RequeueAfter: st.requeueAfter(pending.result.RequeueAfter)}, pending.err
}
- r.event(trigger, corev1.EventTypeWarning, EventReasonReleaseFailed,
+ r.event(claim, corev1.EventTypeWarning, EventReasonReleaseFailed,
"gave up handing %s %s back to %s %s after it began: %s",
- trigger.ApplierKind(), trigger.Applier(), applierTool(trigger), releaseDeadline, pending.left)
+ claim.ApplierKind(), claim.Applier(), applierTool(claim), releaseDeadline, pending.left)
}
if check == nil {
- controllerutil.RemoveFinalizer(trigger, triggerFinalizer(trigger))
- return ctrl.Result{}, client.IgnoreNotFound(r.Update(ctx, trigger))
+ controllerutil.RemoveFinalizer(claim, claimFinalizer(claim))
+ return ctrl.Result{}, client.IgnoreNotFound(r.Update(ctx, claim))
}
- if err := r.setFinalizer(ctx, st, trigger, false); err != nil {
+ if err := r.setFinalizer(ctx, st, claim, false); err != nil {
return ctrl.Result{}, err
}
- r.letGoOfOperation(trigger)
- clearPreviousSuspendState(trigger)
+ r.letGoOfOperation(claim)
+ clearPreviousSuspendState(claim)
held := conditionValue{Status: metav1.ConditionFalse, Reason: applier.ReasonNotReferenced,
- Message: fmt.Sprintf("nothing to hold: no GitTarget names this trigger, and its %s was handed back",
- trigger.ApplierKind())}
+ Message: fmt.Sprintf("nothing to hold: no GitTarget names this applier claim, and its %s was handed back",
+ claim.ApplierKind())}
if check.refs.Named() {
held = conditionValue{Status: metav1.ConditionUnknown, Reason: ReasonNotConfigured,
Message: fmt.Sprintf("the %s was handed back, and is held again once an eligible GitTarget "+
- "references the trigger", trigger.ApplierKind())}
+ "references the applier claim", claim.ApplierKind())}
}
- st.setValue(ApplyTriggerConditionHeld, held)
- st.applyReadiness(r.triggerReadiness(trigger, check.configured, &held))
+ st.setValue(ApplierClaimConditionHeld, held)
+ st.applyReadiness(r.claimReadiness(claim, check.configured, &held))
if err := st.commit(ctx); err != nil {
return ctrl.Result{}, err
}
- return ctrl.Result{RequeueAfter: st.requeueAfter(heldTriggerSteadyInterval)}, nil
+ return ctrl.Result{RequeueAfter: st.requeueAfter(heldClaimSteadyInterval)}, nil
}
-// clearPreviousSuspendState drops what a trigger recorded of its applier before it held it, once it
+// clearPreviousSuspendState drops what an applier claim recorded of its applier before it held it, once it
// has handed it back (or on to a successor): a later adoption records anew.
-func clearPreviousSuspendState(trigger configbutleraiv1alpha3.ApplyTrigger) {
- trigger.SetPreviousSuspendState("")
+func clearPreviousSuspendState(claim configbutleraiv1alpha3.ApplierClaim) {
+ claim.SetPreviousSuspendState("")
}
-// triggerFinalizer is the finalizer of trigger's kind: the one it carries while it holds its
+// claimFinalizer is the finalizer of applier claim's kind: the one it carries while it holds its
// applier.
-func triggerFinalizer(trigger configbutleraiv1alpha3.ApplyTrigger) string {
- if trigger.TriggerKind() == configbutleraiv1alpha3.ApplyTriggerKindArgoCD {
- return configbutleraiv1alpha3.ArgoCDTriggerFinalizer
+func claimFinalizer(claim configbutleraiv1alpha3.ApplierClaim) string {
+ if claim.ClaimKind() == configbutleraiv1alpha3.ApplierClaimKindArgoCD {
+ return configbutleraiv1alpha3.ArgoCDClaimFinalizer
}
- return configbutleraiv1alpha3.FluxTriggerFinalizer
+ return configbutleraiv1alpha3.FluxClaimFinalizer
}
-// applierTool is the tool a trigger's applier belongs to, which a hand-back hands it back to.
-func applierTool(trigger configbutleraiv1alpha3.ApplyTrigger) string {
- if trigger.TriggerKind() == configbutleraiv1alpha3.ApplyTriggerKindArgoCD {
+// applierTool is the tool an applier claim's applier belongs to, which a hand-back hands it back to.
+func applierTool(claim configbutleraiv1alpha3.ApplierClaim) string {
+ if claim.ClaimKind() == configbutleraiv1alpha3.ApplierClaimKindArgoCD {
return "Argo CD"
}
return "Flux"
}
-// publishReleasing sets Held=False (Releasing) and the trio on a trigger whose hand-back is not
+// publishReleasing sets Held=False (Releasing) and the trio on an applier claim whose hand-back is not
// done, its deadline counted from since. The condition is written anew when it was not Releasing
// before, so its lastTransitionTime says when the release began.
-func (r *ApplyTriggerReconciler) publishReleasing(
- st *reconcileStatus, trigger configbutleraiv1alpha3.ApplyTrigger, check *triggerCheck, since time.Time,
+func (r *ApplierClaimReconciler) publishReleasing(
+ st *reconcileStatus, claim configbutleraiv1alpha3.ApplierClaim, check *claimCheck, since time.Time,
) {
- why := "the trigger is being deleted"
+ why := "the applier claim is being deleted"
if check != nil && check.refs.Named() {
- why = "no GitTarget naming the trigger is eligible any more"
+ why = "no GitTarget naming the applier claim is eligible any more"
} else if check != nil {
- why = "no GitTarget names the trigger any more"
+ why = "no GitTarget names the applier claim any more"
}
held := conditionValue{Status: metav1.ConditionFalse, Reason: ReasonReleasing, Message: fmt.Sprintf(
- "%s: handing %s %s back to %s, by %s at the latest", why, trigger.ApplierKind(), trigger.Applier(),
- applierTool(trigger), since.Add(releaseDeadline).UTC().Format(time.RFC3339))}
- if previous := findCondition(*trigger.StatusConditions(), ApplyTriggerConditionHeld); previous == nil ||
+ "%s: handing %s %s back to %s, by %s at the latest", why, claim.ApplierKind(), claim.Applier(),
+ applierTool(claim), since.Add(releaseDeadline).UTC().Format(time.RFC3339))}
+ if previous := findCondition(*claim.StatusConditions(), ApplierClaimConditionHeld); previous == nil ||
previous.Reason != ReasonReleasing {
- st.remove(ApplyTriggerConditionHeld)
+ st.remove(ApplierClaimConditionHeld)
}
- st.setValue(ApplyTriggerConditionHeld, held)
+ st.setValue(ApplierClaimConditionHeld, held)
if check == nil {
- rd := newReadiness("", trigger.TriggerKind()+" is not stalled")
+ rd := newReadiness("", claim.ClaimKind()+" is not stalled")
rd.progressing(held.Status, held.Reason, held.Message)
st.applyReadiness(rd)
return
}
- st.applyReadiness(r.triggerReadiness(trigger, check.configured, &held))
+ st.applyReadiness(r.claimReadiness(claim, check.configured, &held))
}
-// releasingSince is when the trigger's Held condition turned Releasing, or now when it has not.
-func releasingSince(trigger configbutleraiv1alpha3.ApplyTrigger) time.Time {
- held := findCondition(*trigger.StatusConditions(), ApplyTriggerConditionHeld)
+// releasingSince is when the applier claim's Held condition turned Releasing, or now when it has not.
+func releasingSince(claim configbutleraiv1alpha3.ApplierClaim) time.Time {
+ held := findCondition(*claim.StatusConditions(), ApplierClaimConditionHeld)
if held == nil || held.Reason != ReasonReleasing || held.LastTransitionTime.IsZero() {
return time.Now()
}
return held.LastTransitionTime.Time
}
-// letGoOfOperation completes an operation a retired trigger still records in flight, when this
+// letGoOfOperation completes an operation a retired applier claim still records in flight, when this
// process does not follow it: nobody would complete it, and its applier is handed back.
-func (r *ApplyTriggerReconciler) letGoOfOperation(trigger configbutleraiv1alpha3.ApplyTrigger) {
- operation := trigger.Operation()
+func (r *ApplierClaimReconciler) letGoOfOperation(claim configbutleraiv1alpha3.ApplierClaim) {
+ operation := claim.Operation()
if operation == nil || operation.Phase.Completed() {
return
}
- ref := git.TriggerRef{Kind: trigger.TriggerKind(), Namespace: trigger.GetNamespace(), Name: trigger.GetName()}
+ ref := git.ApplierClaimRef{Kind: claim.ClaimKind(), Namespace: claim.GetNamespace(), Name: claim.GetName()}
if operation.Token != "" && r.Follower != nil && r.Follower.Following(ref, operation.Token) {
return
}
completed := operation.DeepCopy()
finished := metav1.NewTime(time.Now())
completed.Phase, completed.FinishedAt = configbutleraiv1alpha3.ApplyOperationFailed, &finished
- completed.Message = fmt.Sprintf("the trigger handed its %s back before the apply was seen to end",
- trigger.ApplierKind())
- trigger.SetOperation(completed, trigger.RepairPending())
- r.unfollowed.forget(trigger, "")
+ completed.Message = fmt.Sprintf("the applier claim handed its %s back before the apply was seen to end",
+ claim.ApplierKind())
+ claim.SetOperation(completed, claim.RepairPending())
+ r.unfollowed.forget(claim, "")
}
-// setFinalizer puts the trigger's finalizer on it, or takes it off, and carries the metadata that
+// setFinalizer puts the applier claim's finalizer on it, or takes it off, and carries the metadata that
// write left into the status session, so the status patch that follows is made against it. The
// finalizer is what hands the applier back, so it is in place before the first write that holds it,
-// and off once the trigger has handed the applier back or over.
-func (r *ApplyTriggerReconciler) setFinalizer(
- ctx context.Context, st *reconcileStatus, trigger configbutleraiv1alpha3.ApplyTrigger, present bool,
+// and off once the applier claim has handed the applier back or over.
+func (r *ApplierClaimReconciler) setFinalizer(
+ ctx context.Context, st *reconcileStatus, claim configbutleraiv1alpha3.ApplierClaim, present bool,
) error {
- update, ok := trigger.DeepCopyObject().(configbutleraiv1alpha3.ApplyTrigger)
+ update, ok := claim.DeepCopyObject().(configbutleraiv1alpha3.ApplierClaim)
if !ok {
- return fmt.Errorf("a copy of %s %s/%s is no trigger", trigger.TriggerKind(), trigger.GetNamespace(),
- trigger.GetName())
+ return fmt.Errorf("a copy of %s %s/%s is no applier claim", claim.ClaimKind(), claim.GetNamespace(),
+ claim.GetName())
}
- changed := controllerutil.RemoveFinalizer(update, triggerFinalizer(trigger))
+ changed := controllerutil.RemoveFinalizer(update, claimFinalizer(claim))
if present {
- changed = controllerutil.AddFinalizer(update, triggerFinalizer(trigger))
+ changed = controllerutil.AddFinalizer(update, claimFinalizer(claim))
}
if !changed {
return nil
@@ -629,29 +673,29 @@ func (r *ApplyTriggerReconciler) setFinalizer(
if err := r.Update(ctx, update); err != nil {
return err
}
- trigger.SetFinalizers(update.GetFinalizers())
- trigger.SetResourceVersion(update.GetResourceVersion())
+ claim.SetFinalizers(update.GetFinalizers())
+ claim.SetResourceVersion(update.GetResourceVersion())
st.metadataWritten()
return nil
}
-// nextInLine is the trigger that takes the applier over once trigger is gone, or nil when no
-// trigger will: one in line that is not being deleted, and whose Configured is True or False only
-// because trigger holds the applier (HeldByAnotherTrigger is checked last, so such a trigger
+// nextInLine is the applier claim that takes the applier over once claim is gone, or nil when no
+// applier claim will: one in line that is not being deleted, and whose Configured is True or False only
+// because claim holds the applier (HeldByAnotherClaim is checked last, so such an applier claim
// passed every other check).
-func (r *ApplyTriggerReconciler) nextInLine(
- ctx context.Context, trigger configbutleraiv1alpha3.ApplyTrigger,
-) (configbutleraiv1alpha3.ApplyTrigger, error) {
- others, err := applier.TriggersOfApplier(ctx, r.Client, trigger)
+func (r *ApplierClaimReconciler) nextInLine(
+ ctx context.Context, claim configbutleraiv1alpha3.ApplierClaim,
+) (configbutleraiv1alpha3.ApplierClaim, error) {
+ others, err := applier.ClaimsOfApplier(ctx, r.Client, claim)
if err != nil {
return nil, err
}
- var next configbutleraiv1alpha3.ApplyTrigger
+ var next configbutleraiv1alpha3.ApplierClaim
var nextCreated metav1.Time
for _, other := range others {
- configured := findCondition(*other.StatusConditions(), ApplyTriggerConditionConfigured)
+ configured := findCondition(*other.StatusConditions(), ApplierClaimConditionConfigured)
takesOver := configured != nil && (configured.Status == metav1.ConditionTrue ||
- configured.Reason == applier.ReasonHeldByAnotherTrigger)
+ configured.Reason == applier.ReasonHeldByAnotherClaim)
created := other.GetCreationTimestamp()
if takesOver && other.GetDeletionTimestamp().IsZero() && (next == nil || created.Before(&nextCreated)) {
next, nextCreated = other, created
@@ -661,42 +705,42 @@ func (r *ApplyTriggerReconciler) nextInLine(
}
// pendingRelease is a hand-back that is not done: what to retry it on, and what the applier is
-// left as should the trigger go now.
+// left as should the applier claim go now.
type pendingRelease struct {
result ctrl.Result
err error
left string
}
-// releaseStep takes the hand-back of trigger's applier one step, and returns nil once the trigger
+// releaseStep takes the hand-back of applier claim's applier one step, and returns nil once the applier claim
// may go.
-func (r *ApplyTriggerReconciler) releaseStep(
- ctx context.Context, trigger, successor configbutleraiv1alpha3.ApplyTrigger,
+func (r *ApplierClaimReconciler) releaseStep(
+ ctx context.Context, claim, successor configbutleraiv1alpha3.ApplierClaim,
) *pendingRelease {
if successor != nil {
- if err := r.passPreviousSuspendState(ctx, trigger, successor); err != nil {
+ if err := r.passPreviousSuspendState(ctx, claim, successor); err != nil {
return &pendingRelease{err: err, left: fmt.Sprintf("it is left held, and %s %s/%s, which holds it "+
- "next, does not know how it stood before it was held (%v)", successor.TriggerKind(),
+ "next, does not know how it stood before it was held (%v)", successor.ClaimKind(),
successor.GetNamespace(), successor.GetName(), err)}
}
}
- switch t := trigger.(type) {
- case *configbutleraiv1alpha3.FluxTrigger:
+ switch t := claim.(type) {
+ case *configbutleraiv1alpha3.FluxClaim:
return r.releaseKustomization(ctx, t, successor)
- case *configbutleraiv1alpha3.ArgoCDTrigger:
+ case *configbutleraiv1alpha3.ArgoCDClaim:
return r.releaseAutomatedSync(ctx, t, successor)
}
return nil
}
-// releaseKustomization takes a FluxTrigger's hand-back one step. With a successor (nextInLine), the
+// releaseKustomization takes a FluxClaim's hand-back one step. With a successor (nextInLine), the
// Kustomization is not released but left suspended, its suspend handled, for it: released, Flux
// would apply it at once, unchecked against the branch, before the successor's adoption could
// suspend it again.
-func (r *ApplyTriggerReconciler) releaseKustomization(
- ctx context.Context, trigger *configbutleraiv1alpha3.FluxTrigger, successor configbutleraiv1alpha3.ApplyTrigger,
+func (r *ApplierClaimReconciler) releaseKustomization(
+ ctx context.Context, claim *configbutleraiv1alpha3.FluxClaim, successor configbutleraiv1alpha3.ApplierClaim,
) *pendingRelease {
- dynamicClient, err := r.releaseClient(ctx, trigger)
+ dynamicClient, err := r.releaseClient(ctx, claim)
if err != nil {
return &pendingRelease{
err: err, left: fmt.Sprintf("its cluster cannot be reached (%v), so it is left as it was", err),
@@ -706,9 +750,9 @@ func (r *ApplyTriggerReconciler) releaseKustomization(
return nil
}
flux := applier.NewFlux(dynamicClient)
- ref := trigger.Applier()
+ ref := claim.Applier()
suspending := &pendingRelease{
- result: ctrl.Result{RequeueAfter: applyTriggerSuspendingInterval},
+ result: ctrl.Result{RequeueAfter: applierClaimSuspendingInterval},
left: "it is suspended, and has not handled the suspend yet (is kustomize-controller running?); " +
"`flux resume` resumes it",
}
@@ -717,12 +761,12 @@ func (r *ApplyTriggerReconciler) releaseKustomization(
case apierrors.IsNotFound(err) || apimeta.IsNoMatchError(err):
return nil
case err != nil:
- return r.releaseFailed(trigger, err, fmt.Sprintf("it cannot be read (%v), so it is left as it was", err))
+ return r.releaseFailed(claim, err, fmt.Sprintf("it cannot be read (%v), so it is left as it was", err))
case !suspension.Held:
return nil
case !suspension.Suspended:
if _, err := flux.Suspend(ctx, ref); err != nil {
- return r.releaseFailed(trigger, err, fmt.Sprintf("it is resumed, and suspending it failed (%v), so "+
+ return r.releaseFailed(claim, err, fmt.Sprintf("it is resumed, and suspending it failed (%v), so "+
"GitOps Reverser's field manager still holds spec.suspend", err))
}
return suspending
@@ -730,117 +774,117 @@ func (r *ApplyTriggerReconciler) releaseKustomization(
return suspending
}
if successor != nil {
- r.event(trigger, corev1.EventTypeNormal, EventReasonReleased,
- "left Kustomization %s suspended for %s %s/%s, which holds it next", ref, successor.TriggerKind(),
+ r.event(claim, corev1.EventTypeNormal, EventReasonReleased,
+ "left Kustomization %s suspended for %s %s/%s, which holds it next", ref, successor.ClaimKind(),
successor.GetNamespace(), successor.GetName())
return nil
}
- return r.handBackKustomization(ctx, flux, trigger)
+ return r.handBackKustomization(ctx, flux, claim)
}
-// handBackKustomization releases a FluxTrigger's Kustomization, suspended and with its suspend
+// handBackKustomization releases a FluxClaim's Kustomization, suspended and with its suspend
// handled, as status.previousSuspendState recorded it, with a Released Event that says how it is left.
-func (r *ApplyTriggerReconciler) handBackKustomization(
- ctx context.Context, flux *applier.Flux, trigger *configbutleraiv1alpha3.FluxTrigger,
+func (r *ApplierClaimReconciler) handBackKustomization(
+ ctx context.Context, flux *applier.Flux, claim *configbutleraiv1alpha3.FluxClaim,
) *pendingRelease {
- ref := trigger.Applier()
- wasSuspended := trigger.Status.PreviousSuspendState == configbutleraiv1alpha3.SuspendStateSuspended
+ ref := claim.Applier()
+ wasSuspended := claim.Status.PreviousSuspendState == configbutleraiv1alpha3.SuspendStateSuspended
keeper, err := flux.Release(ctx, ref, wasSuspended)
if err != nil {
- return r.releaseFailed(trigger, err, fmt.Sprintf("it is suspended, and releasing spec.suspend failed (%v); "+
+ return r.releaseFailed(claim, err, fmt.Sprintf("it is suspended, and releasing spec.suspend failed (%v); "+
"`flux resume` resumes it", err))
}
if wasSuspended {
- r.event(trigger, corev1.EventTypeNormal, EventReasonReleased,
+ r.event(claim, corev1.EventTypeNormal, EventReasonReleased,
"handed Kustomization %s back to Flux: it stays suspended, as it was before GitOps Reverser held it; "+
"`flux resume` resumes it", ref)
return nil
}
if keeper != "" {
- r.event(trigger, corev1.EventTypeNormal, EventReasonReleased,
+ r.event(claim, corev1.EventTypeNormal, EventReasonReleased,
"handed Kustomization %s back to Flux: it stays suspended, because field manager %q also sets "+
"spec.suspend", ref, keeper)
return nil
}
- r.event(trigger, corev1.EventTypeNormal, EventReasonReleased,
+ r.event(claim, corev1.EventTypeNormal, EventReasonReleased,
"handed Kustomization %s back to Flux: it is resumed, and applies on its own again", ref)
return nil
}
// releaseFailed is a hand-back step that failed with err: one the identity may not make lets the
-// trigger go with a Warning Event, since a retry cannot change it, and any other is retried.
-func (r *ApplyTriggerReconciler) releaseFailed(
- trigger configbutleraiv1alpha3.ApplyTrigger, err error, left string,
+// applier claim go with a Warning Event, since a retry cannot change it, and any other is retried.
+func (r *ApplierClaimReconciler) releaseFailed(
+ claim configbutleraiv1alpha3.ApplierClaim, err error, left string,
) *pendingRelease {
if apierrors.IsForbidden(err) {
- r.event(trigger, corev1.EventTypeWarning, EventReasonReleaseFailed,
- "could not hand %s %s back to %s: %v", trigger.ApplierKind(), trigger.Applier(), applierTool(trigger), err)
+ r.event(claim, corev1.EventTypeWarning, EventReasonReleaseFailed,
+ "could not hand %s %s back to %s: %v", claim.ApplierKind(), claim.Applier(), applierTool(claim), err)
return nil
}
return &pendingRelease{err: err, left: left}
}
-// releaseClient is the client of the cluster a retiring trigger hands its applier back in, or nil
+// releaseClient is the client of the cluster a retiring applier claim hands its applier back in, or nil
// when there is nothing to reach: no cluster clients are wired, or the KubeProvider is gone and no
-// client reaches its cluster any more, which lets the trigger go with a Warning Event. A
+// client reaches its cluster any more, which lets the applier claim go with a Warning Event. A
// KubeProvider that is gone does not end the hand-back by itself: an inCluster one named the
// operator's own cluster, which may still answer, and a remote one may still be reached through the
// client it left.
-func (r *ApplyTriggerReconciler) releaseClient(
- ctx context.Context, trigger configbutleraiv1alpha3.ApplyTrigger,
+func (r *ApplierClaimReconciler) releaseClient(
+ ctx context.Context, claim configbutleraiv1alpha3.ApplierClaim,
) (dynamic.Interface, error) {
if r.ApplierClients == nil {
return nil, nil //nolint:nilnil // nothing to reach is no error
}
provider := &configbutleraiv1alpha3.KubeProvider{}
- err := r.Get(ctx, trigger.KubeProviderKey(), provider)
+ err := r.Get(ctx, claim.KubeProviderKey(), provider)
gone := apierrors.IsNotFound(err)
if err != nil && !gone {
return nil, err
}
- dynamicClient, err := r.ApplierClients(ctx, trigger.KubeProviderKey().String())
+ dynamicClient, err := r.ApplierClients(ctx, claim.KubeProviderKey().String())
switch {
case err != nil && gone:
- r.event(trigger, corev1.EventTypeWarning, EventReasonReleaseFailed,
- "could not hand %s %s back to %s: its KubeProvider %s is gone; %s", trigger.ApplierKind(),
- trigger.Applier(), applierTool(trigger), trigger.KubeProviderKey(), leftHeld(trigger))
+ r.event(claim, corev1.EventTypeWarning, EventReasonReleaseFailed,
+ "could not hand %s %s back to %s: its KubeProvider %s is gone; %s", claim.ApplierKind(),
+ claim.Applier(), applierTool(claim), claim.KubeProviderKey(), leftHeld(claim))
return nil, nil //nolint:nilnil // nothing to reach, and the Event says so
case err != nil:
- return nil, fmt.Errorf("reach the cluster of %s %s to hand it back: %w", trigger.ApplierKind(),
- trigger.Applier(), err)
+ return nil, fmt.Errorf("reach the cluster of %s %s to hand it back: %w", claim.ApplierKind(),
+ claim.Applier(), err)
}
return dynamicClient, nil
}
-// leftHeld says what an applier a trigger could not hand back is left as, and how a human hands it
+// leftHeld says what an applier an applier claim could not hand back is left as, and how a human hands it
// back.
-func leftHeld(trigger configbutleraiv1alpha3.ApplyTrigger) string {
- if trigger.TriggerKind() == configbutleraiv1alpha3.ApplyTriggerKindArgoCD {
+func leftHeld(claim configbutleraiv1alpha3.ApplierClaim) string {
+ if claim.ClaimKind() == configbutleraiv1alpha3.ApplierClaimKindArgoCD {
return "its automated sync is left off, and switching it on in Argo CD has it sync on its own again"
}
return "it is left suspended, and `flux resume` resumes it"
}
-// event records an Event on the trigger, when there is a recorder.
-func (r *ApplyTriggerReconciler) event(object client.Object, eventType, reason, messageFmt string, args ...any) {
+// event records an Event on the applier claim, when there is a recorder.
+func (r *ApplierClaimReconciler) event(object client.Object, eventType, reason, messageFmt string, args ...any) {
if r.Recorder != nil {
r.Recorder.Eventf(object, eventType, reason, messageFmt, args...)
}
}
-// publishConsumers sets the trigger's status.source.otherConsumers: the consumers of its GitRepository
-// in the applier's cluster, other than its own Kustomization, that no FluxTrigger on the same
+// publishConsumers sets the applier claim's status.source.otherConsumers: the consumers of its GitRepository
+// in the applier's cluster, other than its own Kustomization, that no FluxClaim on the same
// KubeProvider holds. A listing that fails publishes none. Information only (decision 2): it never
// affects Ready.
-func (r *ApplyTriggerReconciler) publishConsumers(ctx context.Context, trigger *configbutleraiv1alpha3.FluxTrigger) {
- source := trigger.Status.Source
+func (r *ApplierClaimReconciler) publishConsumers(ctx context.Context, claim *configbutleraiv1alpha3.FluxClaim) {
+ source := claim.Status.Source
if source == nil || r.ApplierClients == nil {
return
}
source.OtherConsumers = nil
ctx, cancel := context.WithTimeout(ctx, applierCheckTimeout)
defer cancel()
- flux, err := r.fluxClient(ctx, trigger)
+ flux, err := r.fluxClient(ctx, claim)
if err != nil {
return
}
@@ -852,13 +896,13 @@ func (r *ApplyTriggerReconciler) publishConsumers(ctx context.Context, trigger *
"gitRepository", source.GitRepository.Namespace+"/"+source.GitRepository.Name, "error", err.Error())
return
}
- others, err := r.heldKustomizations(ctx, trigger.KubeProviderKey())
+ others, err := r.heldKustomizations(ctx, claim.KubeProviderKey())
if err != nil {
return
}
for _, consumer := range consumers {
key := k8stypes.NamespacedName{Namespace: consumer.Namespace, Name: consumer.Name}
- itself := key == trigger.Applier()
+ itself := key == claim.Applier()
if consumer.Kind == configbutleraiv1alpha3.ApplierKindKustomization && (itself || others[key]) {
continue
}
@@ -866,22 +910,22 @@ func (r *ApplyTriggerReconciler) publishConsumers(ctx context.Context, trigger *
}
}
-// heldKustomizations is every Kustomization a FluxTrigger on the KubeProvider holds: one whose
-// Held condition is True. A trigger that is not Configured (SuspendOwnedElsewhere), or whose
+// heldKustomizations is every Kustomization a FluxClaim on the KubeProvider holds: one whose
+// Held condition is True. An applier claim that is not Configured (SuspendOwnedElsewhere), or whose
// Kustomization has not handled its suspend yet, does not hold it: Flux may still apply a new artifact on its own.
-func (r *ApplyTriggerReconciler) heldKustomizations(
+func (r *ApplierClaimReconciler) heldKustomizations(
ctx context.Context,
cluster k8stypes.NamespacedName,
) (map[k8stypes.NamespacedName]bool, error) {
- var triggers configbutleraiv1alpha3.FluxTriggerList
- if err := r.List(ctx, &triggers); err != nil {
+ var claims configbutleraiv1alpha3.FluxClaimList
+ if err := r.List(ctx, &claims); err != nil {
return nil, err
}
held := map[k8stypes.NamespacedName]bool{}
- for i := range triggers.Items {
- trigger := &triggers.Items[i]
- if trigger.DeletionTimestamp.IsZero() && trigger.KubeProviderKey() == cluster && heldBefore(trigger) {
- held[trigger.Applier()] = true
+ for i := range claims.Items {
+ claim := &claims.Items[i]
+ if claim.DeletionTimestamp.IsZero() && claim.KubeProviderKey() == cluster && heldBefore(claim) {
+ held[claim.Applier()] = true
}
}
return held, nil
diff --git a/internal/controller/fluxtrigger_hold_test.go b/internal/controller/fluxclaim_hold_test.go
similarity index 60%
rename from internal/controller/fluxtrigger_hold_test.go
rename to internal/controller/fluxclaim_hold_test.go
index be5fe9b91..5477edd6a 100644
--- a/internal/controller/fluxtrigger_hold_test.go
+++ b/internal/controller/fluxclaim_hold_test.go
@@ -33,21 +33,21 @@ import (
var checkoutKustomization = k8stypes.NamespacedName{Namespace: "flux-system", Name: "checkout"}
-// holdFixture is a FluxTrigger team-a/checkout over a simulated Flux cluster holding objs, with
+// holdFixture is a FluxClaim team-a/checkout over a simulated Flux cluster holding objs, with
// Events recorded.
type holdFixture struct {
- r *ApplyTriggerReconciler
+ r *ApplierClaimReconciler
sim *fluxsim.Sim
recorder *record.FakeRecorder
}
-func newHoldFixture(t *testing.T, trigger *configbutleraiv1alpha3.FluxTrigger, objs ...runtime.Object) *holdFixture {
+func newHoldFixture(t *testing.T, claim *configbutleraiv1alpha3.FluxClaim, objs ...runtime.Object) *holdFixture {
t.Helper()
if objs == nil {
objs = applierFluxObjects()
}
sim := applierSim(t, objs...)
- r := triggerReconciler(triggerTestClient(t, trigger), configbutleraiv1alpha3.ApplyTriggerKindFlux, sim.Client, nil,
+ r := claimReconciler(claimTestClient(t, claim), configbutleraiv1alpha3.ApplierClaimKindFlux, sim.Client, nil,
true)
recorder := record.NewFakeRecorder(32)
r.Recorder = recorder
@@ -75,11 +75,11 @@ func (f *holdFixture) events() []string {
}
}
-func (f *holdFixture) trigger(t *testing.T) *configbutleraiv1alpha3.FluxTrigger {
+func (f *holdFixture) claim(t *testing.T) *configbutleraiv1alpha3.FluxClaim {
t.Helper()
- trigger := &configbutleraiv1alpha3.FluxTrigger{}
- require.NoError(t, f.r.Get(context.Background(), triggerKey, trigger))
- return trigger
+ claim := &configbutleraiv1alpha3.FluxClaim{}
+ require.NoError(t, f.r.Get(context.Background(), claimKey, claim))
+ return claim
}
func suspendedSpec(ks *unstructured.Unstructured) bool {
@@ -87,15 +87,15 @@ func suspendedSpec(ks *unstructured.Unstructured) bool {
return value
}
-// TestFluxTrigger_AdoptsItsKustomization: a Configured trigger suspends its Kustomization under GitOps
+// TestFluxClaim_AdoptsItsKustomization: a Configured applier claim suspends its Kustomization under GitOps
// Reverser's field manager, and is Ready only once that suspend's token is handled, so nothing that
// ran before the adoption is still running.
-func TestFluxTrigger_AdoptsItsKustomization(t *testing.T) {
- f := newHoldFixture(t, testFluxTrigger(triggerLocalProvider))
+func TestFluxClaim_AdoptsItsKustomization(t *testing.T) {
+ f := newHoldFixture(t, testFluxClaim(claimLocalProvider))
- got := reconcileTrigger(t, f.r).(*configbutleraiv1alpha3.FluxTrigger)
+ got := reconcileClaim(t, f.r).(*configbutleraiv1alpha3.FluxClaim)
- held := findCondition(got.Status.Conditions, ApplyTriggerConditionHeld)
+ held := findCondition(got.Status.Conditions, ApplierClaimConditionHeld)
require.NotNil(t, held)
assert.Equal(t, metav1.ConditionTrue, held.Status, held.Message)
assert.Equal(t, ReasonSuspended, held.Reason)
@@ -111,12 +111,12 @@ func TestFluxTrigger_AdoptsItsKustomization(t *testing.T) {
events[0])
}
-// TestFluxTrigger_AnExternalResumeIsSuspendedAgain is decision 3: `flux resume` on a held
+// TestFluxClaim_AnExternalResumeIsSuspendedAgain is decision 3: `flux resume` on a held
// Kustomization is suspended again with a token, every time, with a Normal Event each time, and
// recorded as an Unresolved operation that completes once nothing that resume started is running.
-func TestFluxTrigger_AnExternalResumeIsSuspendedAgain(t *testing.T) {
- f := newHoldFixture(t, testFluxTrigger(triggerLocalProvider))
- reconcileTrigger(t, f.r)
+func TestFluxClaim_AnExternalResumeIsSuspendedAgain(t *testing.T) {
+ f := newHoldFixture(t, testFluxClaim(claimLocalProvider))
+ reconcileClaim(t, f.r)
f.events()
for round := range 2 {
@@ -127,9 +127,9 @@ func TestFluxTrigger_AnExternalResumeIsSuspendedAgain(t *testing.T) {
require.Eventually(t, func() bool { return len(f.sim.Applied()) == round+1 }, 5*time.Second,
time.Millisecond, "Flux applies the moment it is resumed")
- _, err = f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ _, err = f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
- recorded := f.trigger(t).Status.Operation
+ recorded := f.claim(t).Status.Operation
require.NotNil(t, recorded, "round %d", round)
assert.Equal(t, configbutleraiv1alpha3.ApplyOperationUnresolved, recorded.Phase)
assert.NotEmpty(t, recorded.SuspendToken)
@@ -140,83 +140,83 @@ func TestFluxTrigger_AnExternalResumeIsSuspendedAgain(t *testing.T) {
assert.True(t, strings.HasPrefix(events[0], "Normal ClaimedResourceReconciled "+
"Kustomization flux-system/checkout was resumed outside GitOps Reverser"), events[0])
- got := reconcileTrigger(t, f.r).(*configbutleraiv1alpha3.FluxTrigger)
+ got := reconcileClaim(t, f.r).(*configbutleraiv1alpha3.FluxClaim)
require.NotNil(t, got.Status.Operation)
assert.Equal(t, configbutleraiv1alpha3.ApplyOperationSucceeded, got.Status.Operation.Phase,
"complete once the suspend's token is handled")
assert.NotNil(t, got.Status.Operation.FinishedAt)
assert.Equal(t, recorded.SuspendToken, got.Status.Operation.SuspendToken)
- assert.True(t, conditionIsTrue(got.Status.Conditions, ApplyTriggerConditionHeld))
+ assert.True(t, conditionIsTrue(got.Status.Conditions, ApplierClaimConditionHeld))
}
assert.Len(t, f.sim.Applied(), 2, "each resume applied once, unchecked; that is what the record says")
}
-// TestFluxTrigger_LeavesABranchWorkersOperationAlone: a worker records its operation before it
+// TestFluxClaim_LeavesABranchWorkersOperationAlone: a worker records its operation before it
// resumes the Kustomization, so a resumed Kustomization with an operation of the worker's in flight
// is that operation, not an external resume; the worker suspends it again itself.
-func TestFluxTrigger_LeavesABranchWorkersOperationAlone(t *testing.T) {
- trigger := testFluxTrigger(triggerLocalProvider)
- trigger.Finalizers = []string{configbutleraiv1alpha3.FluxTriggerFinalizer}
- trigger.Status.Conditions = []metav1.Condition{{
- Type: ApplyTriggerConditionHeld, Status: metav1.ConditionTrue, Reason: ReasonSuspended,
+func TestFluxClaim_LeavesABranchWorkersOperationAlone(t *testing.T) {
+ claim := testFluxClaim(claimLocalProvider)
+ claim.Finalizers = []string{configbutleraiv1alpha3.FluxClaimFinalizer}
+ claim.Status.Conditions = []metav1.Condition{{
+ Type: ApplierClaimConditionHeld, Status: metav1.ConditionTrue, Reason: ReasonSuspended,
LastTransitionTime: metav1.Now(),
}}
- trigger.Status.Operation = &configbutleraiv1alpha3.ApplyOperation{
+ claim.Status.Operation = &configbutleraiv1alpha3.ApplyOperation{
Phase: configbutleraiv1alpha3.ApplyOperationRunning, Reason: configbutleraiv1alpha3.ApplyReasonRefusal,
Token: "T1", RequestedAt: metav1.Now(),
}
- f := newHoldFixture(t, trigger)
+ f := newHoldFixture(t, claim)
- _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
assert.False(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)), "left resumed for the worker")
assert.Empty(t, f.events())
- assert.Equal(t, "T1", f.trigger(t).Status.Operation.Token)
+ assert.Equal(t, "T1", f.claim(t).Status.Operation.Token)
}
// followerStub answers Following for the operations it lists, by token.
type followerStub map[string]bool
-func (f followerStub) Following(_ git.TriggerRef, token string) bool { return f[token] }
+func (f followerStub) Following(_ git.ApplierClaimRef, token string) bool { return f[token] }
-// workerOperationTrigger is a held trigger whose worker recorded operation T1 in flight, the
+// workerOperationClaim is a held applier claim whose worker recorded operation T1 in flight, the
// Kustomization left resumed for it.
-func workerOperationTrigger() *configbutleraiv1alpha3.FluxTrigger {
- trigger := testFluxTrigger(triggerLocalProvider)
- trigger.Finalizers = []string{configbutleraiv1alpha3.FluxTriggerFinalizer}
- trigger.Status.Conditions = []metav1.Condition{{
- Type: ApplyTriggerConditionHeld, Status: metav1.ConditionTrue, Reason: ReasonSuspended,
+func workerOperationClaim() *configbutleraiv1alpha3.FluxClaim {
+ claim := testFluxClaim(claimLocalProvider)
+ claim.Finalizers = []string{configbutleraiv1alpha3.FluxClaimFinalizer}
+ claim.Status.Conditions = []metav1.Condition{{
+ Type: ApplierClaimConditionHeld, Status: metav1.ConditionTrue, Reason: ReasonSuspended,
LastTransitionTime: metav1.Now(),
}}
- trigger.Status.Operation = &configbutleraiv1alpha3.ApplyOperation{
+ claim.Status.Operation = &configbutleraiv1alpha3.ApplyOperation{
Phase: configbutleraiv1alpha3.ApplyOperationRunning, Reason: configbutleraiv1alpha3.ApplyReasonRefusal,
Token: "T1", RequestedAt: metav1.Now(),
}
- return trigger
+ return claim
}
-// TestFluxTrigger_AnOperationNobodyFollowsIsSuspended is review 4's B backstop: the reconciler left a
+// TestFluxClaim_AnOperationNobodyFollowsIsSuspended is review 4's B backstop: the reconciler left a
// Kustomization resumed for any worker operation in flight, for ever, though nothing followed it
// (abandoned, undelivered, lost at startup). Now it leaves it only while this process follows the
// operation, or for a grace after it first saw nobody following it; past that it suspends the
// Kustomization, and completes the operation Failed once the suspend is handled.
-func TestFluxTrigger_AnOperationNobodyFollowsIsSuspended(t *testing.T) {
- f := newHoldFixture(t, workerOperationTrigger())
+func TestFluxClaim_AnOperationNobodyFollowsIsSuspended(t *testing.T) {
+ f := newHoldFixture(t, workerOperationClaim())
f.r.UnfollowedGrace = time.Nanosecond
f.r.Follower = followerStub{"T1": true}
- _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
assert.False(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)), "followed: left to the worker")
- applying := &configbutleraiv1alpha3.FluxTrigger{}
- require.NoError(t, f.r.Get(context.Background(), triggerKey, applying))
- held := findCondition(applying.Status.Conditions, ApplyTriggerConditionHeld)
+ applying := &configbutleraiv1alpha3.FluxClaim{}
+ require.NoError(t, f.r.Get(context.Background(), claimKey, applying))
+ held := findCondition(applying.Status.Conditions, ApplierClaimConditionHeld)
require.NotNil(t, held)
assert.Equal(t, metav1.ConditionTrue, held.Status, "an operation of ours in flight is held")
assert.Equal(t, ReasonApplying, held.Reason)
f.r.Follower = followerStub{}
- got := reconcileTrigger(t, f.r).(*configbutleraiv1alpha3.FluxTrigger)
+ got := reconcileClaim(t, f.r).(*configbutleraiv1alpha3.FluxClaim)
assert.True(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)), "followed by nobody: suspended")
require.NotNil(t, got.Status.Operation)
assert.Equal(t, configbutleraiv1alpha3.ApplyOperationFailed, got.Status.Operation.Phase)
@@ -224,13 +224,13 @@ func TestFluxTrigger_AnOperationNobodyFollowsIsSuspended(t *testing.T) {
assert.NotEmpty(t, got.Status.Operation.SuspendToken)
assert.NotNil(t, got.Status.Operation.FinishedAt)
assert.Contains(t, got.Status.Operation.Message, "followed by nobody")
- assert.True(t, conditionIsTrue(got.Status.Conditions, ApplyTriggerConditionHeld))
+ assert.True(t, conditionIsTrue(got.Status.Conditions, ApplierClaimConditionHeld))
events := f.events()
require.Len(t, events, 1)
assert.True(t, strings.HasPrefix(events[0], "Warning OperationUnfollowed the Refusal apply"), events[0])
}
-// operationFlipper is an APIReader whose reads of the trigger show its operation in flight until the
+// operationFlipper is an APIReader whose reads of the applier claim show its operation in flight until the
// Kustomization was read to be held (the second GET of it, after the Configured check), and completed
// after, as a worker that completes it in between leaves it.
type operationFlipper struct {
@@ -245,19 +245,19 @@ func (o *operationFlipper) Get(
if err := o.Reader.Get(ctx, key, obj, opts...); err != nil {
return err
}
- if trigger, ok := obj.(*configbutleraiv1alpha3.FluxTrigger); ok && trigger.Status.Operation != nil &&
+ if claim, ok := obj.(*configbutleraiv1alpha3.FluxClaim); ok && claim.Status.Operation != nil &&
o.kustomizationReads.Load() >= 2 {
- trigger.Status.Operation.Phase = configbutleraiv1alpha3.ApplyOperationSucceeded
+ claim.Status.Operation.Phase = configbutleraiv1alpha3.ApplyOperationSucceeded
}
return nil
}
-// TestFluxTrigger_AnOperationThatCompletesWhileReadIsNoExternalResume is review 4's F, the
+// TestFluxClaim_AnOperationThatCompletesWhileReadIsNoExternalResume is review 4's F, the
// reconciler's half: the Kustomization read resumed for a worker's operation, which completed before
// the operation was read, looked like an external resume. Now the operation is read before and
// after the Kustomization, and a move between the two reads again.
-func TestFluxTrigger_AnOperationThatCompletesWhileReadIsNoExternalResume(t *testing.T) {
- f := newHoldFixture(t, workerOperationTrigger())
+func TestFluxClaim_AnOperationThatCompletesWhileReadIsNoExternalResume(t *testing.T) {
+ f := newHoldFixture(t, workerOperationClaim())
flipper := &operationFlipper{Reader: f.r.Client}
f.r.APIReader = flipper
f.sim.Client.PrependReactor("get", "kustomizations", func(k8stesting.Action) (bool, runtime.Object, error) {
@@ -265,58 +265,58 @@ func TestFluxTrigger_AnOperationThatCompletesWhileReadIsNoExternalResume(t *test
return false, nil, nil
})
- _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
assert.False(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)), "nothing suspended on a moving read")
assert.Empty(t, f.events(), "and no external resume recorded")
- assert.Equal(t, "T1", f.trigger(t).Status.Operation.Token)
+ assert.Equal(t, "T1", f.claim(t).Status.Operation.Token)
}
-// TestFluxTrigger_ASuspendAppliedElsewhereIsNeverHeld: a Kustomization whose manifest sets
+// TestFluxClaim_ASuspendAppliedElsewhereIsNeverHeld: a Kustomization whose manifest sets
// spec.suspend, applied by another manager, is Configured=False and is never suspended: the next
// apply of that manifest would resume it.
-func TestFluxTrigger_ASuspendAppliedElsewhereIsNeverHeld(t *testing.T) {
+func TestFluxClaim_ASuspendAppliedElsewhereIsNeverHeld(t *testing.T) {
objs := applierFluxObjects()
ks := fluxsim.OwnSuspend(t, objs[0].(*unstructured.Unstructured), false, git.FluxFieldManager,
metav1.ManagedFieldsOperationApply)
- f := newHoldFixture(t, testFluxTrigger(triggerLocalProvider), ks, objs[1])
+ f := newHoldFixture(t, testFluxClaim(claimLocalProvider), ks, objs[1])
- got := reconcileTrigger(t, f.r).(*configbutleraiv1alpha3.FluxTrigger)
+ got := reconcileClaim(t, f.r).(*configbutleraiv1alpha3.FluxClaim)
- configured := findCondition(got.Status.Conditions, ApplyTriggerConditionConfigured)
+ configured := findCondition(got.Status.Conditions, ApplierClaimConditionConfigured)
require.NotNil(t, configured)
assert.Equal(t, applier.ReasonSuspendOwnedElsewhere, configured.Reason)
assert.True(t, conditionIsTrue(got.Status.Conditions, ConditionTypeStalled), "a human fixes the manifest")
assert.False(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)))
}
-// TestFluxTrigger_DeletionHandsTheKustomizationBack: deleting the trigger suspends its Kustomization
+// TestFluxClaim_DeletionHandsTheKustomizationBack: deleting the applier claim suspends its Kustomization
// once more when it is resumed, waits until that suspend is handled, and then releases spec.suspend,
// so Flux applies on its own again; then the finalizer goes.
-func TestFluxTrigger_DeletionHandsTheKustomizationBack(t *testing.T) {
- f := newHoldFixture(t, testFluxTrigger(triggerLocalProvider))
- reconcileTrigger(t, f.r)
+func TestFluxClaim_DeletionHandsTheKustomizationBack(t *testing.T) {
+ f := newHoldFixture(t, testFluxClaim(claimLocalProvider))
+ reconcileClaim(t, f.r)
f.events()
- // A worker's apply is running as the trigger is deleted.
+ // A worker's apply is running as the applier claim is deleted.
patch := []byte(`{"spec":{"suspend":false}}`)
_, err := f.sim.Client.Resource(fluxsim.KustomizationGVR()).Namespace("flux-system").Patch(context.Background(),
"checkout", k8stypes.MergePatchType, patch, metav1.PatchOptions{FieldManager: applier.SuspendFieldManager})
require.NoError(t, err)
- require.NoError(t, f.r.Delete(context.Background(), f.trigger(t)))
+ require.NoError(t, f.r.Delete(context.Background(), f.claim(t)))
require.Eventually(t, func() bool {
- result, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ result, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
if err := f.r.Get(
context.Background(),
- triggerKey,
- &configbutleraiv1alpha3.FluxTrigger{},
+ claimKey,
+ &configbutleraiv1alpha3.FluxClaim{},
); apierrors.IsNotFound(
err,
) {
return true
}
- assert.Equal(t, applyTriggerSuspendingInterval, result.RequeueAfter)
+ assert.Equal(t, applierClaimSuspendingInterval, result.RequeueAfter)
return false
}, 5*time.Second, 5*time.Millisecond, "the finalizer goes once the Kustomization is handed back")
@@ -331,56 +331,56 @@ func TestFluxTrigger_DeletionHandsTheKustomizationBack(t *testing.T) {
events[0])
}
-// TestFluxTrigger_DeletionLetsGoOfWhatIsGone: a Kustomization or a KubeProvider that is gone has
-// nothing to hand back, and must not hold the trigger.
-func TestFluxTrigger_DeletionLetsGoOfWhatIsGone(t *testing.T) {
+// TestFluxClaim_DeletionLetsGoOfWhatIsGone: a Kustomization or a KubeProvider that is gone has
+// nothing to hand back, and must not hold the applier claim.
+func TestFluxClaim_DeletionLetsGoOfWhatIsGone(t *testing.T) {
for name, cluster := range map[string]string{
- "the Kustomization is gone": triggerLocalProvider,
+ "the Kustomization is gone": claimLocalProvider,
"the KubeProvider is gone": "absent",
} {
t.Run(name, func(t *testing.T) {
- trigger := testFluxTrigger(cluster)
- trigger.Finalizers = []string{configbutleraiv1alpha3.FluxTriggerFinalizer}
+ claim := testFluxClaim(cluster)
+ claim.Finalizers = []string{configbutleraiv1alpha3.FluxClaimFinalizer}
sim := applierSim(t)
- r := triggerReconciler(triggerTestClient(t, trigger), configbutleraiv1alpha3.ApplyTriggerKindFlux,
+ r := claimReconciler(claimTestClient(t, claim), configbutleraiv1alpha3.ApplierClaimKindFlux,
sim.Client, nil, true)
r.ApplierClients = func(context.Context, string) (dynamic.Interface, error) { return sim.Client, nil }
- require.NoError(t, r.Delete(context.Background(), trigger))
+ require.NoError(t, r.Delete(context.Background(), claim))
- _, err := r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ _, err := r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
- err = r.Get(context.Background(), triggerKey, &configbutleraiv1alpha3.FluxTrigger{})
+ err = r.Get(context.Background(), claimKey, &configbutleraiv1alpha3.FluxClaim{})
assert.True(t, apierrors.IsNotFound(err), "the finalizer is gone: %v", err)
})
}
}
-// TestFluxTrigger_AGoneKubeProviderStillHandsBack is review 7's C1: a folder holding both a
-// KubeProvider and a FluxTrigger is removed, and the provider goes first. The hand-back was
+// TestFluxClaim_AGoneKubeProviderStillHandsBack is review 7's C1: a folder holding both a
+// KubeProvider and a FluxClaim is removed, and the provider goes first. The hand-back was
// skipped with an Info log line, and the Kustomization stayed suspended under gitops-reverser for
// good, though for an inCluster provider its cluster is still reachable. Now the release
-// goes on through whatever client still resolves, and only when none does is the trigger let go,
+// goes on through whatever client still resolves, and only when none does is the applier claim let go,
// with a Warning ReleaseFailed saying the Kustomization is left suspended.
-func TestFluxTrigger_AGoneKubeProviderStillHandsBack(t *testing.T) {
+func TestFluxClaim_AGoneKubeProviderStillHandsBack(t *testing.T) {
held := func(t *testing.T) *holdFixture {
t.Helper()
- f := newHoldFixture(t, testFluxTrigger(triggerLocalProvider))
- reconcileTrigger(t, f.r)
+ f := newHoldFixture(t, testFluxClaim(claimLocalProvider))
+ reconcileClaim(t, f.r)
f.events()
require.True(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)))
provider := &configbutleraiv1alpha3.KubeProvider{}
require.NoError(t, f.r.Get(context.Background(),
- ctrlclient.ObjectKey{Namespace: triggerProviderNamespace, Name: triggerLocalProvider}, provider))
+ ctrlclient.ObjectKey{Namespace: claimProviderNamespace, Name: claimLocalProvider}, provider))
require.NoError(t, f.r.Delete(context.Background(), provider))
- require.NoError(t, f.r.Delete(context.Background(), f.trigger(t)))
+ require.NoError(t, f.r.Delete(context.Background(), f.claim(t)))
return f
}
gone := func(t *testing.T, f *holdFixture) {
t.Helper()
require.Eventually(t, func() bool {
- _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
- err = f.r.Get(context.Background(), triggerKey, &configbutleraiv1alpha3.FluxTrigger{})
+ err = f.r.Get(context.Background(), claimKey, &configbutleraiv1alpha3.FluxClaim{})
return apierrors.IsNotFound(err)
}, 5*time.Second, 5*time.Millisecond, "the finalizer goes")
}
@@ -394,7 +394,7 @@ func TestFluxTrigger_AGoneKubeProviderStillHandsBack(t *testing.T) {
assert.True(t, strings.HasPrefix(events[0], "Normal Released handed Kustomization flux-system/checkout back"),
events[0])
})
- t.Run("its cluster no longer answers, so the trigger lets go loudly", func(t *testing.T) {
+ t.Run("its cluster no longer answers, so the applier claim lets go loudly", func(t *testing.T) {
f := held(t)
f.r.ApplierClients = func(context.Context, string) (dynamic.Interface, error) {
return nil, errors.New(`KubeProvider clusters/local not found`)
@@ -409,38 +409,38 @@ func TestFluxTrigger_AGoneKubeProviderStillHandsBack(t *testing.T) {
})
}
-// TestFluxTrigger_TakesOverASuspendBySomebodyElse: a Kustomization `flux suspend` suspended is not
-// held: that write may have let a reconcile run, so the trigger takes the field over with a token of
+// TestFluxClaim_TakesOverASuspendBySomebodyElse: a Kustomization `flux suspend` suspended is not
+// held: that write may have let a reconcile run, so the applier claim takes the field over with a token of
// its own and is Ready once it is handled.
-func TestFluxTrigger_TakesOverASuspendBySomebodyElse(t *testing.T) {
+func TestFluxClaim_TakesOverASuspendBySomebodyElse(t *testing.T) {
objs := applierFluxObjects()
ks := fluxsim.OwnSuspend(t, objs[0].(*unstructured.Unstructured), true, "flux",
metav1.ManagedFieldsOperationUpdate)
- f := newHoldFixture(t, testFluxTrigger(triggerLocalProvider), ks, objs[1])
+ f := newHoldFixture(t, testFluxClaim(claimLocalProvider), ks, objs[1])
- got := reconcileTrigger(t, f.r).(*configbutleraiv1alpha3.FluxTrigger)
+ got := reconcileClaim(t, f.r).(*configbutleraiv1alpha3.FluxClaim)
- assert.True(t, conditionIsTrue(got.Status.Conditions, ApplyTriggerConditionHeld))
+ assert.True(t, conditionIsTrue(got.Status.Conditions, ApplierClaimConditionHeld))
assert.Contains(t, kustomizationManagers(f.sim.Kustomization(checkoutKustomization)), applier.SuspendFieldManager)
assert.Empty(t, f.events(), "neither an adoption of a running Kustomization nor an external resume")
}
-// TestFluxTrigger_AHandBackItMayNotMakeLetsGo: an identity that may not write the Kustomization
-// cannot hand it back; the trigger goes anyway, with a Warning that says what is left behind.
-func TestFluxTrigger_AHandBackItMayNotMakeLetsGo(t *testing.T) {
- f := newHoldFixture(t, testFluxTrigger(triggerLocalProvider))
- reconcileTrigger(t, f.r)
+// TestFluxClaim_AHandBackItMayNotMakeLetsGo: an identity that may not write the Kustomization
+// cannot hand it back; the applier claim goes anyway, with a Warning that says what is left behind.
+func TestFluxClaim_AHandBackItMayNotMakeLetsGo(t *testing.T) {
+ f := newHoldFixture(t, testFluxClaim(claimLocalProvider))
+ reconcileClaim(t, f.r)
f.events()
f.sim.Client.PrependReactor("patch", "kustomizations", func(k8stesting.Action) (bool, runtime.Object, error) {
return true, nil, apierrors.NewForbidden(fluxsim.KustomizationGVR().GroupResource(), "checkout",
errors.New("no patch for you"))
})
- require.NoError(t, f.r.Delete(context.Background(), f.trigger(t)))
+ require.NoError(t, f.r.Delete(context.Background(), f.claim(t)))
- _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
- err = f.r.Get(context.Background(), triggerKey, &configbutleraiv1alpha3.FluxTrigger{})
- assert.True(t, apierrors.IsNotFound(err), "the trigger is let go: %v", err)
+ err = f.r.Get(context.Background(), claimKey, &configbutleraiv1alpha3.FluxClaim{})
+ assert.True(t, apierrors.IsNotFound(err), "the applier claim is let go: %v", err)
events := f.events()
require.Len(t, events, 1)
assert.True(t, strings.HasPrefix(events[0], "Warning ReleaseFailed could not hand Kustomization"), events[0])
@@ -456,9 +456,9 @@ func kustomizationManagers(ks *unstructured.Unstructured) []string {
return managers
}
-// TestFluxTrigger_PublishesTheSourcesOtherConsumers is decision 2: the consumers of the
-// GitRepository that no FluxTrigger holds are listed, information only.
-func TestFluxTrigger_PublishesTheSourcesOtherConsumers(t *testing.T) {
+// TestFluxClaim_PublishesTheSourcesOtherConsumers is decision 2: the consumers of the
+// GitRepository that no FluxClaim holds are listed, information only.
+func TestFluxClaim_PublishesTheSourcesOtherConsumers(t *testing.T) {
objs := applierFluxObjects()
infra := objs[0].(*unstructured.Unstructured).DeepCopy()
infra.SetName("infra")
@@ -470,122 +470,122 @@ func TestFluxTrigger_PublishesTheSourcesOtherConsumers(t *testing.T) {
"sourceRef": map[string]any{"kind": "GitRepository", "name": "platform", "namespace": "flux-system"},
}}},
}}
- f := newHoldFixture(t, testFluxTrigger(triggerLocalProvider),
+ f := newHoldFixture(t, testFluxClaim(claimLocalProvider),
append(objs, infra, release)...)
- source := reconcileTrigger(t, f.r).(*configbutleraiv1alpha3.FluxTrigger).Status.Source
+ source := reconcileClaim(t, f.r).(*configbutleraiv1alpha3.FluxClaim).Status.Source
require.NotNil(t, source)
assert.Equal(t, []meta.NamespacedObjectKindReference{
{Kind: "HelmRelease", Namespace: "apps", Name: "podinfo"},
{Kind: "Kustomization", Namespace: "flux-system", Name: "infra"},
}, source.OtherConsumers, "itself is not another consumer")
- assert.True(t, conditionIsTrue(f.trigger(t).Status.Conditions, ConditionTypeReady), "information only")
+ assert.True(t, conditionIsTrue(f.claim(t).Status.Conditions, ConditionTypeReady), "information only")
- // The fourth review's finding C: a trigger holds its Kustomization only once it says so
+ // The fourth review's finding C: an applier claim holds its Kustomization only once it says so
// (Held=True). One that is not Configured (SuspendOwnedElsewhere), or still suspending, leaves
// a Kustomization Flux applies on its own.
- infraTrigger := testFluxTrigger(triggerLocalProvider)
- infraTrigger.Name, infraTrigger.Spec.KustomizationRef.Name = "infra", "infra"
- require.NoError(t, f.r.Create(context.Background(), infraTrigger))
+ infraClaim := testFluxClaim(claimLocalProvider)
+ infraClaim.Name, infraClaim.Spec.KustomizationRef.Name = "infra", "infra"
+ require.NoError(t, f.r.Create(context.Background(), infraClaim))
infraOther := []meta.NamespacedObjectKindReference{
{Kind: "HelmRelease", Namespace: "apps", Name: "podinfo"},
{Kind: "Kustomization", Namespace: "flux-system", Name: "infra"},
}
for _, suspended := range []metav1.Condition{
- {Type: ApplyTriggerConditionHeld, Status: metav1.ConditionUnknown, Reason: ReasonNotConfigured},
- {Type: ApplyTriggerConditionHeld, Status: metav1.ConditionFalse, Reason: ReasonSuspending},
+ {Type: ApplierClaimConditionHeld, Status: metav1.ConditionUnknown, Reason: ReasonNotConfigured},
+ {Type: ApplierClaimConditionHeld, Status: metav1.ConditionFalse, Reason: ReasonSuspending},
} {
- setTriggerCondition(t, f.r, "infra", suspended)
- _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ setClaimCondition(t, f.r, "infra", suspended)
+ _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
- assert.Equal(t, infraOther, f.trigger(t).Status.Source.OtherConsumers,
- "a trigger that is %s does not hold its Kustomization", suspended.Reason)
+ assert.Equal(t, infraOther, f.claim(t).Status.Source.OtherConsumers,
+ "an applier claim that is %s does not hold its Kustomization", suspended.Reason)
}
- setTriggerCondition(t, f.r, "infra", metav1.Condition{
- Type: ApplyTriggerConditionHeld, Status: metav1.ConditionTrue, Reason: ReasonSuspended,
+ setClaimCondition(t, f.r, "infra", metav1.Condition{
+ Type: ApplierClaimConditionHeld, Status: metav1.ConditionTrue, Reason: ReasonSuspended,
})
- _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
assert.Equal(t, []meta.NamespacedObjectKindReference{{Kind: "HelmRelease", Namespace: "apps", Name: "podinfo"}},
- f.trigger(t).Status.Source.OtherConsumers, "a Kustomization another trigger holds is not listed")
+ f.claim(t).Status.Source.OtherConsumers, "a Kustomization another applier claim holds is not listed")
require.NoError(t, f.sim.Client.Resource(k8sschema.GroupVersionResource{
Group: "helm.toolkit.fluxcd.io", Version: "v2", Resource: "helmreleases",
}).Namespace("apps").Delete(context.Background(), "podinfo", metav1.DeleteOptions{}))
- _, err = f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ _, err = f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
- assert.Empty(t, f.trigger(t).Status.Source.OtherConsumers, "every consumer is held")
+ assert.Empty(t, f.claim(t).Status.Source.OtherConsumers, "every consumer is held")
}
-// setTriggerCondition publishes a condition on the FluxTrigger team-a/name.
-func setTriggerCondition(t *testing.T, c ctrlclient.Client, name string, condition metav1.Condition) {
+// setClaimCondition publishes a condition on the FluxClaim team-a/name.
+func setClaimCondition(t *testing.T, c ctrlclient.Client, name string, condition metav1.Condition) {
t.Helper()
- trigger := &configbutleraiv1alpha3.FluxTrigger{}
- require.NoError(t, c.Get(context.Background(), k8stypes.NamespacedName{Namespace: "team-a", Name: name}, trigger))
+ claim := &configbutleraiv1alpha3.FluxClaim{}
+ require.NoError(t, c.Get(context.Background(), k8stypes.NamespacedName{Namespace: "team-a", Name: name}, claim))
condition.LastTransitionTime = metav1.Now()
- trigger.Status.Conditions = []metav1.Condition{condition}
- require.NoError(t, c.Status().Update(context.Background(), trigger))
+ claim.Status.Conditions = []metav1.Condition{condition}
+ require.NoError(t, c.Status().Update(context.Background(), claim))
}
-// TestFluxTrigger_CompletesTheLiveExternalOperation is the fourth review's finding K: the operation
+// TestFluxClaim_CompletesTheLiveExternalOperation is the fourth review's finding K: the operation
// an external resume was recorded as is completed from the uncached read, which is what the hold
-// decided on. The cached trigger may not show it yet, and completing that one dereferenced nil.
-func TestFluxTrigger_CompletesTheLiveExternalOperation(t *testing.T) {
+// decided on. The cached applier claim may not show it yet, and completing that one dereferenced nil.
+func TestFluxClaim_CompletesTheLiveExternalOperation(t *testing.T) {
objs := applierFluxObjects()
ks := fluxsim.OwnSuspend(t, objs[0].(*unstructured.Unstructured), true, applier.SuspendFieldManager,
metav1.ManagedFieldsOperationApply)
ks.SetAnnotations(map[string]string{meta.ReconcileRequestAnnotation: "T2"})
require.NoError(t, unstructured.SetNestedField(ks.Object, "T2", "status", "lastHandledReconcileAt"))
- cached := testFluxTrigger(triggerLocalProvider)
- cached.Finalizers = []string{configbutleraiv1alpha3.FluxTriggerFinalizer}
+ cached := testFluxClaim(claimLocalProvider)
+ cached.Finalizers = []string{configbutleraiv1alpha3.FluxClaimFinalizer}
f := newHoldFixture(t, cached, ks, objs[1])
live := cached.DeepCopy()
live.Status.Operation = &configbutleraiv1alpha3.ApplyOperation{
Phase: configbutleraiv1alpha3.ApplyOperationUnresolved, SuspendToken: "T2", RequestedAt: metav1.Now(),
}
- f.r.APIReader = triggerTestClient(t, live)
+ f.r.APIReader = claimTestClient(t, live)
- _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
- got := f.trigger(t)
+ got := f.claim(t)
require.NotNil(t, got.Status.Operation, "the live operation is completed")
assert.True(t, got.Status.Operation.Phase.Completed(), got.Status.Operation.Phase)
assert.Equal(t, "T2", got.Status.Operation.SuspendToken)
assert.NotNil(t, got.Status.Operation.FinishedAt)
}
-// TestFluxTrigger_ASuspendItMayNotMakeLetsGo is the fourth review's finding D: a resumed
-// Kustomization the identity may not suspend again cannot be handed back either, so the trigger goes,
+// TestFluxClaim_ASuspendItMayNotMakeLetsGo is the fourth review's finding D: a resumed
+// Kustomization the identity may not suspend again cannot be handed back either, so the applier claim goes,
// with a Warning, as it does for a release it may not make. It used to retry forever.
-func TestFluxTrigger_ASuspendItMayNotMakeLetsGo(t *testing.T) {
+func TestFluxClaim_ASuspendItMayNotMakeLetsGo(t *testing.T) {
objs := applierFluxObjects()
ks := fluxsim.OwnSuspend(t, objs[0].(*unstructured.Unstructured), false, applier.SuspendFieldManager,
metav1.ManagedFieldsOperationApply)
- trigger := testFluxTrigger(triggerLocalProvider)
- trigger.Finalizers = []string{configbutleraiv1alpha3.FluxTriggerFinalizer}
- f := newHoldFixture(t, trigger, ks, objs[1])
+ claim := testFluxClaim(claimLocalProvider)
+ claim.Finalizers = []string{configbutleraiv1alpha3.FluxClaimFinalizer}
+ f := newHoldFixture(t, claim, ks, objs[1])
f.sim.Client.PrependReactor("patch", "kustomizations", func(k8stesting.Action) (bool, runtime.Object, error) {
return true, nil, apierrors.NewForbidden(fluxsim.KustomizationGVR().GroupResource(), "checkout",
errors.New("no patch for you"))
})
- require.NoError(t, f.r.Delete(context.Background(), f.trigger(t)))
+ require.NoError(t, f.r.Delete(context.Background(), f.claim(t)))
- _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
- err = f.r.Get(context.Background(), triggerKey, &configbutleraiv1alpha3.FluxTrigger{})
- assert.True(t, apierrors.IsNotFound(err), "the trigger is let go: %v", err)
+ err = f.r.Get(context.Background(), claimKey, &configbutleraiv1alpha3.FluxClaim{})
+ assert.True(t, apierrors.IsNotFound(err), "the applier claim is let go: %v", err)
events := f.events()
require.Len(t, events, 1)
assert.True(t, strings.HasPrefix(events[0], "Warning ReleaseFailed could not hand Kustomization"), events[0])
}
-// TestFluxTrigger_AHandBackThatNeverEndsLetsGoAtTheDeadline is the fourth review's finding D: with
-// kustomize-controller down, the suspend of a hand-back is never handled. The trigger waits for it
+// TestFluxClaim_AHandBackThatNeverEndsLetsGoAtTheDeadline is the fourth review's finding D: with
+// kustomize-controller down, the suspend of a hand-back is never handled. The applier claim waits for it
// until releaseDeadline after its deletion, and then goes, with a Warning that says what the
// Kustomization is left as.
-func TestFluxTrigger_AHandBackThatNeverEndsLetsGoAtTheDeadline(t *testing.T) {
+func TestFluxClaim_AHandBackThatNeverEndsLetsGoAtTheDeadline(t *testing.T) {
for name, deleted := range map[string]time.Duration{
"before the deadline": 0,
"past the deadline": releaseDeadline + time.Minute,
@@ -597,27 +597,27 @@ func TestFluxTrigger_AHandBackThatNeverEndsLetsGoAtTheDeadline(t *testing.T) {
// No source: the simulated kustomize-controller never reconciles it, as one that is down.
unstructured.RemoveNestedField(ks.Object, "spec", "sourceRef")
ks.SetAnnotations(map[string]string{meta.ReconcileRequestAnnotation: "T2"})
- trigger := testFluxTrigger(triggerLocalProvider)
- trigger.Finalizers = []string{configbutleraiv1alpha3.FluxTriggerFinalizer}
- trigger.DeletionTimestamp = &metav1.Time{Time: time.Now()}
+ claim := testFluxClaim(claimLocalProvider)
+ claim.Finalizers = []string{configbutleraiv1alpha3.FluxClaimFinalizer}
+ claim.DeletionTimestamp = &metav1.Time{Time: time.Now()}
// The deadline counts from when Held turned Releasing, not from the deletion.
- trigger.Status.Conditions = []metav1.Condition{{
- Type: ApplyTriggerConditionHeld, Status: metav1.ConditionFalse, Reason: ReasonReleasing,
+ claim.Status.Conditions = []metav1.Condition{{
+ Type: ApplierClaimConditionHeld, Status: metav1.ConditionFalse, Reason: ReasonReleasing,
LastTransitionTime: metav1.NewTime(time.Now().Add(-deleted)),
}}
- f := newHoldFixture(t, trigger, ks, objs[1])
+ f := newHoldFixture(t, claim, ks, objs[1])
- result, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ result, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
- err = f.r.Get(context.Background(), triggerKey, &configbutleraiv1alpha3.FluxTrigger{})
+ err = f.r.Get(context.Background(), claimKey, &configbutleraiv1alpha3.FluxClaim{})
events := f.events()
if deleted == 0 {
require.NoError(t, err, "still waiting for the suspend")
- assert.Equal(t, applyTriggerSuspendingInterval, result.RequeueAfter)
+ assert.Equal(t, applierClaimSuspendingInterval, result.RequeueAfter)
assert.Empty(t, events)
return
}
- assert.True(t, apierrors.IsNotFound(err), "the trigger is let go: %v", err)
+ assert.True(t, apierrors.IsNotFound(err), "the applier claim is let go: %v", err)
require.Len(t, events, 1)
assert.True(t, strings.HasPrefix(events[0], "Warning ReleaseFailed gave up handing Kustomization "+
"flux-system/checkout back to Flux 10m0s after it began: it is suspended, and has "+
@@ -628,10 +628,10 @@ func TestFluxTrigger_AHandBackThatNeverEndsLetsGoAtTheDeadline(t *testing.T) {
}
}
-// TestFluxTrigger_HandsBackASuspendedKustomizationSuspended: adoption records how the
-// Kustomization stood in status.previousSuspendState. One somebody had suspended before the trigger held it is
+// TestFluxClaim_HandsBackASuspendedKustomizationSuspended: adoption records how the
+// Kustomization stood in status.previousSuspendState. One somebody had suspended before the applier claim held it is
// handed back suspended, as it was, and not resumed; one that was running is resumed, as before.
-func TestFluxTrigger_HandsBackASuspendedKustomizationSuspended(t *testing.T) {
+func TestFluxClaim_HandsBackASuspendedKustomizationSuspended(t *testing.T) {
for name, wasSuspended := range map[string]bool{"suspended before": true, "running before": false} {
t.Run(name, func(t *testing.T) {
objs := applierFluxObjects()
@@ -639,16 +639,16 @@ func TestFluxTrigger_HandsBackASuspendedKustomizationSuspended(t *testing.T) {
objs[0] = fluxsim.OwnSuspend(t, objs[0].(*unstructured.Unstructured), true, "flux",
metav1.ManagedFieldsOperationUpdate)
}
- f := newHoldFixture(t, testFluxTrigger(triggerLocalProvider), objs...)
- got := reconcileTrigger(t, f.r).(*configbutleraiv1alpha3.FluxTrigger)
+ f := newHoldFixture(t, testFluxClaim(claimLocalProvider), objs...)
+ got := reconcileClaim(t, f.r).(*configbutleraiv1alpha3.FluxClaim)
require.Equal(t, suspendState(wasSuspended), got.Status.PreviousSuspendState)
f.events()
- require.NoError(t, f.r.Delete(context.Background(), f.trigger(t)))
+ require.NoError(t, f.r.Delete(context.Background(), f.claim(t)))
require.Eventually(t, func() bool {
- _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
- err = f.r.Get(context.Background(), triggerKey, &configbutleraiv1alpha3.FluxTrigger{})
+ err = f.r.Get(context.Background(), claimKey, &configbutleraiv1alpha3.FluxClaim{})
return apierrors.IsNotFound(err)
}, 5*time.Second, 5*time.Millisecond, "the finalizer goes once the Kustomization is handed back")
@@ -695,52 +695,110 @@ func (w commitFailingWriter) Patch(
return w.SubResourceWriter.Patch(ctx, obj, patch, opts...)
}
-// TestFluxTrigger_RecordsHowItStoodBeforeTheSuspend: status.previousSuspendState is written before
+// TestFluxClaim_RecordsHowItStoodBeforeTheSuspend: status.previousSuspendState is written before
// the suspend that hides it. With the commit after the suspend lost, the next reconcile finds the
// Kustomization held and cannot read how it stood, yet the hand-back still leaves it suspended.
-func TestFluxTrigger_RecordsHowItStoodBeforeTheSuspend(t *testing.T) {
+func TestFluxClaim_RecordsHowItStoodBeforeTheSuspend(t *testing.T) {
objs := applierFluxObjects()
objs[0] = fluxsim.OwnSuspend(t, objs[0].(*unstructured.Unstructured), true, "flux",
metav1.ManagedFieldsOperationUpdate)
- f := newHoldFixture(t, testFluxTrigger(triggerLocalProvider), objs...)
+ f := newHoldFixture(t, testFluxClaim(claimLocalProvider), objs...)
f.r.Client = &commitFailingClient{Client: f.r.Client, failures: 1}
- _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.Error(t, err, "the commit after the suspend is lost")
assert.Contains(t, kustomizationManagers(f.sim.Kustomization(checkoutKustomization)), applier.SuspendFieldManager)
- assert.Equal(t, configbutleraiv1alpha3.SuspendStateSuspended, f.trigger(t).Status.PreviousSuspendState,
+ assert.Equal(t, configbutleraiv1alpha3.SuspendStateSuspended, f.claim(t).Status.PreviousSuspendState,
"recorded before the suspend")
- reconcileTrigger(t, f.r)
- require.NoError(t, f.r.Delete(context.Background(), f.trigger(t)))
+ reconcileClaim(t, f.r)
+ require.NoError(t, f.r.Delete(context.Background(), f.claim(t)))
require.Eventually(t, func() bool {
- _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: triggerKey})
+ _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
require.NoError(t, err)
- err = f.r.Get(context.Background(), triggerKey, &configbutleraiv1alpha3.FluxTrigger{})
+ err = f.r.Get(context.Background(), claimKey, &configbutleraiv1alpha3.FluxClaim{})
return apierrors.IsNotFound(err)
}, 5*time.Second, 5*time.Millisecond, "the finalizer goes once the Kustomization is handed back")
assert.True(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)), "suspended, as it was")
}
-// TestFluxTrigger_RecordsNothingOfAKustomizationAnotherHolds: a trigger Configured from a cached read
-// that finds its Kustomization resumed, and another trigger holding it on the uncached read, adopts
+// TestFluxClaim_RecordsNothingOfAKustomizationAnotherHolds: an applier claim Configured from a cached read
+// that finds its Kustomization resumed, and another applier claim holding it on the uncached read, adopts
// nothing, so it records nothing: the resume is the holder's apply, not how the Kustomization stood.
-func TestFluxTrigger_RecordsNothingOfAKustomizationAnotherHolds(t *testing.T) {
- trigger := testFluxTrigger(triggerLocalProvider)
- trigger.CreationTimestamp = metav1.NewTime(time.Unix(2_000_000, 0))
- f := newHoldFixture(t, trigger)
- holder := testFluxTrigger(triggerLocalProvider)
+func TestFluxClaim_RecordsNothingOfAKustomizationAnotherHolds(t *testing.T) {
+ claim := testFluxClaim(claimLocalProvider)
+ claim.CreationTimestamp = metav1.NewTime(time.Unix(2_000_000, 0))
+ f := newHoldFixture(t, claim)
+ holder := testFluxClaim(claimLocalProvider)
holder.Name = "checkout-first"
holder.CreationTimestamp = metav1.NewTime(time.Unix(1_000_000, 0))
- holder.Finalizers = []string{configbutleraiv1alpha3.FluxTriggerFinalizer}
- f.r.APIReader = triggerTestClient(t, trigger.DeepCopy(), holder)
+ holder.Finalizers = []string{configbutleraiv1alpha3.FluxClaimFinalizer}
+ f.r.APIReader = claimTestClient(t, claim.DeepCopy(), holder)
- got := reconcileTrigger(t, f.r).(*configbutleraiv1alpha3.FluxTrigger)
+ got := reconcileClaim(t, f.r).(*configbutleraiv1alpha3.FluxClaim)
- held := findCondition(got.Status.Conditions, ApplyTriggerConditionHeld)
+ held := findCondition(got.Status.Conditions, ApplierClaimConditionHeld)
require.NotNil(t, held)
assert.Equal(t, ReasonNotConfigured, held.Reason)
assert.Contains(t, held.Message, "team-a/checkout-first")
assert.Empty(t, got.Status.PreviousSuspendState)
assert.False(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)), "nothing written")
}
+
+// setReferenced deletes the GitTarget team-a/name that names the applier claim of kind, or creates
+// it again: a GitTarget's spec.applierClaimRef removed and restored.
+func setReferenced(t *testing.T, c ctrlclient.Client, name, kind string, referenced bool) {
+ t.Helper()
+ if referenced {
+ require.NoError(t, c.Create(context.Background(), referencingTarget(name, kind)))
+ return
+ }
+ target := &configbutleraiv1alpha3.GitTarget{}
+ require.NoError(t, c.Get(context.Background(), ctrlclient.ObjectKey{Namespace: "team-a", Name: name}, target))
+ require.NoError(t, c.Delete(context.Background(), target))
+}
+
+// TestFluxClaim_ReadoptsAKustomizationItHandedBackSuspended is the review's reuse finding. A
+// Kustomization suspended before the applier claim held it is handed back suspended, its
+// spec.suspend kept under GitOps Reverser's field manager and its requestedAt given up. Referenced
+// again, the applier claim adopts it anew, as it takes over any suspend: it records how it stood, and
+// suspends it with a fresh token, since Flux still reports the old one handled. It is not taken for a hold already in place,
+// which would wait for a token nobody stamped, and its next hand-back would resume it.
+func TestFluxClaim_ReadoptsAKustomizationItHandedBackSuspended(t *testing.T) {
+ objs := applierFluxObjects()
+ objs[0] = fluxsim.OwnSuspend(t, objs[0].(*unstructured.Unstructured), true, "flux",
+ metav1.ManagedFieldsOperationUpdate)
+ f := newHoldFixture(t, testFluxClaim(claimLocalProvider), objs...)
+ reconcileClaim(t, f.r)
+ setReferenced(t, f.r.Client, "checkout-flux", configbutleraiv1alpha3.ApplierClaimKindFlux, false)
+ got := reconcileClaim(t, f.r).(*configbutleraiv1alpha3.FluxClaim)
+ require.Empty(t, got.Status.PreviousSuspendState, "forgotten once handed back")
+ ks := f.sim.Kustomization(checkoutKustomization)
+ require.True(t, suspendedSpec(ks), "handed back suspended, as it was")
+ require.Contains(
+ t,
+ kustomizationManagers(ks),
+ applier.SuspendFieldManager,
+ "the suspend is kept under GitOps Reverser",
+ )
+ f.events()
+
+ setReferenced(t, f.r.Client, "checkout-flux", configbutleraiv1alpha3.ApplierClaimKindFlux, true)
+ var held *metav1.Condition
+ for range 50 {
+ _, err := f.r.Reconcile(context.Background(), ctrl.Request{NamespacedName: claimKey})
+ require.NoError(t, err)
+ held = findCondition(f.claim(t).Status.Conditions, ApplierClaimConditionHeld)
+ if held != nil && held.Reason == ReasonSuspended {
+ break
+ }
+ }
+ require.NotNil(t, held)
+ assert.Equal(t, ReasonSuspended, held.Reason, held.Message)
+ assert.Equal(t, configbutleraiv1alpha3.SuspendStateSuspended, f.claim(t).Status.PreviousSuspendState,
+ "adopted anew: suspended before the applier claim held it")
+
+ setReferenced(t, f.r.Client, "checkout-flux", configbutleraiv1alpha3.ApplierClaimKindFlux, false)
+ reconcileClaim(t, f.r)
+ assert.True(t, suspendedSpec(f.sim.Kustomization(checkoutKustomization)), "suspended, as it was, again")
+}
diff --git a/internal/controller/fluxtrigger_suspend_apiserver_test.go b/internal/controller/fluxclaim_suspend_apiserver_test.go
similarity index 99%
rename from internal/controller/fluxtrigger_suspend_apiserver_test.go
rename to internal/controller/fluxclaim_suspend_apiserver_test.go
index 004181982..ed9317d6d 100644
--- a/internal/controller/fluxtrigger_suspend_apiserver_test.go
+++ b/internal/controller/fluxclaim_suspend_apiserver_test.go
@@ -144,7 +144,7 @@ var _ = Describe("A Kustomization resume guarded by uid and resourceVersion, on
_, err = applier.NewFlux(dyn).Suspend(ctx, ref)
Expect(err).NotTo(HaveOccurred())
}
- // request asks Flux.Request for an apply of commit, with held run where the trigger's hold is
+ // request asks Flux.Request for an apply of commit, with held run where the applier claim's hold is
// checked: after the resume's read of the Kustomization, before its write.
request := func(held func(context.Context) error) (applier.Started, error) {
return applier.NewFlux(dyn).Request(ctx, ref, applier.Ask{
diff --git a/internal/controller/gittarget_apply_trigger.go b/internal/controller/gittarget_applier_claim.go
similarity index 53%
rename from internal/controller/gittarget_apply_trigger.go
rename to internal/controller/gittarget_applier_claim.go
index 56ae3d09e..8cf5ed6a3 100644
--- a/internal/controller/gittarget_apply_trigger.go
+++ b/internal/controller/gittarget_applier_claim.go
@@ -13,91 +13,91 @@ import (
"github.com/ConfigButler/gitops-reverser/internal/git"
)
-// GitTargetConditionApplyTriggerReady reports whether spec.applyTriggerRef names a trigger that is
+// GitTargetConditionApplierClaimReady reports whether spec.applierClaimRef names an applier claim that is
// Ready and whose applier applies this target's repository and branch. Present only while
-// spec.applyTriggerRef is set.
-const GitTargetConditionApplyTriggerReady = "ApplyTriggerReady"
+// spec.applierClaimRef is set.
+const GitTargetConditionApplierClaimReady = "ApplierClaimReady"
-// ReasonApplyTriggerNotFound is ApplyTriggerReady=False for a GitTarget naming a trigger that does
+// ReasonApplierClaimNotFound is ApplierClaimReady=False for a GitTarget naming an applier claim that does
// not exist in its namespace.
-const ReasonApplyTriggerNotFound = "ApplyTriggerNotFound"
+const ReasonApplierClaimNotFound = "ApplierClaimNotFound"
-// applyTriggerVerdict is the ApplyTriggerReady value, and whether waiting will not clear it.
-type applyTriggerVerdict struct {
+// applierClaimVerdict is the ApplierClaimReady value, and whether waiting will not clear it.
+type applierClaimVerdict struct {
value conditionValue
stalled bool
}
-// observeApplyTrigger publishes the ApplyTriggerReady condition and returns it for the readiness
-// trio. present is false when the target names no trigger, in which case the condition is removed.
+// observeApplierClaim publishes the ApplierClaimReady condition and returns it for the readiness
+// trio. present is false when the target names no applier claim, in which case the condition is removed.
//
-// It reads the trigger from the cache; the trigger's own reconciler checks its applier. What is
+// It reads the applier claim from the cache; the applier claim's own reconciler checks its applier. What is
// left to check here is what only the GitTarget knows: the repository and branch it writes.
-func (r *GitTargetReconciler) observeApplyTrigger(
+func (r *GitTargetReconciler) observeApplierClaim(
ctx context.Context,
st *reconcileStatus,
target *configbutleraiv1alpha3.GitTarget,
dest gitDestination,
-) (applyTriggerVerdict, bool) {
- ref := target.Spec.ApplyTriggerRef
+) (applierClaimVerdict, bool) {
+ ref := target.Spec.ApplierClaimRef
if ref == nil {
- st.remove(GitTargetConditionApplyTriggerReady)
- return applyTriggerVerdict{}, false
+ st.remove(GitTargetConditionApplierClaimReady)
+ return applierClaimVerdict{}, false
}
- verdict := r.applyTriggerVerdict(ctx, target, dest)
- st.setValue(GitTargetConditionApplyTriggerReady, verdict.value)
+ verdict := r.applierClaimVerdict(ctx, target, dest)
+ st.setValue(GitTargetConditionApplierClaimReady, verdict.value)
return verdict, true
}
-func (r *GitTargetReconciler) applyTriggerVerdict(
+func (r *GitTargetReconciler) applierClaimVerdict(
ctx context.Context,
target *configbutleraiv1alpha3.GitTarget,
dest gitDestination,
-) applyTriggerVerdict {
- ref := target.Spec.ApplyTriggerRef
+) applierClaimVerdict {
+ ref := target.Spec.ApplierClaimRef
name := fmt.Sprintf("%s %s/%s", ref.Kind, target.Namespace, ref.Name)
- trigger, err := git.ResolveApplyTrigger(ctx, r.Client, target)
+ applierClaim, err := git.ResolveApplierClaim(ctx, r.Client, target)
switch {
case err != nil:
- return applyTriggerVerdict{value: keptCondition(target.Status.Conditions, GitTargetConditionApplyTriggerReady,
+ return applierClaimVerdict{value: keptCondition(target.Status.Conditions, GitTargetConditionApplierClaimReady,
fmt.Sprintf("Cannot read %s: %v", name, err))}
- case trigger == nil:
- return applyTriggerVerdict{value: conditionValue{
+ case applierClaim == nil:
+ return applierClaimVerdict{value: conditionValue{
Status: metav1.ConditionFalse,
- Reason: ReasonApplyTriggerNotFound,
+ Reason: ReasonApplierClaimNotFound,
Message: name + " not found",
}}
}
- ready := findCondition(*trigger.StatusConditions(), ConditionTypeReady)
+ ready := findCondition(*applierClaim.StatusConditions(), ConditionTypeReady)
switch {
- case ready == nil || ready.ObservedGeneration != trigger.GetGeneration() || ready.Status == metav1.ConditionUnknown:
+ case ready == nil || ready.ObservedGeneration != applierClaim.GetGeneration() || ready.Status == metav1.ConditionUnknown:
reason, message := conditionReasonMessage(ready, ReasonProgressing, "has not been checked yet")
- return applyTriggerVerdict{value: conditionValue{
+ return applierClaimVerdict{value: conditionValue{
Status: metav1.ConditionUnknown, Reason: reason, Message: name + ": " + message,
}}
case ready.Reason == applier.ReasonNotReferenced:
- // The trigger's reconcile has not seen this reference yet: naming a trigger does not change its
+ // The applier claim's reconcile has not seen this reference yet: naming an applier claim does not change its
// generation, so the generation check above cannot tell.
- return applyTriggerVerdict{value: conditionValue{
+ return applierClaimVerdict{value: conditionValue{
Status: metav1.ConditionUnknown, Reason: applier.ReasonNotReferenced,
Message: name + " has not observed that this GitTarget names it yet",
}}
case ready.Status == metav1.ConditionFalse:
- return applyTriggerVerdict{
+ return applierClaimVerdict{
value: conditionValue{
Status: metav1.ConditionFalse,
Reason: ready.Reason,
Message: name + ": " + ready.Message,
},
- stalled: conditionIsTrue(*trigger.StatusConditions(), ConditionTypeStalled),
+ stalled: conditionIsTrue(*applierClaim.StatusConditions(), ConditionTypeStalled),
}
}
if dest.provider != nil && dest.branch != nil {
want := applier.Binding{RepositoryURL: dest.provider.Spec.URL, Branch: dest.branch.Spec.Branch}
- if mismatch := applier.BindingMismatch(trigger, want); mismatch != "" {
- return applyTriggerVerdict{
+ if mismatch := applier.BindingMismatch(applierClaim, want); mismatch != "" {
+ return applierClaimVerdict{
value: conditionValue{
Status: metav1.ConditionFalse,
Reason: applier.ReasonSourceMismatch,
@@ -107,18 +107,18 @@ func (r *GitTargetReconciler) applyTriggerVerdict(
}
}
}
- return applyTriggerVerdict{value: conditionValue{
+ return applierClaimVerdict{value: conditionValue{
Status: metav1.ConditionTrue,
Reason: ReasonSucceeded,
Message: fmt.Sprintf("%s holds %s %s, which applies this repository and branch",
- name, trigger.ApplierKind(), trigger.Applier()),
+ name, applierClaim.ApplierKind(), applierClaim.Applier()),
}}
}
-// applyTriggerReadiness contributes ApplyTriggerReady to the trio. A trigger that applies another
+// applierClaimReadiness contributes ApplierClaimReady to the trio. An applier claim that applies another
// repository, or one stalled itself, needs a human; one that is missing may be on its way, and one
// that is not Ready yet is still settling.
-func applyTriggerReadiness(rd *readiness, verdict applyTriggerVerdict, present bool) {
+func applierClaimReadiness(rd *readiness, verdict applierClaimVerdict, present bool) {
if !present {
return
}
diff --git a/internal/controller/gittarget_applier_claim_test.go b/internal/controller/gittarget_applier_claim_test.go
new file mode 100644
index 000000000..234033a48
--- /dev/null
+++ b/internal/controller/gittarget_applier_claim_test.go
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: Apache-2.0
+
+package controller
+
+import (
+ "context"
+ "errors"
+ "testing"
+
+ meta "github.com/fluxcd/pkg/apis/meta"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/client/interceptor"
+ "sigs.k8s.io/controller-runtime/pkg/event"
+
+ configbutleraiv1alpha3 "github.com/ConfigButler/gitops-reverser/api/v1alpha3"
+ "github.com/ConfigButler/gitops-reverser/internal/applier"
+)
+
+// claimTarget is the GitTarget team-a/checkout mirroring through the KubeProvider
+// claimLocalProvider on branch main, naming the FluxClaim checkout when named is set.
+func claimTarget(named bool) *configbutleraiv1alpha3.GitTarget {
+ target := &configbutleraiv1alpha3.GitTarget{
+ ObjectMeta: metav1.ObjectMeta{Name: "checkout", Namespace: "team-a"},
+ Spec: configbutleraiv1alpha3.GitTargetSpec{
+ KubeProviderRef: claimProviderRef(claimLocalProvider),
+ GitBranchRef: meta.NamespacedObjectReference{Name: gitBranchNameForTest("platform", "main")},
+ Path: "apps",
+ },
+ }
+ if named {
+ target.Spec.ApplierClaimRef = &configbutleraiv1alpha3.ApplierClaimReference{
+ Kind: configbutleraiv1alpha3.ApplierClaimKindFlux, Name: "checkout",
+ }
+ }
+ return target
+}
+
+// claimDestination is what claimTarget's spec.gitBranchRef resolves to: branch main of the test
+// repository.
+func claimDestination() gitDestination {
+ branch := gitBranch("platform", "team-a")
+ return gitDestination{
+ key: ctrlclient.ObjectKeyFromObject(branch),
+ branch: branch,
+ provider: &configbutleraiv1alpha3.GitProvider{
+ Spec: configbutleraiv1alpha3.GitProviderSpec{URL: applierTestRepo},
+ },
+ }
+}
+
+// readyClaim is the FluxClaim checkout, as its reconciler published it: Ready with status,
+// Stalled when stalled, and applying branch of the test repository.
+func readyClaim(status metav1.ConditionStatus, reason string, stalled bool, branch string) ctrlclient.Object {
+ applierClaim := testFluxClaim(claimLocalProvider)
+ stalledStatus := metav1.ConditionFalse
+ if stalled {
+ stalledStatus = metav1.ConditionTrue
+ }
+ applierClaim.Status.Conditions = []metav1.Condition{
+ {Type: ConditionTypeReady, Status: status, Reason: reason, Message: "as published", ObservedGeneration: 1,
+ LastTransitionTime: metav1.Now()},
+ {Type: ConditionTypeStalled, Status: stalledStatus, Reason: reason, ObservedGeneration: 1,
+ LastTransitionTime: metav1.Now()},
+ }
+ applierClaim.Status.Source = &configbutleraiv1alpha3.FluxClaimSource{
+ URL: "https://github.com/acme/platform",
+ Branch: branch,
+ }
+ return applierClaim
+}
+
+func TestObserveApplierClaim_NoReferenceRemovesTheCondition(t *testing.T) {
+ target := claimTarget(false)
+ target.Status.Conditions = []metav1.Condition{{Type: GitTargetConditionApplierClaimReady, Status: "True"}}
+ st := beginStatus(nil, nil, target)
+
+ r := &GitTargetReconciler{Client: claimTestClient(t)}
+ _, present := r.observeApplierClaim(context.Background(), st, target, claimDestination())
+
+ assert.False(t, present)
+ assert.Nil(t, findCondition(target.Status.Conditions, GitTargetConditionApplierClaimReady))
+}
+
+func TestObserveApplierClaim_Verdicts(t *testing.T) {
+ cases := []struct {
+ name string
+ applierClaim ctrlclient.Object
+ wantStatus metav1.ConditionStatus
+ wantReason string
+ wantMessage string
+ wantStalled bool
+ wantConverged bool
+ }{
+ {name: "a Ready applier claim whose applier applies this repository and branch",
+ applierClaim: readyClaim(metav1.ConditionTrue, ReasonSucceeded, false, "main"),
+ wantStatus: metav1.ConditionTrue, wantReason: ReasonSucceeded, wantConverged: true,
+ wantMessage: "FluxClaim team-a/checkout holds Kustomization flux-system/checkout"},
+ {name: "no such applier claim, which may be on its way",
+ wantStatus: metav1.ConditionFalse, wantReason: ReasonApplierClaimNotFound,
+ wantMessage: "FluxClaim team-a/checkout not found"},
+ {name: "an applier claim whose applier applies another branch",
+ applierClaim: readyClaim(metav1.ConditionTrue, ReasonSucceeded, false, "release"),
+ wantStatus: metav1.ConditionFalse, wantReason: applier.ReasonSourceMismatch, wantStalled: true,
+ wantMessage: `applies branch "release", not main`},
+ {name: "an applier claim stalled itself",
+ applierClaim: readyClaim(metav1.ConditionFalse, applier.ReasonForbidden, true, "main"),
+ wantStatus: metav1.ConditionFalse, wantReason: applier.ReasonForbidden, wantStalled: true,
+ wantMessage: "FluxClaim team-a/checkout: as published"},
+ {name: "an applier claim whose operation is unresolved, which clears on its own",
+ applierClaim: readyClaim(metav1.ConditionFalse, ReasonOperationUnresolved, false, "main"),
+ wantStatus: metav1.ConditionFalse, wantReason: ReasonOperationUnresolved},
+ {name: "an applier claim that has not observed this reference yet, which clears once it does",
+ applierClaim: readyClaim(metav1.ConditionTrue, applier.ReasonNotReferenced, false, "main"),
+ wantStatus: metav1.ConditionUnknown, wantReason: applier.ReasonNotReferenced,
+ wantMessage: "has not observed that this GitTarget names it yet"},
+ {name: "an applier claim not checked yet",
+ applierClaim: testFluxClaim(claimLocalProvider),
+ wantStatus: metav1.ConditionUnknown, wantReason: ReasonProgressing},
+ }
+ for _, tc := range cases {
+ t.Run(tc.name, func(t *testing.T) {
+ var objects []ctrlclient.Object
+ if tc.applierClaim != nil {
+ objects = append(objects, tc.applierClaim)
+ }
+ r := &GitTargetReconciler{Client: claimTestClient(t, objects...)}
+ target := claimTarget(true)
+ st := beginStatus(nil, nil, target)
+
+ verdict, present := r.observeApplierClaim(context.Background(), st, target, claimDestination())
+
+ require.True(t, present)
+ assert.Equal(t, tc.wantStatus, verdict.value.Status, verdict.value.Message)
+ assert.Equal(t, tc.wantReason, verdict.value.Reason)
+ assert.Contains(t, verdict.value.Message, tc.wantMessage)
+ published := findCondition(target.Status.Conditions, GitTargetConditionApplierClaimReady)
+ require.NotNil(t, published)
+ assert.Equal(t, tc.wantReason, published.Reason)
+
+ rd := newGitTargetReadiness()
+ applierClaimReadiness(rd, verdict, present)
+ assert.Equal(t, tc.wantConverged, rd.converged())
+ assert.Equal(t, tc.wantStalled, !rd.converged() && !rd.converging())
+ })
+ }
+}
+
+// TestObserveApplierClaim_AReadyOfAnOlderGenerationIsNotReady: a Ready the applier claim's reconciler
+// published for an earlier spec says nothing about the one it has now.
+func TestObserveApplierClaim_AReadyOfAnOlderGenerationIsNotReady(t *testing.T) {
+ applierClaim := readyClaim(metav1.ConditionTrue, ReasonSucceeded, false, "main")
+ applierClaim.SetGeneration(2)
+ r := &GitTargetReconciler{Client: claimTestClient(t, applierClaim)}
+ target := claimTarget(true)
+
+ verdict, _ := r.observeApplierClaim(
+ context.Background(),
+ beginStatus(nil, nil, target),
+ target,
+ claimDestination(),
+ )
+
+ assert.Equal(t, metav1.ConditionUnknown, verdict.value.Status)
+}
+
+// TestApplierClaimToGitTargets_FindsTheTargetsNamingIt: an applier claim event re-runs the GitTargets in its
+// namespace that name it by kind and name, and no other.
+func TestApplierClaimToGitTargets_FindsTheTargetsNamingIt(t *testing.T) {
+ argo := claimTarget(true)
+ argo.Name = "argo"
+ argo.Spec.ApplierClaimRef.Kind = configbutleraiv1alpha3.ApplierClaimKindArgoCD
+ unnamed := claimTarget(false)
+ unnamed.Name = "unnamed"
+ r := &GitTargetReconciler{Client: claimTestClient(t, claimTarget(true), argo, unnamed)}
+
+ requests := r.applierClaimToGitTargets(context.Background(),
+ testFluxClaim(claimLocalProvider))
+
+ require.Len(t, requests, 1)
+ assert.Equal(t, "checkout", requests[0].Name)
+}
+
+// TestApplierClaimReadinessChanged_IgnoresTheWorkersWrites: the GitTarget re-reads its applier claim when
+// Ready, Stalled, the source or the spec moved, and not on every operation transition.
+func TestApplierClaimReadinessChanged_IgnoresTheWorkersWrites(t *testing.T) {
+ p := applierClaimReadinessChanged()
+ before := readyClaim(metav1.ConditionTrue, ReasonSucceeded, false, "main")
+
+ operation := before.DeepCopyObject().(*configbutleraiv1alpha3.FluxClaim)
+ operation.Status.Operation = &configbutleraiv1alpha3.ApplyOperation{
+ Phase: configbutleraiv1alpha3.ApplyOperationRunning,
+ }
+ assert.False(t, p.Update(event.UpdateEvent{ObjectOld: before, ObjectNew: operation}))
+
+ source := before.DeepCopyObject().(*configbutleraiv1alpha3.FluxClaim)
+ source.Status.Source.Branch = "release"
+ assert.True(t, p.Update(event.UpdateEvent{ObjectOld: before, ObjectNew: source}))
+
+ notReady := readyClaim(metav1.ConditionFalse, applier.ReasonForbidden, true, "main")
+ assert.True(t, p.Update(event.UpdateEvent{ObjectOld: before, ObjectNew: notReady}))
+}
+
+// TestObserveApplierClaim_AFailedReadKeepsThePublishedVerdict: a blip reading the applier claim says
+// nothing about it.
+func TestObserveApplierClaim_AFailedReadKeepsThePublishedVerdict(t *testing.T) {
+ failing := interceptor.NewClient(claimTestClient(t).(ctrlclient.WithWatch), interceptor.Funcs{
+ Get: func(context.Context, ctrlclient.WithWatch, ctrlclient.ObjectKey, ctrlclient.Object,
+ ...ctrlclient.GetOption) error {
+ return errors.New("the API server is away")
+ },
+ })
+ r := &GitTargetReconciler{Client: failing}
+
+ target := claimTarget(true)
+ verdict, _ := r.observeApplierClaim(
+ context.Background(),
+ beginStatus(nil, nil, target),
+ target,
+ claimDestination(),
+ )
+ assert.Equal(t, metav1.ConditionUnknown, verdict.value.Status, "nothing published yet")
+
+ target = claimTarget(true)
+ target.Status.Conditions = []metav1.Condition{{
+ Type: GitTargetConditionApplierClaimReady, Status: metav1.ConditionTrue, Reason: ReasonSucceeded,
+ }}
+ verdict, _ = r.observeApplierClaim(
+ context.Background(),
+ beginStatus(nil, nil, target),
+ target,
+ claimDestination(),
+ )
+ assert.Equal(t, metav1.ConditionTrue, verdict.value.Status, "the published verdict stands")
+}
diff --git a/internal/controller/gittarget_apply_trigger_test.go b/internal/controller/gittarget_apply_trigger_test.go
deleted file mode 100644
index 98589fc70..000000000
--- a/internal/controller/gittarget_apply_trigger_test.go
+++ /dev/null
@@ -1,237 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-
-package controller
-
-import (
- "context"
- "errors"
- "testing"
-
- meta "github.com/fluxcd/pkg/apis/meta"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
- "sigs.k8s.io/controller-runtime/pkg/client/interceptor"
- "sigs.k8s.io/controller-runtime/pkg/event"
-
- configbutleraiv1alpha3 "github.com/ConfigButler/gitops-reverser/api/v1alpha3"
- "github.com/ConfigButler/gitops-reverser/internal/applier"
-)
-
-// triggerTarget is the GitTarget team-a/checkout mirroring through the KubeProvider
-// triggerLocalProvider on branch main, naming the FluxTrigger checkout when named is set.
-func triggerTarget(named bool) *configbutleraiv1alpha3.GitTarget {
- target := &configbutleraiv1alpha3.GitTarget{
- ObjectMeta: metav1.ObjectMeta{Name: "checkout", Namespace: "team-a"},
- Spec: configbutleraiv1alpha3.GitTargetSpec{
- KubeProviderRef: triggerProviderRef(triggerLocalProvider),
- GitBranchRef: meta.NamespacedObjectReference{Name: gitBranchNameForTest("platform", "main")},
- Path: "apps",
- },
- }
- if named {
- target.Spec.ApplyTriggerRef = &configbutleraiv1alpha3.ApplyTriggerReference{
- Kind: configbutleraiv1alpha3.ApplyTriggerKindFlux, Name: "checkout",
- }
- }
- return target
-}
-
-// triggerDestination is what triggerTarget's spec.gitBranchRef resolves to: branch main of the test
-// repository.
-func triggerDestination() gitDestination {
- branch := gitBranch("platform", "team-a")
- return gitDestination{
- key: ctrlclient.ObjectKeyFromObject(branch),
- branch: branch,
- provider: &configbutleraiv1alpha3.GitProvider{
- Spec: configbutleraiv1alpha3.GitProviderSpec{URL: applierTestRepo},
- },
- }
-}
-
-// readyTrigger is the FluxTrigger checkout, as its reconciler published it: Ready with status,
-// Stalled when stalled, and applying branch of the test repository.
-func readyTrigger(status metav1.ConditionStatus, reason string, stalled bool, branch string) ctrlclient.Object {
- trigger := testFluxTrigger(triggerLocalProvider)
- stalledStatus := metav1.ConditionFalse
- if stalled {
- stalledStatus = metav1.ConditionTrue
- }
- trigger.Status.Conditions = []metav1.Condition{
- {Type: ConditionTypeReady, Status: status, Reason: reason, Message: "as published", ObservedGeneration: 1,
- LastTransitionTime: metav1.Now()},
- {Type: ConditionTypeStalled, Status: stalledStatus, Reason: reason, ObservedGeneration: 1,
- LastTransitionTime: metav1.Now()},
- }
- trigger.Status.Source = &configbutleraiv1alpha3.FluxTriggerSource{
- URL: "https://github.com/acme/platform",
- Branch: branch,
- }
- return trigger
-}
-
-func TestObserveApplyTrigger_NoReferenceRemovesTheCondition(t *testing.T) {
- target := triggerTarget(false)
- target.Status.Conditions = []metav1.Condition{{Type: GitTargetConditionApplyTriggerReady, Status: "True"}}
- st := beginStatus(nil, nil, target)
-
- r := &GitTargetReconciler{Client: triggerTestClient(t)}
- _, present := r.observeApplyTrigger(context.Background(), st, target, triggerDestination())
-
- assert.False(t, present)
- assert.Nil(t, findCondition(target.Status.Conditions, GitTargetConditionApplyTriggerReady))
-}
-
-func TestObserveApplyTrigger_Verdicts(t *testing.T) {
- cases := []struct {
- name string
- trigger ctrlclient.Object
- wantStatus metav1.ConditionStatus
- wantReason string
- wantMessage string
- wantStalled bool
- wantConverged bool
- }{
- {name: "a Ready trigger whose applier applies this repository and branch",
- trigger: readyTrigger(metav1.ConditionTrue, ReasonSucceeded, false, "main"),
- wantStatus: metav1.ConditionTrue, wantReason: ReasonSucceeded, wantConverged: true,
- wantMessage: "FluxTrigger team-a/checkout holds Kustomization flux-system/checkout"},
- {name: "no such trigger, which may be on its way",
- wantStatus: metav1.ConditionFalse, wantReason: ReasonApplyTriggerNotFound,
- wantMessage: "FluxTrigger team-a/checkout not found"},
- {name: "a trigger whose applier applies another branch",
- trigger: readyTrigger(metav1.ConditionTrue, ReasonSucceeded, false, "release"),
- wantStatus: metav1.ConditionFalse, wantReason: applier.ReasonSourceMismatch, wantStalled: true,
- wantMessage: `applies branch "release", not main`},
- {name: "a trigger stalled itself",
- trigger: readyTrigger(metav1.ConditionFalse, applier.ReasonForbidden, true, "main"),
- wantStatus: metav1.ConditionFalse, wantReason: applier.ReasonForbidden, wantStalled: true,
- wantMessage: "FluxTrigger team-a/checkout: as published"},
- {name: "a trigger whose operation is unresolved, which clears on its own",
- trigger: readyTrigger(metav1.ConditionFalse, ReasonOperationUnresolved, false, "main"),
- wantStatus: metav1.ConditionFalse, wantReason: ReasonOperationUnresolved},
- {name: "a trigger that has not observed this reference yet, which clears once it does",
- trigger: readyTrigger(metav1.ConditionTrue, applier.ReasonNotReferenced, false, "main"),
- wantStatus: metav1.ConditionUnknown, wantReason: applier.ReasonNotReferenced,
- wantMessage: "has not observed that this GitTarget names it yet"},
- {name: "a trigger not checked yet",
- trigger: testFluxTrigger(triggerLocalProvider),
- wantStatus: metav1.ConditionUnknown, wantReason: ReasonProgressing},
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- var objects []ctrlclient.Object
- if tc.trigger != nil {
- objects = append(objects, tc.trigger)
- }
- r := &GitTargetReconciler{Client: triggerTestClient(t, objects...)}
- target := triggerTarget(true)
- st := beginStatus(nil, nil, target)
-
- verdict, present := r.observeApplyTrigger(context.Background(), st, target, triggerDestination())
-
- require.True(t, present)
- assert.Equal(t, tc.wantStatus, verdict.value.Status, verdict.value.Message)
- assert.Equal(t, tc.wantReason, verdict.value.Reason)
- assert.Contains(t, verdict.value.Message, tc.wantMessage)
- published := findCondition(target.Status.Conditions, GitTargetConditionApplyTriggerReady)
- require.NotNil(t, published)
- assert.Equal(t, tc.wantReason, published.Reason)
-
- rd := newGitTargetReadiness()
- applyTriggerReadiness(rd, verdict, present)
- assert.Equal(t, tc.wantConverged, rd.converged())
- assert.Equal(t, tc.wantStalled, !rd.converged() && !rd.converging())
- })
- }
-}
-
-// TestObserveApplyTrigger_AReadyOfAnOlderGenerationIsNotReady: a Ready the trigger's reconciler
-// published for an earlier spec says nothing about the one it has now.
-func TestObserveApplyTrigger_AReadyOfAnOlderGenerationIsNotReady(t *testing.T) {
- trigger := readyTrigger(metav1.ConditionTrue, ReasonSucceeded, false, "main")
- trigger.SetGeneration(2)
- r := &GitTargetReconciler{Client: triggerTestClient(t, trigger)}
- target := triggerTarget(true)
-
- verdict, _ := r.observeApplyTrigger(
- context.Background(),
- beginStatus(nil, nil, target),
- target,
- triggerDestination(),
- )
-
- assert.Equal(t, metav1.ConditionUnknown, verdict.value.Status)
-}
-
-// TestApplyTriggerToGitTargets_FindsTheTargetsNamingIt: a trigger event re-runs the GitTargets in its
-// namespace that name it by kind and name, and no other.
-func TestApplyTriggerToGitTargets_FindsTheTargetsNamingIt(t *testing.T) {
- argo := triggerTarget(true)
- argo.Name = "argo"
- argo.Spec.ApplyTriggerRef.Kind = configbutleraiv1alpha3.ApplyTriggerKindArgoCD
- unnamed := triggerTarget(false)
- unnamed.Name = "unnamed"
- r := &GitTargetReconciler{Client: triggerTestClient(t, triggerTarget(true), argo, unnamed)}
-
- requests := r.applyTriggerToGitTargets(context.Background(),
- testFluxTrigger(triggerLocalProvider))
-
- require.Len(t, requests, 1)
- assert.Equal(t, "checkout", requests[0].Name)
-}
-
-// TestApplyTriggerReadinessChanged_IgnoresTheWorkersWrites: the GitTarget re-reads its trigger when
-// Ready, Stalled, the source or the spec moved, and not on every operation transition.
-func TestApplyTriggerReadinessChanged_IgnoresTheWorkersWrites(t *testing.T) {
- p := applyTriggerReadinessChanged()
- before := readyTrigger(metav1.ConditionTrue, ReasonSucceeded, false, "main")
-
- operation := before.DeepCopyObject().(*configbutleraiv1alpha3.FluxTrigger)
- operation.Status.Operation = &configbutleraiv1alpha3.ApplyOperation{
- Phase: configbutleraiv1alpha3.ApplyOperationRunning,
- }
- assert.False(t, p.Update(event.UpdateEvent{ObjectOld: before, ObjectNew: operation}))
-
- source := before.DeepCopyObject().(*configbutleraiv1alpha3.FluxTrigger)
- source.Status.Source.Branch = "release"
- assert.True(t, p.Update(event.UpdateEvent{ObjectOld: before, ObjectNew: source}))
-
- notReady := readyTrigger(metav1.ConditionFalse, applier.ReasonForbidden, true, "main")
- assert.True(t, p.Update(event.UpdateEvent{ObjectOld: before, ObjectNew: notReady}))
-}
-
-// TestObserveApplyTrigger_AFailedReadKeepsThePublishedVerdict: a blip reading the trigger says
-// nothing about it.
-func TestObserveApplyTrigger_AFailedReadKeepsThePublishedVerdict(t *testing.T) {
- failing := interceptor.NewClient(triggerTestClient(t).(ctrlclient.WithWatch), interceptor.Funcs{
- Get: func(context.Context, ctrlclient.WithWatch, ctrlclient.ObjectKey, ctrlclient.Object,
- ...ctrlclient.GetOption) error {
- return errors.New("the API server is away")
- },
- })
- r := &GitTargetReconciler{Client: failing}
-
- target := triggerTarget(true)
- verdict, _ := r.observeApplyTrigger(
- context.Background(),
- beginStatus(nil, nil, target),
- target,
- triggerDestination(),
- )
- assert.Equal(t, metav1.ConditionUnknown, verdict.value.Status, "nothing published yet")
-
- target = triggerTarget(true)
- target.Status.Conditions = []metav1.Condition{{
- Type: GitTargetConditionApplyTriggerReady, Status: metav1.ConditionTrue, Reason: ReasonSucceeded,
- }}
- verdict, _ = r.observeApplyTrigger(
- context.Background(),
- beginStatus(nil, nil, target),
- target,
- triggerDestination(),
- )
- assert.Equal(t, metav1.ConditionTrue, verdict.value.Status, "the published verdict stands")
-}
diff --git a/internal/controller/gittarget_controller.go b/internal/controller/gittarget_controller.go
index 5ee71a434..754831e54 100644
--- a/internal/controller/gittarget_controller.go
+++ b/internal/controller/gittarget_controller.go
@@ -148,12 +148,12 @@ type GitTargetReconciler struct {
// +kubebuilder:rbac:groups=configbutler.ai,resources=gittargets/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=configbutler.ai,resources=gitproviders,verbs=get;list;watch
// +kubebuilder:rbac:groups=configbutler.ai,resources=gitbranches,verbs=get;list;watch
-// +kubebuilder:rbac:groups=configbutler.ai,resources=fluxtriggers;argocdtriggers,verbs=get;list;watch
+// +kubebuilder:rbac:groups=configbutler.ai,resources=fluxclaims;argocdclaims,verbs=get;list;watch
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;create;update
// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch
-// The Flux and Argo CD kinds a trigger holds are NOT granted here: nothing needs them until a
-// trigger names an applier, and an install that does not use Flux or Argo CD should not hold
+// The Flux and Argo CD kinds an applier claim holds are NOT granted here: nothing needs them until an
+// applier claim names an applier, and an install that does not use Flux or Argo CD should not hold
// write rights on their objects. config/rbac/flux-applier-role.yaml and argocd-applier-role.yaml,
// and the chart's applierAccess.flux and applierAccess.argocd, grant them.
@@ -237,7 +237,7 @@ func (r *GitTargetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
})
}
- // The refresher's trigger is this reconcile, which is already a scheduled tick looking for a
+ // The refresher's applier claim is this reconcile, which is already a scheduled tick looking for a
// job: a converged GitTarget requeues on RequeueSteadyInterval and those passes publish
// status without touching Git. Nothing new schedules anything, and the request is ENQUEUED
// rather than run here — the reconcile must not block on a network round trip, and the work
@@ -262,13 +262,13 @@ func (r *GitTargetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
st.setValue(GitTargetConditionRenderMatchesLive, observed.axes.Render)
refs := r.publishReferenceReadiness(st, &target, gitBranchReadiness(dest, true), sourceProvider)
- triggerVerdict, hasTrigger := r.observeApplyTrigger(ctx, st, &target, dest)
+ claimVerdict, hasClaim := r.observeApplierClaim(ctx, st, &target, dest)
rd := newGitTargetReadiness()
convergeAsSuspended(rd, &target)
gitTargetReadinessGates(rd, observed,
refs.gitBranch, refs.kubeProvider, refs.sourceCluster, publicationCondition(publication))
- applyTriggerReadiness(rd, triggerVerdict, hasTrigger)
+ applierClaimReadiness(rd, claimVerdict, hasClaim)
st.applyReadiness(rd)
if err := st.commit(ctx); err != nil {
@@ -988,7 +988,7 @@ func (r *GitTargetReconciler) recoverBranchAfterReplacement(
// gitDestination is what a GitTarget's spec.gitBranchRef resolves to, read ONCE for the whole
// reconcile: the GitBranch, and that GitBranch's GitProvider. That one pair answers the Validated
// gate, projects GitBranchReady, names the repository the branch worker has to be about, and the
-// binding the apply trigger is checked against. Reading them several times let those disagree about
+// binding the applier claim is checked against. Reading them several times let those disagree about
// which repository this target points at, which is the disagreement the worker's repository
// identity exists to prevent.
type gitDestination struct {
@@ -1482,18 +1482,18 @@ func (r *GitTargetReconciler) SetupWithManager(mgr ctrl.Manager) error {
handler.EnqueueRequestsFromMapFunc(r.clusterWatchRuleToGitTarget),
builder.WithPredicates(predicate.GenerationChangedPredicate{}),
).
- // React to an apply trigger appearing, going, or changing its Ready or its source, so
- // ApplyTriggerReady follows the trigger within one reconcile. The trigger's own reconciler
+ // React to an applier claim appearing, going, or changing its Ready or its source, so
+ // ApplierClaimReady follows the applier claim within one reconcile. The applier claim's own reconciler
// checks its applier; a status write that moves neither is filtered.
Watches(
- &configbutleraiv1alpha3.FluxTrigger{},
- handler.EnqueueRequestsFromMapFunc(r.applyTriggerToGitTargets),
- builder.WithPredicates(applyTriggerReadinessChanged()),
+ &configbutleraiv1alpha3.FluxClaim{},
+ handler.EnqueueRequestsFromMapFunc(r.applierClaimToGitTargets),
+ builder.WithPredicates(applierClaimReadinessChanged()),
).
Watches(
- &configbutleraiv1alpha3.ArgoCDTrigger{},
- handler.EnqueueRequestsFromMapFunc(r.applyTriggerToGitTargets),
- builder.WithPredicates(applyTriggerReadinessChanged()),
+ &configbutleraiv1alpha3.ArgoCDClaim{},
+ handler.EnqueueRequestsFromMapFunc(r.applierClaimToGitTargets),
+ builder.WithPredicates(applierClaimReadinessChanged()),
).
Named("gittarget")
@@ -1642,13 +1642,13 @@ func (r *GitTargetReconciler) kubeProviderToGitTargets(
return requests
}
-// applyTriggerToGitTargets maps an apply trigger event to every GitTarget in its namespace that
+// applierClaimToGitTargets maps an applier claim event to every GitTarget in its namespace that
// names it.
-func (r *GitTargetReconciler) applyTriggerToGitTargets(
+func (r *GitTargetReconciler) applierClaimToGitTargets(
ctx context.Context,
obj client.Object,
) []ctrlreconcile.Request {
- trigger, ok := obj.(configbutleraiv1alpha3.ApplyTrigger)
+ applierClaim, ok := obj.(configbutleraiv1alpha3.ApplierClaim)
if !ok {
return nil
}
@@ -1660,8 +1660,8 @@ func (r *GitTargetReconciler) applyTriggerToGitTargets(
var requests []ctrlreconcile.Request
for i := range targets.Items {
t := &targets.Items[i]
- ref := t.Spec.ApplyTriggerRef
- if ref == nil || ref.Kind != trigger.TriggerKind() || ref.Name != obj.GetName() {
+ ref := t.Spec.ApplierClaimRef
+ if ref == nil || ref.Kind != applierClaim.ClaimKind() || ref.Name != obj.GetName() {
continue
}
requests = append(requests, ctrlreconcile.Request{
@@ -1671,22 +1671,22 @@ func (r *GitTargetReconciler) applyTriggerToGitTargets(
return requests
}
-// applyTriggerReadinessChanged fires on create and delete, and on an update that changes the
-// trigger's generation, its Ready or Stalled condition, or its source: what ApplyTriggerReady reads.
-func applyTriggerReadinessChanged() predicate.Predicate {
+// applierClaimReadinessChanged fires on create and delete, and on an update that changes the
+// applier claim's generation, its Ready or Stalled condition, or its source: what ApplierClaimReady reads.
+func applierClaimReadinessChanged() predicate.Predicate {
summary := func(obj client.Object) string {
- trigger, ok := obj.(configbutleraiv1alpha3.ApplyTrigger)
+ applierClaim, ok := obj.(configbutleraiv1alpha3.ApplierClaim)
if !ok {
return ""
}
var out strings.Builder
- out.WriteString(strconv.FormatInt(trigger.GetGeneration(), 10))
+ out.WriteString(strconv.FormatInt(applierClaim.GetGeneration(), 10))
for _, conditionType := range []string{ConditionTypeReady, ConditionTypeStalled} {
- if c := findCondition(*trigger.StatusConditions(), conditionType); c != nil {
+ if c := findCondition(*applierClaim.StatusConditions(), conditionType); c != nil {
fmt.Fprintf(&out, "|%s/%s/%s/%d", c.Status, c.Reason, c.Message, c.ObservedGeneration)
}
}
- url, branch, known := trigger.SourceBinding()
+ url, branch, known := applierClaim.SourceBinding()
fmt.Fprintf(&out, "|%s|%s|%t", url, branch, known)
return out.String()
}
diff --git a/internal/controller/gittarget_dependency_status.go b/internal/controller/gittarget_dependency_status.go
index 516389303..ac257fce1 100644
--- a/internal/controller/gittarget_dependency_status.go
+++ b/internal/controller/gittarget_dependency_status.go
@@ -70,7 +70,7 @@ func gitTargetReadyCondition(target configbutleraiv1alpha3.GitTarget) conditionV
// with transient stream detail; including it would re-enqueue every rule of a target on churn that
// changes no verdict. A message-only drift self-corrects on the next move of any of the three.
//
-// The verdict is compared on its own because one reason can be either: the apply-trigger gate
+// The verdict is compared on its own because one reason can be either: the applier-claim gate
// reports the same reason as a stall or as progress, so a target can flip Stalled while its mirrored
// Status and Reason stay put, and the rule judges its stall from that verdict (see ruleReadiness).
func gitTargetReadyProjectionChanged() predicate.Predicate {
diff --git a/internal/controller/gittarget_dependency_status_test.go b/internal/controller/gittarget_dependency_status_test.go
index f0fc8f08a..c9abfb4c1 100644
--- a/internal/controller/gittarget_dependency_status_test.go
+++ b/internal/controller/gittarget_dependency_status_test.go
@@ -70,7 +70,7 @@ func TestGitTargetReadyProjectionChanged(t *testing.T) {
ObjectNew: gitTargetWithReady(1, metav1.ConditionTrue, GitTargetReasonOK, "ready"),
}), "the first Ready publish must fire")
- // One reason can be a stall or progress (the apply-trigger gate reports it either way), so a
+ // One reason can be a stall or progress (the applier-claim gate reports it either way), so a
// Stalled flip under an unchanged Status and Reason is a move the rule has to see: it stalls on
// that verdict, not on the reason.
progressing := gitTargetWithReady(1, metav1.ConditionFalse, "SourceMismatch", "applier reads another source")
diff --git a/internal/controller/gittarget_gitbranch_test.go b/internal/controller/gittarget_gitbranch_test.go
index ef227ac4e..33d9d474d 100644
--- a/internal/controller/gittarget_gitbranch_test.go
+++ b/internal/controller/gittarget_gitbranch_test.go
@@ -48,13 +48,13 @@ func newDestinationFixture(t *testing.T) *destinationFixture {
target := &configbutleraiv1alpha3.GitTarget{
ObjectMeta: metav1.ObjectMeta{Namespace: "team-a", Name: "apps"},
Spec: configbutleraiv1alpha3.GitTargetSpec{
- KubeProviderRef: triggerProviderRef(triggerLocalProvider),
+ KubeProviderRef: claimProviderRef(claimLocalProvider),
GitBranchRef: meta.NamespacedObjectReference{Name: "main", Namespace: "platform"},
Path: "apps",
},
}
c := fake.NewClientBuilder().WithScheme(scheme).
- WithObjects(provider, branch, target, admitting(triggerLocalProvider, "team-a")).
+ WithObjects(provider, branch, target, admitting(claimLocalProvider, "team-a")).
WithStatusSubresource(&configbutleraiv1alpha3.GitTarget{}).Build()
workers := git.NewWorkerManager(c, logr.Discard(), git.BranchWorkerLimits{}, types.SensitiveResourcePolicy{})
ctx, cancel := context.WithCancel(context.Background())
@@ -194,7 +194,7 @@ func TestGitBranchReadyOrSpecChanged(t *testing.T) {
}
// TestGitTargetsOn_FindsTheTargetsOfABranchOrItsProvider is the shared mapping behind the GitTarget
-// and apply-trigger watches on GitBranch and GitProvider.
+// and applier-claim watches on GitBranch and GitProvider.
func TestGitTargetsOn_FindsTheTargetsOfABranchOrItsProvider(t *testing.T) {
scheme := runtime.NewScheme()
require.NoError(t, configbutleraiv1alpha3.AddToScheme(scheme))
diff --git a/internal/controller/gittarget_shared_branch_test.go b/internal/controller/gittarget_shared_branch_test.go
index 979c98571..df50af592 100644
--- a/internal/controller/gittarget_shared_branch_test.go
+++ b/internal/controller/gittarget_shared_branch_test.go
@@ -31,7 +31,7 @@ func sharedProviderTarget(
CreationTimestamp: metav1.NewTime(time.Date(2026, 10, 21, 12, 0, 0, 0, time.UTC).Add(-age)),
},
Spec: configbutleraiv1alpha3.GitTargetSpec{
- KubeProviderRef: triggerProviderRef(triggerLocalProvider),
+ KubeProviderRef: claimProviderRef(claimLocalProvider),
GitBranchRef: meta.NamespacedObjectReference{
Name: gitBranchNameForTest("monorepo", "main"),
Namespace: providerNS,
@@ -47,7 +47,7 @@ func sharedProviderReconciler(t *testing.T, objects ...ctrlclient.Object) *GitTa
require.NoError(t, clientgoscheme.AddToScheme(scheme))
require.NoError(t, configbutleraiv1alpha3.AddToScheme(scheme))
// The KubeProvider every GitTarget here mirrors through admits every namespace.
- local := admitting(triggerLocalProvider)
+ local := admitting(claimLocalProvider)
local.Spec.AccessFrom = &configbutleraiv1alpha3.NamespaceMatcher{Selector: &metav1.LabelSelector{}}
objects = append(objects, local)
for _, ns := range []string{"platform", "team-b"} {
diff --git a/internal/controller/resource_condition_metrics.go b/internal/controller/resource_condition_metrics.go
index 9923dda8c..4959cd39b 100644
--- a/internal/controller/resource_condition_metrics.go
+++ b/internal/controller/resource_condition_metrics.go
@@ -34,8 +34,8 @@ const (
conditionKindGitBranch = "GitBranch"
conditionKindGitReceiver = "GitReceiver"
conditionKindKubeProvider = "KubeProvider"
- conditionKindFluxTrigger = configbutleraiv1alpha3.ApplyTriggerKindFlux
- conditionKindArgoCDTrigger = configbutleraiv1alpha3.ApplyTriggerKindArgoCD
+ conditionKindFluxClaim = configbutleraiv1alpha3.ApplierClaimKindFlux
+ conditionKindArgoCDClaim = configbutleraiv1alpha3.ApplierClaimKindArgoCD
)
// conditionMetricKind names the kind an object publishes under, or "" for one that is not on the
@@ -63,10 +63,10 @@ func conditionMetricKind(obj client.Object) string {
return conditionKindGitReceiver
case *configbutleraiv1alpha3.KubeProvider:
return conditionKindKubeProvider
- case *configbutleraiv1alpha3.FluxTrigger:
- return conditionKindFluxTrigger
- case *configbutleraiv1alpha3.ArgoCDTrigger:
- return conditionKindArgoCDTrigger
+ case *configbutleraiv1alpha3.FluxClaim:
+ return conditionKindFluxClaim
+ case *configbutleraiv1alpha3.ArgoCDClaim:
+ return conditionKindArgoCDClaim
default:
return ""
}
diff --git a/internal/controller/stream_status_test.go b/internal/controller/stream_status_test.go
index 88692b9d2..4b715d665 100644
--- a/internal/controller/stream_status_test.go
+++ b/internal/controller/stream_status_test.go
@@ -85,7 +85,7 @@ func TestRuleReadiness_StallsOnTheGitTargetsVerdictWhateverItsReason(t *testing.
metav1.ConditionTrue, GitTargetReasonAmbiguousLayout},
{"a conflicting path", stalledTarget(GitTargetReasonTargetConflict),
metav1.ConditionTrue, GitTargetReasonTargetConflict},
- {"apply trigger on another source", stalledTarget(applier.ReasonSourceMismatch),
+ {"applier claim on another source", stalledTarget(applier.ReasonSourceMismatch),
metav1.ConditionTrue, applier.ReasonSourceMismatch},
{"a reason this package has never heard of", stalledTarget("SomeFutureReason"),
metav1.ConditionTrue, "SomeFutureReason"},
diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go
index 3fb4080a9..f125506dc 100644
--- a/internal/controller/suite_test.go
+++ b/internal/controller/suite_test.go
@@ -49,7 +49,7 @@ var (
// The suite's shared KubeProvider: the operator's own identity, in the "default" namespace, listed
// in the suite reconciler's --in-cluster-kube-providers equivalent and admitting every namespace,
-// the way an install that mirrors its own cluster ships one. A spec's GitTargets and triggers name
+// the way an install that mirrors its own cluster ships one. A spec's GitTargets and applier claims name
// it through testKubeProviderRef.
const (
testKubeProviderNamespace = "default"
@@ -157,12 +157,12 @@ var _ = BeforeSuite(func() {
}).SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred())
- // No ApplierClients: a trigger's applier is never read here, so Configured stays Unknown
- // (NotChecked), which is enough to drive the watches between a trigger and its GitTargets.
+ // No ApplierClients: an applier claim's applier is never read here, so Configured stays Unknown
+ // (NotChecked), which is enough to drive the watches between an applier claim and its GitTargets.
for _, kind := range []string{
- configbutleraiv1alpha3.ApplyTriggerKindFlux, configbutleraiv1alpha3.ApplyTriggerKindArgoCD,
+ configbutleraiv1alpha3.ApplierClaimKindFlux, configbutleraiv1alpha3.ApplierClaimKindArgoCD,
} {
- err = (&ApplyTriggerReconciler{Client: mgr.GetClient(), Kind: kind}).SetupWithManager(mgr)
+ err = (&ApplierClaimReconciler{Client: mgr.GetClient(), Kind: kind}).SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred())
}
@@ -195,7 +195,7 @@ var _ = BeforeSuite(func() {
// Note: Old git.Worker has been replaced by WorkerManager + BranchWorker architecture
// Webhook tests are handled separately in webhook package
- // Ship the suite's shared in-cluster KubeProvider, so a GitTarget or trigger naming it
+ // Ship the suite's shared in-cluster KubeProvider, so a GitTarget or applier claim naming it
// (testKubeProviderRef) passes the reconcile-time hard gate that REQUIRES the referenced
// KubeProvider to exist. Its empty selector admits every namespace the specs create.
Expect(k8sClient.Create(ctx, &configbutleraiv1alpha3.KubeProvider{
diff --git a/internal/controller/superseded_fields_admission_test.go b/internal/controller/superseded_fields_admission_test.go
index d84c4ca4c..94ff19e33 100644
--- a/internal/controller/superseded_fields_admission_test.go
+++ b/internal/controller/superseded_fields_admission_test.go
@@ -245,9 +245,9 @@ var _ = Describe("Retained and refused clusterProviderRef", func() {
case "GitTarget":
spec["gitBranchRef"] = map[string]any{"name": "main"}
spec["path"] = "apps"
- case "FluxTrigger":
+ case "FluxClaim":
spec["kustomizationRef"] = map[string]any{"name": "apps"}
- case "ArgoCDTrigger":
+ case "ArgoCDClaim":
spec["applicationRef"] = map[string]any{"name": "apps"}
}
return &unstructured.Unstructured{Object: map[string]any{
@@ -258,7 +258,7 @@ var _ = Describe("Retained and refused clusterProviderRef", func() {
}}
}
- for _, kind := range []string{"GitTarget", "FluxTrigger", "ArgoCDTrigger"} {
+ for _, kind := range []string{"GitTarget", "FluxClaim", "ArgoCDClaim"} {
It("refuses spec.clusterProviderRef on a "+kind+", naming spec.kubeProviderRef", func() {
ctx := context.Background()
obj := referrer(kind, "legacy-cluster-provider", map[string]any{
diff --git a/internal/git/apply_trigger.go b/internal/git/applier_claim.go
similarity index 91%
rename from internal/git/apply_trigger.go
rename to internal/git/applier_claim.go
index db5a27e79..7c9714f6e 100644
--- a/internal/git/apply_trigger.go
+++ b/internal/git/applier_claim.go
@@ -19,7 +19,7 @@ import (
itypes "github.com/ConfigButler/gitops-reverser/internal/types"
)
-// This file is the branch worker's half of the apply trigger: deciding WHEN the appliers of the
+// This file is the branch worker's half of the applier claim: deciding WHEN the appliers of the
// GitTargets on this branch apply. See docs/design/apply-trigger.md and
// docs/design/applier-echoes.md.
//
@@ -39,7 +39,7 @@ import (
// ADOPTS it: it is pending once the worker observes an API create in a folder an applier applies,
// or a resync adds an object to Git, and is never skipped as already applied, because applying
// the same commit again is what stamps the object. A CatchUp also CONVERGES: once when the worker
-// starts, and once when a GitTarget first names the trigger; that one asks nothing of an applier
+// starts, and once when a GitTarget first names the applier claim; that one asks nothing of an applier
// that already applied the tip (S2 in docs/design/applier-echoes.md).
// - Repair: the apply window withheld a late apply over a newer API edit (apply_window.go), so
// the cluster runs an older value than Git. Asked like a Refusal, and of
@@ -166,20 +166,20 @@ type branchTarget struct {
applier ApplierRef
// consents is whether it asks for a Refusal request now (ConsentsToReapply).
consents bool
- // recorded and repairPending are the trigger's status.operation and status.repairPending, as listed:
- // what an earlier process, or the trigger's reconciler, recorded (apply_recovery.go).
+ // recorded and repairPending are the applier claim's status.operation and status.repairPending, as listed:
+ // what an earlier process, or the applier claim's reconciler, recorded (apply_recovery.go).
recorded *v1alpha3.ApplyOperation
repairPending bool
- // group names the GitRepository a Flux trigger's Kustomization applies, in its cluster; empty
+ // group names the GitRepository a Flux applier claim's Kustomization applies, in its cluster; empty
// for an Argo CD Application or a source not read yet (apply_groups.go).
group string
}
// appliersOnBranch lists the GitTargets that write this worker's branch through its GitProvider and
-// name an apply trigger that exists, with the applier each trigger holds. A GitTarget may name a
+// name an applier claim that exists, with the applier each applier claim holds. A GitTarget may name a
// GitProvider in another namespace, so every namespace is looked in. The client is cached, so this
-// costs no round trip. A GitTarget whose trigger does not exist (yet) names no applier: its
-// ApplyTriggerReady condition says so, and so does one its providers do not admit.
+// costs no round trip. A GitTarget whose applier claim does not exist (yet) names no applier: its
+// ApplierClaimReady condition says so, and so does one its providers do not admit.
func (w *BranchWorker) appliersOnBranch(ctx context.Context) ([]branchTarget, error) {
var targets v1alpha3.GitTargetList
if err := w.Client.List(ctx, &targets); err != nil {
@@ -188,22 +188,22 @@ func (w *BranchWorker) appliersOnBranch(ctx context.Context) ([]branchTarget, er
listed := make([]branchTarget, 0, len(targets.Items))
for i := range targets.Items {
target := &targets.Items[i]
- if !target.DeletionTimestamp.IsZero() || target.Spec.ApplyTriggerRef == nil ||
+ if !target.DeletionTimestamp.IsZero() || target.Spec.ApplierClaimRef == nil ||
!WritesThrough(target, w.GitBranch) {
continue
}
- trigger, err := ResolveApplyTrigger(ctx, w.Client, target)
+ applierClaim, err := ResolveApplierClaim(ctx, w.Client, target)
if err != nil {
return nil, err
}
- if trigger == nil || !trigger.GetDeletionTimestamp().IsZero() {
+ if applierClaim == nil || !applierClaim.GetDeletionTimestamp().IsZero() {
continue
}
// The dispatcher refuses an attempt for a GitTarget its providers do not admit. Listed, it
// would make the worker flush, raise its apply epoch and hold the applier, or its whole
// source group, for an attempt that cannot go.
if w.applierAdmitted != nil {
- decision, err := w.applierAdmitted(ctx, w.Client, target, trigger)
+ decision, err := w.applierAdmitted(ctx, w.Client, target, applierClaim)
if err != nil {
return nil, fmt.Errorf("check whether %s may ask its applier: %w", target.Name, err)
}
@@ -214,11 +214,11 @@ func (w *BranchWorker) appliersOnBranch(ctx context.Context) ([]branchTarget, er
listed = append(listed, branchTarget{
ref: itypes.NewResourceReference(target.Name, target.Namespace),
uid: target.UID,
- applier: ApplierOf(trigger),
+ applier: ApplierOf(applierClaim),
consents: ConsentsToReapply(target),
- recorded: trigger.Operation(),
- repairPending: trigger.RepairPending(),
- group: sourceGroup(trigger),
+ recorded: applierClaim.Operation(),
+ repairPending: applierClaim.RepairPending(),
+ group: sourceGroup(applierClaim),
})
}
sort.Slice(listed, func(i, j int) bool { return listed[i].ref.String() < listed[j].ref.String() })
@@ -240,7 +240,7 @@ type applierState struct {
// time leaves a converge pending.
named map[k8stypes.UID]bool
// appliedRevision is the revision the last operation that succeeded applied, as the applier
- // reported it, recovered on restart from the trigger's status.operation. A converge of the commit
+ // reported it, recovered on restart from the applier claim's status.operation. A converge of the commit
// it names asks nothing.
appliedRevision string
// inflight is the attempt whose report has not ended it yet. While it is out, nothing else is
@@ -261,18 +261,18 @@ type applierState struct {
// not sent yet, by objectKey: they are not in the commit the attempt applies, so its window holds
// them from the start (S1). Nil while no flush is waiting for this applier.
flushEdits map[string]time.Time
- // operation is the last operation an attempt started, as its trigger's status.operation shows
+ // operation is the last operation an attempt started, as its applier claim's status.operation shows
// it, and operationID the attempt it belongs to. Nil until this worker started one.
operation *v1alpha3.ApplyOperation
operationID uint64
- // published is what was last handed to the trigger status writer, nil before the first.
- published *ApplyTriggerStatus
- // withdrawn is an operation an attempt recorded on the trigger and then did not start, until
+ // published is what was last handed to the applier claim status writer, nil before the first.
+ published *ApplierClaimStatus
+ // withdrawn is an operation an attempt recorded on the applier claim and then did not start, until
// it is published as withdrawn.
withdrawn *v1alpha3.ApplyOperation
- // recovered is whether the trigger's recorded status was read back for this applier, once,
+ // recovered is whether the applier claim's recorded status was read back for this applier, once,
// when this process first listed it; followedSuspend is the suspend token of the last operation
- // the trigger's reconciler recorded that this worker followed (apply_recovery.go).
+ // the applier claim's reconciler recorded that this worker followed (apply_recovery.go).
recovered bool
followedSuspend string
}
@@ -297,11 +297,11 @@ type inflightAttempt struct {
// put back should this attempt ask for no apply.
previous *v1alpha3.ApplyOperation
previousID uint64
- // recovered is an attempt that asks nothing: it follows an operation recorded on the trigger,
- // by an earlier process or by the trigger's reconciler, to its end (apply_recovery.go).
+ // recovered is an attempt that asks nothing: it follows an operation recorded on the applier claim,
+ // by an earlier process or by the applier claim's reconciler, to its end (apply_recovery.go).
recovered bool
// recorded is set, on the requester's goroutine and before its reports, once the requester
- // called the attempt's Record: from then on the trigger may hold its operation, so an ending
+ // called the attempt's Record: from then on the applier claim may hold its operation, so an ending
// that asked for no apply withdraws it.
recorded atomic.Bool
// abandon closes the request's Abandoned channel.
@@ -356,7 +356,7 @@ func (l *branchWorkerEventLoop) serviceAppliers() {
if w.applyRequester == nil {
return
}
- defer l.publishTriggerStatus()
+ defer l.publishClaimStatus()
now := time.Now()
l.settleApplyResults(now)
l.followRecordedOperations(now)
@@ -379,7 +379,7 @@ func (l *branchWorkerEventLoop) serviceAppliers() {
}
if !l.flushed(due) {
// The publication wakes the loop when it lands (a push, or its retry); a save, a parent
- // recovery or a dirty checkout end with a wake of their own, and so does a sibling's flush.
+ // recovery or a dirty checkout end with a wake of their own.
l.armApplierTimer(now, wait)
return
}
@@ -472,7 +472,7 @@ func (l *branchWorkerEventLoop) alreadyConverged(
// pastBarrier reports whether every initial snapshot of every GitTarget the applier covers is
// published (initialSnapshotsPublished), so it may be asked for something. Before, an apply would
// destroy what is not mirrored yet: at startup the edits made while the operator was down, and for a
-// GitTarget that newly names the trigger, what its folder does not hold yet.
+// GitTarget that newly names the applier claim, what its folder does not hold yet.
func (l *branchWorkerEventLoop) pastBarrier(state *applierState) bool {
for _, target := range state.targets {
if !l.initialSnapshotsPublished(target.ref) {
@@ -570,7 +570,7 @@ func (l *branchWorkerEventLoop) appliersHaveWork() bool {
// a converge pending, for Flux and Argo CD alike: a commit observed before was pending for nobody,
// and was taken, so without one it would not be applied until some later push. It waits for that
// GitTarget's initial snapshots (pastBarrier), and asks nothing of an applier that already applied
-// the tip. An applier listed for the first time starts from what its trigger recorded as last
+// the tip. An applier listed for the first time starts from what its applier claim recorded as last
// applied. An unsent refusal of a GitTarget that is gone, or is now another incarnation, is dropped
// the same way.
func (l *branchWorkerEventLoop) syncAppliers(targets []branchTarget) {
@@ -621,7 +621,7 @@ func (l *branchWorkerEventLoop) listApplier(ref ApplierRef, named []branchTarget
}
}
-// lastApplied is the revision the last operation that succeeded applied, as an applier's trigger
+// lastApplied is the revision the last operation that succeeded applied, as an applier's applier claim
// recorded it in status.operation: its appliedRevision, or the commit it was asked for when the
// applier reported none. Empty when the last recorded operation did not succeed.
func lastApplied(targets []branchTarget) string {
@@ -709,7 +709,7 @@ func (l *branchWorkerEventLoop) sendAttempt(state *applierState, tip string, ref
for key := range refusals {
delete(l.unsentRefusals, key)
}
- // What the trigger shows now goes out before the request, so the record replaces the latest of
+ // What the applier claim shows now goes out before the request, so the record replaces the latest of
// it: the operation before this one may have completed on this very pass.
l.publishStatusOf(state)
token := operationToken(sentAt)
@@ -848,9 +848,9 @@ func operationPhase(outcome ApplyOutcome) (v1alpha3.ApplyOperationPhase, bool) {
return "", false
}
-// recordOperation records an attempt's report in the operation its trigger's status shows, which
+// recordOperation records an attempt's report in the operation its applier claim's status shows, which
// sendAttempt started as Requested when it sent the attempt. A report that ends an attempt that
-// asked for no apply puts back the operation before it, and withdraws its own from the trigger if
+// asked for no apply puts back the operation before it, and withdraws its own from the applier claim if
// the requester recorded it there. Unresolved stays until the operation completes, as
// advanceOperation keeps it.
func (s *applierState) recordOperation(attempt *inflightAttempt, res ApplyResult, now time.Time) {
@@ -897,24 +897,24 @@ func operationToken(sentAt time.Time) string {
}
// recorder is the attempt's ApplyRequest.Record: the requester writes the operation the attempt
-// starts through to the trigger, just before it starts the apply. That is the only way it reaches
-// the trigger before the requester reports it, so an attempt that ends before it leaves the trigger
-// as it was. Nil without a trigger status writer.
+// starts through to the applier claim, just before it starts the apply. That is the only way it reaches
+// the applier claim before the requester reports it, so an attempt that ends before it leaves the applier claim
+// as it was. Nil without an applier claim status writer.
func (l *branchWorkerEventLoop) recorder(state *applierState, attempt *inflightAttempt) func(context.Context) error {
- writer := l.w.triggerStatus
+ writer := l.w.claimStatus
if writer == nil {
return nil
}
- trigger, operation := state.ref.Trigger, state.operation.DeepCopy()
+ applierClaim, operation := state.ref.ApplierClaim, state.operation.DeepCopy()
return func(ctx context.Context) error {
attempt.recorded.Store(true)
- return writer.RecordApplyOperation(ctx, trigger, operation)
+ return writer.RecordApplyOperation(ctx, applierClaim, operation)
}
}
-// shownOperation is the operation the applier's trigger is to show: the last one an attempt
+// shownOperation is the operation the applier's applier claim is to show: the last one an attempt
// started, once the requester reported it, and the one before it until then. An attempt that
-// follows a recorded operation shows it from the start: the trigger holds it already.
+// follows a recorded operation shows it from the start: the applier claim holds it already.
func (s *applierState) shownOperation() *v1alpha3.ApplyOperation {
if attempt := s.inflight; attempt != nil && !attempt.recovered && attempt.phase == "" &&
s.operationID == attempt.id {
@@ -923,24 +923,24 @@ func (s *applierState) shownOperation() *v1alpha3.ApplyOperation {
return s.operation
}
-// publishTriggerStatus hands each applier's trigger what its status should show, when that changed
+// publishClaimStatus hands each applier's applier claim what its status should show, when that changed
// since the last time.
-func (l *branchWorkerEventLoop) publishTriggerStatus() {
+func (l *branchWorkerEventLoop) publishClaimStatus() {
for _, ref := range l.sortedAppliers() {
l.publishStatusOf(l.appliers[ref])
}
}
-// publishStatusOf hands one applier's trigger what its status should show, and the operation it
+// publishStatusOf hands one applier's applier claim what its status should show, and the operation it
// withdraws, when that changed since the last time. A worker that has started no operation and
// has no Repair pending has nothing to say yet, and says nothing: the status may hold what an earlier
// process recorded.
func (l *branchWorkerEventLoop) publishStatusOf(state *applierState) {
- writer := l.w.triggerStatus
+ writer := l.w.claimStatus
if writer == nil {
return
}
- next := ApplyTriggerStatus{
+ next := ApplierClaimStatus{
Operation: state.shownOperation(), Withdrawn: state.withdrawn, RepairPending: state.repairPending(),
}
if next.Withdrawn == nil {
@@ -952,7 +952,7 @@ func (l *branchWorkerEventLoop) publishStatusOf(state *applierState) {
return
}
}
- writer.PublishApplyTriggerStatus(state.ref.Trigger, next)
+ writer.PublishApplierClaimStatus(state.ref.ApplierClaim, next)
state.published, state.withdrawn = &next, nil
}
diff --git a/internal/git/apply_trigger_test.go b/internal/git/applier_claim_test.go
similarity index 79%
rename from internal/git/apply_trigger_test.go
rename to internal/git/applier_claim_test.go
index d8adc8fbc..3bcc19d6c 100644
--- a/internal/git/apply_trigger_test.go
+++ b/internal/git/applier_claim_test.go
@@ -2,7 +2,7 @@
package git
-// The branch worker's half of the apply trigger, from docs/design/apply-trigger.md: which reasons are
+// The branch worker's half of the applier claim, from docs/design/apply-trigger.md: which reasons are
// pending for the appliers, and the rule that nothing is asked while anything live is unpublished.
import (
@@ -86,25 +86,25 @@ func TestApplyInbox_RestorePutsBackWhatWasTaken(t *testing.T) {
assert.True(t, inbox.foreignPending())
}
-// applierRef is the applier the trigger testApplier(name) names holds, for a GitTarget in team-a.
+// applierRef is the applier the applier claim testApplier(name) names holds, for a GitTarget in team-a.
func applierRef(name string) ApplierRef {
return ApplierRef{
- Trigger: TriggerRef{Kind: configv1alpha3.ApplyTriggerKindFlux, Namespace: "team-a", Name: name},
- Cluster: "team-a/" + testKubeProvider, Kind: configv1alpha3.ApplierKindKustomization,
+ ApplierClaim: ApplierClaimRef{Kind: configv1alpha3.ApplierClaimKindFlux, Namespace: "team-a", Name: name},
+ Cluster: "team-a/" + testKubeProvider, Kind: configv1alpha3.ApplierKindKustomization,
Namespace: "team-a", Name: name,
}
}
-// argoApplierRef is the applier the ArgoCDTrigger argoApplier names holds.
+// argoApplierRef is the applier the ArgoCDClaim argoApplier names holds.
func argoApplierRef(name string) ApplierRef {
return ApplierRef{
- Trigger: TriggerRef{Kind: configv1alpha3.ApplyTriggerKindArgoCD, Namespace: "team-a", Name: name},
- Cluster: "team-a/" + testKubeProvider, Kind: configv1alpha3.ApplierKindApplication,
+ ApplierClaim: ApplierClaimRef{Kind: configv1alpha3.ApplierClaimKindArgoCD, Namespace: "team-a", Name: name},
+ Cluster: "team-a/" + testKubeProvider, Kind: configv1alpha3.ApplierKindApplication,
Namespace: "argocd", Name: name,
}
}
-// applierTestTarget is a GitTarget on the trigger worker's provider and branch unless overridden.
+// applierTestTarget is a GitTarget on the applier claim worker's provider and branch unless overridden.
func applierTestTarget(name string, applier bool, mutate ...func(*configv1alpha3.GitTarget)) *configv1alpha3.GitTarget {
target := &configv1alpha3.GitTarget{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "team-a", UID: k8stypes.UID("uid-" + name)},
@@ -114,7 +114,7 @@ func applierTestTarget(name string, applier bool, mutate ...func(*configv1alpha3
},
}
if applier {
- target.Spec.ApplyTriggerRef = testApplier(name)
+ target.Spec.ApplierClaimRef = testApplier(name)
}
for _, m := range mutate {
m(target)
@@ -122,10 +122,10 @@ func applierTestTarget(name string, applier bool, mutate ...func(*configv1alpha3
return target
}
-// applyTriggerLoop is an event loop on provider team-a/test-provider, branch main, whose fake client
-// holds the given objects and the trigger each GitTarget among them names, and whose requests are
+// applierClaimLoop is an event loop on provider team-a/test-provider, branch main, whose fake client
+// holds the given objects and the applier claim each GitTarget among them names, and whose requests are
// recorded.
-func applyTriggerLoop(t *testing.T, targets ...client.Object) (*branchWorkerEventLoop, *recordingRequester) {
+func applierClaimLoop(t *testing.T, targets ...client.Object) (*branchWorkerEventLoop, *recordingRequester) {
t.Helper()
scheme := runtime.NewScheme()
require.NoError(t, clientgoscheme.AddToScheme(scheme))
@@ -135,8 +135,8 @@ func applyTriggerLoop(t *testing.T, targets ...client.Object) (*branchWorkerEven
w.GitProviderNamespace = "team-a"
w.GitBranch = testGitBranch(w.GitProviderRef, w.GitProviderNamespace, w.Branch)
w.repo = RepoIdentity{URL: "https://github.com/acme/platform"}
- w.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(testTriggersFor(targets)...).
- WithStatusSubresource(&configv1alpha3.FluxTrigger{}, &configv1alpha3.ArgoCDTrigger{}).Build()
+ w.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(testClaimsFor(targets)...).
+ WithStatusSubresource(&configv1alpha3.FluxClaim{}, &configv1alpha3.ArgoCDClaim{}).Build()
w.Log = logr.Discard()
w.ctx = context.Background()
requester := &recordingRequester{}
@@ -162,7 +162,7 @@ func observeForeignCommit(w *BranchWorker) {
}
func TestServiceAppliers_ForeignCommitAsksEveryApplierOnTheBranch(t *testing.T) {
- loop, requester := applyTriggerLoop(t,
+ loop, requester := applierClaimLoop(t,
applierTestTarget("alpha", true),
applierTestTarget("bravo", true, func(g *configv1alpha3.GitTarget) { g.Spec.Suspend = true }),
applierTestTarget("no-applier", false),
@@ -206,7 +206,7 @@ func TestServiceAppliers_NothingIsSentWhileNoFlushCanBeMade(t *testing.T) {
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
- loop, requester := applyTriggerLoop(t, applierTestTarget("alpha", true))
+ loop, requester := applierClaimLoop(t, applierTestTarget("alpha", true))
observeForeignCommit(loop.w)
tc.busy(loop)
@@ -218,11 +218,11 @@ func TestServiceAppliers_NothingIsSentWhileNoFlushCanBeMade(t *testing.T) {
}
}
-// argoApplier makes a GitTarget name the ArgoCDTrigger of its own name, which holds the Application
+// argoApplier makes a GitTarget name the ArgoCDClaim of its own name, which holds the Application
// argocd/.
func argoApplier(g *configv1alpha3.GitTarget) {
- g.Spec.ApplyTriggerRef = &configv1alpha3.ApplyTriggerReference{
- Kind: configv1alpha3.ApplyTriggerKindArgoCD, Name: g.Name,
+ g.Spec.ApplierClaimRef = &configv1alpha3.ApplierClaimReference{
+ Kind: configv1alpha3.ApplierClaimKindArgoCD, Name: g.Name,
}
}
@@ -232,7 +232,7 @@ func TestServiceAppliers_AnApplicationIsAskedOnAForeignCommitAndARefusal(t *test
argo := argoApplierRef("argo")
t.Run("a foreign commit", func(t *testing.T) {
- loop, requester := applyTriggerLoop(t, applierTestTarget("argo", true, argoApplier))
+ loop, requester := applierClaimLoop(t, applierTestTarget("argo", true, argoApplier))
observeForeignCommit(loop.w)
loop.serviceAppliers()
requests := requester.taken()
@@ -243,7 +243,7 @@ func TestServiceAppliers_AnApplicationIsAskedOnAForeignCommitAndARefusal(t *test
})
t.Run("a refusal", func(t *testing.T) {
- loop, requester := applyTriggerLoop(t, applierTestTarget("argo", true, argoApplier,
+ loop, requester := applierClaimLoop(t, applierTestTarget("argo", true, argoApplier,
func(g *configv1alpha3.GitTarget) { g.Spec.OnRefusal = configv1alpha3.RefusalActionReapply }))
loop.w.inbox.observe(RemoteObservation{Commit: "aaa", By: ObservedByFetch})
loop.addUnsentRefusal(refusalKeyFor(itypes.NewResourceReference("argo", "team-a"), "deployments"),
@@ -257,7 +257,7 @@ func TestServiceAppliers_AnApplicationIsAskedOnAForeignCommitAndARefusal(t *test
}
func TestServiceAppliers_AForeignCommitAnswersEverythingElsePending(t *testing.T) {
- loop, requester := applyTriggerLoop(t, applierTestTarget("alpha", true))
+ loop, requester := applierClaimLoop(t, applierTestTarget("alpha", true))
loop.addUnsentRefusal(refusalKeyFor(itypes.NewResourceReference("alpha", "team-a"), "deployments"),
unsentRefusal{detail: "refused", uid: "uid-alpha"}, "obs")
observeForeignCommit(loop.w)
@@ -272,7 +272,7 @@ func TestServiceAppliers_AForeignCommitAnswersEverythingElsePending(t *testing.T
}
func TestServiceAppliers_NoApplierOnTheBranchClearsThePendingReasons(t *testing.T) {
- loop, requester := applyTriggerLoop(t, applierTestTarget("plain", false))
+ loop, requester := applierClaimLoop(t, applierTestTarget("plain", false))
observeForeignCommit(loop.w)
loop.w.inbox.observe(RemoteObservation{Commit: "ccc", By: ObservedByPush})
@@ -286,13 +286,13 @@ func TestServiceAppliers_NoApplierOnTheBranchClearsThePendingReasons(t *testing.
// TestServiceAppliers_ANewlyNamedApplierIsCaughtUp is review 7's W1: a merge to the branch is
// observed while no GitTarget names an applier, so the ForeignCommit is taken and dropped; then a
-// GitTarget names a trigger, whose Kustomization was suspended before its GitRepository fetched the
+// GitTarget names an applier claim, whose Kustomization was suspended before its GitRepository fetched the
// merge. The new applier had nothing pending, so the merge was never deployed until some later push.
// A GitTarget that names an applier for the first time leaves a converge pending, for a Kustomization
// and an Application alike (S2).
func TestServiceAppliers_ANewlyNamedApplierIsCaughtUp(t *testing.T) {
- loop, requester := applyTriggerLoop(t,
- applierTestTarget("flux", false), fluxTrigger("flux"),
+ loop, requester := applierClaimLoop(t,
+ applierTestTarget("flux", false), fluxClaim("flux"),
applierTestTarget("argo", false))
observeForeignCommit(loop.w)
loop.serviceAppliers()
@@ -300,7 +300,7 @@ func TestServiceAppliers_ANewlyNamedApplierIsCaughtUp(t *testing.T) {
require.False(t, loop.w.inbox.foreignPending(), "taken, with no applier to send it to")
for name, names := range map[string]func(*configv1alpha3.GitTarget){
- "flux": func(g *configv1alpha3.GitTarget) { g.Spec.ApplyTriggerRef = testApplier("flux") },
+ "flux": func(g *configv1alpha3.GitTarget) { g.Spec.ApplierClaimRef = testApplier("flux") },
"argo": argoApplier,
} {
target := &configv1alpha3.GitTarget{}
@@ -309,9 +309,9 @@ func TestServiceAppliers_ANewlyNamedApplierIsCaughtUp(t *testing.T) {
names(target)
require.NoError(t, loop.w.Client.Update(context.Background(), target))
}
- require.NoError(t, loop.w.Client.Create(context.Background(), &configv1alpha3.ArgoCDTrigger{
+ require.NoError(t, loop.w.Client.Create(context.Background(), &configv1alpha3.ArgoCDClaim{
ObjectMeta: metav1.ObjectMeta{Name: "argo", Namespace: "team-a"},
- Spec: configv1alpha3.ArgoCDTriggerSpec{
+ Spec: configv1alpha3.ArgoCDClaimSpec{
KubeProviderRef: meta.NamespacedObjectReference{Name: testKubeProvider},
ApplicationRef: meta.NamespacedObjectReference{Namespace: "argocd", Name: "argo"},
},
@@ -328,8 +328,8 @@ func TestServiceAppliers_ANewlyNamedApplierIsCaughtUp(t *testing.T) {
[]ApplierRef{requests[0].Applier, requests[1].Applier})
}
-func TestServiceAppliers_NoRequesterSwitchesTheTriggerOff(t *testing.T) {
- loop, _ := applyTriggerLoop(t, applierTestTarget("alpha", true))
+func TestServiceAppliers_NoRequesterSwitchesTheClaimOff(t *testing.T) {
+ loop, _ := applierClaimLoop(t, applierTestTarget("alpha", true))
loop.w.applyRequester = nil
observeForeignCommit(loop.w)
@@ -370,9 +370,9 @@ func applierLedgerFixture(t *testing.T, slug string) (*ledgerFixture, *branchWor
target := &configv1alpha3.GitTarget{}
require.NoError(t, f.worker.Client.Get(f.worker.ctx,
client.ObjectKey{Name: ledgerTargetName, Namespace: "default"}, target))
- target.Spec.ApplyTriggerRef = testApplier("apps")
+ target.Spec.ApplierClaimRef = testApplier("apps")
require.NoError(t, f.worker.Client.Update(f.worker.ctx, target))
- createTestTrigger(t, f.worker.Client, target)
+ createTestClaim(t, f.worker.Client, target)
requester := &recordingRequester{}
f.worker.applyRequester = requester
@@ -391,11 +391,11 @@ func foreignRequests(requests []ApplyRequest) []ApplyRequest {
return foreign
}
-// TestApplyTrigger_APushNoticeFindsAForeignCommit is §3.3 end to end over a real Git server: the
+// TestApplierClaim_APushNoticeFindsAForeignCommit is §3.3 end to end over a real Git server: the
// notice names nothing the worker trusts, the advertisement shows a tip the worker did not push,
// and the request names that tip.
-func TestApplyTrigger_APushNoticeFindsAForeignCommit(t *testing.T) {
- f, loop, requester := applierLedgerFixture(t, "apply-trigger-notice")
+func TestApplierClaim_APushNoticeFindsAForeignCommit(t *testing.T) {
+ f, loop, requester := applierLedgerFixture(t, "applier-claim-notice")
f.publish("ours")
loop.endWake(0)
assert.Empty(t, foreignRequests(requester.taken()), "the worker's own push is not foreign")
@@ -416,11 +416,11 @@ func TestApplyTrigger_APushNoticeFindsAForeignCommit(t *testing.T) {
assert.Empty(t, foreignRequests(requester.taken()))
}
-// TestApplyTrigger_ARejectedPushAsksForTheReplayedTip is §3.4: the foreign commit is found by the
+// TestApplierClaim_ARejectedPushAsksForTheReplayedTip is §3.4: the foreign commit is found by the
// compare-and-swap, the pending write is replayed onto it, and the request names the replayed tip,
// which carries the API side's answer.
-func TestApplyTrigger_ARejectedPushAsksForTheReplayedTip(t *testing.T) {
- f, loop, requester := applierLedgerFixture(t, "apply-trigger-rejected")
+func TestApplierClaim_ARejectedPushAsksForTheReplayedTip(t *testing.T) {
+ f, loop, requester := applierLedgerFixture(t, "applier-claim-rejected")
f.publish("prime")
loop.endWake(0)
requester.taken()
@@ -437,76 +437,76 @@ func TestApplyTrigger_ARejectedPushAsksForTheReplayedTip(t *testing.T) {
assert.Equal(t, "from-another-writer", gitOut(t, f.repoDir, "show", head+":OUTSIDE.md"))
}
-// TestApplierOf_KeysTheApplierByItsTriggerClusterAndKind: one name in two clusters is two appliers,
-// and the trigger's spec.kubeProviderRef, not the GitTarget's source, decides which cluster: the
-// KubeProvider's namespace/name, its namespace defaulted to the trigger's own. A Kustomization and
+// TestApplierOf_KeysTheApplierByItsClaimClusterAndKind: one name in two clusters is two appliers,
+// and the applier claim's spec.kubeProviderRef, not the GitTarget's source, decides which cluster: the
+// KubeProvider's namespace/name, its namespace defaulted to the applier claim's own. A Kustomization and
// an Application of one name are two appliers too.
-func TestApplierOf_KeysTheApplierByItsTriggerClusterAndKind(t *testing.T) {
- flux := fluxTrigger("apps", func(t *configv1alpha3.FluxTrigger) {
+func TestApplierOf_KeysTheApplierByItsClaimClusterAndKind(t *testing.T) {
+ flux := fluxClaim("apps", func(t *configv1alpha3.FluxClaim) {
t.Spec.KubeProviderRef.Name = "prod-eu-1"
})
kustomization := configv1alpha3.ApplierKindKustomization
- byFlux := TriggerRef{Kind: configv1alpha3.ApplyTriggerKindFlux, Namespace: "team-a", Name: "apps"}
- assert.Equal(t, ApplierRef{Trigger: byFlux, Cluster: "team-a/prod-eu-1", Kind: kustomization,
+ byFlux := ApplierClaimRef{Kind: configv1alpha3.ApplierClaimKindFlux, Namespace: "team-a", Name: "apps"}
+ assert.Equal(t, ApplierRef{ApplierClaim: byFlux, Cluster: "team-a/prod-eu-1", Kind: kustomization,
Namespace: "team-a", Name: "apps"}, ApplierOf(flux))
flux.Spec.KubeProviderRef.Name = "hub"
- assert.NotEqual(t, ApplierRef{Trigger: byFlux, Cluster: "team-a/prod-eu-1", Kind: kustomization,
+ assert.NotEqual(t, ApplierRef{ApplierClaim: byFlux, Cluster: "team-a/prod-eu-1", Kind: kustomization,
Namespace: "team-a", Name: "apps"}, ApplierOf(flux))
// One provider name in another namespace is another cluster.
flux.Spec.KubeProviderRef = meta.NamespacedObjectReference{Namespace: "platform", Name: "prod-eu-1"}
- assert.Equal(t, ApplierRef{Trigger: byFlux, Cluster: "platform/prod-eu-1", Kind: kustomization,
+ assert.Equal(t, ApplierRef{ApplierClaim: byFlux, Cluster: "platform/prod-eu-1", Kind: kustomization,
Namespace: "team-a", Name: "apps"}, ApplierOf(flux))
- argo := testTrigger("team-a", &configv1alpha3.ApplyTriggerReference{
- Kind: configv1alpha3.ApplyTriggerKindArgoCD, Name: "apps",
- }).(configv1alpha3.ApplyTrigger)
+ argo := testClaim("team-a", &configv1alpha3.ApplierClaimReference{
+ Kind: configv1alpha3.ApplierClaimKindArgoCD, Name: "apps",
+ }).(configv1alpha3.ApplierClaim)
assert.Equal(t, argoApplierRef("apps"), ApplierOf(argo))
}
-// TestResolveApplyTrigger_NamesNothingItCannotFind: a GitTarget that names no trigger, a kind this
-// release does not serve, or a trigger not created yet resolves to none, without an error; a read
+// TestResolveApplierClaim_NamesNothingItCannotFind: a GitTarget that names no applier claim, a kind this
+// release does not serve, or an applier claim not created yet resolves to none, without an error; a read
// that fails is an error, so a blip never reads as "no applier".
-func TestResolveApplyTrigger_NamesNothingItCannotFind(t *testing.T) {
- loop, _ := applyTriggerLoop(t, fluxTrigger("apps"))
+func TestResolveApplierClaim_NamesNothingItCannotFind(t *testing.T) {
+ loop, _ := applierClaimLoop(t, fluxClaim("apps"))
reader := loop.w.Client
- resolve := func(ref *configv1alpha3.ApplyTriggerReference) (configv1alpha3.ApplyTrigger, error) {
- target := applierTestTarget("alpha", false, func(g *configv1alpha3.GitTarget) { g.Spec.ApplyTriggerRef = ref })
- return ResolveApplyTrigger(context.Background(), reader, target)
+ resolve := func(ref *configv1alpha3.ApplierClaimReference) (configv1alpha3.ApplierClaim, error) {
+ target := applierTestTarget("alpha", false, func(g *configv1alpha3.GitTarget) { g.Spec.ApplierClaimRef = ref })
+ return ResolveApplierClaim(context.Background(), reader, target)
}
- for name, ref := range map[string]*configv1alpha3.ApplyTriggerReference{
+ for name, ref := range map[string]*configv1alpha3.ApplierClaimReference{
"no reference": nil,
- "an unknown kind": {Kind: "WebhookTrigger", Name: "apps"},
- "a missing object": {Kind: configv1alpha3.ApplyTriggerKindArgoCD, Name: "apps"},
+ "an unknown kind": {Kind: "WebhookClaim", Name: "apps"},
+ "a missing object": {Kind: configv1alpha3.ApplierClaimKindArgoCD, Name: "apps"},
} {
- trigger, err := resolve(ref)
+ applierClaim, err := resolve(ref)
require.NoError(t, err, name)
- assert.Nil(t, trigger, name)
+ assert.Nil(t, applierClaim, name)
}
- trigger, err := resolve(testApplier("apps"))
+ applierClaim, err := resolve(testApplier("apps"))
require.NoError(t, err)
- require.NotNil(t, trigger)
- assert.Equal(t, "team-a/apps", trigger.Applier().String())
- assert.Equal(t, "FluxTrigger/team-a/apps", ApplierOf(trigger).Trigger.String())
+ require.NotNil(t, applierClaim)
+ assert.Equal(t, "team-a/apps", applierClaim.Applier().String())
+ assert.Equal(t, "FluxClaim/team-a/apps", ApplierOf(applierClaim).ApplierClaim.String())
failing := interceptor.NewClient(reader.(client.WithWatch), interceptor.Funcs{
Get: func(context.Context, client.WithWatch, client.ObjectKey, client.Object, ...client.GetOption) error {
return errors.New("the API server is away")
},
})
- _, err = ResolveApplyTrigger(
+ _, err = ResolveApplierClaim(
context.Background(),
failing,
applierTestTarget(
"alpha",
false,
- func(g *configv1alpha3.GitTarget) { g.Spec.ApplyTriggerRef = testApplier("apps") },
+ func(g *configv1alpha3.GitTarget) { g.Spec.ApplierClaimRef = testApplier("apps") },
),
)
- require.ErrorContains(t, err, "read FluxTrigger team-a/apps")
+ require.ErrorContains(t, err, "read FluxClaim team-a/apps")
}
// TestServiceAppliers_AFailedListingKeepsItsRetry: a listing of the branch's appliers that fails
@@ -514,7 +514,7 @@ func TestResolveApplyTrigger_NamesNothingItCannotFind(t *testing.T) {
// armed that timer and then disarmed it, with nothing else to wait for, so the apply waited on some
// unrelated activity to wake the loop.
func TestServiceAppliers_AFailedListingKeepsItsRetry(t *testing.T) {
- loop, requester := applyTriggerLoop(t, applierTestTarget("alpha", true))
+ loop, requester := applierClaimLoop(t, applierTestTarget("alpha", true))
reader := loop.w.Client
loop.w.Client = interceptor.NewClient(reader.(client.WithWatch), interceptor.Funcs{
List: func(context.Context, client.WithWatch, client.ObjectList, ...client.ListOption) error {
@@ -538,7 +538,7 @@ func TestServiceAppliers_AFailedListingKeepsItsRetry(t *testing.T) {
// operations, failing while nothing else is pending must leave its retry armed: the same pass found
// no reason to send, and disarmed the timer.
func TestServiceAppliers_AFailedRecordedListingKeepsItsRetry(t *testing.T) {
- loop, _ := applyTriggerLoop(t, applierTestTarget("alpha", true))
+ loop, _ := applierClaimLoop(t, applierTestTarget("alpha", true))
loop.serviceAppliers()
loop.w.inbox.take()
for _, state := range loop.appliers {
@@ -564,9 +564,9 @@ func TestServiceAppliers_AFailedRecordedListingKeepsItsRetry(t *testing.T) {
// for a GitTarget its providers do not admit, so the worker leaves it out before it flushes or holds
// the applier. A check that fails is an error, retried like a failed listing, never a refusal.
func TestServiceAppliers_AGitTargetItsProvidersDoNotAdmitAsksNothing(t *testing.T) {
- loop, requester := applyTriggerLoop(t, applierTestTarget("alpha", true), applierTestTarget("bravo", true))
+ loop, requester := applierClaimLoop(t, applierTestTarget("alpha", true), applierTestTarget("bravo", true))
loop.w.applierAdmitted = func(
- _ context.Context, _ client.Reader, target *configv1alpha3.GitTarget, _ configv1alpha3.ApplyTrigger,
+ _ context.Context, _ client.Reader, target *configv1alpha3.GitTarget, _ configv1alpha3.ApplierClaim,
) (authz.Decision, error) {
return authz.Decision{Allowed: target.Name != "alpha"}, nil
}
@@ -579,7 +579,7 @@ func TestServiceAppliers_AGitTargetItsProvidersDoNotAdmitAsksNothing(t *testing.
assert.Equal(t, applierRef("bravo"), requests[0].Applier, "alpha's applier is asked nothing")
loop.w.applierAdmitted = func(
- context.Context, client.Reader, *configv1alpha3.GitTarget, configv1alpha3.ApplyTrigger,
+ context.Context, client.Reader, *configv1alpha3.GitTarget, configv1alpha3.ApplierClaim,
) (authz.Decision, error) {
return authz.Decision{}, errors.New("the cache is not having it")
}
diff --git a/internal/git/applier_echoes_test.go b/internal/git/applier_echoes_test.go
index bbfa8c27a..73c5db65c 100644
--- a/internal/git/applier_echoes_test.go
+++ b/internal/git/applier_echoes_test.go
@@ -272,16 +272,16 @@ func TestApplierEchoes_AReplayedResyncObeysATightenedPruneMode(t *testing.T) {
// keyOf is the key of a GitTarget in the default namespace.
func keyOf(name string) client.ObjectKey { return client.ObjectKey{Namespace: "default", Name: name} }
-// withTrigger names a FluxTrigger on team-a and gives the worker a requester that reports nothing,
+// withClaim names a FluxClaim on team-a and gives the worker a requester that reports nothing,
// so the test reports for it. The loop's commit window is an hour, so a window stays open until
// something closes it.
-func (f *echoFixture) withTrigger() *recordingRequester {
+func (f *echoFixture) withClaim() *recordingRequester {
f.t.Helper()
var target configv1alpha3.GitTarget
require.NoError(f.t, f.worker.Client.Get(f.worker.ctx, keyOf(echoTarget), &target))
- target.Spec.ApplyTriggerRef = testApplier(echoTarget)
+ target.Spec.ApplierClaimRef = testApplier(echoTarget)
require.NoError(f.t, f.worker.Client.Update(f.worker.ctx, &target))
- createTestTrigger(f.t, f.worker.Client, &target)
+ createTestClaim(f.t, f.worker.Client, &target)
requester := &recordingRequester{manual: true}
f.worker.applyRequester = requester
f.loop.stopTimers()
@@ -297,7 +297,7 @@ func (f *echoFixture) withTrigger() *recordingRequester {
return requester
}
-// echoApplier is the applier team-a's trigger holds.
+// echoApplier is the applier team-a's applier claim holds.
func (f *echoFixture) echoApplier() *applierState {
f.t.Helper()
require.Len(f.t, f.loop.appliers, 1)
@@ -313,7 +313,7 @@ func (f *echoFixture) echoApplier() *applierState {
// in the apply window, and the apply landing over it is withheld, with a Repair pending.
func TestApplierEchoes_FlushThenApply(t *testing.T) {
f := newEchoFixture(t, "echo-flush", configv1alpha3.PruneOnEvent, map[string]string{"x": "x1", "y": "y1"})
- requester := f.withTrigger()
+ requester := f.withClaim()
f.loop.lastPushAt = time.Now() // the push cooldown runs: a flush does not wait for it
f.writeWindow(echoEvent("x", "x2")) // E0, in the open window when the apply comes due
@@ -354,7 +354,7 @@ func TestApplierEchoes_FlushThenApply(t *testing.T) {
// there. It is adopted; a resync that only updates adopts nothing.
func TestApplierEchoes_AResyncThatAddsAnObjectAdoptsIt(t *testing.T) {
f := newEchoFixture(t, "echo-resync-adopt", configv1alpha3.PruneOnEvent, map[string]string{"x": "x1"})
- f.withTrigger()
+ f.withClaim()
resync := func(desired ...manifestanalyzer.DesiredResource) {
req := &ResyncRequest{Desired: desired, GitTargetName: echoTarget, GitTargetNamespace: "default",
@@ -390,7 +390,7 @@ func TestApplierEchoes_ASuspendedResyncIsNotRetained(t *testing.T) {
// TestApplierEchoes_AnAppliedFolderIsCreatedByItsResync: a folder an applier applies must exist
// before the applier is asked to apply it, or Argo CD retries a sync of a missing path for minutes
-// and Flux fails it. A resync of a GitTarget naming a trigger creates the folder with its bootstrap
+// and Flux fails it. A resync of a GitTarget naming an applier claim creates the folder with its bootstrap
// files, with no object to write; once it exists, an empty resync commits nothing. A GitTarget no
// applier applies keeps the old rule: an empty resync commits nothing.
func TestApplierEchoes_AnAppliedFolderIsCreatedByItsResync(t *testing.T) {
@@ -415,7 +415,7 @@ func TestApplierEchoes_AnAppliedFolderIsCreatedByItsResync(t *testing.T) {
applied := newEchoFixture(t, "echo-bootstrap-applied", configv1alpha3.PruneOnEvent, nil)
seed(applied)
- applied.withTrigger()
+ applied.withClaim()
emptyResync(applied)
assert.NotEmpty(t, applied.onMain(echoTarget+"/README.md"), "the folder exists, with its bootstrap files")
created := main(applied)
diff --git a/internal/git/apply_attempt_test.go b/internal/git/apply_attempt_test.go
index d8fc3e153..96d9f3dfe 100644
--- a/internal/git/apply_attempt_test.go
+++ b/internal/git/apply_attempt_test.go
@@ -23,10 +23,10 @@ import (
itypes "github.com/ConfigButler/gitops-reverser/internal/types"
)
-// manualLoop is applyTriggerLoop with a requester that reports nothing until the test does.
+// manualLoop is applierClaimLoop with a requester that reports nothing until the test does.
func manualLoop(t *testing.T, targets ...client.Object) (*branchWorkerEventLoop, *recordingRequester) {
t.Helper()
- loop, requester := applyTriggerLoop(t, targets...)
+ loop, requester := applierClaimLoop(t, targets...)
requester.manual = true
return loop, requester
}
@@ -130,7 +130,7 @@ func TestApplier_WithdrawnConsentNeverBecomesAnImmediateApply(t *testing.T) {
// recovered while its attempt was out came back with the failed attempt and was requested again.
func TestApplier_ARecoveredRefusalIsNotRetried(t *testing.T) {
loop, requester := manualLoop(t, applierTestTarget("alpha", true, reapplying),
- fluxTrigger("alpha", recording(recordedOperation(configv1alpha3.ApplyOperationSucceeded), false)))
+ fluxClaim("alpha", recording(recordedOperation(configv1alpha3.ApplyOperationSucceeded), false)))
loop.w.inbox.observe(RemoteObservation{Commit: "bbb", By: ObservedByFetch}) // the tip it applied
key := addAlphaUnsentRefusal(loop)
loop.serviceAppliers()
@@ -203,14 +203,14 @@ func TestApplier_EachApplierRetriesOnItsOwnClock(t *testing.T) {
assert.Equal(t, applierRef("alpha"), retry[0].Applier)
}
-// TestApplier_OneAttemptForEveryTargetNamingIt: GitTargets that share a trigger are asked for
+// TestApplier_OneAttemptForEveryTargetNamingIt: GitTargets that share an applier claim are asked for
// together, each with its own reason, because its apply reaches every one of their folders.
func TestApplier_OneAttemptForEveryTargetNamingIt(t *testing.T) {
- shared := func(g *configv1alpha3.GitTarget) { g.Spec.ApplyTriggerRef = testApplier("shared") }
- loop, requester := applyTriggerLoop(t,
+ shared := func(g *configv1alpha3.GitTarget) { g.Spec.ApplierClaimRef = testApplier("shared") }
+ loop, requester := applierClaimLoop(t,
applierTestTarget("alpha", true, shared, reapplying),
applierTestTarget("bravo", true, shared),
- fluxTrigger("shared"),
+ fluxClaim("shared"),
)
loop.w.inbox.observe(RemoteObservation{Commit: "aaa", By: ObservedByPush}) // the startup converge
addAlphaUnsentRefusal(loop)
@@ -263,7 +263,7 @@ func TestApplier_ATargetThatNoLongerNamesAnApplierHasNothingPending(t *testing.T
out := requester.taken()
require.Len(t, out, 1)
- updateTarget(t, loop, "alpha", func(g *configv1alpha3.GitTarget) { g.Spec.ApplyTriggerRef = nil })
+ updateTarget(t, loop, "alpha", func(g *configv1alpha3.GitTarget) { g.Spec.ApplierClaimRef = nil })
loop.w.inbox.observe(RemoteObservation{Commit: "ccc", By: ObservedByPush})
loop.serviceAppliers()
report(out[0], ApplyRequestFailed)
@@ -335,7 +335,7 @@ func TestPushNotice_TheTipAlreadySeenDoesNotCutAWindow(t *testing.T) {
{name: "a notice that names no commit", after: "", wantClose: true},
} {
t.Run(tc.name, func(t *testing.T) {
- loop, _ := applyTriggerLoop(t, applierTestTarget("alpha", true), &configv1alpha3.GitProvider{
+ loop, _ := applierClaimLoop(t, applierTestTarget("alpha", true), &configv1alpha3.GitProvider{
ObjectMeta: metav1.ObjectMeta{Name: "test-provider", Namespace: "team-a"},
Spec: configv1alpha3.GitProviderSpec{URL: "https://github.com/acme/platform"},
})
@@ -352,7 +352,7 @@ func TestPushNotice_TheTipAlreadySeenDoesNotCutAWindow(t *testing.T) {
// TestWriteBusy_ASaveAnnouncedBeforeItsWrites: a registered CommitRequest makes the branch busy, so
// a catch-up cannot land between a save's announcement and its first writes.
func TestWriteBusy_ASaveAnnouncedBeforeItsWrites(t *testing.T) {
- loop, _ := applyTriggerLoop(t, applierTestTarget("alpha", true))
+ loop, _ := applierClaimLoop(t, applierTestTarget("alpha", true))
assert.False(t, loop.writeBusy())
loop.pendingCRs = map[commitRequestID]*pendingCommitRequest{{}: {}}
assert.True(t, loop.writeBusy())
diff --git a/internal/git/apply_catchup_test.go b/internal/git/apply_catchup_test.go
index 00821e77f..0c51da5ce 100644
--- a/internal/git/apply_catchup_test.go
+++ b/internal/git/apply_catchup_test.go
@@ -16,8 +16,8 @@ import (
// for Flux and Argo CD alike, decided in the branch worker; no apply goes out before every initial
// snapshot is published.
-// appliedAt is a trigger whose last recorded operation succeeded, applying revision.
-func appliedAt(revision string) func(*configv1alpha3.FluxTrigger) {
+// appliedAt is an applier claim whose last recorded operation succeeded, applying revision.
+func appliedAt(revision string) func(*configv1alpha3.FluxClaim) {
return recording(&configv1alpha3.ApplyOperation{
Phase: configv1alpha3.ApplyOperationSucceeded, Reason: configv1alpha3.ApplyReasonCatchUp,
Commit: "older", AppliedRevision: revision,
@@ -32,12 +32,12 @@ func createdEvent(target string, managers ...string) Event {
}
// TestCatchUp_TheStartupConvergeAsksNothingOfAnApplierAtTheTip: the first observation leaves a
-// converge pending for every applier. One whose trigger recorded the tip as applied is asked
+// converge pending for every applier. One whose applier claim recorded the tip as applied is asked
// nothing; the other is asked a CatchUp of the tip.
func TestCatchUp_TheStartupConvergeAsksNothingOfAnApplierAtTheTip(t *testing.T) {
loop, requester := manualLoop(t,
applierTestTarget("fresh", true),
- applierTestTarget("current", true), fluxTrigger("current", appliedAt("main@sha1:AAA")),
+ applierTestTarget("current", true), fluxClaim("current", appliedAt("main@sha1:AAA")),
)
loop.w.inbox.observe(RemoteObservation{Commit: "aaa", By: ObservedByFetch})
loop.serviceAppliers()
@@ -150,11 +150,11 @@ func TestCatchUp_AFirstNamingConvergesOnce(t *testing.T) {
loop.w.inbox.observe(RemoteObservation{Commit: "bbb", By: ObservedByPush})
shared := applierTestTarget("bravo", true,
- func(g *configv1alpha3.GitTarget) { g.Spec.ApplyTriggerRef = testApplier("alpha") })
+ func(g *configv1alpha3.GitTarget) { g.Spec.ApplierClaimRef = testApplier("alpha") })
require.NoError(t, loop.w.Client.Create(t.Context(), shared))
loop.serviceAppliers()
second := requester.taken()
- require.Len(t, second, 1, "bravo names alpha's trigger for the first time")
+ require.Len(t, second, 1, "bravo names alpha's applier claim for the first time")
assert.Equal(t, "bbb", second[0].Commit)
report(second[0], ApplySucceeded)
diff --git a/internal/git/apply_epoch.go b/internal/git/apply_epoch.go
index 9c24f3b9b..ff8fabdfb 100644
--- a/internal/git/apply_epoch.go
+++ b/internal/git/apply_epoch.go
@@ -26,7 +26,7 @@ import (
//
// Three exceptions keep that from holding anything forever:
//
-// - A branch with no trigger never waits: nothing applies its commits.
+// - A branch with no applier claim never waits: nothing applies its commits.
// - A collection's initial snapshot (the first one applied for it, ResyncRequest.Initial) is
// exempt. It is the startup baseline, and S2 asks no applier for anything before it is in Git,
// so holding it for an apply would wait on itself.
diff --git a/internal/git/apply_epoch_test.go b/internal/git/apply_epoch_test.go
index 8d84c1855..bd1cefd9c 100644
--- a/internal/git/apply_epoch_test.go
+++ b/internal/git/apply_epoch_test.go
@@ -122,7 +122,7 @@ func TestApplyEpoch_InitialSnapshotsAreExempt(t *testing.T) {
}
// TestApplyEpoch_ABranchWithNoTriggerNeverWaits: nothing applies its commits.
-func TestApplyEpoch_ABranchWithNoTriggerNeverWaits(t *testing.T) {
+func TestApplyEpoch_ABranchWithNoClaimNeverWaits(t *testing.T) {
loop, _ := manualLoop(t, applierTestTarget("alpha", false))
loop.followRecordedOperations(time.Now())
observeForeignCommit(loop.w)
diff --git a/internal/git/apply_groups.go b/internal/git/apply_groups.go
index 4b10911cb..c8a1f93bc 100644
--- a/internal/git/apply_groups.go
+++ b/internal/git/apply_groups.go
@@ -16,14 +16,14 @@ import (
// another commit than the one it checked. The appliers of a group take turns (turnOrder). One
// worker writes a repository branch (branch_owner.go), so a group's appliers are all its own.
-// sourceGroup is the group a trigger's applier belongs to: its cluster and GitRepository, or "" for
+// sourceGroup is the group an applier claim's applier belongs to: its cluster and GitRepository, or "" for
// an applier that shares nothing this way.
-func sourceGroup(trigger v1alpha3.ApplyTrigger) string {
- source, ok := trigger.SharedSource()
+func sourceGroup(applierClaim v1alpha3.ApplierClaim) string {
+ source, ok := applierClaim.SharedSource()
if !ok {
return ""
}
- return trigger.KubeProviderKey().String() + "/" + source.Namespace + "/" + source.Name
+ return applierClaim.KubeProviderKey().String() + "/" + source.Namespace + "/" + source.Name
}
// group is the applier's group, as listed with any GitTarget naming it.
diff --git a/internal/git/apply_groups_test.go b/internal/git/apply_groups_test.go
index c773fb9f2..b60953767 100644
--- a/internal/git/apply_groups_test.go
+++ b/internal/git/apply_groups_test.go
@@ -16,10 +16,10 @@ import (
configv1alpha3 "github.com/ConfigButler/gitops-reverser/api/v1alpha3"
)
-// applying gives a trigger the source its reconciler published.
-func applying(gitRepository string) func(*configv1alpha3.FluxTrigger) {
- return func(trigger *configv1alpha3.FluxTrigger) {
- trigger.Status.Source = &configv1alpha3.FluxTriggerSource{
+// applying gives an applier claim the source its reconciler published.
+func applying(gitRepository string) func(*configv1alpha3.FluxClaim) {
+ return func(applierClaim *configv1alpha3.FluxClaim) {
+ applierClaim.Status.Source = &configv1alpha3.FluxClaimSource{
GitRepository: meta.NamespacedObjectReference{Namespace: "flux-system", Name: gitRepository},
URL: "https://example.com/repo", Branch: "main",
}
@@ -30,9 +30,9 @@ func applying(gitRepository string) func(*configv1alpha3.FluxTrigger) {
// after the other, and two on different GitRepositories at once.
func TestGroups_OneOperationAtATimePerSource(t *testing.T) {
loop, requester := manualLoop(t,
- applierTestTarget("alpha", true), fluxTrigger("alpha", applying("platform")),
- applierTestTarget("bravo", true), fluxTrigger("bravo", applying("platform")),
- applierTestTarget("charlie", true), fluxTrigger("charlie", applying("other")),
+ applierTestTarget("alpha", true), fluxClaim("alpha", applying("platform")),
+ applierTestTarget("bravo", true), fluxClaim("bravo", applying("platform")),
+ applierTestTarget("charlie", true), fluxClaim("charlie", applying("other")),
)
observeForeignCommit(loop.w)
loop.serviceAppliers()
@@ -57,8 +57,8 @@ func TestGroups_OneOperationAtATimePerSource(t *testing.T) {
// turn waits for its siblings instead of going again.
func TestGroups_TheSourceIsSharedInTurn(t *testing.T) {
loop, requester := manualLoop(t,
- applierTestTarget("alpha", true), fluxTrigger("alpha", applying("platform")),
- applierTestTarget("bravo", true), fluxTrigger("bravo", applying("platform")),
+ applierTestTarget("alpha", true), fluxClaim("alpha", applying("platform")),
+ applierTestTarget("bravo", true), fluxClaim("bravo", applying("platform")),
)
observeForeignCommit(loop.w)
loop.serviceAppliers()
@@ -75,17 +75,17 @@ func TestGroups_TheSourceIsSharedInTurn(t *testing.T) {
}
}
-// TestSourceGroup_KeysByTheTriggersKubeProvider: a group is one GitRepository in one cluster, and a
+// TestSourceGroup_KeysByTheClaimsKubeProvider: a group is one GitRepository in one cluster, and a
// cluster is a KubeProvider's namespace/name, so the same GitRepository reached through providers
-// sharing a name in two namespaces is two groups. A trigger that has read no source shares none.
-func TestSourceGroup_KeysByTheTriggersKubeProvider(t *testing.T) {
- assert.Empty(t, sourceGroup(fluxTrigger("alpha")), "a trigger with no source shares nothing")
+// sharing a name in two namespaces is two groups. An applier claim that has read no source shares none.
+func TestSourceGroup_KeysByTheClaimsKubeProvider(t *testing.T) {
+ assert.Empty(t, sourceGroup(fluxClaim("alpha")), "an applier claim with no source shares nothing")
- own := fluxTrigger("alpha", applying("platform"))
+ own := fluxClaim("alpha", applying("platform"))
assert.Equal(t, "team-a/"+testKubeProvider+"/flux-system/platform", sourceGroup(own))
- elsewhere := fluxTrigger("alpha", applying("platform"), func(trigger *configv1alpha3.FluxTrigger) {
- trigger.Spec.KubeProviderRef.Namespace = "platform"
+ elsewhere := fluxClaim("alpha", applying("platform"), func(applierClaim *configv1alpha3.FluxClaim) {
+ applierClaim.Spec.KubeProviderRef.Namespace = "platform"
})
assert.Equal(t, "platform/"+testKubeProvider+"/flux-system/platform", sourceGroup(elsewhere))
assert.NotEqual(t, sourceGroup(own), sourceGroup(elsewhere))
diff --git a/internal/git/apply_operation_test.go b/internal/git/apply_operation_test.go
index 5a0d22100..0f65e5b7d 100644
--- a/internal/git/apply_operation_test.go
+++ b/internal/git/apply_operation_test.go
@@ -138,7 +138,7 @@ func TestApplier_AnApplierNoLongerNamedAbandonsItsOperation(t *testing.T) {
loop.serviceAppliers()
require.Equal(t, int64(1), loop.w.unresolvedOperations.Load())
- updateTarget(t, loop, "alpha", func(g *configv1alpha3.GitTarget) { g.Spec.ApplyTriggerRef = nil })
+ updateTarget(t, loop, "alpha", func(g *configv1alpha3.GitTarget) { g.Spec.ApplierClaimRef = nil })
observeForeignCommit(loop.w)
loop.serviceAppliers()
@@ -187,73 +187,73 @@ func onlyApplier(t *testing.T, loop *branchWorkerEventLoop) *applierState {
return nil
}
-// recordingStatusWriter records what a worker publishes on its triggers' status, in order, and the
+// recordingStatusWriter records what a worker publishes on its applier claims' status, in order, and the
// operations the requester recorded.
type recordingStatusWriter struct {
mu sync.Mutex
- published map[TriggerRef][]ApplyTriggerStatus
- recorded map[TriggerRef][]*configv1alpha3.ApplyOperation
+ published map[ApplierClaimRef][]ApplierClaimStatus
+ recorded map[ApplierClaimRef][]*configv1alpha3.ApplyOperation
}
func (r *recordingStatusWriter) RecordApplyOperation(
- _ context.Context, trigger TriggerRef, operation *configv1alpha3.ApplyOperation,
+ _ context.Context, applierClaim ApplierClaimRef, operation *configv1alpha3.ApplyOperation,
) error {
r.mu.Lock()
defer r.mu.Unlock()
if r.recorded == nil {
- r.recorded = map[TriggerRef][]*configv1alpha3.ApplyOperation{}
+ r.recorded = map[ApplierClaimRef][]*configv1alpha3.ApplyOperation{}
}
- r.recorded[trigger] = append(r.recorded[trigger], operation)
+ r.recorded[applierClaim] = append(r.recorded[applierClaim], operation)
return nil
}
-func (r *recordingStatusWriter) PublishApplyTriggerStatus(trigger TriggerRef, status ApplyTriggerStatus) {
+func (r *recordingStatusWriter) PublishApplierClaimStatus(applierClaim ApplierClaimRef, status ApplierClaimStatus) {
r.mu.Lock()
defer r.mu.Unlock()
if r.published == nil {
- r.published = map[TriggerRef][]ApplyTriggerStatus{}
+ r.published = map[ApplierClaimRef][]ApplierClaimStatus{}
}
- r.published[trigger] = append(r.published[trigger], status)
+ r.published[applierClaim] = append(r.published[applierClaim], status)
}
-// last is the last publication for trigger, and how many there were.
-func (r *recordingStatusWriter) last(trigger TriggerRef) (ApplyTriggerStatus, int) {
+// last is the last publication for applierClaim, and how many there were.
+func (r *recordingStatusWriter) last(applierClaim ApplierClaimRef) (ApplierClaimStatus, int) {
r.mu.Lock()
defer r.mu.Unlock()
- all := r.published[trigger]
+ all := r.published[applierClaim]
if len(all) == 0 {
- return ApplyTriggerStatus{}, 0
+ return ApplierClaimStatus{}, 0
}
return all[len(all)-1], len(all)
}
-// records are the operations recorded on trigger, in order.
-func (r *recordingStatusWriter) records(trigger TriggerRef) []*configv1alpha3.ApplyOperation {
+// records are the operations recorded on applierClaim, in order.
+func (r *recordingStatusWriter) records(applierClaim ApplierClaimRef) []*configv1alpha3.ApplyOperation {
r.mu.Lock()
defer r.mu.Unlock()
- return r.recorded[trigger]
+ return r.recorded[applierClaim]
}
-// TestApplier_TheTriggerStatusFollowsEveryTransition: the operation reaches the trigger's status
+// TestApplier_TheClaimStatusFollowsEveryTransition: the operation reaches the applier claim's status
// first through the request's Record, which the requester writes through before it applies (so a
// restart finds every apply that may have started), and moves with every report, so `kubectl get
-// fluxtriggers` shows where an apply stands without anything having to reconcile. Unresolved holds
+// fluxclaims` shows where an apply stands without anything having to reconcile. Unresolved holds
// until the operation completes.
-func TestApplier_TheTriggerStatusFollowsEveryTransition(t *testing.T) {
+func TestApplier_TheClaimStatusFollowsEveryTransition(t *testing.T) {
loop, requester := manualLoop(t, applierTestTarget("alpha", true))
writer := &recordingStatusWriter{}
- loop.w.triggerStatus = writer
- trigger := applierRef("alpha").Trigger
+ loop.w.claimStatus = writer
+ applierClaim := applierRef("alpha").ApplierClaim
observeForeignCommit(loop.w)
loop.serviceAppliers()
sent := requester.taken()
require.Len(t, sent, 1)
- _, n := writer.last(trigger)
+ _, n := writer.last(applierClaim)
require.Zero(t, n, "nothing is published as the attempt is sent")
require.NotNil(t, sent[0].Record)
require.NoError(t, sent[0].Record(context.Background()))
- records := writer.records(trigger)
+ records := writer.records(applierClaim)
require.Len(t, records, 1, "the request writes it through before it applies")
requested := records[0]
assert.Equal(t, configv1alpha3.ApplyOperationRequested, requested.Phase)
@@ -269,7 +269,7 @@ func TestApplier_TheTriggerStatusFollowsEveryTransition(t *testing.T) {
res.ID = sent[0].ID
sent[0].Report(res)
loop.serviceAppliers()
- status, _ := writer.last(trigger)
+ status, _ := writer.last(applierClaim)
require.NotNil(t, status.Operation)
assert.Nil(t, status.Withdrawn)
return status.Operation
@@ -290,26 +290,26 @@ func TestApplier_TheTriggerStatusFollowsEveryTransition(t *testing.T) {
assert.Equal(t, requested.RequestedAt, done.RequestedAt, "one operation, one record")
assert.Equal(t, requested.Token, done.Token)
- _, before := writer.last(trigger)
+ _, before := writer.last(applierClaim)
loop.serviceAppliers()
- _, after := writer.last(trigger)
+ _, after := writer.last(applierClaim)
assert.Equal(t, before, after, "an unchanged status is not published again")
}
-// TestApplier_AnAttemptEndedBeforeItsRecordLeavesTheTriggerAlone is the fourth review's first
+// TestApplier_AnAttemptEndedBeforeItsRecordLeavesTheClaimAlone is the fourth review's first
// finding: the Requested operation was published as the attempt was sent, so an attempt the
// requester refused (an Argo CD Application with automated sync on: Rejected) left a Requested
-// operation on the trigger that nothing would ever complete. Whatever ends an attempt before its
-// Record, the trigger is told nothing about it.
-func TestApplier_AnAttemptEndedBeforeItsRecordLeavesTheTriggerAlone(t *testing.T) {
+// operation on the applier claim that nothing would ever complete. Whatever ends an attempt before its
+// Record, the applier claim is told nothing about it.
+func TestApplier_AnAttemptEndedBeforeItsRecordLeavesTheClaimAlone(t *testing.T) {
for _, outcome := range []ApplyOutcome{
ApplyRejected, ApplyDropped, ApplyStale, ApplyRequestFailed,
} {
t.Run(string(outcome), func(t *testing.T) {
loop, requester := manualLoop(t, applierTestTarget("alpha", true, argoApplier))
writer := &recordingStatusWriter{}
- loop.w.triggerStatus = writer
- trigger := argoApplierRef("alpha").Trigger
+ loop.w.claimStatus = writer
+ applierClaim := argoApplierRef("alpha").ApplierClaim
observeForeignCommit(loop.w)
loop.serviceAppliers()
@@ -318,7 +318,7 @@ func TestApplier_AnAttemptEndedBeforeItsRecordLeavesTheTriggerAlone(t *testing.T
report(first[0], ApplyRequested)
report(first[0], ApplySucceeded)
loop.serviceAppliers()
- completed, published := writer.last(trigger)
+ completed, published := writer.last(applierClaim)
require.Equal(t, configv1alpha3.ApplyOperationSucceeded, completed.Operation.Phase)
observeForeignCommit(loop.w)
@@ -328,9 +328,9 @@ func TestApplier_AnAttemptEndedBeforeItsRecordLeavesTheTriggerAlone(t *testing.T
report(second[0], outcome)
loop.serviceAppliers()
- _, after := writer.last(trigger)
+ _, after := writer.last(applierClaim)
assert.Equal(t, published, after, "nothing is published for an attempt that was never recorded")
- assert.Empty(t, writer.records(trigger))
+ assert.Empty(t, writer.records(applierClaim))
assert.Equal(t, completed.Operation, onlyApplier(t, loop).shownOperation(),
"the last operation stands")
})
@@ -338,13 +338,13 @@ func TestApplier_AnAttemptEndedBeforeItsRecordLeavesTheTriggerAlone(t *testing.T
}
// TestApplier_AnAttemptEndedAfterItsRecordWithdrawsIt: the requester recorded the operation, and
-// then could not start the apply (the resume failed). The operation is withdrawn, so the trigger
+// then could not start the apply (the resume failed). The operation is withdrawn, so the applier claim
// shows the one before it again, and a restart does not follow an apply that never started.
func TestApplier_AnAttemptEndedAfterItsRecordWithdrawsIt(t *testing.T) {
loop, requester := manualLoop(t, applierTestTarget("alpha", true))
writer := &recordingStatusWriter{}
- loop.w.triggerStatus = writer
- trigger := applierRef("alpha").Trigger
+ loop.w.claimStatus = writer
+ applierClaim := applierRef("alpha").ApplierClaim
observeForeignCommit(loop.w)
loop.serviceAppliers()
@@ -353,34 +353,34 @@ func TestApplier_AnAttemptEndedAfterItsRecordWithdrawsIt(t *testing.T) {
report(first[0], ApplyRequested)
report(first[0], ApplySucceeded)
loop.serviceAppliers()
- completed, _ := writer.last(trigger)
+ completed, _ := writer.last(applierClaim)
observeForeignCommit(loop.w)
loop.serviceAppliers()
second := requester.taken()
require.Len(t, second, 1)
require.NoError(t, second[0].Record(context.Background()))
- recorded := writer.records(trigger)
+ recorded := writer.records(applierClaim)
require.Len(t, recorded, 1)
report(second[0], ApplyRequestFailed)
loop.serviceAppliers()
- withdrawal, n := writer.last(trigger)
+ withdrawal, n := writer.last(applierClaim)
assert.Equal(t, recorded[0], withdrawal.Withdrawn, "the recorded operation is withdrawn")
assert.Equal(t, completed.Operation, withdrawal.Operation, "and the one before it shown again")
loop.serviceAppliers()
- _, again := writer.last(trigger)
+ _, again := writer.last(applierClaim)
assert.Equal(t, n, again, "withdrawn once")
}
-// TestApplier_TheTriggerStatusSaysARepairIsPending: repairPending is published from the withheld write
+// TestApplier_TheClaimStatusSaysARepairIsPending: repairPending is published from the withheld write
// until an operation that carried the repair succeeded.
-func TestApplier_TheTriggerStatusSaysARepairIsPending(t *testing.T) {
+func TestApplier_TheClaimStatusSaysARepairIsPending(t *testing.T) {
loop, requester := manualLoop(t, applierTestTarget("alpha", true))
writer := &recordingStatusWriter{}
- loop.w.triggerStatus = writer
- trigger := applierRef("alpha").Trigger
+ loop.w.claimStatus = writer
+ applierClaim := applierRef("alpha").ApplierClaim
observeForeignCommit(loop.w)
loop.serviceAppliers()
@@ -390,20 +390,20 @@ func TestApplier_TheTriggerStatusSaysARepairIsPending(t *testing.T) {
loop.withholdLateApply(windowEvent("alpha", "3", "kubectl-edit"))
require.True(t, loop.withholdLateApply(windowEvent("alpha", "2", FluxFieldManager)))
loop.serviceAppliers()
- status, _ := writer.last(trigger)
+ status, _ := writer.last(applierClaim)
assert.True(t, status.RepairPending, "the withheld write leaves a repair pending")
report(first[0], ApplySucceeded)
loop.serviceAppliers()
repair := requester.taken()
require.Len(t, repair, 1)
- status, _ = writer.last(trigger)
+ status, _ = writer.last(applierClaim)
assert.True(t, status.RepairPending, "still pending while the repair is out")
report(repair[0], ApplyRequested)
report(repair[0], ApplySucceeded)
loop.serviceAppliers()
- status, _ = writer.last(trigger)
+ status, _ = writer.last(applierClaim)
assert.False(t, status.RepairPending, "repaired")
assert.Equal(t, configv1alpha3.ApplyReasonRepair, status.Operation.Reason)
}
diff --git a/internal/git/apply_ownership.go b/internal/git/apply_ownership.go
index 4bafe82ec..650eb4478 100644
--- a/internal/git/apply_ownership.go
+++ b/internal/git/apply_ownership.go
@@ -14,11 +14,11 @@ import (
"github.com/ConfigButler/gitops-reverser/internal/telemetry"
)
-// A folder a trigger applies belongs to that trigger's applier (docs/design/applier-echoes.md, F4).
+// A coordinated folder belongs to its applier (docs/design/applier-echoes.md, F4).
// An object another applier applies, mirrored into it, would be applied by this applier too, and two
// appliers applying one object fight each other. So an object owned by another applier is excluded
-// from a triggered folder: a live event of it is not mirrored, and a snapshot of it is neither
-// upserted nor swept. A folder with no trigger excludes nothing: mirroring another applier's objects
+// from a coordinated folder: a live event of it is not mirrored, and a snapshot of it is neither
+// upserted nor swept. A folder with no applier claim excludes nothing: mirroring another applier's objects
// one way is what such a folder can exist for.
//
// The owner is read from the raw object, where the watch holds it before sanitize strips it: Flux's
@@ -80,7 +80,7 @@ func AppliedByOf(u *unstructured.Unstructured) AppliedBy {
return AppliedBy{Kind: v1alpha3.ApplierKindApplication, Name: app}
}
-// applierOf is the applier whose trigger a GitTarget names on this branch, nil when it names none.
+// applierOf is the applier whose applier claim a GitTarget names on this branch, nil when it names none.
func (l *branchWorkerEventLoop) applierOf(name, namespace string) *applierState {
for _, ref := range l.sortedAppliers() {
state := l.appliers[ref]
@@ -94,7 +94,7 @@ func (l *branchWorkerEventLoop) applierOf(name, namespace string) *applierState
}
// ownedElsewhere reports whether an object of the GitTarget's folder is owned by an applier other
-// than the one the folder's trigger names, and counts and logs it when it is.
+// than the one the folder's applier claim names, and counts and logs it when it is.
func (l *branchWorkerEventLoop) ownedElsewhere(name, namespace, object string, owner AppliedBy) bool {
if owner.IsZero() {
return false
@@ -120,7 +120,7 @@ func (l *branchWorkerEventLoop) noteCreated(event Event) {
l.adopt(event.GitTargetName, event.GitTargetNamespace, event.Identifier.String())
}
-// adopt leaves an adopt pending for the applier of a GitTarget's folder, when its trigger names one.
+// adopt leaves an adopt pending for the applier of a GitTarget's folder, when its applier claim names one.
func (l *branchWorkerEventLoop) adopt(name, namespace, object string) {
applier := l.applierOf(name, namespace)
if applier == nil {
@@ -174,7 +174,7 @@ func (l *branchWorkerEventLoop) withoutOwnedElsewhere(
// ownershipLogLimit bounds the objects recordOwnershipConflict remembers having logged.
const ownershipLogLimit = 1024
-// recordOwnershipConflict counts an object another applier owns, kept out of a triggered folder, and
+// recordOwnershipConflict counts an object another applier owns, kept out of a coordinated folder, and
// logs it once per object.
func (l *branchWorkerEventLoop) recordOwnershipConflict(target, object string, applier ApplierRef, owner AppliedBy) {
if telemetry.ApplyOwnershipConflictsTotal != nil {
@@ -191,7 +191,7 @@ func (l *branchWorkerEventLoop) recordOwnershipConflict(target, object string, a
l.ownershipLogged = map[string]struct{}{}
}
l.ownershipLogged[key] = struct{}{}
- l.w.Log.Info("Not mirroring an object another applier owns into a folder its trigger's applier applies: "+
+ l.w.Log.Info("Not mirroring an object another applier owns into a folder its applier claim's applier applies: "+
"two appliers applying one object fight each other",
"gitTarget", target, "object", object, "applier", applier.String(), "ownedBy", owner.String())
}
diff --git a/internal/git/apply_ownership_test.go b/internal/git/apply_ownership_test.go
index f29868232..6594d7661 100644
--- a/internal/git/apply_ownership_test.go
+++ b/internal/git/apply_ownership_test.go
@@ -19,8 +19,8 @@ import (
"github.com/ConfigButler/gitops-reverser/internal/types"
)
-// F4 (docs/design/applier-echoes.md): an object another applier owns is not mirrored into a folder a
-// trigger applies.
+// F4 (docs/design/applier-echoes.md): an object another applier owns is not mirrored into a coordinated
+// folder.
func ownedObject(labels map[string]string, annotations map[string]string) *unstructured.Unstructured {
u := &unstructured.Unstructured{Object: map[string]any{"apiVersion": "v1", "kind": "ConfigMap"}}
@@ -67,9 +67,9 @@ func TestAppliedBy_IsTheApplierItNames(t *testing.T) {
assert.False(t, AppliedBy{Kind: argo.Kind, Namespace: "tenants", Name: "shop"}.is(argo))
}
-// TestOwnership_ALiveWriteOfAnotherAppliersObjectIsNotMirrored: in alpha's triggered folder, an
+// TestOwnership_ALiveWriteOfAnotherAppliersObjectIsNotMirrored: in alpha's coordinated folder, an
// object another Kustomization owns is kept out, counted; one alpha's applier owns, or nobody does,
-// is mirrored. bravo's folder names no trigger and excludes nothing.
+// is mirrored. bravo's folder names no applier claim and excludes nothing.
func TestOwnership_ALiveWriteOfAnotherAppliersObjectIsNotMirrored(t *testing.T) {
reader, err := telemetry.InitTestExporter()
require.NoError(t, err)
@@ -81,7 +81,11 @@ func TestOwnership_ALiveWriteOfAnotherAppliersObjectIsNotMirrored(t *testing.T)
assert.True(t, loop.ownedElsewhere("alpha", "team-a", "cm/x", other))
assert.False(t, loop.ownedElsewhere("alpha", "team-a", "cm/y", own))
assert.False(t, loop.ownedElsewhere("alpha", "team-a", "cm/z", AppliedBy{}))
- assert.False(t, loop.ownedElsewhere("bravo", "team-a", "cm/x", other), "a folder with no trigger excludes nothing")
+ assert.False(
+ t,
+ loop.ownedElsewhere("bravo", "team-a", "cm/x", other),
+ "a folder with no applier claim excludes nothing",
+ )
got, _ := telemetry.CollectInt64Sum(reader, "gitopsreverser_apply_ownership_conflicts_total", nil)
assert.Equal(t, int64(1), got)
diff --git a/internal/git/apply_recovery.go b/internal/git/apply_recovery.go
index acafe59bd..8b4fbbbd2 100644
--- a/internal/git/apply_recovery.go
+++ b/internal/git/apply_recovery.go
@@ -10,7 +10,7 @@ import (
)
// Restart recovery (docs/design/apply-trigger.md, "Restart recovery"). The worker's attempts live
-// in memory, and the trigger's status.operation is their durable record: the worker writes it
+// in memory, and the applier claim's status.operation is their durable record: the worker writes it
// before an apply can start (ApplyRequest.Record). So a process that starts reads it back, before
// its first live event is mirrored:
//
@@ -19,7 +19,7 @@ import (
// the Kustomization may have been left resumed, and the follow suspends it again.
// - repairPending is restored.
//
-// The trigger's reconciler records an apply somebody else started (`flux resume`) the same way, as
+// The applier claim's reconciler records an apply somebody else started (`flux resume`) the same way, as
// an operation with no request token. The worker follows such a record whenever it lists one, not
// only on start, so its apply window opens for an apply nobody checked.
//
@@ -27,8 +27,8 @@ import (
// recovered operation decides nothing about which reasons are pending, and asks the applier for
// nothing.
-// followRecordedOperations reads the triggers' recorded status on every pass, and follows what it
-// finds. Every pass, and not only while the branch has appliers: a GitTarget naming a trigger for
+// followRecordedOperations reads the applier claims' recorded status on every pass, and follows what it
+// finds. Every pass, and not only while the branch has appliers: a GitTarget naming an applier claim for
// the first time is seen here, and gets its first apply (syncAppliers). The listing is cached. A
// listing that fails is tried again on the applier timer (listRetryAt), as takeReasons' is.
func (l *branchWorkerEventLoop) followRecordedOperations(now time.Time) {
@@ -38,7 +38,7 @@ func (l *branchWorkerEventLoop) followRecordedOperations(now time.Time) {
}
targets, err := w.appliersOnBranch(w.ctx)
if err != nil {
- w.Log.Error(err, "Cannot read the apply triggers' recorded operations; retrying", "branch", w.Branch)
+ w.Log.Error(err, "Cannot read the applier claims' recorded operations; retrying", "branch", w.Branch)
l.listRetryAt = time.Now().Add(applierRetryInterval)
l.resetApplierTimer(applierRetryInterval)
return
@@ -50,7 +50,7 @@ func (l *branchWorkerEventLoop) followRecordedOperations(now time.Time) {
}
}
-// followRecordedFor follows what one applier's trigger records: anything in flight, and the
+// followRecordedFor follows what one applier's applier claim records: anything in flight, and the
// pending repair, the first time this process lists it; afterwards only an apply started elsewhere.
func (l *branchWorkerEventLoop) followRecordedFor(state *applierState, now time.Time) {
recorded, repairPending := recordedStatus(state)
@@ -68,8 +68,8 @@ func (l *branchWorkerEventLoop) followRecordedFor(state *applierState, now time.
}
}
-// recordedStatus is the operation and repairPending recorded on an applier's trigger, as listed with
-// any GitTarget naming it: they all name the same trigger.
+// recordedStatus is the operation and repairPending recorded on an applier's applier claim, as listed with
+// any GitTarget naming it: they all name the same applier claim.
func recordedStatus(state *applierState) (*v1alpha3.ApplyOperation, bool) {
for _, target := range state.targets {
return target.recorded, target.repairPending
@@ -100,7 +100,7 @@ func (l *branchWorkerEventLoop) followRecorded(state *applierState, recorded *v1
for _, target := range state.targets {
targets = append(targets, ApplyTarget{Ref: target.ref, UID: target.uid, Reason: recorded.Reason})
}
- w.Log.Info("Following an apply recorded on the trigger, with its apply window open",
+ w.Log.Info("Following an apply recorded on the applier claim, with its apply window open",
"branch", w.Branch, "applier", state.ref.String(), "phase", string(recorded.Phase),
"reason", string(recorded.Reason), "commit", recorded.Commit, "startedElsewhere", recorded.Token == "")
w.applyRequester.RequestApply(ApplyRequest{
diff --git a/internal/git/apply_recovery_test.go b/internal/git/apply_recovery_test.go
index 8ea33c7f8..ab6332c31 100644
--- a/internal/git/apply_recovery_test.go
+++ b/internal/git/apply_recovery_test.go
@@ -2,7 +2,7 @@
package git
-// Restart recovery (docs/design/apply-trigger.md, "Restart recovery"): the trigger's
+// Restart recovery (docs/design/apply-trigger.md, "Restart recovery"): the applier claim's
// status.operation is the durable record of an apply in flight, written before the apply could
// start, and a worker that starts follows what it finds there, with the apply window open, before
// it mirrors a live event.
@@ -19,10 +19,10 @@ import (
configv1alpha3 "github.com/ConfigButler/gitops-reverser/api/v1alpha3"
)
-// recording gives a trigger a recorded status.
-func recording(operation *configv1alpha3.ApplyOperation, repairPending bool) func(*configv1alpha3.FluxTrigger) {
- return func(trigger *configv1alpha3.FluxTrigger) {
- trigger.Status.Operation, trigger.Status.RepairPending = operation, repairPending
+// recording gives an applier claim a recorded status.
+func recording(operation *configv1alpha3.ApplyOperation, repairPending bool) func(*configv1alpha3.FluxClaim) {
+ return func(applierClaim *configv1alpha3.FluxClaim) {
+ applierClaim.Status.Operation, applierClaim.Status.RepairPending = operation, repairPending
}
}
@@ -41,9 +41,9 @@ func recordedOperation(phase configv1alpha3.ApplyOperationPhase) *configv1alpha3
func TestRecovery_AnOperationLeftInFlightIsFollowedWithItsWindowOpen(t *testing.T) {
recorded := recordedOperation(configv1alpha3.ApplyOperationRunning)
loop, requester := manualLoop(t, applierTestTarget("alpha", true),
- fluxTrigger("alpha", recording(recorded, false)))
+ fluxClaim("alpha", recording(recorded, false)))
writer := &recordingStatusWriter{}
- loop.w.triggerStatus = writer
+ loop.w.claimStatus = writer
loop.w.inbox.observe(RemoteObservation{Commit: "bbb", By: ObservedByFetch}) // the tip it applies
loop.followRecordedOperations(time.Now())
@@ -67,7 +67,7 @@ func TestRecovery_AnOperationLeftInFlightIsFollowedWithItsWindowOpen(t *testing.
AppliedRevision: "main@sha1:bbb"})
holdApplies(loop) // the repair the withheld write left pending waits
loop.serviceAppliers()
- status, _ := writer.last(applierRef("alpha").Trigger)
+ status, _ := writer.last(applierRef("alpha").ApplierClaim)
completed := status.Operation
require.NotNil(t, completed)
assert.Equal(t, configv1alpha3.ApplyOperationSucceeded, completed.Phase, "the record is completed")
@@ -87,7 +87,7 @@ func TestRecovery_AnOperationLeftInFlightIsFollowedWithItsWindowOpen(t *testing.
// the cluster still runs the older value, so the new process sends the repair.
func TestRecovery_APendingRepairIsRestored(t *testing.T) {
loop, requester := manualLoop(t, applierTestTarget("alpha", true),
- fluxTrigger("alpha", recording(recordedOperation(configv1alpha3.ApplyOperationSucceeded), true)))
+ fluxClaim("alpha", recording(recordedOperation(configv1alpha3.ApplyOperationSucceeded), true)))
loop.w.inbox.observe(RemoteObservation{Commit: "bbb", By: ObservedByFetch}) // the tip it applied
loop.followRecordedOperations(time.Now())
assert.True(t, onlyApplier(t, loop).repair)
@@ -99,10 +99,10 @@ func TestRecovery_APendingRepairIsRestored(t *testing.T) {
}
// TestRecovery_OnlyAnOperationInFlightIsFollowed: a completed record is history, and asks for
-// nothing; a worker with no trigger record says nothing.
+// nothing; a worker with no applier claim record says nothing.
func TestRecovery_OnlyAnOperationInFlightIsFollowed(t *testing.T) {
loop, requester := manualLoop(t, applierTestTarget("alpha", true),
- fluxTrigger("alpha", recording(recordedOperation(configv1alpha3.ApplyOperationSucceeded), false)))
+ fluxClaim("alpha", recording(recordedOperation(configv1alpha3.ApplyOperationSucceeded), false)))
loop.followRecordedOperations(time.Now())
assert.Empty(t, requester.taken())
assert.Nil(t, onlyApplier(t, loop).inflight)
@@ -110,7 +110,7 @@ func TestRecovery_OnlyAnOperationInFlightIsFollowed(t *testing.T) {
// TestRecovery_ARecordIsReadOnceButAnApplyStartedElsewhereWhenever: a worker record is read back
// once, when this process first lists the applier, because afterwards the cache can lag the worker's
-// own writes. The trigger's reconciler records an apply somebody else started (no request token)
+// own writes. The applier claim's reconciler records an apply somebody else started (no request token)
// at any time, and the worker follows each such record once, so its window opens for it.
func TestRecovery_ARecordIsReadOnceButAnApplyStartedElsewhereWhenever(t *testing.T) {
loop, requester := manualLoop(t, applierTestTarget("alpha", true))
@@ -119,13 +119,13 @@ func TestRecovery_ARecordIsReadOnceButAnApplyStartedElsewhereWhenever(t *testing
setRecorded := func(operation *configv1alpha3.ApplyOperation) {
t.Helper()
- trigger := &configv1alpha3.FluxTrigger{}
+ applierClaim := &configv1alpha3.FluxClaim{}
require.NoError(
t,
- loop.w.Client.Get(t.Context(), client.ObjectKey{Namespace: "team-a", Name: "alpha"}, trigger),
+ loop.w.Client.Get(t.Context(), client.ObjectKey{Namespace: "team-a", Name: "alpha"}, applierClaim),
)
- trigger.Status.Operation = operation
- require.NoError(t, loop.w.Client.Status().Update(t.Context(), trigger))
+ applierClaim.Status.Operation = operation
+ require.NoError(t, loop.w.Client.Status().Update(t.Context(), applierClaim))
}
setRecorded(recordedOperation(configv1alpha3.ApplyOperationRunning))
loop.serviceAppliers()
diff --git a/internal/git/apply_request.go b/internal/git/apply_request.go
index b43a01b4d..9e6d71f00 100644
--- a/internal/git/apply_request.go
+++ b/internal/git/apply_request.go
@@ -16,18 +16,18 @@ import (
itypes "github.com/ConfigButler/gitops-reverser/internal/types"
)
-// ApplierRef is one applier, as the apply trigger that holds it names it: a Flux Kustomization or
-// an Argo CD Application in a cluster. Several GitTargets may name one trigger, and its applier is
+// ApplierRef is one applier, as the applier claim that holds it names it: a Flux Kustomization or
+// an Argo CD Application in a cluster. Several GitTargets may name one applier claim, and its applier is
// asked once for all of them.
type ApplierRef struct {
- // Trigger is the apply trigger that holds the applier.
- Trigger TriggerRef
- // Cluster is the KubeProvider (namespace/name) whose cluster the applier lives in (the trigger's
+ // Applier claim is the applier claim that holds the applier.
+ ApplierClaim ApplierClaimRef
+ // Cluster is the KubeProvider (namespace/name) whose cluster the applier lives in (the applier claim's
// spec.kubeProviderRef).
Cluster string
// Kind is v1alpha3.ApplierKindKustomization or v1alpha3.ApplierKindApplication.
Kind string
- // Namespace and Name are the object's, the namespace defaulted to the trigger's own.
+ // Namespace and Name are the object's, the namespace defaulted to the applier claim's own.
Namespace string
Name string
}
@@ -36,46 +36,52 @@ func (a ApplierRef) String() string {
return a.Cluster + "/" + a.Kind + "/" + a.Namespace + "/" + a.Name
}
-// TriggerRef is one apply trigger: its kind (v1alpha3.ApplyTriggerKind*), namespace and name.
-type TriggerRef struct {
+// ApplierClaimRef is one applier claim: its kind (v1alpha3.ApplierClaimKind*), namespace and name.
+type ApplierClaimRef struct {
Kind string
Namespace string
Name string
}
-func (t TriggerRef) String() string { return t.Kind + "/" + t.Namespace + "/" + t.Name }
+func (t ApplierClaimRef) String() string { return t.Kind + "/" + t.Namespace + "/" + t.Name }
-// ApplierOf is the applier a trigger holds.
-func ApplierOf(trigger v1alpha3.ApplyTrigger) ApplierRef {
- applier := trigger.Applier()
+// ApplierOf is the applier an applier claim holds.
+func ApplierOf(applierClaim v1alpha3.ApplierClaim) ApplierRef {
+ applier := applierClaim.Applier()
return ApplierRef{
- Trigger: TriggerRef{Kind: trigger.TriggerKind(), Namespace: trigger.GetNamespace(), Name: trigger.GetName()},
- Cluster: trigger.KubeProviderKey().String(), Kind: trigger.ApplierKind(),
- Namespace: applier.Namespace, Name: applier.Name,
+ ApplierClaim: ApplierClaimRef{
+ Kind: applierClaim.ClaimKind(),
+ Namespace: applierClaim.GetNamespace(),
+ Name: applierClaim.GetName(),
+ },
+ Cluster: applierClaim.KubeProviderKey().String(),
+ Kind: applierClaim.ApplierKind(),
+ Namespace: applier.Namespace,
+ Name: applier.Name,
}
}
-// ResolveApplyTrigger reads the apply trigger a GitTarget names, in the GitTarget's namespace. It
+// ResolveApplierClaim reads the applier claim a GitTarget names, in the GitTarget's namespace. It
// returns nil, and no error, when the GitTarget names none, names a kind this release does not
// know, or names one that does not exist.
-func ResolveApplyTrigger(
+func ResolveApplierClaim(
ctx context.Context, reader client.Reader, target *v1alpha3.GitTarget,
-) (v1alpha3.ApplyTrigger, error) {
- ref := target.Spec.ApplyTriggerRef
+) (v1alpha3.ApplierClaim, error) {
+ ref := target.Spec.ApplierClaimRef
if ref == nil {
- return nil, nil //nolint:nilnil // a GitTarget that names no trigger has none, and is no error
+ return nil, nil //nolint:nilnil // a GitTarget that names no applier claim has none, and is no error
}
- trigger := v1alpha3.NewApplyTrigger(ref.Kind)
- if trigger == nil {
+ applierClaim := v1alpha3.NewApplierClaim(ref.Kind)
+ if applierClaim == nil {
return nil, nil //nolint:nilnil // as above: a kind this release does not serve names none
}
- if err := reader.Get(ctx, client.ObjectKey{Namespace: target.Namespace, Name: ref.Name}, trigger); err != nil {
+ if err := reader.Get(ctx, client.ObjectKey{Namespace: target.Namespace, Name: ref.Name}, applierClaim); err != nil {
if apierrors.IsNotFound(err) {
- return nil, nil //nolint:nilnil // as above: a trigger not created yet holds nothing
+ return nil, nil //nolint:nilnil // as above: an applier claim not created yet holds nothing
}
return nil, fmt.Errorf("read %s %s/%s: %w", ref.Kind, target.Namespace, ref.Name, err)
}
- return trigger, nil
+ return applierClaim, nil
}
// ApplyRequest is one attempt, decided by a branch worker after a flush, to have one applier
@@ -100,7 +106,7 @@ type ApplyRequest struct {
// pushed or proved by a fetch. Empty when the remote does not carry the branch.
Commit string
// Token identifies the operation the attempt starts, in the applier's own status and in the
- // trigger's status.operation, which Record writes with it.
+ // applier claim's status.operation, which Record writes with it.
Token string
// Targets are the GitTargets the request is for, each the incarnation the worker decided for
// and with its own reason. The requester asks nothing for a target that has changed since.
@@ -115,11 +121,11 @@ type ApplyRequest struct {
// yet pushed does not make it stale: the flush published every edit decided before the attempt,
// and the apply window holds the rest. Nil is always current. It does not block.
Current func() bool
- // Record writes the operation this attempt starts on its trigger's status, and returns once it
- // is written: the only way the operation reaches the trigger before the requester reports it.
+ // Record writes the operation this attempt starts on its applier claim's status, and returns once it
+ // is written: the only way the operation reaches the applier claim before the requester reports it.
// The requester calls it just before the write that starts the apply, and asks nothing when it
- // fails: a restart reads status.operation to find every apply that may have started, and a
- // trigger that is being deleted, or that records an apply somebody else started, starts none.
+ // fails: a restart reads status.operation to find every apply that may have started, and an
+ // applier claim that is being deleted, or that records an apply somebody else started, starts none.
// Nil records nothing.
Record func(ctx context.Context) error
// Report receives the attempt's reports, in order, and does not block. An attempt that asked the
@@ -127,8 +133,8 @@ type ApplyRequest struct {
// that ends it (ApplyOutcome.Ends); one that asked nothing reports once, with an outcome that
// ends it.
Report func(ApplyResult)
- // Recovered, when set, is an operation recorded on the trigger (by an earlier process, or by the
- // trigger's reconciler for an apply somebody else started) that the attempt follows to its end
+ // Recovered, when set, is an operation recorded on the applier claim (by an earlier process, or by the
+ // applier claim's reconciler for an apply somebody else started) that the attempt follows to its end
// instead of asking the applier for anything. Its token and suspend token are the ones to read.
Recovered *v1alpha3.ApplyOperation
// Abandoned is closed when the worker stops following the attempt: no GitTarget names its
@@ -216,9 +222,9 @@ type ApplyResult struct {
Err error
}
-// ApplyTriggerStatus is what a branch worker publishes on an apply trigger: the operation it last
-// started on the trigger's applier, and whether a Repair is pending.
-type ApplyTriggerStatus struct {
+// ApplierClaimStatus is what a branch worker publishes on an applier claim: the operation it last
+// started on the applier claim's applier, and whether a Repair is pending.
+type ApplierClaimStatus struct {
// Operation is the operation status.operation shows, once it holds it: the writer moves an
// operation on, and writes one on a status that holds none, but never replaces another. Nil
// leaves it as it is: a worker that has started no operation since it began has nothing to say
@@ -231,17 +237,17 @@ type ApplyTriggerStatus struct {
RepairPending bool
}
-// ApplyTriggerStatusWriter writes what the branch workers publish on their triggers' status.
-// PublishApplyTriggerStatus must not block: it is called on a branch worker's loop goroutine. A
-// later publication for the same trigger supersedes one not yet written, except for what that one
+// ApplierClaimStatusWriter writes what the branch workers publish on their applier claims' status.
+// PublishApplierClaimStatus must not block: it is called on a branch worker's loop goroutine. A
+// later publication for the same applier claim supersedes one not yet written, except for what that one
// withdrew.
-type ApplyTriggerStatusWriter interface {
- PublishApplyTriggerStatus(trigger TriggerRef, status ApplyTriggerStatus)
- // RecordApplyOperation writes the operation an attempt starts on the trigger's status now, and
+type ApplierClaimStatusWriter interface {
+ PublishApplierClaimStatus(applierClaim ApplierClaimRef, status ApplierClaimStatus)
+ // RecordApplyOperation writes the operation an attempt starts on the applier claim's status now, and
// returns once it is written. It blocks, so it is never called on a worker's loop: the requester
- // calls it (ApplyRequest.Record) just before it starts the apply. It fails when the trigger is
+ // calls it (ApplyRequest.Record) just before it starts the apply. It fails when the applier claim is
// gone or being deleted, or its status holds another operation that has not completed.
- RecordApplyOperation(ctx context.Context, trigger TriggerRef, operation *v1alpha3.ApplyOperation) error
+ RecordApplyOperation(ctx context.Context, applierClaim ApplierClaimRef, operation *v1alpha3.ApplyOperation) error
}
// ApplyRequester carries out apply requests. RequestApply must not block: it is called on the branch
diff --git a/internal/git/apply_window.go b/internal/git/apply_window.go
index 1ed3d0aa5..cbc959407 100644
--- a/internal/git/apply_window.go
+++ b/internal/git/apply_window.go
@@ -25,7 +25,7 @@ import (
// the window: a write by the applier's own field manager, during an attempt, to an object the API
// side edited after that attempt was sent, is the apply arriving late with no intent behind it. It
// is withheld. Git keeps the edit, and the cluster runs the reverted value until an apply puts it
-// back, so the withheld write leaves a Repair pending for its applier (apply_trigger.go).
+// back, so the withheld write leaves a Repair pending for its applier (applier_claim.go).
//
// The applier's write to an object nobody edited is the apply itself, landing: its content is the
// commit's, which Git already holds. It is not mirrored either (docs/design/applier-echoes.md, F1).
diff --git a/internal/git/branch_worker.go b/internal/git/branch_worker.go
index a132db3b8..33c48b0d0 100644
--- a/internal/git/branch_worker.go
+++ b/internal/git/branch_worker.go
@@ -138,15 +138,15 @@ type BranchWorker struct {
// simply not shared.
remoteReporter RemoteReporter
- // applyRequester delivers the apply requests this worker decides (apply_trigger.go), and inbox
+ // applyRequester delivers the apply requests this worker decides (applier_claim.go), and inbox
// holds the reasons pending for the branch's appliers from what was observed about the remote,
// and the reports of attempts that came back. Set by the
- // WorkerManager before Start; a nil requester switches the apply trigger off (the CLI, and tests
+ // WorkerManager before Start; a nil requester switches the applier claim off (the CLI, and tests
// that do not exercise it).
applyRequester ApplyRequester
- // triggerStatus writes each applier's operation to its trigger's status; nil writes nothing.
- triggerStatus ApplyTriggerStatusWriter
- inbox applyInbox
+ // claimStatus writes each applier's operation to its applier claim's status; nil writes nothing.
+ claimStatus ApplierClaimStatusWriter
+ inbox applyInbox
// applyWake wakes the loop to service the appliers without queueing a work item: an attempt
// reported back.
applyWake chan struct{}
@@ -247,7 +247,7 @@ type BranchWorker struct {
// coveredRefusal records, per GitTarget AND WATCHED COLLECTION, the refusal observation the last
// reapply request was decided for: a digest of the objects that were refused together with the
// issues raised about them. A recheck that observes exactly the same thing is already covered
- // by that request, so it is not another trigger. It is dropped when a resync for that collection is
+ // by that request, so it is not another applier claim. It is dropped when a resync for that collection is
// ACCEPTED — see refusalRecovered, which is what makes a live edit re-made after a revert
// count as new, and what keeps one watched type's success from speaking for another's.
// Guarded by refusalMu with the rate limit, because the two are read together.
@@ -1212,12 +1212,12 @@ type branchWorkerEventLoop struct {
// unsentRefusals holds the refusals the worker decided to send their target's applier a Refusal
// request for, until an attempt carries them after a flush. It is keyed like refusalPending,
// by target AND collection, so a collection that recovers cancels its own request and no other
- // (refusalRecovered). See apply_trigger.go.
+ // (refusalRecovered). See applier_claim.go.
unsentRefusals map[refusalKey]unsentRefusal
// appliers holds the reasons pending for each applier named on this branch and how its attempts
// are going, and nextApplyID numbers the attempts. applierTimer fires when the earliest waiting
- // attempt comes due. See apply_trigger.go.
+ // attempt comes due. See applier_claim.go.
appliers map[ApplierRef]*applierState
nextApplyID uint64
applierTimer *time.Timer
@@ -1270,7 +1270,7 @@ func (l *branchWorkerEventLoop) run() {
// Before the first live event can be mirrored: an operation an earlier process left in flight
// is followed again, with its apply window open.
l.followRecordedOperations(time.Now())
- l.publishTriggerStatus()
+ l.publishClaimStatus()
l.publishLoopState(0)
for {
commitC, pushC, attachC, refusalC := l.timerChannels()
@@ -1489,7 +1489,7 @@ func (l *branchWorkerEventLoop) handleLiveEvents(request *WriteRequest) {
}
// mirrors reports whether a live event reaches the commit window. It does not while the target's
-// render fidelity is not established, for an object another applier owns in a folder a trigger
+// render fidelity is not established, for an object another applier owns in a folder an applier claim
// applies (F4), and for the applier's own write in its apply window (apply_window.go).
func (l *branchWorkerEventLoop) mirrors(event Event) bool {
if status, closed := l.w.renderFidelityClosed(event.GitTargetName, event.GitTargetNamespace); closed {
diff --git a/internal/git/pending_writes.go b/internal/git/pending_writes.go
index 426cc9bdf..ddc9234b6 100644
--- a/internal/git/pending_writes.go
+++ b/internal/git/pending_writes.go
@@ -185,7 +185,7 @@ func (w *BranchWorker) resolveTargetMetadata(
Namespaces: namespacePolicyFor(target.Spec, sourceNamespaces, wildcard),
PruneMode: target.EffectivePruneMode(),
SourceCluster: target.SourceCluster(),
- Applied: target.Spec.ApplyTriggerRef != nil,
+ Applied: target.Spec.ApplierClaimRef != nil,
Suspend: target.Spec.Suspend,
}, nil
}
diff --git a/internal/git/push_notification_test.go b/internal/git/push_notification_test.go
index 7785fc389..f7ca39cb6 100644
--- a/internal/git/push_notification_test.go
+++ b/internal/git/push_notification_test.go
@@ -43,7 +43,7 @@ func TestPushNotice_ARunOfReportsQueuesOneLook(t *testing.T) {
{name: "somebody else's push, then ours", reports: []string{"theirs", "ours"}, wantClose: true},
} {
t.Run(tc.name, func(t *testing.T) {
- loop, _ := applyTriggerLoop(t, applierTestTarget("alpha", true), &configv1alpha3.GitProvider{
+ loop, _ := applierClaimLoop(t, applierTestTarget("alpha", true), &configv1alpha3.GitProvider{
ObjectMeta: metav1.ObjectMeta{Name: "test-provider", Namespace: "team-a"},
Spec: configv1alpha3.GitProviderSpec{URL: "https://github.com/acme/platform"},
})
@@ -96,7 +96,7 @@ func TestPushNotice_LastPushNoticeIsWhenItWasReported(t *testing.T) {
// TestDispatch_APushNoticeNotFoldedKeepsItsCommit: only a notice EnqueuePushNotification queued
// takes the fold, so a refresh queued with its own commit keeps it.
func TestDispatch_APushNoticeNotFoldedKeepsItsCommit(t *testing.T) {
- loop, _ := applyTriggerLoop(t, applierTestTarget("alpha", true))
+ loop, _ := applierClaimLoop(t, applierTestTarget("alpha", true))
req := &RefreshRequest{PushNotice: true, After: "ccc"}
loop.dispatch(WorkItem{Refresh: req})
assert.Equal(t, "ccc", req.After)
diff --git a/internal/git/refusal_observation_test.go b/internal/git/refusal_observation_test.go
index eb766fcc7..bfcb9e0b8 100644
--- a/internal/git/refusal_observation_test.go
+++ b/internal/git/refusal_observation_test.go
@@ -7,7 +7,7 @@ package git
// The rate limit alone only decides how OFTEN a refusal may be requested, never whether the refusal
// is the same one. A forced recheck re-lists from the API server every minute, so a refused live
// edit that nobody corrects is observed again and again, and each observation used to be another
-// trigger: a request per interval, for ever, making the applier reapply each time. These pin that a repeat is recognised as a repeat, and — just as important — that a real
+// applier claim: a request per interval, for ever, making the applier reapply each time. These pin that a repeat is recognised as a repeat, and — just as important — that a real
// second edit is not mistaken for one.
import (
@@ -45,9 +45,9 @@ func standingRefusalFixture(t *testing.T, slug string) *ledgerFixture {
require.NoError(t, f.worker.Client.Get(f.worker.ctx,
client.ObjectKey{Name: ledgerTargetName, Namespace: "default"}, target))
target.Spec.OnRefusal = configv1alpha3.RefusalActionReapply
- target.Spec.ApplyTriggerRef = testApplier(ledgerTargetName)
+ target.Spec.ApplierClaimRef = testApplier(ledgerTargetName)
require.NoError(t, f.worker.Client.Update(f.worker.ctx, target))
- createTestTrigger(t, f.worker.Client, target)
+ createTestClaim(t, f.worker.Client, target)
f.worker.applyRequester = &recordingRequester{}
f.worker.mapper = deploymentMapper()
@@ -109,9 +109,9 @@ func refusalRequests(t *testing.T, f *ledgerFixture) int {
return requester.refusals()
}
-// TestRefusalReapply_AnUnchangedRefusalIsNotASecondTrigger is the regression a review reproduced:
-// three rechecks of one refused edit, each with the window open, produced three triggers.
-func TestRefusalReapply_AnUnchangedRefusalIsNotASecondTrigger(t *testing.T) {
+// TestRefusalReapply_AnUnchangedRefusalIsNotASecondRequest is the regression a review reproduced:
+// three rechecks of one refused edit, each with the window open, produced three apply requests.
+func TestRefusalReapply_AnUnchangedRefusalIsNotASecondRequest(t *testing.T) {
f := standingRefusalFixture(t, "standing-refusal-unchanged")
l := newBranchWorkerEventLoop(f.worker, time.Hour)
t.Cleanup(l.stopTimers)
@@ -147,7 +147,7 @@ func TestRefusalReapply_ASecondRefusedEditEarnsItsOwnRequest(t *testing.T) {
// TestRefusalReapply_AResyncsRefusalIsRequestedByTheLoopItself: a refusal a resync discovers is decided
// while the resync's outcome is being settled, inside the commit pass, and the wake that ends the
// pass has to send it. The fixture's refuseResync pushes by hand, which is how a pushed-by-hand unit
-// test once missed a trigger that never left the worker.
+// test once missed an applier claim that never left the worker.
func TestRefusalReapply_AResyncsRefusalIsRequestedByTheLoopItself(t *testing.T) {
f := standingRefusalFixture(t, "standing-refusal-pushed")
l := newBranchWorkerEventLoop(f.worker, time.Hour)
@@ -395,7 +395,7 @@ func TestRefusalReapply_AQueuedRequestIsDroppedOnceSomethingElseCoveredIt(t *tes
// A GitTarget watches more than one type. The Deployment stays refused; a ConfigMap reconcile for
// the same target succeeds, as an empty or unchanged one does every time it runs. If that success
// is read as recovery for the whole target, the next identical Deployment observation becomes a
-// new trigger, and one request per ConfigMap pass is the loop again wearing a different hat.
+// new applier claim, and one request per ConfigMap pass is the loop again wearing a different hat.
func TestRefusalReapply_AnAcceptedSiblingTypeDoesNotRearmAStandingRefusal(t *testing.T) {
f := standingRefusalFixture(t, "standing-refusal-sibling-type")
l := newBranchWorkerEventLoop(f.worker, time.Hour)
@@ -522,9 +522,9 @@ func overlayRefusalFixture(t *testing.T, slug string) *ledgerFixture {
require.NoError(t, f.worker.Client.Get(f.worker.ctx,
client.ObjectKey{Name: ledgerTargetName, Namespace: "default"}, target))
target.Spec.OnRefusal = configv1alpha3.RefusalActionReapply
- target.Spec.ApplyTriggerRef = testApplier(ledgerTargetName)
+ target.Spec.ApplierClaimRef = testApplier(ledgerTargetName)
require.NoError(t, f.worker.Client.Update(f.worker.ctx, target))
- createTestTrigger(t, f.worker.Client, target)
+ createTestClaim(t, f.worker.Client, target)
f.worker.applyRequester = &recordingRequester{}
f.worker.mapper = deploymentMapper()
return f
diff --git a/internal/git/refusal_reapply.go b/internal/git/refusal_reapply.go
index 9a7033ff5..268b9bbe2 100644
--- a/internal/git/refusal_reapply.go
+++ b/internal/git/refusal_reapply.go
@@ -101,7 +101,7 @@ type unsentRefusal struct {
// refusal. The worker asks before it decides a Refusal request and again before it sends one, and the
// applier dispatcher asks before every attempt to deliver one.
func ConsentsToReapply(gitTarget *v1alpha3.GitTarget) bool {
- if gitTarget.EffectiveOnRefusal() != v1alpha3.RefusalActionReapply || gitTarget.Spec.ApplyTriggerRef == nil {
+ if gitTarget.EffectiveOnRefusal() != v1alpha3.RefusalActionReapply || gitTarget.Spec.ApplierClaimRef == nil {
return false
}
// A suspended target has stopped publishing the live edits a reapply would be weighed against.
@@ -160,7 +160,7 @@ func (l *branchWorkerEventLoop) reapplyForRefusal(
}
key := newRefusalKey(target, collection)
- // An observation this collection's last request already covered is not a new trigger. Checked
+ // An observation this collection's last request already covered is not a new applier claim. Checked
// here, after consent and before the rate limit, so a recheck of unchanged input neither
// requests nor arms the trailing timer: arming it would turn the rate limit into a schedule,
// which is the loop this fence exists to close.
diff --git a/internal/git/refusal_reapply_test.go b/internal/git/refusal_reapply_test.go
index ccb9ca54b..010a4c6a8 100644
--- a/internal/git/refusal_reapply_test.go
+++ b/internal/git/refusal_reapply_test.go
@@ -48,8 +48,8 @@ func refusalTouchWorker(t *testing.T, spec configv1alpha3.GitTargetSpec) *Branch
if spec.Path == "" {
spec.Path = "apps"
}
- if spec.OnRefusal == configv1alpha3.RefusalActionReapply && spec.ApplyTriggerRef == nil {
- spec.ApplyTriggerRef = testApplier("editing")
+ if spec.OnRefusal == configv1alpha3.RefusalActionReapply && spec.ApplierClaimRef == nil {
+ spec.ApplierClaimRef = testApplier("editing")
}
target := &configv1alpha3.GitTarget{
ObjectMeta: metav1.ObjectMeta{Name: "editing", Namespace: "team-a", UID: editingUID},
@@ -57,7 +57,7 @@ func refusalTouchWorker(t *testing.T, spec configv1alpha3.GitTargetSpec) *Branch
}
client := fake.NewClientBuilder().
WithScheme(scheme).
- WithObjects(testTriggersFor([]client.Object{target})...).
+ WithObjects(testClaimsFor([]client.Object{target})...).
Build()
w := newMetricsTestWorker()
@@ -408,7 +408,7 @@ func TestRefusalReapply_OneTargetDoesNotDropAnothersPendingRequest(t *testing.T)
Spec: configv1alpha3.GitTargetSpec{
Path: "apps/" + name,
OnRefusal: configv1alpha3.RefusalActionReapply,
- ApplyTriggerRef: testApplier(name),
+ ApplierClaimRef: testApplier(name),
Suspend: suspend,
},
}
@@ -417,7 +417,7 @@ func TestRefusalReapply_OneTargetDoesNotDropAnothersPendingRequest(t *testing.T)
}
// B is suspended, so its delayed request must be declined when the timer fires. A is not.
client := fake.NewClientBuilder().WithScheme(scheme).
- WithObjects(testTriggersFor([]client.Object{newTarget("alpha", false), newTarget("bravo", true)})...).Build()
+ WithObjects(testClaimsFor([]client.Object{newTarget("alpha", false), newTarget("bravo", true)})...).Build()
w := newMetricsTestWorker()
w.Client = client
@@ -473,79 +473,79 @@ func TestRefusalReapply_TheTimerTracksTheEarliestDeadline(t *testing.T) {
}
}
-// testApplier is a GitTarget's reference to the FluxTrigger of the given name. testTriggersFor
-// creates the trigger, holding a Kustomization of the same name.
-func testApplier(name string) *configv1alpha3.ApplyTriggerReference {
- return &configv1alpha3.ApplyTriggerReference{Kind: configv1alpha3.ApplyTriggerKindFlux, Name: name}
+// testApplier is a GitTarget's reference to the FluxClaim of the given name. testClaimsFor
+// creates the applier claim, holding a Kustomization of the same name.
+func testApplier(name string) *configv1alpha3.ApplierClaimReference {
+ return &configv1alpha3.ApplierClaimReference{Kind: configv1alpha3.ApplierClaimKindFlux, Name: name}
}
-// testKubeProvider is the KubeProvider the test triggers reach their appliers through, in the
-// trigger's own namespace.
+// testKubeProvider is the KubeProvider the test applier claims reach their appliers through, in the
+// applier claim's own namespace.
const testKubeProvider = "local"
-// fluxTrigger is a FluxTrigger in team-a holding the Kustomization of its own name, in the cluster of
+// fluxClaim is a FluxClaim in team-a holding the Kustomization of its own name, in the cluster of
// testKubeProvider.
-func fluxTrigger(name string, mutate ...func(*configv1alpha3.FluxTrigger)) *configv1alpha3.FluxTrigger {
- trigger := &configv1alpha3.FluxTrigger{
+func fluxClaim(name string, mutate ...func(*configv1alpha3.FluxClaim)) *configv1alpha3.FluxClaim {
+ applierClaim := &configv1alpha3.FluxClaim{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "team-a"},
- Spec: configv1alpha3.FluxTriggerSpec{
+ Spec: configv1alpha3.FluxClaimSpec{
KubeProviderRef: meta.NamespacedObjectReference{Name: testKubeProvider},
KustomizationRef: meta.NamespacedObjectReference{Name: name},
},
}
for _, m := range mutate {
- m(trigger)
+ m(applierClaim)
}
- return trigger
+ return applierClaim
}
-// testTriggersFor adds to objects the trigger each GitTarget among them names that objects does not
-// hold already: a FluxTrigger as fluxTrigger builds it, or an ArgoCDTrigger holding the Application
+// testClaimsFor adds to objects the applier claim each GitTarget among them names that objects does not
+// hold already: a FluxClaim as fluxClaim builds it, or an ArgoCDClaim holding the Application
// argocd/, each in the GitTarget's namespace.
-func testTriggersFor(objects []client.Object) []client.Object {
+func testClaimsFor(objects []client.Object) []client.Object {
held := map[string]bool{}
for _, obj := range objects {
- if trigger, ok := obj.(configv1alpha3.ApplyTrigger); ok {
- held[trigger.TriggerKind()+"/"+obj.GetNamespace()+"/"+obj.GetName()] = true
+ if applierClaim, ok := obj.(configv1alpha3.ApplierClaim); ok {
+ held[applierClaim.ClaimKind()+"/"+obj.GetNamespace()+"/"+obj.GetName()] = true
}
}
out := append([]client.Object(nil), objects...)
for _, obj := range objects {
target, ok := obj.(*configv1alpha3.GitTarget)
- if !ok || target.Spec.ApplyTriggerRef == nil {
+ if !ok || target.Spec.ApplierClaimRef == nil {
continue
}
- ref := target.Spec.ApplyTriggerRef
+ ref := target.Spec.ApplierClaimRef
key := ref.Kind + "/" + target.Namespace + "/" + ref.Name
if held[key] {
continue
}
held[key] = true
- out = append(out, testTrigger(target.Namespace, ref))
+ out = append(out, testClaim(target.Namespace, ref))
}
return out
}
-// testTrigger is the trigger testTriggersFor creates for ref, in namespace.
-func testTrigger(namespace string, ref *configv1alpha3.ApplyTriggerReference) client.Object {
- if ref.Kind == configv1alpha3.ApplyTriggerKindArgoCD {
- return &configv1alpha3.ArgoCDTrigger{
+// testClaim is the applier claim testClaimsFor creates for ref, in namespace.
+func testClaim(namespace string, ref *configv1alpha3.ApplierClaimReference) client.Object {
+ if ref.Kind == configv1alpha3.ApplierClaimKindArgoCD {
+ return &configv1alpha3.ArgoCDClaim{
ObjectMeta: metav1.ObjectMeta{Name: ref.Name, Namespace: namespace},
- Spec: configv1alpha3.ArgoCDTriggerSpec{
+ Spec: configv1alpha3.ArgoCDClaimSpec{
KubeProviderRef: meta.NamespacedObjectReference{Name: testKubeProvider},
ApplicationRef: meta.NamespacedObjectReference{Namespace: "argocd", Name: ref.Name},
},
}
}
- trigger := fluxTrigger(ref.Name)
- trigger.Namespace = namespace
- return trigger
+ applierClaim := fluxClaim(ref.Name)
+ applierClaim.Namespace = namespace
+ return applierClaim
}
-// createTestTrigger creates, through c, the trigger target names, unless it exists.
-func createTestTrigger(t *testing.T, c client.Client, target *configv1alpha3.GitTarget) {
+// createTestClaim creates, through c, the applier claim target names, unless it exists.
+func createTestClaim(t *testing.T, c client.Client, target *configv1alpha3.GitTarget) {
t.Helper()
- err := c.Create(context.Background(), testTrigger(target.Namespace, target.Spec.ApplyTriggerRef))
+ err := c.Create(context.Background(), testClaim(target.Namespace, target.Spec.ApplierClaimRef))
if err != nil && !apierrors.IsAlreadyExists(err) {
require.NoError(t, err)
}
diff --git a/internal/git/types.go b/internal/git/types.go
index d331af935..1bbce3596 100644
--- a/internal/git/types.go
+++ b/internal/git/types.go
@@ -339,7 +339,7 @@ type ResolvedTargetMetadata struct {
// against the right cluster's mapping.
SourceCluster string
- // Applied is whether the GitTarget names an apply trigger: an applier applies its folder, which
+ // Applied is whether the GitTarget names an applier claim: an applier applies its folder, which
// must then exist before it is asked to. A resync into a folder that does not exist yet commits
// the folder's bootstrap files even when it holds no object (executeResyncPendingWrite).
Applied bool
@@ -424,7 +424,7 @@ type PendingWrite struct {
// documents its sweep removed. Nil until decided. See actionsDecided.
resyncActions *resyncActions
// ownedElsewhere names, by objectKey, the snapshot objects another applier owns, which a resync
- // into a triggered folder neither upserts nor sweeps (apply_ownership.go).
+ // into a coordinated folder neither upserts nor sweeps (apply_ownership.go).
ownedElsewhere map[string]bool
// committedOnce is set once the loop has committed this write at least once. It stays set after
// a reset discards that commit: whether the checkout holds it now is checkoutApplied's to say. A
@@ -576,7 +576,7 @@ type ResyncRequest struct {
// The worker records that scope clean once the resync applies (recordReplayClean).
RenderFidelityRevision uint64
// Owners is the applier that owns each snapshot object some applier owns, read from the raw
- // object (AppliedByOf). An object another applier owns is neither upserted into a triggered
+ // object (AppliedByOf). An object another applier owns is neither upserted into a claimed
// folder nor swept from it (apply_ownership.go).
Owners map[types.ResourceIdentifier]AppliedBy
// Initial marks the first snapshot of its collection for this GitTarget: none was applied for it
@@ -738,7 +738,7 @@ type Event struct {
// AppliedBy is the applier that owns the object, read from its raw labels and annotations
// (AppliedByOf). PROVENANCE like LastFieldManagers, stamped by the live watch; zero when no
- // applier owns it. An object another applier owns is not mirrored into a folder a trigger applies
+ // applier owns it. An object another applier owns is not mirrored into a coordinated folder
// (apply_ownership.go).
AppliedBy AppliedBy
@@ -757,7 +757,7 @@ type Event struct {
Attribution AttributionOutcome
// Path is the POSIX-like relative path prefix for this event's files.
- // This comes from the GitTarget that triggered this event.
+ // This comes from the GitTarget that claimed this event.
// Empty string means write to repository root.
Path string
diff --git a/internal/git/worker_manager.go b/internal/git/worker_manager.go
index 0d956b3ba..14b2ff5be 100644
--- a/internal/git/worker_manager.go
+++ b/internal/git/worker_manager.go
@@ -185,22 +185,22 @@ type WorkerManager struct {
// applyRequester delivers every worker's apply requests. Set once at startup
// (SetApplyRequester) before any worker is created; nil in the CLI and in tests, which switches
- // the apply trigger off.
+ // the applier claim off.
applyRequester ApplyRequester
- // triggerStatus writes what every worker publishes on its apply triggers' status. Set once at
- // startup (SetApplyTriggerStatusWriter); nil in the CLI and in tests, which publishes nothing.
- triggerStatus ApplyTriggerStatusWriter
+ // claimStatus writes what every worker publishes on its applier claims' status. Set once at
+ // startup (SetApplierClaimStatusWriter); nil in the CLI and in tests, which publishes nothing.
+ claimStatus ApplierClaimStatusWriter
- // applierAdmitted is the dispatcher's own check of whether a GitTarget may ask its trigger's
+ // applierAdmitted is the dispatcher's own check of whether a GitTarget may ask its applier claim's
// applier (authz.ApplierAdmitted), so a worker never flushes or holds an applier for a GitTarget
// the dispatcher would refuse. Always set by NewWorkerManager.
applierAdmitted ApplierAdmission
}
-// ApplierAdmission reports whether target may ask the applier trigger holds to apply.
+// ApplierAdmission reports whether target may ask the applier applierClaim holds to apply.
type ApplierAdmission func(
- ctx context.Context, reader client.Reader, target *configv1alpha3.GitTarget, trigger configv1alpha3.ApplyTrigger,
+ ctx context.Context, reader client.Reader, target *configv1alpha3.GitTarget, applierClaim configv1alpha3.ApplierClaim,
) (authz.Decision, error)
// NewWorkerManager creates a new worker manager. limits bounds every worker this manager
@@ -327,12 +327,12 @@ func (m *WorkerManager) SetApplyRequester(requester ApplyRequester) {
m.applyRequester = requester
}
-// SetApplyTriggerStatusWriter injects what writes every worker's apply trigger status. Like
+// SetApplierClaimStatusWriter injects what writes every worker's applier claim status. Like
// SetApplyRequester, it is called once at startup before any worker is created.
-func (m *WorkerManager) SetApplyTriggerStatusWriter(writer ApplyTriggerStatusWriter) {
+func (m *WorkerManager) SetApplierClaimStatusWriter(writer ApplierClaimStatusWriter) {
m.mu.Lock()
defer m.mu.Unlock()
- m.triggerStatus = writer
+ m.claimStatus = writer
}
// recordRemoteObservation is the delivery point for one branch's confirmed observation. Every
@@ -513,7 +513,7 @@ func (m *WorkerManager) startWorkerLocked(key BranchKey, req WorkerRequest) erro
worker.publicationReporter = func() { go m.notifyPublication(managerCtx, gitBranch) }
worker.scanAcceptance = m.scanAcceptance
worker.applyRequester = m.applyRequester
- worker.triggerStatus = m.triggerStatus
+ worker.claimStatus = m.claimStatus
worker.initialSnapshots = m.initialSnapshots
worker.resyncRecheck = m.resyncRecheck
worker.applierAdmitted = m.applierAdmitted
@@ -853,7 +853,7 @@ const WorkerSweepInterval = time.Minute
// that reconcile runs, and the controller sees the successor, wires its worker, and never learns
// that the predecessor existed: the old branch's worker, its goroutine and its clone are then held
// until the process restarts. Kubernetes reconciliation cannot be built on observing every
-// intermediate state, so the sweep needs a trigger that does not depend on seeing the delete.
+// intermediate state, so the sweep needs an applier claim that does not depend on seeing the delete.
//
// It is a separate goroutine rather than a step in the GitTarget reconcile on purpose: the sweep
// takes lifecycleMu and holds it across Stop, and GitTarget reconciles are serialized, so a slow
diff --git a/internal/telemetry/apply_window.go b/internal/telemetry/apply_window.go
index 183107e3e..08d7b2f15 100644
--- a/internal/telemetry/apply_window.go
+++ b/internal/telemetry/apply_window.go
@@ -38,7 +38,7 @@ var ApplyWindowCollisionsTotal metric.Int64Counter
var ApplyEchoesTotal metric.Int64Counter
// ApplyOwnershipConflictsTotal counts objects another applier owns that a branch worker kept out of a
-// folder an apply trigger applies: a live write not mirrored, or a snapshot object neither upserted
+// coordinated folder: a live write not mirrored, or a snapshot object neither upserted
// nor swept (docs/design/applier-echoes.md, F4). Labelled by {provider_namespace, provider_name,
// branch}. Any non-zero rate is a misconfiguration: another Kustomization or Application applies
// objects into a namespace the folder mirrors, and its log line names both.
diff --git a/test/e2e/Taskfile-e2e.yml b/test/e2e/Taskfile-e2e.yml
index 92dedacb6..ee0c08044 100644
--- a/test/e2e/Taskfile-e2e.yml
+++ b/test/e2e/Taskfile-e2e.yml
@@ -1329,7 +1329,7 @@ tasks:
crd/commitrequests.configbutler.ai crd/gitproviders.configbutler.ai \
crd/gitbranches.configbutler.ai crd/gitreceivers.configbutler.ai \
crd/gittargets.configbutler.ai crd/watchrules.configbutler.ai \
- crd/fluxtriggers.configbutler.ai crd/argocdtriggers.configbutler.ai
+ crd/fluxclaims.configbutler.ai crd/argocdclaims.configbutler.ai
- touch "{{.CS}}/crds.installed"
_install-config-dir:
diff --git a/test/e2e/apply_trigger.go b/test/e2e/applier_claim.go
similarity index 66%
rename from test/e2e/apply_trigger.go
rename to test/e2e/applier_claim.go
index 09c840513..da00603f0 100644
--- a/test/e2e/apply_trigger.go
+++ b/test/e2e/applier_claim.go
@@ -12,18 +12,18 @@ import (
. "github.com/onsi/gomega"
)
-// The apply trigger kinds as kubectl names them.
+// The applier claim kinds as kubectl names them.
const (
- fluxTriggerResource = "fluxtrigger"
- argoCDTriggerResource = "argocdtrigger"
+ fluxClaimResource = "fluxclaim"
+ argoCDClaimResource = "argocdclaim"
)
-// createFluxTrigger creates the FluxTrigger name in namespace, holding the Kustomization
+// createFluxClaim creates the FluxClaim name in namespace, holding the Kustomization
// flux-system/ in the operator's own cluster (the install's "local" KubeProvider).
-func createFluxTrigger(namespace, name, kustomization string) {
+func createFluxClaim(namespace, name, kustomization string) {
GinkgoHelper()
manifest := fmt.Sprintf(`apiVersion: configbutler.ai/v1alpha3
-kind: FluxTrigger
+kind: FluxClaim
metadata:
name: %s
namespace: %s
@@ -34,15 +34,15 @@ spec:
name: %s
`, name, namespace, localKubeProviderRef(), kustomization)
_, err := kubectlRunWithStdin(namespace, manifest, "apply", "-f", "-")
- Expect(err).NotTo(HaveOccurred(), "failed to create FluxTrigger %s/%s", namespace, name)
+ Expect(err).NotTo(HaveOccurred(), "failed to create FluxClaim %s/%s", namespace, name)
}
-// createArgoCDTrigger creates the ArgoCDTrigger name in namespace, holding the Application
+// createArgoCDClaim creates the ArgoCDClaim name in namespace, holding the Application
// / in the operator's own cluster.
-func createArgoCDTrigger(namespace, name, appNamespace, application string) {
+func createArgoCDClaim(namespace, name, appNamespace, application string) {
GinkgoHelper()
manifest := fmt.Sprintf(`apiVersion: configbutler.ai/v1alpha3
-kind: ArgoCDTrigger
+kind: ArgoCDClaim
metadata:
name: %s
namespace: %s
@@ -53,51 +53,51 @@ spec:
name: %s
`, name, namespace, localKubeProviderRef(), appNamespace, application)
_, err := kubectlRunWithStdin(namespace, manifest, "apply", "-f", "-")
- Expect(err).NotTo(HaveOccurred(), "failed to create ArgoCDTrigger %s/%s", namespace, name)
+ Expect(err).NotTo(HaveOccurred(), "failed to create ArgoCDClaim %s/%s", namespace, name)
}
-// allowCrossNamespaceAppliers lets the triggers that reach their applier through the install's
+// allowCrossNamespaceAppliers lets the applier claims that reach their applier through the install's
// KubeProvider name one in another namespace, as the e2e appliers are: in flux-system and argocd,
-// while each spec's triggers live in its own namespace. Granting it is idempotent, and no spec
+// while each spec's applier claims live in its own namespace. Granting it is idempotent, and no spec
// relies on its absence on that provider, so parallel specs may all ask for it.
func allowCrossNamespaceAppliers() {
GinkgoHelper()
_, err := kubectlRunInNamespace(resolveE2ENamespace(), "patch", "kubeprovider", e2eKubeProviderName,
"--type=merge", "-p", `{"spec":{"allowCrossNamespace":{"appliers":true}}}`)
- Expect(err).NotTo(HaveOccurred(), "failed to let the install KubeProvider's triggers reach other namespaces")
+ Expect(err).NotTo(HaveOccurred(), "failed to let the install KubeProvider's applier claims reach other namespaces")
}
-// nameApplyTrigger points a GitTarget at the trigger of kind (FluxTrigger or ArgoCDTrigger) and
+// nameApplierClaim points a GitTarget at the applier claim of kind (FluxClaim or ArgoCDClaim) and
// name, and sets onRefusal when it is not empty.
-func nameApplyTrigger(namespace, gitTarget, kind, trigger, onRefusal string) {
+func nameApplierClaim(namespace, gitTarget, kind, claim, onRefusal string) {
GinkgoHelper()
refusal := ""
if onRefusal != "" {
refusal = fmt.Sprintf(`,"onRefusal":%q`, onRefusal)
}
_, err := kubectlRunInNamespace(namespace, "patch", "gittarget", gitTarget, "--type=merge", "-p",
- fmt.Sprintf(`{"spec":{"applyTriggerRef":{"kind":%q,"name":%q}%s}}`, kind, trigger, refusal))
- Expect(err).NotTo(HaveOccurred(), "failed to point GitTarget %s/%s at %s %s", namespace, gitTarget, kind, trigger)
+ fmt.Sprintf(`{"spec":{"applierClaimRef":{"kind":%q,"name":%q}%s}}`, kind, claim, refusal))
+ Expect(err).NotTo(HaveOccurred(), "failed to point GitTarget %s/%s at %s %s", namespace, gitTarget, kind, claim)
}
-// requestTriggerReconcile asks a trigger's reconciler to read its applier now. It reads the
+// requestClaimReconcile asks an applier claim's reconciler to read its applier now. It reads the
// applier on its own cadence and watches nothing in the applier's cluster, so a test that changes
// the applier asks for the read rather than wait for it.
-func requestTriggerReconcile(namespace, resource, name string) {
+func requestClaimReconcile(namespace, resource, name string) {
GinkgoHelper()
_, err := kubectlRunInNamespace(namespace, "annotate", resource, name, "--overwrite",
"reconcile.configbutler.ai/requestedAt="+strconv.FormatInt(time.Now().UnixNano(), 10))
Expect(err).NotTo(HaveOccurred(), "failed to request a %s reconcile", resource)
}
-// triggerOperation is a trigger's status.operation: its reason, commit, phase and token, as the
+// claimOperation is an applier claim's status.operation: its reason, commit, phase and token, as the
// branch worker writes them on every transition.
-type triggerOperation struct {
+type claimOperation struct {
Reason, Commit, Phase, Token string
}
-// readTriggerOperation reads a trigger's status.operation; the zero value when it has none.
-func readTriggerOperation(g Gomega, namespace, resource, name string) triggerOperation {
+// readClaimOperation reads an applier claim's status.operation; the zero value when it has none.
+func readClaimOperation(g Gomega, namespace, resource, name string) claimOperation {
out, err := kubectlRunInNamespace(namespace, "get", resource, name, "-o",
"jsonpath={.status.operation.reason}|{.status.operation.commit}|{.status.operation.phase}|"+
"{.status.operation.token}")
@@ -107,7 +107,7 @@ func readTriggerOperation(g Gomega, namespace, resource, name string) triggerOpe
for len(parts) < fields {
parts = append(parts, "")
}
- return triggerOperation{Reason: parts[0], Commit: parts[1], Phase: parts[2], Token: parts[3]}
+ return claimOperation{Reason: parts[0], Commit: parts[1], Phase: parts[2], Token: parts[3]}
}
// fluxSourceStamp is the reconcile.fluxcd.io/requestedAt of a GitRepository in flux-system: set
@@ -135,10 +135,10 @@ func kustomizationManagers(g Gomega, name string) string {
return strings.TrimSpace(out)
}
-// fluxTriggerEvents is every message of the Events with reason recorded on the FluxTrigger name.
-func fluxTriggerEvents(g Gomega, namespace, name, reason string) string {
+// fluxClaimEvents is every message of the Events with reason recorded on the FluxClaim name.
+func fluxClaimEvents(g Gomega, namespace, name, reason string) string {
out, err := kubectlRunInNamespace(namespace, "get", "events", "--field-selector",
- "involvedObject.kind=FluxTrigger,involvedObject.name="+name+",reason="+reason,
+ "involvedObject.kind=FluxClaim,involvedObject.name="+name+",reason="+reason,
"-o", "jsonpath={.items[*].message}")
g.Expect(err).NotTo(HaveOccurred())
return out
diff --git a/test/e2e/argocd_bi_directional_e2e_test.go b/test/e2e/argocd_bi_directional_e2e_test.go
index 22c089b5e..12597b230 100644
--- a/test/e2e/argocd_bi_directional_e2e_test.go
+++ b/test/e2e/argocd_bi_directional_e2e_test.go
@@ -21,13 +21,13 @@ import (
// Argo CD is only installed for this corner, by the `_argocd-installed` Taskfile
// node. `task test-e2e` skips these specs; `task test-e2e-bi-directional` runs them.
//
-// Every spec runs the standard (docs/bi-directional-argocd.md): the ArgoCDTrigger the
+// Every spec runs the standard (docs/bi-directional-argocd.md): the ArgoCDClaim the
// GitTarget names holds the Application's automated sync off, so every sync it runs is
// one GitOps Reverser started, pinned to a commit it checked. Argo CD's own Gitea
// webhook stays wired, and is shown to be harmless: with automated sync off it only
// refreshes. Two specs take the switch: one switches automated sync on and proves the
-// trigger switches it off again, and one generates the Application from an
-// ApplicationSet, whose switch the trigger never writes.
+// applier claim switches it off again, and one generates the Application from an
+// ApplicationSet, whose switch the applier claim never writes.
//
// Everything is driven through kubectl, so the devcontainer needs no `argocd`
// binary.
@@ -83,12 +83,12 @@ type argoBiDirectionalRun struct {
editedOrderName string
automatedOrderName string
- // The ApplicationSet spec's own folder, GitTarget, trigger and generated Application.
- appSetPath string
- appSetName string
- appSetTargetName string
- generatedAppName string
- appSetTriggerName string
+ // The ApplicationSet spec's own folder, GitTarget, applier claim and generated Application.
+ appSetPath string
+ appSetName string
+ appSetTargetName string
+ generatedAppName string
+ appSetClaimName string
}
// argoAppConfig is the knob set the specs turn. The Application is re-applied (not
@@ -103,7 +103,7 @@ type argoAppConfig struct {
// Kustomize renders the path as a kustomization instead of a recursive directory.
Kustomize bool
- // Automated turns automated sync ON, which the trigger switches off again.
+ // Automated turns automated sync ON, which the applier claim switches off again.
Automated bool
}
@@ -188,16 +188,16 @@ var _ = Describe("Bi Directional (Argo CD)", Label("bi-directional", "argocd"),
By("naming the Application converges it once, after the folder's initial snapshot is in Git")
Eventually(func(g Gomega) {
- operation := run.triggerOperation(g)
+ operation := run.claimOperation(g)
g.Expect(operation.Reason).To(Equal("CatchUp"))
g.Expect(operation.Phase).To(Equal("Succeeded"))
}, argoRequestedSyncTimeout, biPollInterval).Should(Succeed())
- converged := run.triggerOperation(Default)
+ converged := run.claimOperation(Default)
// Absorbs the reverser's bootstrap commit, so the counts below measure the
// Git-side push and Argo CD's sync, and nothing else.
baselineCommitCount := run.waitForStableRemoteCommitCount(biStableCountShortWait)
- Expect(run.triggerOperation(Default)).To(Equal(converged),
+ Expect(run.claimOperation(Default)).To(Equal(converged),
"GitOps Reverser's own pushes ask Argo CD for nothing more")
By("pushing a clean IceCreamOrder from the Git side, and nothing else")
@@ -215,10 +215,10 @@ var _ = Describe("Bi Directional (Argo CD)", Label("bi-directional", "argocd"),
By("GitOps Reverser asks Argo CD to sync exactly that commit, as a ForeignCommit")
run.waitForReverserSync(head, "ForeignCommit")
run.waitForOrderContainer(run.syncedOrderName, "Cone")
- // The branch worker writes the operation on the trigger at every transition, so nothing
+ // The branch worker writes the operation on the applier claim at every transition, so nothing
// has to reconcile for it to show.
Eventually(func(g Gomega) {
- operation := run.triggerOperation(g)
+ operation := run.claimOperation(g)
g.Expect(operation.Reason).To(Equal("ForeignCommit"))
g.Expect(operation.Commit).To(Equal(head))
g.Expect(operation.Phase).To(Equal("Succeeded"))
@@ -244,7 +244,7 @@ var _ = Describe("Bi Directional (Argo CD)", Label("bi-directional", "argocd"),
Expect(committed).NotTo(ContainSubstring("resourceVersion"))
})
- // The race the apply trigger exists for (docs/design/apply-trigger.md): two API
+ // The race the applier claim exists for (docs/design/apply-trigger.md): two API
// edits to one field a few seconds apart. With automated sync, Argo CD synced the
// first edit's commit over the second edit. Under the standard nothing syncs
// GitOps Reverser's own commits at all (an API edit asks for no apply), so the
@@ -290,8 +290,8 @@ var _ = Describe("Bi Directional (Argo CD)", Label("bi-directional", "argocd"),
}, argoSyncTimeout, biPollInterval).Should(Succeed())
})
- // The standard is full control, so the trigger holds automated sync off: switching it
- // on (as Argo CD's Enable Auto-Sync does) is undone at the trigger's next read, with an
+ // The standard is full control, so the applier claim holds automated sync off: switching it
+ // on (as Argo CD's Enable Auto-Sync does) is undone at the applier claim's next read, with an
// Event, and a Git-side push after that is synced by GitOps Reverser, not by Argo CD.
It("switches automated sync off again when somebody switches it on", func() {
By("switching automated sync on: a client-side apply that drops enabled from the manifest")
@@ -300,14 +300,14 @@ var _ = Describe("Bi Directional (Argo CD)", Label("bi-directional", "argocd"),
run.applyArgoApp(automated)
Expect(run.automatedEnabled()).To(BeEmpty(), "the apply removed the switch, so automated sync is on")
- // The applier is read on the trigger's reconcile, not watched; ask for one now.
- requestTriggerReconcile(testNs, argoCDTriggerResource, run.appName)
+ // The applier is read on the applier claim's reconcile, not watched; ask for one now.
+ requestClaimReconcile(testNs, argoCDClaimResource, run.appName)
Eventually(func(g Gomega) {
g.Expect(run.automatedEnabled()).To(Equal("false"))
- g.Expect(triggerEventMessages(g, testNs, "ArgoCDTrigger", run.appName, "ClaimedResourceReconciled")).
+ g.Expect(claimEventMessages(g, testNs, "ArgoCDClaim", run.appName, "ClaimedResourceReconciled")).
To(ContainSubstring("was switched on outside GitOps Reverser, which switched it off again"))
}, biEventuallyTimeout, biPollInterval).Should(Succeed())
- verifyResourceCondition(argoCDTriggerResource, run.appName, testNs, "Held", "True", "AutomatedSyncOff",
+ verifyResourceCondition(argoCDClaimResource, run.appName, testNs, "Held", "True", "AutomatedSyncOff",
"held off by GitOps Reverser")
Expect(run.applicationManagers()).To(ContainElement(argoReverserInitiator),
"the switch is held under GitOps Reverser's field manager")
@@ -328,7 +328,7 @@ var _ = Describe("Bi Directional (Argo CD)", Label("bi-directional", "argocd"),
})
// An Application an ApplicationSet generates is rewritten from the ApplicationSet's
- // template on every reconcile, so the switch is the template's. The trigger never writes
+ // template on every reconcile, so the switch is the template's. The applier claim never writes
// it: with automated sync on in the template it refuses the Application with a message
// that says what to change, and once the template has it off it holds nothing and is
// Ready.
@@ -344,7 +344,7 @@ var _ = Describe("Bi Directional (Argo CD)", Label("bi-directional", "argocd"),
By("generating the Application with automated sync on in the template")
DeferCleanup(func() {
cleanupNamespacedResource(run.argoNs, "applicationset.argoproj.io", run.appSetName)
- cleanupNamespacedResource(testNs, argoCDTriggerResource, run.appSetTriggerName)
+ cleanupNamespacedResource(testNs, argoCDClaimResource, run.appSetClaimName)
cleanupGitTarget(run.appSetTargetName, testNs)
})
run.applyApplicationSet(true)
@@ -357,17 +357,17 @@ var _ = Describe("Bi Directional (Argo CD)", Label("bi-directional", "argocd"),
By("naming the generated Application from a GitTarget of its own")
createGitTarget(run.appSetTargetName, testNs, run.gitProviderName, run.appSetPath, "main")
- createArgoCDTrigger(testNs, run.appSetTriggerName, run.argoNs, run.generatedAppName)
- nameApplyTrigger(testNs, run.appSetTargetName, "ArgoCDTrigger", run.appSetTriggerName, "")
+ createArgoCDClaim(testNs, run.appSetClaimName, run.argoNs, run.generatedAppName)
+ nameApplierClaim(testNs, run.appSetTargetName, "ArgoCDClaim", run.appSetClaimName, "")
- verifyResourceCondition(argoCDTriggerResource, run.appSetTriggerName, testNs, "Configured", "False",
+ verifyResourceCondition(argoCDClaimResource, run.appSetClaimName, testNs, "Configured", "False",
"OwnedByApplicationSet", "is generated by ApplicationSet "+run.argoNs+"/"+run.appSetName)
- verifyResourceCondition("gittarget", run.appSetTargetName, testNs, "ApplyTriggerReady", "False",
+ verifyResourceCondition("gittarget", run.appSetTargetName, testNs, "ApplierClaimReady", "False",
"OwnedByApplicationSet", "spec.template.spec.syncPolicy.automated.enabled")
Expect(run.applicationManagersOf(run.generatedAppName)).NotTo(ContainElement(argoReverserInitiator),
"the switch of a generated Application is never written")
- By("switching automated sync off in the template, which the trigger then only checks")
+ By("switching automated sync off in the template, which the applier claim then only checks")
run.applyApplicationSet(false)
Eventually(func(g Gomega) {
enabled, err := kubectlRunInNamespace(run.argoNs, "get", "application.argoproj.io", run.generatedAppName,
@@ -375,16 +375,16 @@ var _ = Describe("Bi Directional (Argo CD)", Label("bi-directional", "argocd"),
g.Expect(err).NotTo(HaveOccurred())
g.Expect(strings.TrimSpace(enabled)).To(Equal("false"))
}, argoSyncTimeout, biPollInterval).Should(Succeed())
- requestTriggerReconcile(testNs, argoCDTriggerResource, run.appSetTriggerName)
- verifyResourceCondition(argoCDTriggerResource, run.appSetTriggerName, testNs, "Held", "True",
+ requestClaimReconcile(testNs, argoCDClaimResource, run.appSetClaimName)
+ verifyResourceCondition(argoCDClaimResource, run.appSetClaimName, testNs, "Held", "True",
"AutomatedSyncOff", "kept off by ApplicationSet "+run.argoNs+"/"+run.appSetName)
- verifyResourceCondition("gittarget", run.appSetTargetName, testNs, "ApplyTriggerReady", "True",
+ verifyResourceCondition("gittarget", run.appSetTargetName, testNs, "ApplierClaimReady", "True",
"Succeeded", "")
Expect(run.applicationManagersOf(run.generatedAppName)).NotTo(ContainElement(argoReverserInitiator))
- finalizers, err := kubectlRunInNamespace(testNs, "get", argoCDTriggerResource, run.appSetTriggerName,
+ finalizers, err := kubectlRunInNamespace(testNs, "get", argoCDClaimResource, run.appSetClaimName,
"-o", "jsonpath={.metadata.finalizers}")
Expect(err).NotTo(HaveOccurred())
- Expect(finalizers).To(BeEmpty(), "a trigger that holds no switch has nothing to hand back")
+ Expect(finalizers).To(BeEmpty(), "an applier claim that holds no switch has nothing to hand back")
})
})
@@ -458,9 +458,9 @@ var _ = Describe("Bi Directional (Argo CD) refusal", Label("bi-directional", "ar
By("creating the GitTarget on the overlay, naming the Application and asking it to reapply")
createGitTarget(run.gitTargetName, testNs, run.gitProviderName, overlayPath, "main")
- createArgoCDTrigger(testNs, run.appName, run.argoNs, run.appName)
- nameApplyTrigger(testNs, run.gitTargetName, "ArgoCDTrigger", run.appName, "Reapply")
- verifyResourceCondition("gittarget", run.gitTargetName, testNs, "ApplyTriggerReady", "True", "Succeeded", "")
+ createArgoCDClaim(testNs, run.appName, run.argoNs, run.appName)
+ nameApplierClaim(testNs, run.gitTargetName, "ArgoCDClaim", run.appName, "Reapply")
+ verifyResourceCondition("gittarget", run.gitTargetName, testNs, "ApplierClaimReady", "True", "Succeeded", "")
Expect(applyFromTemplate("test/e2e/templates/manager/watchrule-resources.tmpl", struct {
Name string
Namespace string
@@ -525,7 +525,7 @@ func newArgoBiDirectionalRun(testNs string, repo *RepoArtifacts, prefix string)
appSetName: fmt.Sprintf("%s-appset-%s", prefix, testID),
appSetTargetName: fmt.Sprintf("%s-appset-target-%s", prefix, testID),
generatedAppName: fmt.Sprintf("%s-generated-%s", prefix, testID),
- appSetTriggerName: fmt.Sprintf("%s-generated-%s", prefix, testID),
+ appSetClaimName: fmt.Sprintf("%s-generated-%s", prefix, testID),
}
}
@@ -570,9 +570,9 @@ func (r argoBiDirectionalRun) startReverserPipeline() int64 {
hookID := registerOperatorReceiver(r.testNs, r.receiverSecretName, r.repoName)
createGitTarget(r.gitTargetName, r.testNs, r.gitProviderName, r.livePath, "main")
- createArgoCDTrigger(r.testNs, r.appName, r.argoNs, r.appName)
- nameApplyTrigger(r.testNs, r.gitTargetName, "ArgoCDTrigger", r.appName, "")
- verifyResourceCondition("gittarget", r.gitTargetName, r.testNs, "ApplyTriggerReady", "True", "Succeeded", "")
+ createArgoCDClaim(r.testNs, r.appName, r.argoNs, r.appName)
+ nameApplierClaim(r.testNs, r.gitTargetName, "ArgoCDClaim", r.appName, "")
+ verifyResourceCondition("gittarget", r.gitTargetName, r.testNs, "ApplierClaimReady", "True", "Succeeded", "")
Expect(applyFromTemplate("test/e2e/templates/watchrule-crd.tmpl", struct {
Name string
@@ -670,8 +670,8 @@ func (r argoBiDirectionalRun) applicationManagersOf(name string) []string {
return strings.Fields(out)
}
-// triggerEventMessages is the messages of the Events with reason on the trigger kind/name.
-func triggerEventMessages(g Gomega, namespace, kind, name, reason string) string {
+// claimEventMessages is the messages of the Events with reason on the applier claim kind/name.
+func claimEventMessages(g Gomega, namespace, kind, name, reason string) string {
out, err := kubectlRunInNamespace(namespace, "get", "events", "--field-selector",
"involvedObject.kind="+kind+",involvedObject.name="+name+",reason="+reason,
"-o", "jsonpath={.items[*].message}")
@@ -816,10 +816,10 @@ func (r argoBiDirectionalRun) lastOperation(g Gomega) (string, string) {
return username, revision
}
-// triggerOperation is the ArgoCDTrigger's status.operation: the sync GitOps Reverser
+// claimOperation is the ArgoCDClaim's status.operation: the sync GitOps Reverser
// last started on the Application.
-func (r argoBiDirectionalRun) triggerOperation(g Gomega) triggerOperation {
- return readTriggerOperation(g, r.testNs, argoCDTriggerResource, r.appName)
+func (r argoBiDirectionalRun) claimOperation(g Gomega) claimOperation {
+ return readClaimOperation(g, r.testNs, argoCDClaimResource, r.appName)
}
// operationFinishedAt returns "" when the Application has never run an operation.
diff --git a/test/e2e/bi_directional_echoes_e2e_test.go b/test/e2e/bi_directional_echoes_e2e_test.go
index e3a2c443a..57a885f2c 100644
--- a/test/e2e/bi_directional_echoes_e2e_test.go
+++ b/test/e2e/bi_directional_echoes_e2e_test.go
@@ -33,14 +33,14 @@ type echoApplier struct {
// owns reports whether the applier owns the live ConfigMap: Flux's owner labels name its
// Kustomization, or Argo CD's tracking annotation names its Application.
owns func(g Gomega, testNs, configMap string) bool
- // operation is the trigger's status.operation.
- operation func(g Gomega) triggerOperation
- // startOther starts another applier applying folder, which is not the trigger's, and returns its
+ // operation is the applier claim's status.operation.
+ operation func(g Gomega) claimOperation
+ // startOther starts another applier applying folder, which is not the applier claim's, and returns its
// cleanup.
startOther func(folder string) func()
}
-// fluxEchoApplier is the FluxTrigger fluxName of testNs, holding the Kustomization of that name.
+// fluxEchoApplier is the FluxClaim fluxName of testNs, holding the Kustomization of that name.
func fluxEchoApplier(testNs, fluxName string, repo *RepoArtifacts) echoApplier {
return echoApplier{
name: "Flux",
@@ -50,8 +50,8 @@ func fluxEchoApplier(testNs, fluxName string, repo *RepoArtifacts) echoApplier {
g.Expect(err).NotTo(HaveOccurred())
return strings.TrimSpace(out) == fluxName
},
- operation: func(g Gomega) triggerOperation {
- return readTriggerOperation(g, testNs, fluxTriggerResource, fluxName)
+ operation: func(g Gomega) claimOperation {
+ return readClaimOperation(g, testNs, fluxClaimResource, fluxName)
},
startOther: func(folder string) func() {
other := "other-" + fluxName
@@ -65,7 +65,7 @@ func fluxEchoApplier(testNs, fluxName string, repo *RepoArtifacts) echoApplier {
}
}
-// argoEchoApplier is the ArgoCDTrigger of run, holding its Application.
+// argoEchoApplier is the ArgoCDClaim of run, holding its Application.
func argoEchoApplier(run argoBiDirectionalRun) echoApplier {
return echoApplier{
name: "Argo CD",
@@ -75,12 +75,12 @@ func argoEchoApplier(run argoBiDirectionalRun) echoApplier {
g.Expect(err).NotTo(HaveOccurred())
return strings.HasPrefix(strings.TrimSpace(out), run.appName+":")
},
- operation: run.triggerOperation,
+ operation: run.claimOperation,
startOther: func(folder string) func() {
other := run.baseAppConfig()
other.Name = "other-" + run.appName
other.Path = folder
- other.Automated = true // not a trigger's: it syncs on its own
+ other.Automated = true // not an applier claim's: it syncs on its own
run.applyArgoApp(other)
return func() { cleanupNamespacedResource(run.argoNs, "application.argoproj.io", other.Name) }
},
@@ -350,9 +350,9 @@ func echoScenarios(run func() *echoRun) {
folder := "other/" + r.testNs
head := pushFileFromOutside(r.repo, folder+"/owned-elsewhere.yaml",
echoConfigMap(r.testNs, "owned-elsewhere", "e1"), "e2e: another applier's folder")
- // The push is a foreign commit to the trigger's repository too, so it is applied first; what
+ // The push is a foreign commit to the applier claim's repository too, so it is applied first; what
// must not follow is an apply of the object the other applier creates.
- var before triggerOperation
+ var before claimOperation
Eventually(func(g Gomega) {
before = r.applier.operation(g)
g.Expect(before.Commit).To(Equal(head), "the push is applied")
@@ -368,7 +368,7 @@ func echoScenarios(run func() *echoRun) {
}, echoApplyBudget, 2*time.Second).Should(Succeed())
Consistently(func(g Gomega) {
_, inGit := r.committedValue(g, "owned-elsewhere")
- g.Expect(inGit).To(BeFalse(), "it is not mirrored into the triggered folder")
+ g.Expect(inGit).To(BeFalse(), "it is not mirrored into the coordinated folder")
g.Expect(r.applier.operation(g).Token).To(Equal(before.Token), "nor adopted")
}, echoHold, 2*time.Second).Should(Succeed())
})
@@ -436,11 +436,11 @@ var _ = Describe("Bi Directional applier echoes (Flux)", Label("bi-directional",
pipeline.start()
applyFluxApplierWithPrune(testNs, repo, fluxName, fluxName+"-git", "main", "./"+pipeline.gitPath, true)
- createFluxTrigger(testNs, fluxName, fluxName)
- nameApplyTrigger(testNs, pipeline.targetName, "FluxTrigger", fluxName, "")
+ createFluxClaim(testNs, fluxName, fluxName)
+ nameApplierClaim(testNs, pipeline.targetName, "FluxClaim", fluxName, "")
verifyResourceCondition("gittarget", pipeline.targetName, testNs,
- "ApplyTriggerReady", "True", "Succeeded", "")
- verifyResourceCondition(fluxTriggerResource, fluxName, testNs, "Held", "True", "Suspended", "")
+ "ApplierClaimReady", "True", "Succeeded", "")
+ verifyResourceCondition(fluxClaimResource, fluxName, testNs, "Held", "True", "Suspended", "")
r = &echoRun{timingPipeline: pipeline, applier: fluxEchoApplier(testNs, fluxName, repo)}
})
@@ -450,7 +450,7 @@ var _ = Describe("Bi Directional applier echoes (Flux)", Label("bi-directional",
return
}
r.cleanup()
- cleanupNamespacedResource(testNs, fluxTriggerResource, fluxName)
+ cleanupNamespacedResource(testNs, fluxClaimResource, fluxName)
cleanupNamespacedResource("flux-system", "kustomization", fluxName)
cleanupNamespacedResource("flux-system", "gitrepository", fluxName)
cleanupNamespacedResource("flux-system", "secret", fluxName+"-git")
@@ -484,10 +484,10 @@ var _ = Describe("Bi Directional applier echoes (Argo CD)", Label("bi-directiona
cfg := run.baseAppConfig()
cfg.Path = pipeline.gitPath
run.applyArgoApp(cfg)
- createArgoCDTrigger(testNs, run.appName, run.argoNs, run.appName)
- nameApplyTrigger(testNs, pipeline.targetName, "ArgoCDTrigger", run.appName, "")
+ createArgoCDClaim(testNs, run.appName, run.argoNs, run.appName)
+ nameApplierClaim(testNs, pipeline.targetName, "ArgoCDClaim", run.appName, "")
verifyResourceCondition("gittarget", pipeline.targetName, testNs,
- "ApplyTriggerReady", "True", "Succeeded", "")
+ "ApplierClaimReady", "True", "Succeeded", "")
r = &echoRun{timingPipeline: pipeline, applier: argoEchoApplier(run)}
})
@@ -498,7 +498,7 @@ var _ = Describe("Bi Directional applier echoes (Argo CD)", Label("bi-directiona
}
cleanupNamespacedResource(run.argoNs, "application.argoproj.io", run.appName)
cleanupNamespacedResource(run.argoNs, "secret", run.repoSecretName)
- cleanupNamespacedResource(testNs, argoCDTriggerResource, run.appName)
+ cleanupNamespacedResource(testNs, argoCDClaimResource, run.appName)
r.cleanup()
cleanupNamespace(testNs)
})
diff --git a/test/e2e/bi_directional_push_race_e2e_test.go b/test/e2e/bi_directional_push_race_e2e_test.go
index 9527a7079..d7b7efa90 100644
--- a/test/e2e/bi_directional_push_race_e2e_test.go
+++ b/test/e2e/bi_directional_push_race_e2e_test.go
@@ -83,11 +83,11 @@ var _ = Describe("Bi Directional push race (Flux)", Label("bi-directional", "flu
pipeline.start()
applyFluxApplier(testNs, repo, fluxName, fluxName+"-git", "main", "./"+pipeline.gitPath)
- createFluxTrigger(testNs, fluxName, fluxName)
- nameApplyTrigger(testNs, pipeline.targetName, "FluxTrigger", fluxName, "")
+ createFluxClaim(testNs, fluxName, fluxName)
+ nameApplierClaim(testNs, pipeline.targetName, "FluxClaim", fluxName, "")
verifyResourceCondition("gittarget", pipeline.targetName, testNs,
- "ApplyTriggerReady", "True", "Succeeded", "")
- verifyResourceCondition(fluxTriggerResource, fluxName, testNs, "Held", "True", "Suspended", "")
+ "ApplierClaimReady", "True", "Succeeded", "")
+ verifyResourceCondition(fluxClaimResource, fluxName, testNs, "Held", "True", "Suspended", "")
})
AfterAll(func() {
@@ -97,7 +97,7 @@ var _ = Describe("Bi Directional push race (Flux)", Label("bi-directional", "flu
if pipeline != nil {
pipeline.cleanup()
}
- cleanupNamespacedResource(testNs, fluxTriggerResource, fluxName)
+ cleanupNamespacedResource(testNs, fluxClaimResource, fluxName)
cleanupNamespacedResource("flux-system", "kustomization", fluxName)
cleanupNamespacedResource("flux-system", "gitrepository", fluxName)
cleanupNamespacedResource("flux-system", "secret", fluxName+"-git")
@@ -133,10 +133,10 @@ var _ = Describe("Bi Directional push race (Argo CD)", Label("bi-directional", "
cfg := run.baseAppConfig()
cfg.Path = pipeline.gitPath
run.applyArgoApp(cfg)
- createArgoCDTrigger(testNs, run.appName, run.argoNs, run.appName)
- nameApplyTrigger(testNs, pipeline.targetName, "ArgoCDTrigger", run.appName, "")
+ createArgoCDClaim(testNs, run.appName, run.argoNs, run.appName)
+ nameApplierClaim(testNs, pipeline.targetName, "ArgoCDClaim", run.appName, "")
verifyResourceCondition("gittarget", pipeline.targetName, testNs,
- "ApplyTriggerReady", "True", "Succeeded", "")
+ "ApplierClaimReady", "True", "Succeeded", "")
})
AfterAll(func() {
@@ -145,7 +145,7 @@ var _ = Describe("Bi Directional push race (Argo CD)", Label("bi-directional", "
}
cleanupNamespacedResource(run.argoNs, "application.argoproj.io", run.appName)
cleanupNamespacedResource(run.argoNs, "secret", run.repoSecretName)
- cleanupNamespacedResource(testNs, argoCDTriggerResource, run.appName)
+ cleanupNamespacedResource(testNs, argoCDClaimResource, run.appName)
if pipeline != nil {
pipeline.cleanup()
}
diff --git a/test/e2e/bi_directional_restart_e2e_test.go b/test/e2e/bi_directional_restart_e2e_test.go
index f7d313a2f..bf010a481 100644
--- a/test/e2e/bi_directional_restart_e2e_test.go
+++ b/test/e2e/bi_directional_restart_e2e_test.go
@@ -103,10 +103,10 @@ var _ = Describe("Bi Directional restart with work made while down (Flux)",
pipeline := newTimingPipeline(testNs, "flux-restart", repo)
pipeline.start()
applyFluxApplierWithPrune(testNs, repo, fluxName, fluxName+"-git", "main", "./"+pipeline.gitPath, true)
- createFluxTrigger(testNs, fluxName, fluxName)
- nameApplyTrigger(testNs, pipeline.targetName, "FluxTrigger", fluxName, "")
+ createFluxClaim(testNs, fluxName, fluxName)
+ nameApplierClaim(testNs, pipeline.targetName, "FluxClaim", fluxName, "")
verifyResourceCondition("gittarget", pipeline.targetName, testNs,
- "ApplyTriggerReady", "True", "Succeeded", "")
+ "ApplierClaimReady", "True", "Succeeded", "")
r = &echoRun{timingPipeline: pipeline, applier: fluxEchoApplier(testNs, fluxName, repo)}
})
@@ -115,7 +115,7 @@ var _ = Describe("Bi Directional restart with work made while down (Flux)",
return
}
r.cleanup()
- cleanupNamespacedResource(testNs, fluxTriggerResource, fluxName)
+ cleanupNamespacedResource(testNs, fluxClaimResource, fluxName)
cleanupNamespacedResource("flux-system", "kustomization", fluxName)
cleanupNamespacedResource("flux-system", "gitrepository", fluxName)
cleanupNamespacedResource("flux-system", "secret", fluxName+"-git")
@@ -151,10 +151,10 @@ var _ = Describe("Bi Directional restart with work made while down (Argo CD)",
cfg := run.baseAppConfig()
cfg.Path = pipeline.gitPath
run.applyArgoApp(cfg)
- createArgoCDTrigger(testNs, run.appName, run.argoNs, run.appName)
- nameApplyTrigger(testNs, pipeline.targetName, "ArgoCDTrigger", run.appName, "")
+ createArgoCDClaim(testNs, run.appName, run.argoNs, run.appName)
+ nameApplierClaim(testNs, pipeline.targetName, "ArgoCDClaim", run.appName, "")
verifyResourceCondition("gittarget", pipeline.targetName, testNs,
- "ApplyTriggerReady", "True", "Succeeded", "")
+ "ApplierClaimReady", "True", "Succeeded", "")
r = &echoRun{timingPipeline: pipeline, applier: argoEchoApplier(run)}
})
@@ -164,7 +164,7 @@ var _ = Describe("Bi Directional restart with work made while down (Argo CD)",
}
cleanupNamespacedResource(run.argoNs, "application.argoproj.io", run.appName)
cleanupNamespacedResource(run.argoNs, "secret", run.repoSecretName)
- cleanupNamespacedResource(testNs, argoCDTriggerResource, run.appName)
+ cleanupNamespacedResource(testNs, argoCDClaimResource, run.appName)
r.cleanup()
cleanupNamespace(testNs)
})
diff --git a/test/e2e/bi_directional_timing_e2e_test.go b/test/e2e/bi_directional_timing_e2e_test.go
index 1e2d72248..381717f19 100644
--- a/test/e2e/bi_directional_timing_e2e_test.go
+++ b/test/e2e/bi_directional_timing_e2e_test.go
@@ -27,7 +27,7 @@ import (
// purpose: they catch a path that silently fell back to a periodic tick (minutes), not a slow CI
// runner (seconds). The table is the measurement; the budget is the guarantee.
//
-// The folder is SHARED, which is the case a trigger exists for: GitOps Reverser mirrors a
+// The folder is SHARED, which is the case an applier claim exists for: GitOps Reverser mirrors a
// labeled ConfigMap into the folder the applier applies. The WatchRule selects only that label,
// so the namespace's kube-root-ca.crt never reaches the folder and the applier applies exactly one
// object.
@@ -43,7 +43,7 @@ const (
timingCommitBudget = 45 * time.Second
// A Git-side push the receiver hears of has reached the live object, through the applier.
timingReceiverBudget = 90 * time.Second
- // An apply the trigger started has finished, and the trigger holds its applier again.
+ // An apply the applier claim started has finished, and the applier claim holds its applier again.
timingHeldBudget = 60 * time.Second
timingDefaultSamples = 3
@@ -263,13 +263,13 @@ var _ = Describe("Bi Directional timing (Flux)", Label("bi-directional", "flux",
pipeline = newTimingPipeline(testNs, "flux-timing", repo)
pipeline.start()
- By("letting Flux apply the shared folder once, then naming a trigger")
+ By("letting Flux apply the shared folder once, then naming an applier claim")
applyFluxApplier(testNs, repo, fluxName, fluxName+"-git", "main", "./"+pipeline.gitPath)
- createFluxTrigger(testNs, fluxName, fluxName)
- nameApplyTrigger(testNs, pipeline.targetName, "FluxTrigger", fluxName, "")
+ createFluxClaim(testNs, fluxName, fluxName)
+ nameApplierClaim(testNs, pipeline.targetName, "FluxClaim", fluxName, "")
verifyResourceCondition("gittarget", pipeline.targetName, testNs,
- "ApplyTriggerReady", "True", "Succeeded", "")
- verifyResourceCondition(fluxTriggerResource, fluxName, testNs, "Held", "True", "Suspended", "")
+ "ApplierClaimReady", "True", "Succeeded", "")
+ verifyResourceCondition(fluxClaimResource, fluxName, testNs, "Held", "True", "Suspended", "")
})
AfterAll(func() {
@@ -280,18 +280,18 @@ var _ = Describe("Bi Directional timing (Flux)", Label("bi-directional", "flux",
// nil when BeforeAll failed before building it.
pipeline.cleanup()
}
- cleanupNamespacedResource(testNs, fluxTriggerResource, fluxName)
+ cleanupNamespacedResource(testNs, fluxClaimResource, fluxName)
cleanupNamespacedResource("flux-system", "kustomization", fluxName)
cleanupNamespacedResource("flux-system", "gitrepository", fluxName)
cleanupNamespacedResource("flux-system", "secret", fluxName+"-git")
cleanupNamespace(testNs)
})
- // heldAgain passes once the trigger reports the operation for commit Succeeded, with the
+ // heldAgain passes once the applier claim reports the operation for commit Succeeded, with the
// Kustomization suspended again.
heldAgain := func(g Gomega, commit string) {
- operation := readTriggerOperation(g, testNs, fluxTriggerResource, fluxName)
+ operation := readClaimOperation(g, testNs, fluxClaimResource, fluxName)
g.Expect(operation.Commit).To(Equal(commit))
g.Expect(operation.Phase).To(Equal("Succeeded"))
g.Expect(kustomizationSuspended(g, fluxName)).To(BeTrue())
@@ -303,7 +303,7 @@ var _ = Describe("Bi Directional timing (Flux)", Label("bi-directional", "flux",
apiTimings(fluxEchoApplier(testNs, fluxName, pipeline.repo), pipeline, "Flux")
})
- // The Git side: another writer pushes, Gitea tells the receiver, and the trigger asks Flux to
+ // The Git side: another writer pushes, Gitea tells the receiver, and the applier claim asks Flux to
// apply that commit. A ForeignCommit goes out after a flush.
It("times a Git-side push through the receiver to the live object", func() {
experiment := gmeasure.NewExperiment("Flux: Git-side push")
@@ -352,10 +352,10 @@ var _ = Describe("Bi Directional timing (Argo CD)", Label("bi-directional", "arg
cfg := run.baseAppConfig()
cfg.Path = pipeline.gitPath
run.applyArgoApp(cfg)
- createArgoCDTrigger(testNs, run.appName, run.argoNs, run.appName)
- nameApplyTrigger(testNs, pipeline.targetName, "ArgoCDTrigger", run.appName, "")
+ createArgoCDClaim(testNs, run.appName, run.argoNs, run.appName)
+ nameApplierClaim(testNs, pipeline.targetName, "ArgoCDClaim", run.appName, "")
verifyResourceCondition("gittarget", pipeline.targetName, testNs,
- "ApplyTriggerReady", "True", "Succeeded", "")
+ "ApplierClaimReady", "True", "Succeeded", "")
})
AfterAll(func() {
@@ -364,7 +364,7 @@ var _ = Describe("Bi Directional timing (Argo CD)", Label("bi-directional", "arg
}
cleanupNamespacedResource(run.argoNs, "application.argoproj.io", run.appName)
cleanupNamespacedResource(run.argoNs, "secret", run.repoSecretName)
- cleanupNamespacedResource(testNs, argoCDTriggerResource, run.appName)
+ cleanupNamespacedResource(testNs, argoCDClaimResource, run.appName)
if pipeline != nil {
// nil when BeforeAll failed before building it.
pipeline.cleanup()
@@ -392,7 +392,7 @@ var _ = Describe("Bi Directional timing (Argo CD)", Label("bi-directional", "arg
})
timedUntil(experiment, measurePushToSyncDone, pushed, timingReceiverBudget+timingHeldBudget,
func(g Gomega) {
- operation := run.triggerOperation(g)
+ operation := run.claimOperation(g)
g.Expect(operation.Reason).To(Equal("ForeignCommit"))
g.Expect(operation.Commit).To(Equal(head))
g.Expect(operation.Phase).To(Equal("Succeeded"))
diff --git a/test/e2e/flux_applier_e2e_test.go b/test/e2e/flux_applier_e2e_test.go
index c6af2ed3b..7bf794101 100644
--- a/test/e2e/flux_applier_e2e_test.go
+++ b/test/e2e/flux_applier_e2e_test.go
@@ -15,12 +15,12 @@ import (
. "github.com/onsi/gomega"
)
-// A FluxTrigger, named by a GitTarget's spec.applyTriggerRef, against a real Flux, from what a request is allowed to do to how GitOps
+// A FluxClaim, named by a GitTarget's spec.applierClaimRef, against a real Flux, from what a request is allowed to do to how GitOps
// Reverser learns that it has to make one. See docs/design/apply-trigger.md.
//
// The Kustomization applies its own folder (fluxApplierFolder) into a namespace the WatchRule does
// not watch, so nothing Flux applies is mirrored back and every commit on the branch is either this
-// spec's API edit or this spec's Git-side push. Once the trigger holds it, the Kustomization is
+// spec's API edit or this spec's Git-side push. Once the applier claim holds it, the Kustomization is
// suspended, and no Flux Receiver is wired to the repository, so any apply inside the spec is one
// GitOps Reverser started.
var _ = Describe("Manager Flux Applier", Label("manager", "flux-applier"), Ordered, SpecPriority(90), func() {
@@ -67,7 +67,7 @@ var _ = Describe("Manager Flux Applier", Label("manager", "flux-applier"), Order
deleteRepoHook(repo.RepoName, receiverHookID)
cleanupWatchRule(ruleName, testNs)
cleanupGitTarget(destName, testNs)
- cleanupNamespacedResource(testNs, fluxTriggerResource, fluxName)
+ cleanupNamespacedResource(testNs, fluxClaimResource, fluxName)
deleteGitProvider(testNs, providerName)
cleanupNamespacedResource(testNs, "gitreceiver", receiverSecretName)
cleanupNamespacedResource(testNs, "secret", receiverSecretName)
@@ -78,30 +78,32 @@ var _ = Describe("Manager Flux Applier", Label("manager", "flux-applier"), Order
cleanupNamespace(testNs)
})
- // A trigger acts only while an eligible GitTarget references it. One nothing names, or one whose
+ // An applier claim acts only while an eligible GitTarget references it. One nothing names, or one whose
// Kustomization's source follows another branch than the GitTarget writes, holds nothing, and a
- // request for it never stamps that source; once the source is fixed, the trigger adopts the
+ // request for it never stamps that source; once the source is fixed, the applier claim adopts the
// Kustomization and Flux reaches the tip. Dropping the reference hands it back, and naming the
- // trigger again adopts it anew.
- It("holds the Kustomization only while an eligible GitTarget names its trigger", func() {
+ // applier claim again adopts it anew.
+ It("holds the Kustomization only while an eligible GitTarget names its applier claim", func() {
By("creating the Kustomization, with a GitRepository that follows the release branch")
applyFluxApplier(testNs, repo, fluxName, fluxSecretName, "release", "./"+fluxApplierFolder)
- By("creating a trigger no GitTarget names: it is Ready, and does nothing")
+ By("creating an applier claim no GitTarget names: it is Ready, and does nothing")
createGitTarget(destName, testNs, providerName, gitPath, "main")
- createFluxTrigger(testNs, fluxName, fluxName)
- verifyResourceCondition(fluxTriggerResource, fluxName, testNs, "Ready", "True", "NotReferenced", "")
- verifyResourceCondition(fluxTriggerResource, fluxName, testNs, "Held", "False", "NotReferenced", "")
+ createFluxClaim(testNs, fluxName, fluxName)
+ verifyResourceCondition(fluxClaimResource, fluxName, testNs, "Ready", "True", "NotReferenced", "")
+ verifyResourceCondition(fluxClaimResource, fluxName, testNs, "Held", "False", "NotReferenced", "")
Expect(kustomizationSuspended(Default, fluxName)).To(BeFalse(), "Flux applies it on its own")
- By("naming it from a GitTarget that writes main: the trigger is refused, and holds nothing")
- nameApplyTrigger(testNs, destName, "FluxTrigger", fluxName, "")
- verifyResourceCondition(fluxTriggerResource, fluxName, testNs, "Configured", "False", "SourceMismatch",
+ By("naming it from a GitTarget that writes main: the applier claim is refused, and holds nothing")
+ nameApplierClaim(testNs, destName, "FluxClaim", fluxName, "")
+ verifyResourceCondition(fluxClaimResource, fluxName, testNs, "Configured", "False", "SourceMismatch",
`applies branch "release", not main`)
- verifyResourceCondition("gittarget", destName, testNs, "ApplyTriggerReady", "False", "SourceMismatch",
+ verifyResourceCondition("gittarget", destName, testNs, "ApplierClaimReady", "False", "SourceMismatch",
`applies branch "release", not main`)
- Expect(kustomizationSuspended(Default, fluxName)).To(BeFalse(), "a trigger that is not bound adopts nothing")
- Expect(fluxTriggerEvents(Default, testNs, fluxName, "Adopted")).To(BeEmpty())
+ Expect(
+ kustomizationSuspended(Default, fluxName),
+ ).To(BeFalse(), "an applier claim that is not bound adopts nothing")
+ Expect(fluxClaimEvents(Default, testNs, fluxName, "Adopted")).To(BeEmpty())
Expect(fluxSourceStamp(fluxName)).To(BeEmpty(), "nothing has asked Flux to fetch yet")
By("mirroring a first API edit, so the branch has a tip GitOps Reverser pushed")
@@ -121,7 +123,7 @@ var _ = Describe("Manager Flux Applier", Label("manager", "flux-applier"), Order
pushAppliedColorFromOutside(repo, appliedNs, "green")
applyConfigMap(testNs, "applier-second-edit")
- // Naming the trigger converges it once (a CatchUp), as soon as the branch has a tip, so that is
+ // Naming the applier claim converges it once (a CatchUp), as soon as the branch has a tip, so that is
// the first ask refused; the ForeignCommit joins the same run of failures, and ApplyRequestFailed
// is recorded only for a run's first.
By("every ask is refused at the patch, and the source on release is never stamped")
@@ -135,46 +137,46 @@ var _ = Describe("Manager Flux Applier", Label("manager", "flux-applier"), Order
"a GitRepository that follows another branch must never be stamped for this one")
}, 20*time.Second, 2*time.Second).Should(Succeed())
- // Changing the GitRepository's branch binds the trigger, which adopts the Kustomization. The
+ // Changing the GitRepository's branch binds the applier claim, which adopts the Kustomization. The
// branch worker keeps the ForeignCommit across the rejected attempts and decides it again
// after its backoff; Flux may also apply the new artifact on its own before the adoption.
- By("pointing the GitRepository at main: the trigger adopts the Kustomization, and Flux reaches the tip")
+ By("pointing the GitRepository at main: the applier claim adopts the Kustomization, and Flux reaches the tip")
head := remoteHeadSHA(Default, repo.CheckoutDir)
_, err := kubectlRunInNamespace("flux-system", "patch", "gitrepository", fluxName, "--type=merge", "-p",
`{"spec":{"ref":{"branch":"main"}}}`)
Expect(err).NotTo(HaveOccurred(), "failed to point the GitRepository at main")
- requestTriggerReconcile(testNs, fluxTriggerResource, fluxName)
- verifyResourceCondition(fluxTriggerResource, fluxName, testNs, "Ready", "True", "Succeeded", "")
- verifyResourceCondition("gittarget", destName, testNs, "ApplyTriggerReady", "True", "Succeeded", "")
- Expect(fluxTriggerEvents(Default, testNs, fluxName, "Adopted")).
+ requestClaimReconcile(testNs, fluxClaimResource, fluxName)
+ verifyResourceCondition(fluxClaimResource, fluxName, testNs, "Ready", "True", "Succeeded", "")
+ verifyResourceCondition("gittarget", destName, testNs, "ApplierClaimReady", "True", "Succeeded", "")
+ Expect(fluxClaimEvents(Default, testNs, fluxName, "Adopted")).
To(ContainSubstring("suspended Kustomization flux-system/" + fluxName))
Eventually(func(g Gomega) {
g.Expect(fluxLastAppliedRevision(g, fluxName)).To(ContainSubstring(head))
g.Expect(appliedColor(g, appliedNs)).To(Equal("green"))
}, 5*time.Minute, 5*time.Second).Should(Succeed())
- verifyResourceCondition(fluxTriggerResource, fluxName, testNs, "Held", "True", "Suspended", "")
- Expect(kustomizationSuspended(Default, fluxName)).To(BeTrue(), "the trigger holds its Kustomization")
- others, err := kubectlRunInNamespace(testNs, "get", fluxTriggerResource, fluxName,
+ verifyResourceCondition(fluxClaimResource, fluxName, testNs, "Held", "True", "Suspended", "")
+ Expect(kustomizationSuspended(Default, fluxName)).To(BeTrue(), "the applier claim holds its Kustomization")
+ others, err := kubectlRunInNamespace(testNs, "get", fluxClaimResource, fluxName,
"-o", "jsonpath={.status.source.otherConsumers}")
Expect(err).NotTo(HaveOccurred())
Expect(strings.TrimSpace(others)).To(BeEmpty(), "its Kustomization is the GitRepository's only consumer")
- By("dropping the reference: the trigger hands the Kustomization back, and is idle again")
+ By("dropping the reference: the applier claim hands the Kustomization back, and is idle again")
_, err = kubectlRunInNamespace(testNs, "patch", "gittarget", destName, "--type=merge", "-p",
- `{"spec":{"applyTriggerRef":null}}`)
- Expect(err).NotTo(HaveOccurred(), "failed to drop the GitTarget's applyTriggerRef")
- verifyResourceCondition(fluxTriggerResource, fluxName, testNs, "Held", "False", "NotReferenced", "")
- verifyResourceCondition(fluxTriggerResource, fluxName, testNs, "Ready", "True", "NotReferenced", "")
+ `{"spec":{"applierClaimRef":null}}`)
+ Expect(err).NotTo(HaveOccurred(), "failed to drop the GitTarget's applierClaimRef")
+ verifyResourceCondition(fluxClaimResource, fluxName, testNs, "Held", "False", "NotReferenced", "")
+ verifyResourceCondition(fluxClaimResource, fluxName, testNs, "Ready", "True", "NotReferenced", "")
Eventually(func(g Gomega) {
g.Expect(kustomizationSuspended(g, fluxName)).To(BeFalse())
g.Expect(kustomizationManagers(g, fluxName)).NotTo(ContainSubstring("gitops-reverser"))
- g.Expect(fluxTriggerEvents(g, testNs, fluxName, "Released")).
+ g.Expect(fluxClaimEvents(g, testNs, fluxName, "Released")).
To(ContainSubstring("handed Kustomization flux-system/" + fluxName + " back to Flux"))
}, time.Minute, 2*time.Second).Should(Succeed())
- By("naming the trigger again: it adopts the Kustomization anew")
- nameApplyTrigger(testNs, destName, "FluxTrigger", fluxName, "")
- verifyResourceCondition(fluxTriggerResource, fluxName, testNs, "Held", "True", "Suspended", "")
+ By("naming the applier claim again: it adopts the Kustomization anew")
+ nameApplierClaim(testNs, destName, "FluxClaim", fluxName, "")
+ verifyResourceCondition(fluxClaimResource, fluxName, testNs, "Held", "True", "Suspended", "")
Expect(kustomizationSuspended(Default, fluxName)).To(BeTrue())
})
@@ -256,9 +258,9 @@ data:
`, laterNs), "e2e: another writer adds a ConfigMap in a namespace that does not exist")
By("the operation fails, and the Kustomization is suspended again after it")
- var failed triggerOperation
+ var failed claimOperation
Eventually(func(g Gomega) {
- failed = readTriggerOperation(g, testNs, fluxTriggerResource, fluxName)
+ failed = readClaimOperation(g, testNs, fluxClaimResource, fluxName)
g.Expect(failed.Phase).To(Equal("Failed"))
g.Expect(kustomizationSuspended(g, fluxName)).To(BeTrue())
}, 3*time.Minute, 2*time.Second).Should(Succeed())
@@ -271,7 +273,7 @@ data:
g.Expect(err).NotTo(HaveOccurred())
}, 5*time.Minute, 2*time.Second).Should(Succeed())
Eventually(func(g Gomega) {
- done := readTriggerOperation(g, testNs, fluxTriggerResource, fluxName)
+ done := readClaimOperation(g, testNs, fluxClaimResource, fluxName)
g.Expect(done.Phase).To(Equal("Succeeded"))
g.Expect(done.Token).NotTo(Equal(failed.Token),
"applied by an operation the worker decided, not by Flux's retryInterval retry")
@@ -282,20 +284,20 @@ data:
_, err = kubectlRunInNamespace("flux-system", "patch", "kustomization", fluxName, "--type=merge",
"--field-manager=flux", "-p", `{"spec":{"suspend":false}}`)
Expect(err).NotTo(HaveOccurred())
- requestTriggerReconcile(testNs, fluxTriggerResource, fluxName)
+ requestClaimReconcile(testNs, fluxClaimResource, fluxName)
Eventually(func(g Gomega) {
- g.Expect(fluxTriggerEvents(g, testNs, fluxName, "ClaimedResourceReconciled")).
+ g.Expect(fluxClaimEvents(g, testNs, fluxName, "ClaimedResourceReconciled")).
To(ContainSubstring("was resumed outside GitOps Reverser"))
g.Expect(kustomizationSuspended(g, fluxName)).To(BeTrue(), "suspended again")
}, 2*time.Minute, 2*time.Second).Should(Succeed())
- verifyResourceCondition(fluxTriggerResource, fluxName, testNs, "Held", "True", "Suspended", "")
+ verifyResourceCondition(fluxClaimResource, fluxName, testNs, "Held", "True", "Suspended", "")
})
- // Deleting the trigger hands the Kustomization back: its finalizer waits until nothing is
+ // Deleting the applier claim hands the Kustomization back: its finalizer waits until nothing is
// running and releases spec.suspend, so Flux applies on its own again.
- It("hands the Kustomization back to Flux when the trigger is deleted", func() {
- _, err := kubectlRunInNamespace(testNs, "delete", fluxTriggerResource, fluxName, "--timeout=2m")
- Expect(err).NotTo(HaveOccurred(), "the finalizer must let the trigger go")
+ It("hands the Kustomization back to Flux when the applier claim is deleted", func() {
+ _, err := kubectlRunInNamespace(testNs, "delete", fluxClaimResource, fluxName, "--timeout=2m")
+ Expect(err).NotTo(HaveOccurred(), "the finalizer must let the applier claim go")
Eventually(func(g Gomega) {
g.Expect(kustomizationSuspended(g, fluxName)).To(BeFalse())
g.Expect(kustomizationManagers(g, fluxName)).NotTo(ContainSubstring("gitops-reverser"))
@@ -307,7 +309,7 @@ data:
const fluxApplierFolder = "flux/applier"
// applyFluxApplier creates a GitRepository following branch and a Kustomization applying path from
-// it, both on a 24h interval, and waits for the Kustomization's first apply, made before any trigger
+// it, both on a 24h interval, and waits for the Kustomization's first apply, made before any applier claim
// holds it. The Flux specs share it.
func applyFluxApplier(testNs string, repo *RepoArtifacts, name, secretName, branch, path string) {
GinkgoHelper()
diff --git a/test/e2e/flux_bi_directional_e2e_test.go b/test/e2e/flux_bi_directional_e2e_test.go
index 0505dc325..4e645fb9b 100644
--- a/test/e2e/flux_bi_directional_e2e_test.go
+++ b/test/e2e/flux_bi_directional_e2e_test.go
@@ -77,8 +77,8 @@ var biDirectionalRepo *RepoArtifacts
// only place Argo CD is installed, and the two specs share that one cluster.
//
// Exact commit counts are safe here because Flux is driven as a *manually
-// triggered applier* — every reconcile in this spec is explicit, until its last
-// phase hands the Kustomization to a FluxTrigger.
+// claimed applier* — every reconcile in this spec is explicit, until its last
+// phase hands the Kustomization to a FluxClaim.
//
// Not Serial: a whole-cluster bidirectional behavioral test (Flux applies from
// git while gitops-reverser mirrors live state back) that asserts on *exact*
@@ -428,10 +428,10 @@ var _ = Describe("Bi Directional (Flux)", Label("bi-directional", "flux"), Order
// edit onto the Git-side commit, and only then asks Flux to apply, so the commit Flux applies
// already carries the API side's answer. Nobody reconciles Flux by hand.
It("applies a Git-side push only after replaying the API edit made on top of it", func() {
- By("naming a trigger that holds the live Kustomization")
- createFluxTrigger(testNs, run.fluxLiveName, run.fluxLiveName)
- nameApplyTrigger(testNs, run.gitTargetName, "FluxTrigger", run.fluxLiveName, "")
- verifyResourceCondition("gittarget", run.gitTargetName, testNs, "ApplyTriggerReady", "True", "Succeeded", "")
+ By("naming an applier claim that holds the live Kustomization")
+ createFluxClaim(testNs, run.fluxLiveName, run.fluxLiveName)
+ nameApplierClaim(testNs, run.gitTargetName, "FluxClaim", run.fluxLiveName, "")
+ verifyResourceCondition("gittarget", run.gitTargetName, testNs, "ApplierClaimReady", "True", "Succeeded", "")
run.waitForGitTargetReady()
Expect(run.gitPull()).To(Succeed())
diff --git a/test/e2e/flux_restart_recovery_e2e_test.go b/test/e2e/flux_restart_recovery_e2e_test.go
index e1c625ca4..eec7d0bdd 100644
--- a/test/e2e/flux_restart_recovery_e2e_test.go
+++ b/test/e2e/flux_restart_recovery_e2e_test.go
@@ -11,8 +11,8 @@ import (
. "github.com/onsi/gomega"
)
-// Restart recovery for a FluxTrigger (docs/design/apply-trigger.md, "Restart recovery"). The
-// operation an apply runs as is recorded on the trigger before the Kustomization is resumed, so a
+// Restart recovery for a FluxClaim (docs/design/apply-trigger.md, "Restart recovery"). The
+// operation an apply runs as is recorded on the applier claim before the Kustomization is resumed, so a
// manager that restarts while the apply runs follows that same operation to its end, and asks for
// nothing new until it has. The stopping manager suspends the Kustomization it had resumed (a
// graceful stop), so Flux applies nothing on its own while no process follows the apply; the apply
@@ -67,9 +67,9 @@ var _ = Describe("Flux Restart Recovery", Label("flux-restart-recovery"), Serial
`{"spec":{"wait":true,"timeout":"5m"}}`)
Expect(err).NotTo(HaveOccurred(), "the Kustomization waits for what it applies to be ready")
createGitTarget(destName, testNs, providerName, gitPath, "main")
- createFluxTrigger(testNs, fluxName, fluxName)
- nameApplyTrigger(testNs, destName, "FluxTrigger", fluxName, "")
- verifyResourceCondition("gittarget", destName, testNs, "ApplyTriggerReady", "True", "Succeeded", "")
+ createFluxClaim(testNs, fluxName, fluxName)
+ nameApplierClaim(testNs, destName, "FluxClaim", fluxName, "")
+ verifyResourceCondition("gittarget", destName, testNs, "ApplierClaimReady", "True", "Succeeded", "")
Expect(applyFromTemplate("test/e2e/templates/manager/watchrule-resources.tmpl", struct {
Name, Namespace, DestinationName, Resources string
}{Name: ruleName, Namespace: testNs, DestinationName: destName, Resources: `"configmaps"`},
@@ -80,7 +80,7 @@ var _ = Describe("Flux Restart Recovery", Label("flux-restart-recovery"), Serial
_, _ = kubectlRun("label", "node", node, nodeLabel+"-")
cleanupWatchRule(ruleName, testNs)
cleanupGitTarget(destName, testNs)
- cleanupNamespacedResource(testNs, fluxTriggerResource, fluxName)
+ cleanupNamespacedResource(testNs, fluxClaimResource, fluxName)
deleteGitProvider(testNs, providerName)
cleanupNamespacedResource("flux-system", "kustomization", fluxName)
cleanupNamespacedResource("flux-system", "gitrepository", fluxName)
@@ -96,9 +96,9 @@ var _ = Describe("Flux Restart Recovery", Label("flux-restart-recovery"), Serial
applyConfigMap(testNs, "recovery-edit")
By("the ForeignCommit's apply starts, and waits for the Deployment")
- var before triggerOperation
+ var before claimOperation
Eventually(func(g Gomega) {
- before = readTriggerOperation(g, testNs, fluxTriggerResource, fluxName)
+ before = readClaimOperation(g, testNs, fluxClaimResource, fluxName)
g.Expect(before.Reason).To(Equal("ForeignCommit"))
g.Expect(before.Phase).To(BeElementOf("Requested", "Running"))
g.Expect(kustomizationSuspended(g, fluxName)).To(BeFalse(), "resumed for the apply")
@@ -116,7 +116,7 @@ var _ = Describe("Flux Restart Recovery", Label("flux-restart-recovery"), Serial
By("the restarted manager completes that same operation, and the Kustomization stays suspended")
Eventually(func(g Gomega) {
- after := readTriggerOperation(g, testNs, fluxTriggerResource, fluxName)
+ after := readClaimOperation(g, testNs, fluxClaimResource, fluxName)
g.Expect(after.Token).To(Equal(before.Token), "the operation recorded before the restart, not a new one")
g.Expect(after.Phase).To(Equal("Succeeded"))
g.Expect(kustomizationSuspended(g, fluxName)).To(BeTrue())
diff --git a/test/e2e/refusal_reapply_e2e_test.go b/test/e2e/refusal_reapply_e2e_test.go
index 67f374bba..d7a6ec01e 100644
--- a/test/e2e/refusal_reapply_e2e_test.go
+++ b/test/e2e/refusal_reapply_e2e_test.go
@@ -29,7 +29,7 @@ import (
// var, which no kustomize declaration in the overlay can express. Read scope reaches the base;
// write scope never does.
//
-// Flux applies the overlay when its Kustomization is created, and the trigger then holds it
+// Flux applies the overlay when its Kustomization is created, and the applier claim then holds it
// suspended, so a revert of the drifted env var inside this spec is an apply GitOps Reverser started,
// and nothing else: a resume of the unchanged commit, which is all a Refusal needs.
var _ = Describe("Manager Refusal Reapply", Label("manager", "refusal-reapply"), Ordered, SpecPriority(80), func() {
@@ -80,7 +80,7 @@ var _ = Describe("Manager Refusal Reapply", Label("manager", "refusal-reapply"),
DeferCleanup(func() { _ = os.RemoveAll(seedFolder) })
seedRenderedFolderIntoRepo(repo, testNs, seedFolder, "apps/checkout")
- By("letting Flux apply the overlay once, before any trigger holds it")
+ By("letting Flux apply the overlay once, before any applier claim holds it")
applyFluxApplier(testNs, repo, fluxName, fluxSecretName, "main", "./"+gitPath)
Eventually(func(g Gomega) {
g.Expect(checkoutLogLevel(g, testNs)).To(Equal("info"))
@@ -96,9 +96,9 @@ var _ = Describe("Manager Refusal Reapply", Label("manager", "refusal-reapply"),
By("creating the GitTarget on the OVERLAY, naming the Kustomization and asking it to reapply")
createGitTarget(destName, testNs, providerName, gitPath, "main")
- createFluxTrigger(testNs, fluxName, fluxName)
- nameApplyTrigger(testNs, destName, "FluxTrigger", fluxName, "Reapply")
- verifyResourceCondition("gittarget", destName, testNs, "ApplyTriggerReady", "True", "Succeeded", "")
+ createFluxClaim(testNs, fluxName, fluxName)
+ nameApplierClaim(testNs, destName, "FluxClaim", fluxName, "Reapply")
+ verifyResourceCondition("gittarget", destName, testNs, "ApplierClaimReady", "True", "Succeeded", "")
// The overlay renders into THIS namespace, so the rule watches its own and needs no
// sourceNamespace override. ConfigMaps ride along so this target has an ACCEPTED watched
@@ -134,9 +134,9 @@ var _ = Describe("Manager Refusal Reapply", Label("manager", "refusal-reapply"),
// (the env var changed back), so one more request may follow it. What must never grow is
// the count after that: a forced recheck re-observes the standing refusal every minute.
By("and it settles instead of asking on every recheck")
- seen := map[string]struct{}{readTriggerOperation(Default, testNs, fluxTriggerResource, fluxName).Token: {}}
+ seen := map[string]struct{}{readClaimOperation(Default, testNs, fluxClaimResource, fluxName).Token: {}}
Consistently(func(g Gomega) {
- seen[readTriggerOperation(g, testNs, fluxTriggerResource, fluxName).Token] = struct{}{}
+ seen[readClaimOperation(g, testNs, fluxClaimResource, fluxName).Token] = struct{}{}
g.Expect(len(seen)).To(BeNumerically("<=", 2),
"a standing refusal must not be requested once per recheck")
}, 150*time.Second, 10*time.Second).Should(Succeed())
diff --git a/test/e2e/setup/argocd/values.yaml b/test/e2e/setup/argocd/values.yaml
index cee5e4f16..c4306a764 100644
--- a/test/e2e/setup/argocd/values.yaml
+++ b/test/e2e/setup/argocd/values.yaml
@@ -21,7 +21,7 @@ configs:
cm:
# Make Argo's *spontaneous* refresh slow, not fast: a NEW GIT REVISION is only
# noticed on this timed refresh. The specs drive refresh/sync explicitly (an
- # ArgoCDTrigger's sync, or the spec itself), so they never wait on it. Longer
+ # ArgoCDClaim's sync, or the spec itself), so they never wait on it. Longer
# than the 120s default.
timeout.reconciliation: 180s
params: