Skip to content

Commit 8f3abe8

Browse files
(chore): add catalog metrics
Signed-off-by: abhijith-darshan <abhijith.ravindra@sap.com> (chore): set secret missing and repo auth metrics Signed-off-by: abhijith-darshan <abhijith.ravindra@sap.com> (chore): set catalog ready metric Signed-off-by: abhijith-darshan <abhijith.ravindra@sap.com> (chore): add license header Signed-off-by: abhijith-darshan <abhijith.ravindra@sap.com> (chore): update catalog object metrics func name Signed-off-by: abhijith-darshan <abhijith.ravindra@sap.com> (chore): use simple if Signed-off-by: abhijith-darshan <abhijith.ravindra@sap.com> (chore): suppress unparam lint issue Signed-off-by: abhijith-darshan <abhijith.ravindra@sap.com>
1 parent a86c1d1 commit 8f3abe8

File tree

3 files changed

+163
-1
lines changed

3 files changed

+163
-1
lines changed

internal/controller/catalog/catalog_controller.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ func (r *CatalogReconciler) suspendArtifactGenerator(ctx context.Context, name,
214214

215215
func (r *CatalogReconciler) EnsureCreated(ctx context.Context, obj lifecycle.RuntimeObject) (ctrl.Result, lifecycle.ReconcileResult, error) {
216216
catalog := obj.(*greenhousev1alpha1.Catalog) //nolint:errcheck
217+
defer UpdateCatalogReadyMetric(catalog)
217218
catalog.SetUnknownCondition()
218219

219220
if len(catalog.Spec.Sources) == 0 {
@@ -375,6 +376,7 @@ func (r *CatalogReconciler) deleteOrphanedResource(ctx context.Context, obj clie
375376
return nil
376377
}
377378

379+
//nolint:unparam
378380
func (r *CatalogReconciler) verifyStatus(ctx context.Context, catalog *greenhousev1alpha1.Catalog, allErrors []catalogError) (ctrl.Result, lifecycle.ReconcileResult, error) {
379381
existingNotReady := catalog.Status.GetConditionByType(greenhousemetav1alpha1.ReadyCondition)
380382
if existingNotReady != nil && existingNotReady.Status == metav1.ConditionFalse {
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and Greenhouse contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package catalog
5+
6+
import (
7+
"github.com/prometheus/client_golang/prometheus"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
crmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
10+
11+
greenhouseapis "github.com/cloudoperators/greenhouse/api"
12+
greenhousemetav1alpha1 "github.com/cloudoperators/greenhouse/api/meta/v1alpha1"
13+
greenhousev1alpha1 "github.com/cloudoperators/greenhouse/api/v1alpha1"
14+
)
15+
16+
var (
17+
// ReadyGauge indicates whether the catalog is ready
18+
// A catalog is considered ready when all its sources and their resources are ready
19+
ReadyGauge = prometheus.NewGaugeVec(
20+
prometheus.GaugeOpts{
21+
Name: "greenhouse_catalog_ready",
22+
Help: "Indicates whether the catalog is ready (1 = ready, 0 = not ready)",
23+
},
24+
[]string{"catalog", "namespace", "owned_by"})
25+
26+
// MissingSecretGauge indicates whether a specific catalog source is missing its secret
27+
// This metric is per-source, allowing you to identify which repository is affected
28+
MissingSecretGauge = prometheus.NewGaugeVec(
29+
prometheus.GaugeOpts{
30+
Name: "greenhouse_catalog_missing_secret",
31+
Help: "Indicates whether a catalog source is missing its secret (1 = missing, 0 = present)",
32+
},
33+
[]string{"catalog", "namespace", "owned_by", "repository_url", "git_repo_name", "ref", "secret_name"})
34+
35+
// AuthErrorGauge indicates whether a catalog source has an authentication error
36+
// This metric is per-source, allowing you to identify which repository has auth issues
37+
AuthErrorGauge = prometheus.NewGaugeVec(
38+
prometheus.GaugeOpts{
39+
Name: "greenhouse_catalog_auth_error",
40+
Help: "Indicates whether a catalog source has an authentication error (1 = error, 0 = no error)",
41+
},
42+
[]string{"catalog", "namespace", "owned_by", "repository_url", "git_repo_name", "ref"})
43+
)
44+
45+
func init() {
46+
crmetrics.Registry.MustRegister(ReadyGauge)
47+
crmetrics.Registry.MustRegister(MissingSecretGauge)
48+
crmetrics.Registry.MustRegister(AuthErrorGauge)
49+
}
50+
51+
// UpdateCatalogReadyMetric updates the catalog ready metric based on the catalog's ready condition
52+
func UpdateCatalogReadyMetric(catalog *greenhousev1alpha1.Catalog) {
53+
catalogReadyLabels := prometheus.Labels{
54+
"catalog": catalog.Name,
55+
"namespace": catalog.Namespace,
56+
"owned_by": catalog.Labels[greenhouseapis.LabelKeyOwnedBy],
57+
}
58+
59+
// Check if the catalog has a Ready condition set to True
60+
readyCondition := catalog.Status.GetConditionByType(greenhousemetav1alpha1.ReadyCondition)
61+
if readyCondition != nil && readyCondition.Status == metav1.ConditionTrue {
62+
ReadyGauge.With(catalogReadyLabels).Set(1)
63+
} else {
64+
ReadyGauge.With(catalogReadyLabels).Set(0)
65+
}
66+
}
67+
68+
// UpdateCatalogMissingSecretMetric updates the missing secret metric for a specific catalog source
69+
func UpdateCatalogMissingSecretMetric(catalog *greenhousev1alpha1.Catalog, repositoryURL, gitRepoName, ref, secretName string, isMissing bool) {
70+
catalogLabels := prometheus.Labels{
71+
"catalog": catalog.Name,
72+
"namespace": catalog.Namespace,
73+
"owned_by": catalog.Labels[greenhouseapis.LabelKeyOwnedBy],
74+
"repository_url": repositoryURL,
75+
"git_repo_name": gitRepoName,
76+
"ref": ref,
77+
"secret_name": secretName,
78+
}
79+
if isMissing {
80+
MissingSecretGauge.With(catalogLabels).Set(1)
81+
} else {
82+
MissingSecretGauge.With(catalogLabels).Set(0)
83+
}
84+
}
85+
86+
// UpdateCatalogAuthErrorMetric updates the authentication error metric for a specific catalog source
87+
func UpdateCatalogAuthErrorMetric(catalog *greenhousev1alpha1.Catalog, repositoryURL, gitRepoName, ref string, hasError bool) {
88+
catalogLabels := prometheus.Labels{
89+
"catalog": catalog.Name,
90+
"namespace": catalog.Namespace,
91+
"owned_by": catalog.Labels[greenhouseapis.LabelKeyOwnedBy],
92+
"repository_url": repositoryURL,
93+
"git_repo_name": gitRepoName,
94+
"ref": ref,
95+
}
96+
if hasError {
97+
AuthErrorGauge.With(catalogLabels).Set(1)
98+
} else {
99+
AuthErrorGauge.With(catalogLabels).Set(0)
100+
}
101+
}

internal/controller/catalog/source.go

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
sourcev2 "github.com/fluxcd/source-watcher/api/v2/v1beta1"
1919
"github.com/go-logr/logr"
2020
corev1 "k8s.io/api/core/v1"
21+
apierrors "k8s.io/apimachinery/pkg/api/errors"
2122
"k8s.io/apimachinery/pkg/api/meta"
2223
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2324
"k8s.io/apimachinery/pkg/runtime"
@@ -191,8 +192,28 @@ func (s *source) getSourceSecret(ctx context.Context) (*corev1.Secret, error) {
191192
secret.SetNamespace(s.catalog.Namespace)
192193
err := s.Get(ctx, client.ObjectKeyFromObject(secret), secret)
193194
if err != nil {
195+
// Track missing secret in metrics if it's a NotFound error
196+
if apierrors.IsNotFound(err) {
197+
UpdateCatalogMissingSecretMetric(
198+
s.catalog,
199+
s.source.Repository,
200+
s.getGitRepoName(),
201+
s.source.GetRefValue(),
202+
*s.source.SecretName,
203+
true,
204+
)
205+
}
194206
return nil, fmt.Errorf("failed to get secret %s/%s: %w", s.catalog.Namespace, *s.source.SecretName, err)
195207
}
208+
// Secret found, update metric to show it's present
209+
UpdateCatalogMissingSecretMetric(
210+
s.catalog,
211+
s.source.Repository,
212+
s.getGitRepoName(),
213+
s.source.GetRefValue(),
214+
*s.source.SecretName,
215+
false,
216+
)
196217
return secret, nil
197218
}
198219

@@ -544,7 +565,6 @@ func (s *source) objectReadiness(ctx context.Context, obj client.Object) (ready
544565
msg = err.Error()
545566
return
546567
}
547-
548568
conditions := cObj.GetConditions()
549569
readyCondition := meta.FindStatusCondition(conditions, fluxmeta.ReadyCondition)
550570

@@ -555,5 +575,44 @@ func (s *source) objectReadiness(ctx context.Context, obj client.Object) (ready
555575
}
556576
ready = readyCondition.Status
557577
msg = readyCondition.Message
578+
579+
// Update metrics based on object conditions
580+
s.updateCatalogObjectMetrics(cObj)
558581
return
559582
}
583+
584+
// updateCatalogObjectMetrics updates metrics based on the object's conditions
585+
// This is a helper method to track specific error conditions for different Flux resource types
586+
func (s *source) updateCatalogObjectMetrics(obj lifecycle.CatalogObject) {
587+
kind := obj.GetObjectKind().GroupVersionKind().Kind
588+
conditions := obj.GetConditions()
589+
590+
// TODO: use switch for future object type metrics
591+
if kind == sourcev1.GitRepositoryKind {
592+
// Check for authentication errors in GitRepository
593+
s.checkGitRepositoryAuthError(conditions)
594+
}
595+
}
596+
597+
// checkGitRepositoryAuthError checks if a GitRepository has an authentication error
598+
// and updates the authentication error metric accordingly
599+
func (s *source) checkGitRepositoryAuthError(conditions []metav1.Condition) {
600+
hasAuthError := false
601+
602+
// Check for FetchFailed condition with AuthenticationFailed reason
603+
fetchFailedCondition := meta.FindStatusCondition(conditions, sourcev1.FetchFailedCondition)
604+
if fetchFailedCondition != nil &&
605+
fetchFailedCondition.Status == metav1.ConditionTrue &&
606+
fetchFailedCondition.Reason == sourcev1.AuthenticationFailedReason {
607+
hasAuthError = true
608+
}
609+
610+
// Update the authentication error metric
611+
UpdateCatalogAuthErrorMetric(
612+
s.catalog,
613+
s.source.Repository,
614+
s.getGitRepoName(),
615+
s.source.GetRefValue(),
616+
hasAuthError,
617+
)
618+
}

0 commit comments

Comments
 (0)