@@ -45,7 +45,8 @@ import (
4545 "sigs.k8s.io/controller-runtime/pkg/controller"
4646
4747 "github.com/fluxcd/pkg/apis/meta"
48- "github.com/fluxcd/pkg/recorder"
48+ "github.com/fluxcd/pkg/runtime/events"
49+ "github.com/fluxcd/pkg/runtime/metrics"
4950 "github.com/fluxcd/pkg/runtime/predicates"
5051 sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
5152
@@ -62,7 +63,8 @@ type HelmReleaseReconciler struct {
6263 Scheme * runtime.Scheme
6364 requeueDependency time.Duration
6465 EventRecorder kuberecorder.EventRecorder
65- ExternalEventRecorder * recorder.EventRecorder
66+ ExternalEventRecorder * events.Recorder
67+ MetricsRecorder * metrics.Recorder
6668}
6769
6870// ConditionError represents an error with a status condition reason attached.
@@ -107,17 +109,18 @@ func (r *HelmReleaseReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error)
107109 if containsString (hr .ObjectMeta .Finalizers , v2 .HelmReleaseFinalizer ) {
108110 // Our finalizer is still present, so lets handle garbage collection
109111 if err := r .garbageCollect (ctx , log , hr ); err != nil {
110- r .event (hr , hr .Status .LastAttemptedRevision , recorder .EventSeverityError , err .Error ())
112+ r .event (hr , hr .Status .LastAttemptedRevision , events .EventSeverityError , err .Error ())
111113 // Return the error so we retry the failed garbage collection
112114 return ctrl.Result {}, err
113115 }
114-
116+ // Record deleted status
117+ r .recordReadiness (hr , true )
115118 // Remove our finalizer from the list and update it
116119 hr .ObjectMeta .Finalizers = removeString (hr .ObjectMeta .Finalizers , v2 .HelmReleaseFinalizer )
117120 if err := r .Update (ctx , & hr ); err != nil {
118121 return ctrl.Result {}, err
119122 }
120- r .event (hr , hr .Status .LastAttemptedRevision , recorder .EventSeverityInfo , "Helm uninstall for deleted resource succeeded" )
123+ r .event (hr , hr .Status .LastAttemptedRevision , events .EventSeverityInfo , "Helm uninstall for deleted resource succeeded" )
121124 }
122125 // Stop reconciliation as the object is being deleted
123126 return ctrl.Result {}, nil
@@ -131,6 +134,9 @@ func (r *HelmReleaseReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error)
131134 return ctrl.Result {Requeue : true }, updateStatusErr
132135 }
133136
137+ // Record ready status
138+ r .recordReadiness (hr , false )
139+
134140 // Log reconciliation duration
135141 durationMsg := fmt .Sprintf ("reconcilation finished in %s" , time .Now ().Sub (start ).String ())
136142 if result .RequeueAfter > 0 {
@@ -142,6 +148,7 @@ func (r *HelmReleaseReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error)
142148}
143149
144150func (r * HelmReleaseReconciler ) reconcile (ctx context.Context , log logr.Logger , hr v2.HelmRelease ) (v2.HelmRelease , ctrl.Result , error ) {
151+ reconcileStart := time .Now ()
145152 // Record the value of the reconciliation request, if any
146153 if v , ok := hr .GetAnnotations ()[meta .ReconcileAtAnnotation ]; ok {
147154 hr .Status .LastHandledReconcileAt = v
@@ -155,6 +162,8 @@ func (r *HelmReleaseReconciler) reconcile(ctx context.Context, log logr.Logger,
155162 log .Error (updateStatusErr , "unable to update status after generation update" )
156163 return hr , ctrl.Result {Requeue : true }, updateStatusErr
157164 }
165+ // Record progressing status
166+ r .recordReadiness (hr , false )
158167 }
159168
160169 if hr .Spec .Suspend {
@@ -163,24 +172,33 @@ func (r *HelmReleaseReconciler) reconcile(ctx context.Context, log logr.Logger,
163172 return v2 .HelmReleaseNotReady (hr , meta .SuspendedReason , msg ), ctrl.Result {}, nil
164173 }
165174
175+ // record reconciliation duration
176+ if r .MetricsRecorder != nil {
177+ objRef , err := reference .GetReference (r .Scheme , & hr )
178+ if err != nil {
179+ return hr , ctrl.Result {Requeue : true }, err
180+ }
181+ defer r .MetricsRecorder .RecordDuration (* objRef , reconcileStart )
182+ }
183+
166184 // Reconcile chart based on the HelmChartTemplate
167185 hc , ok , reconcileErr := r .reconcileChart (ctx , & hr )
168186 if ! ok {
169187 var msg string
170188 if reconcileErr != nil {
171189 msg = fmt .Sprintf ("chart reconciliation failed: %s" , reconcileErr .Error ())
172- r .event (hr , hr .Status .LastAttemptedRevision , recorder .EventSeverityError , msg )
190+ r .event (hr , hr .Status .LastAttemptedRevision , events .EventSeverityError , msg )
173191 } else {
174192 msg = "HelmChart is not ready"
175- r .event (hr , hr .Status .LastAttemptedRevision , recorder .EventSeverityInfo , msg )
193+ r .event (hr , hr .Status .LastAttemptedRevision , events .EventSeverityInfo , msg )
176194 }
177195 return v2 .HelmReleaseNotReady (hr , v2 .ArtifactFailedReason , msg ), ctrl.Result {}, reconcileErr
178196 }
179197
180198 // Check chart artifact readiness
181199 if hc .GetArtifact () == nil {
182200 msg := "HelmChart is not ready"
183- r .event (hr , hr .Status .LastAttemptedRevision , recorder .EventSeverityInfo , msg )
201+ r .event (hr , hr .Status .LastAttemptedRevision , events .EventSeverityInfo , msg )
184202 log .Info (msg )
185203 return v2 .HelmReleaseNotReady (hr , v2 .ArtifactFailedReason , msg ), ctrl.Result {}, nil
186204 }
@@ -190,7 +208,7 @@ func (r *HelmReleaseReconciler) reconcile(ctx context.Context, log logr.Logger,
190208 if err := r .checkDependencies (hr ); err != nil {
191209 msg := fmt .Sprintf ("dependencies do not meet ready condition (%s), retrying in %s" ,
192210 err .Error (), r .requeueDependency .String ())
193- r .event (hr , hc .GetArtifact ().Revision , recorder .EventSeverityInfo , msg )
211+ r .event (hr , hc .GetArtifact ().Revision , events .EventSeverityInfo , msg )
194212 log .Info (msg )
195213
196214 // Exponential backoff would cause execution to be prolonged too much,
@@ -204,21 +222,21 @@ func (r *HelmReleaseReconciler) reconcile(ctx context.Context, log logr.Logger,
204222 // Compose values
205223 values , err := r .composeValues (ctx , hr )
206224 if err != nil {
207- r .event (hr , hr .Status .LastAttemptedRevision , recorder .EventSeverityError , err .Error ())
225+ r .event (hr , hr .Status .LastAttemptedRevision , events .EventSeverityError , err .Error ())
208226 return v2 .HelmReleaseNotReady (hr , v2 .InitFailedReason , err .Error ()), ctrl.Result {}, nil
209227 }
210228
211229 // Load chart from artifact
212230 chart , err := r .loadHelmChart (hc )
213231 if err != nil {
214- r .event (hr , hr .Status .LastAttemptedRevision , recorder .EventSeverityError , err .Error ())
232+ r .event (hr , hr .Status .LastAttemptedRevision , events .EventSeverityError , err .Error ())
215233 return v2 .HelmReleaseNotReady (hr , v2 .ArtifactFailedReason , err .Error ()), ctrl.Result {}, nil
216234 }
217235
218236 // Reconcile Helm release
219237 reconciledHr , reconcileErr := r .reconcileRelease (ctx , log , * hr .DeepCopy (), chart , values )
220238 if reconcileErr != nil {
221- r .event (hr , hc .GetArtifact ().Revision , recorder .EventSeverityError ,
239+ r .event (hr , hc .GetArtifact ().Revision , events .EventSeverityError ,
222240 fmt .Sprintf ("reconciliation failed: %s" , reconcileErr .Error ()))
223241 }
224242 return reconciledHr , ctrl.Result {RequeueAfter : hr .Spec .Interval .Duration }, reconcileErr
@@ -303,6 +321,8 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, log logr.L
303321 log .Error (updateStatusErr , "unable to update status after state update" )
304322 return hr , updateStatusErr
305323 }
324+ // Record progressing status
325+ r .recordReadiness (hr , false )
306326 }
307327
308328 // Check status of any previous release attempt.
@@ -629,12 +649,12 @@ func (r *HelmReleaseReconciler) handleHelmActionResult(hr *v2.HelmRelease, revis
629649 if err != nil {
630650 msg := fmt .Sprintf ("Helm %s failed: %s" , action , err .Error ())
631651 v2 .SetHelmReleaseCondition (hr , condition , corev1 .ConditionFalse , failedReason , msg )
632- r .event (* hr , revision , recorder .EventSeverityError , msg )
652+ r .event (* hr , revision , events .EventSeverityError , msg )
633653 return & ConditionError {Reason : failedReason , Err : errors .New (msg )}
634654 } else {
635655 msg := fmt .Sprintf ("Helm %s succeeded" , action )
636656 v2 .SetHelmReleaseCondition (hr , condition , corev1 .ConditionTrue , succeededReason , msg )
637- r .event (* hr , revision , recorder .EventSeverityInfo , msg )
657+ r .event (* hr , revision , events .EventSeverityInfo , msg )
638658 return nil
639659 }
640660}
@@ -666,6 +686,29 @@ func (r *HelmReleaseReconciler) event(hr v2.HelmRelease, revision, severity, msg
666686 }
667687}
668688
689+ func (r * HelmReleaseReconciler ) recordReadiness (hr v2.HelmRelease , deleted bool ) {
690+ if r .MetricsRecorder == nil {
691+ return
692+ }
693+
694+ objRef , err := reference .GetReference (r .Scheme , & hr )
695+ if err != nil {
696+ r .Log .WithValues (
697+ strings .ToLower (hr .Kind ),
698+ fmt .Sprintf ("%s/%s" , hr .GetNamespace (), hr .GetName ()),
699+ ).Error (err , "unable to record readiness metric" )
700+ return
701+ }
702+ if rc := meta .GetCondition (hr .Status .Conditions , meta .ReadyCondition ); rc != nil {
703+ r .MetricsRecorder .RecordCondition (* objRef , * rc , deleted )
704+ } else {
705+ r .MetricsRecorder .RecordCondition (* objRef , meta.Condition {
706+ Type : meta .ReadyCondition ,
707+ Status : corev1 .ConditionUnknown ,
708+ }, deleted )
709+ }
710+ }
711+
669712func helmChartFromTemplate (hr v2.HelmRelease ) * sourcev1.HelmChart {
670713 template := hr .Spec .Chart
671714 return & sourcev1.HelmChart {
0 commit comments