Add progressive status in helmrepo-oci reconciler
Signed-off-by: Sunny <darkowlzz@protonmail.com>
This commit is contained in:
parent
55573f5eb6
commit
7b44c9db0d
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-containerregistry/pkg/authn"
|
||||||
helmgetter "helm.sh/helm/v3/pkg/getter"
|
helmgetter "helm.sh/helm/v3/pkg/getter"
|
||||||
helmreg "helm.sh/helm/v3/pkg/registry"
|
helmreg "helm.sh/helm/v3/pkg/registry"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
|
@ -45,7 +46,7 @@ import (
|
||||||
helper "github.com/fluxcd/pkg/runtime/controller"
|
helper "github.com/fluxcd/pkg/runtime/controller"
|
||||||
"github.com/fluxcd/pkg/runtime/patch"
|
"github.com/fluxcd/pkg/runtime/patch"
|
||||||
"github.com/fluxcd/pkg/runtime/predicates"
|
"github.com/fluxcd/pkg/runtime/predicates"
|
||||||
"github.com/google/go-containerregistry/pkg/authn"
|
rreconcile "github.com/fluxcd/pkg/runtime/reconcile"
|
||||||
|
|
||||||
"github.com/fluxcd/source-controller/api/v1beta2"
|
"github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
|
|
@ -79,6 +80,8 @@ type HelmRepositoryOCIReconciler struct {
|
||||||
Getters helmgetter.Providers
|
Getters helmgetter.Providers
|
||||||
ControllerName string
|
ControllerName string
|
||||||
RegistryClientGenerator RegistryClientGeneratorFunc
|
RegistryClientGenerator RegistryClientGeneratorFunc
|
||||||
|
|
||||||
|
patchOptions []patch.Option
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegistryClientGeneratorFunc is a function that returns a registry client
|
// RegistryClientGeneratorFunc is a function that returns a registry client
|
||||||
|
|
@ -92,6 +95,8 @@ func (r *HelmRepositoryOCIReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *HelmRepositoryOCIReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, opts HelmRepositoryReconcilerOptions) error {
|
func (r *HelmRepositoryOCIReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, opts HelmRepositoryReconcilerOptions) error {
|
||||||
|
r.patchOptions = getPatchOptions(helmRepositoryOCIOwnedConditions, r.ControllerName)
|
||||||
|
|
||||||
return ctrl.NewControllerManagedBy(mgr).
|
return ctrl.NewControllerManagedBy(mgr).
|
||||||
For(&sourcev1.HelmRepository{}).
|
For(&sourcev1.HelmRepository{}).
|
||||||
WithEventFilter(
|
WithEventFilter(
|
||||||
|
|
@ -122,34 +127,26 @@ func (r *HelmRepositoryOCIReconciler) Reconcile(ctx context.Context, req ctrl.Re
|
||||||
r.RecordSuspend(ctx, obj, obj.Spec.Suspend)
|
r.RecordSuspend(ctx, obj, obj.Spec.Suspend)
|
||||||
|
|
||||||
// Initialize the patch helper with the current version of the object.
|
// Initialize the patch helper with the current version of the object.
|
||||||
patchHelper, err := patch.NewHelper(obj, r.Client)
|
serialPatcher := patch.NewSerialPatcher(obj, r.Client)
|
||||||
if err != nil {
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always attempt to patch the object after each reconciliation.
|
// Always attempt to patch the object after each reconciliation.
|
||||||
defer func() {
|
defer func() {
|
||||||
// Patch the object, prioritizing the conditions owned by the controller in
|
|
||||||
// case of any conflicts.
|
|
||||||
patchOpts := []patch.Option{
|
|
||||||
patch.WithOwnedConditions{
|
|
||||||
Conditions: helmRepositoryOCIOwnedConditions,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
patchOpts = append(patchOpts, patch.WithFieldOwner(r.ControllerName))
|
|
||||||
// If a reconcile annotation value is found, set it in the object status
|
// If a reconcile annotation value is found, set it in the object status
|
||||||
// as status.lastHandledReconcileAt.
|
// as status.lastHandledReconcileAt.
|
||||||
if v, ok := meta.ReconcileAnnotationValue(obj.GetAnnotations()); ok {
|
if v, ok := meta.ReconcileAnnotationValue(obj.GetAnnotations()); ok {
|
||||||
object.SetStatusLastHandledReconcileAt(obj, v)
|
object.SetStatusLastHandledReconcileAt(obj, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
patchOpts := []patch.Option{}
|
||||||
|
patchOpts = append(patchOpts, r.patchOptions...)
|
||||||
|
|
||||||
// Set status observed generation option if the object is stalled, or
|
// Set status observed generation option if the object is stalled, or
|
||||||
// if the object is ready.
|
// if the object is ready.
|
||||||
if conditions.IsStalled(obj) || conditions.IsReady(obj) {
|
if conditions.IsStalled(obj) || conditions.IsReady(obj) {
|
||||||
patchOpts = append(patchOpts, patch.WithStatusObservedGeneration{})
|
patchOpts = append(patchOpts, patch.WithStatusObservedGeneration{})
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = patchHelper.Patch(ctx, obj, patchOpts...); err != nil {
|
if err := serialPatcher.Patch(ctx, obj, patchOpts...); err != nil {
|
||||||
// Ignore patch error "not found" when the object is being deleted.
|
// Ignore patch error "not found" when the object is being deleted.
|
||||||
if !obj.GetDeletionTimestamp().IsZero() {
|
if !obj.GetDeletionTimestamp().IsZero() {
|
||||||
err = kerrors.FilterOut(err, func(e error) bool { return apierrors.IsNotFound(e) })
|
err = kerrors.FilterOut(err, func(e error) bool { return apierrors.IsNotFound(e) })
|
||||||
|
|
@ -188,7 +185,7 @@ func (r *HelmRepositoryOCIReconciler) Reconcile(ctx context.Context, req ctrl.Re
|
||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
result, retErr = r.reconcile(ctx, obj)
|
result, retErr = r.reconcile(ctx, serialPatcher, obj)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -198,7 +195,7 @@ func (r *HelmRepositoryOCIReconciler) Reconcile(ctx context.Context, req ctrl.Re
|
||||||
// status conditions and the returned results are evaluated in the deferred
|
// status conditions and the returned results are evaluated in the deferred
|
||||||
// block at the very end to summarize the conditions to be in a consistent
|
// block at the very end to summarize the conditions to be in a consistent
|
||||||
// state.
|
// state.
|
||||||
func (r *HelmRepositoryOCIReconciler) reconcile(ctx context.Context, obj *v1beta2.HelmRepository) (result ctrl.Result, retErr error) {
|
func (r *HelmRepositoryOCIReconciler) reconcile(ctx context.Context, sp *patch.SerialPatcher, obj *v1beta2.HelmRepository) (result ctrl.Result, retErr error) {
|
||||||
ctxTimeout, cancel := context.WithTimeout(ctx, obj.Spec.Timeout.Duration)
|
ctxTimeout, cancel := context.WithTimeout(ctx, obj.Spec.Timeout.Duration)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
|
@ -224,6 +221,15 @@ func (r *HelmRepositoryOCIReconciler) reconcile(ctx context.Context, obj *v1beta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Presence of reconciling means that the reconciliation didn't succeed.
|
||||||
|
// Set the Reconciling reason to ProgressingWithRetry to indicate a
|
||||||
|
// failure retry.
|
||||||
|
if conditions.IsReconciling(obj) {
|
||||||
|
reconciling := conditions.Get(obj, meta.ReconcilingCondition)
|
||||||
|
reconciling.Reason = meta.ProgressingWithRetryReason
|
||||||
|
conditions.Set(obj, reconciling)
|
||||||
|
}
|
||||||
|
|
||||||
// If it's still a successful reconciliation and it's not reconciling or
|
// If it's still a successful reconciliation and it's not reconciling or
|
||||||
// stalled, mark Ready=True.
|
// stalled, mark Ready=True.
|
||||||
if !conditions.IsReconciling(obj) && !conditions.IsStalled(obj) &&
|
if !conditions.IsReconciling(obj) && !conditions.IsStalled(obj) &&
|
||||||
|
|
@ -244,8 +250,27 @@ func (r *HelmRepositoryOCIReconciler) reconcile(ctx context.Context, obj *v1beta
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Set reconciling condition.
|
// Set reconciling condition.
|
||||||
if obj.Generation != obj.Status.ObservedGeneration {
|
rreconcile.ProgressiveStatus(false, obj, meta.ProgressingReason, "reconciliation in progress")
|
||||||
conditions.MarkReconciling(obj, "NewGeneration", "reconciling new object generation (%d)", obj.Generation)
|
|
||||||
|
var reconcileAtVal string
|
||||||
|
if v, ok := meta.ReconcileAnnotationValue(obj.GetAnnotations()); ok {
|
||||||
|
reconcileAtVal = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist reconciling if generation differs or reconciliation is requested.
|
||||||
|
switch {
|
||||||
|
case obj.Generation != obj.Status.ObservedGeneration:
|
||||||
|
rreconcile.ProgressiveStatus(false, obj, meta.ProgressingReason,
|
||||||
|
"processing object: new generation %d -> %d", obj.Status.ObservedGeneration, obj.Generation)
|
||||||
|
if err := sp.Patch(ctx, obj, r.patchOptions...); err != nil {
|
||||||
|
result, retErr = ctrl.Result{}, err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case reconcileAtVal != obj.Status.GetLastHandledReconcileRequest():
|
||||||
|
if err := sp.Patch(ctx, obj, r.patchOptions...); err != nil {
|
||||||
|
result, retErr = ctrl.Result{}, err
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that it's an OCI URL before continuing.
|
// Ensure that it's an OCI URL before continuing.
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,7 @@ func TestHelmRepositoryOCIReconciler_authStrategy(t *testing.T) {
|
||||||
password: "wrong-pass",
|
password: "wrong-pass",
|
||||||
},
|
},
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingWithRetryReason, "processing object: new generation"),
|
||||||
*conditions.FalseCondition(meta.ReadyCondition, sourcev1.AuthenticationFailedReason, "failed to login to registry"),
|
*conditions.FalseCondition(meta.ReadyCondition, sourcev1.AuthenticationFailedReason, "failed to login to registry"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -217,6 +218,7 @@ func TestHelmRepositoryOCIReconciler_authStrategy(t *testing.T) {
|
||||||
provider: "aws",
|
provider: "aws",
|
||||||
providerImg: "oci://123456789000.dkr.ecr.us-east-2.amazonaws.com/test",
|
providerImg: "oci://123456789000.dkr.ecr.us-east-2.amazonaws.com/test",
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingWithRetryReason, "processing object: new generation"),
|
||||||
*conditions.FalseCondition(meta.ReadyCondition, sourcev1.AuthenticationFailedReason, "failed to get credential from"),
|
*conditions.FalseCondition(meta.ReadyCondition, sourcev1.AuthenticationFailedReason, "failed to get credential from"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -249,6 +251,7 @@ func TestHelmRepositoryOCIReconciler_authStrategy(t *testing.T) {
|
||||||
obj := &sourcev1.HelmRepository{
|
obj := &sourcev1.HelmRepository{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
GenerateName: "auth-strategy-",
|
GenerateName: "auth-strategy-",
|
||||||
|
Generation: 1,
|
||||||
},
|
},
|
||||||
Spec: sourcev1.HelmRepositorySpec{
|
Spec: sourcev1.HelmRepositorySpec{
|
||||||
Interval: metav1.Duration{Duration: interval},
|
Interval: metav1.Duration{Duration: interval},
|
||||||
|
|
@ -293,12 +296,27 @@ func TestHelmRepositoryOCIReconciler_authStrategy(t *testing.T) {
|
||||||
EventRecorder: record.NewFakeRecorder(32),
|
EventRecorder: record.NewFakeRecorder(32),
|
||||||
Getters: testGetters,
|
Getters: testGetters,
|
||||||
RegistryClientGenerator: registry.ClientGenerator,
|
RegistryClientGenerator: registry.ClientGenerator,
|
||||||
|
patchOptions: getPatchOptions(helmRepositoryOCIOwnedConditions, "sc"),
|
||||||
}
|
}
|
||||||
|
|
||||||
got, err := r.reconcile(ctx, obj)
|
g.Expect(r.Client.Create(ctx, obj)).ToNot(HaveOccurred())
|
||||||
|
defer func() {
|
||||||
|
g.Expect(r.Client.Delete(ctx, obj)).ToNot(HaveOccurred())
|
||||||
|
}()
|
||||||
|
|
||||||
|
sp := patch.NewSerialPatcher(obj, r.Client)
|
||||||
|
|
||||||
|
got, err := r.reconcile(ctx, sp, obj)
|
||||||
g.Expect(err != nil).To(Equal(tt.wantErr))
|
g.Expect(err != nil).To(Equal(tt.wantErr))
|
||||||
g.Expect(got).To(Equal(tt.want))
|
g.Expect(got).To(Equal(tt.want))
|
||||||
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions(tt.assertConditions))
|
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions(tt.assertConditions))
|
||||||
|
|
||||||
|
// In-progress status condition validity.
|
||||||
|
checker := conditionscheck.NewInProgressChecker(r.Client)
|
||||||
|
// NOTE: Check the object directly as reconcile() doesn't apply the
|
||||||
|
// final patch, the object has unapplied changes.
|
||||||
|
checker.DisableFetch = true
|
||||||
|
checker.CheckErr(ctx, obj)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue