diff --git a/controllers/consoleplugin/consoleplugin_reconciler.go b/controllers/consoleplugin/consoleplugin_reconciler.go index ea5ea9e2d..6cad122c5 100644 --- a/controllers/consoleplugin/consoleplugin_reconciler.go +++ b/controllers/consoleplugin/consoleplugin_reconciler.go @@ -64,18 +64,9 @@ func NewReconciler(cl reconcilers.ClientHelper, ns, prevNS, imageName string, av return CPReconciler{ClientHelper: cl, nobjMngr: nobjMngr, owned: owned, image: imageName, availableAPIs: availableAPIs} } -// InitStaticResources inits some "static" / one-shot resources, usually not subject to reconciliation -func (r *CPReconciler) InitStaticResources(ctx context.Context) error { - cr := buildClusterRole() - return r.ReconcileClusterRole(ctx, cr) -} - -// PrepareNamespaceChange cleans up old namespace and restore the relevant "static" resources -func (r *CPReconciler) PrepareNamespaceChange(ctx context.Context) error { - // Switching namespace => delete everything in the previous namespace +// CleanupNamespace cleans up old namespace +func (r *CPReconciler) CleanupNamespace(ctx context.Context) { r.nobjMngr.CleanupPreviousNamespace(ctx) - cr := buildClusterRole() - return r.ReconcileClusterRole(ctx, cr) } // Reconcile is the reconciler entry point to reconcile the current plugin state with the desired configuration @@ -147,6 +138,11 @@ func (r *CPReconciler) reconcilePermissions(ctx context.Context, builder *builde return r.CreateOwned(ctx, builder.serviceAccount()) } // update not needed for now + cr := buildClusterRole() + if err := r.ReconcileClusterRole(ctx, cr); err != nil { + return err + } + desired := builder.clusterRoleBinding() if err := r.ReconcileClusterRoleBinding(ctx, desired); err != nil { return err @@ -227,18 +223,18 @@ func (r *CPReconciler) reconcileService(ctx context.Context, builder builder, de if err := r.CreateOwned(ctx, newSVC); err != nil { return err } - if r.availableAPIs.HasSvcMonitor() { - serviceMonitor := builder.serviceMonitor() - if err := r.CreateOwned(ctx, serviceMonitor); err != nil { - return err - } - } } else if serviceNeedsUpdate(r.owned.service, &desired.ConsolePlugin, &report) { newSVC := builder.service(r.owned.service) if err := r.UpdateOwned(ctx, r.owned.service, newSVC); err != nil { return err } } + if r.availableAPIs.HasSvcMonitor() { + serviceMonitor := builder.serviceMonitor() + if err := reconcilers.GenericReconcile(ctx, r.nobjMngr, &r.ClientHelper, r.owned.serviceMonitor, serviceMonitor, &report, helper.ServiceMonitorChanged); err != nil { + return err + } + } return nil } diff --git a/controllers/flowcollector_controller.go b/controllers/flowcollector_controller.go index 02ebeb392..e843630ad 100644 --- a/controllers/flowcollector_controller.go +++ b/controllers/flowcollector_controller.go @@ -202,31 +202,12 @@ func (r *FlowCollectorReconciler) handleNamespaceChanged( cpReconciler *consoleplugin.CPReconciler, ) error { log := log.FromContext(ctx) - if oldNS == "" { - // First install: create one-shot resources - log.Info("FlowCollector first install: creating initial resources") - err := flpReconciler.InitStaticResources(ctx) - if err != nil { - return err - } - if r.availableAPIs.HasConsolePlugin() { - err := cpReconciler.InitStaticResources(ctx) - if err != nil { - return err - } - } - } else { + if oldNS != "" { // Namespace updated, clean up previous namespace - log.Info("FlowCollector namespace change detected: cleaning up previous namespace and preparing next one", "old namespace", oldNS, "new namepace", newNS) - err := flpReconciler.PrepareNamespaceChange(ctx) - if err != nil { - return err - } + log.Info("FlowCollector namespace change detected: cleaning up previous namespace", "old namespace", oldNS, "new namepace", newNS) + flpReconciler.CleanupNamespace(ctx) if r.availableAPIs.HasConsolePlugin() { - err := cpReconciler.PrepareNamespaceChange(ctx) - if err != nil { - return err - } + cpReconciler.CleanupNamespace(ctx) } } diff --git a/controllers/flowcollector_controller_console_test.go b/controllers/flowcollector_controller_console_test.go index 37dee2e12..3387668dd 100644 --- a/controllers/flowcollector_controller_console_test.go +++ b/controllers/flowcollector_controller_console_test.go @@ -6,6 +6,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" operatorsv1 "github.com/openshift/api/operator/v1" + monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" appsv1 "k8s.io/api/apps/v1" ascv2 "k8s.io/api/autoscaling/v2" v1 "k8s.io/api/core/v1" @@ -172,6 +173,36 @@ func flowCollectorConsolePluginSpecs() { return svc.Spec.Ports[0].Port }, timeout, interval).Should(Equal(int32(9099))) }) + + It("Should create desired objects when they're not found (e.g. case of an operator upgrade)", func() { + sm := monitoringv1.ServiceMonitor{} + + By("Expecting ServiceMonitor to exist") + Eventually(func() interface{} { + return k8sClient.Get(ctx, types.NamespacedName{ + Name: "netobserv-plugin", + Namespace: cpNamespace, + }, &sm) + }, timeout, interval).Should(Succeed()) + + // Manually delete ServiceMonitor + By("Deleting ServiceMonitor") + Eventually(func() error { + return k8sClient.Delete(ctx, &sm) + }, timeout, interval).Should(Succeed()) + + // Do a dummy change that will trigger reconcile, and make sure SM is created again + UpdateCR(crKey, func(fc *flowslatest.FlowCollector) { + fc.Spec.Processor.LogLevel = "info" + }) + By("Expecting ServiceMonitor to exist") + Eventually(func() interface{} { + return k8sClient.Get(ctx, types.NamespacedName{ + Name: "netobserv-plugin", + Namespace: cpNamespace, + }, &sm) + }, timeout, interval).Should(Succeed()) + }) }) Context("Configuring the Loki URL", func() { diff --git a/controllers/flowcollector_controller_test.go b/controllers/flowcollector_controller_test.go index 81e7261bf..450088b6d 100644 --- a/controllers/flowcollector_controller_test.go +++ b/controllers/flowcollector_controller_test.go @@ -7,6 +7,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" appsv1 "k8s.io/api/apps/v1" ascv2 "k8s.io/api/autoscaling/v2" v1 "k8s.io/api/core/v1" @@ -339,6 +340,71 @@ func flowCollectorControllerSpecs() { return ofc.Data["sampling"] }, timeout, interval).Should(Equal("1")) }) + + It("Should create desired objects when they're not found (e.g. case of an operator upgrade)", func() { + psvc := v1.Service{} + sm := monitoringv1.ServiceMonitor{} + pr := monitoringv1.PrometheusRule{} + By("Expecting prometheus service to exist") + Eventually(func() interface{} { + return k8sClient.Get(ctx, types.NamespacedName{ + Name: "flowlogs-pipeline-prom", + Namespace: operatorNamespace, + }, &psvc) + }, timeout, interval).Should(Succeed()) + + By("Expecting ServiceMonitor to exist") + Eventually(func() interface{} { + return k8sClient.Get(ctx, types.NamespacedName{ + Name: "flowlogs-pipeline-monitor", + Namespace: operatorNamespace, + }, &sm) + }, timeout, interval).Should(Succeed()) + + By("Expecting PrometheusRule to exist") + Eventually(func() interface{} { + return k8sClient.Get(ctx, types.NamespacedName{ + Name: "flowlogs-pipeline-alert", + Namespace: operatorNamespace, + }, &pr) + }, timeout, interval).Should(Succeed()) + + // Manually delete ServiceMonitor + By("Deleting ServiceMonitor") + Eventually(func() error { + return k8sClient.Delete(ctx, &sm) + }, timeout, interval).Should(Succeed()) + + // Do a dummy change that will trigger reconcile, and make sure SM is created again + UpdateCR(crKey, func(fc *flowslatest.FlowCollector) { + fc.Spec.Processor.LogLevel = "info" + }) + By("Expecting ServiceMonitor to exist") + Eventually(func() interface{} { + return k8sClient.Get(ctx, types.NamespacedName{ + Name: "flowlogs-pipeline-monitor", + Namespace: operatorNamespace, + }, &sm) + }, timeout, interval).Should(Succeed()) + + // Manually delete Rule + By("Deleting prom rule") + Eventually(func() error { + return k8sClient.Delete(ctx, &pr) + }, timeout, interval).Should(Succeed()) + + // Do a dummy change that will trigger reconcile, and make sure Rule is created again + UpdateCR(crKey, func(fc *flowslatest.FlowCollector) { + fc.Spec.Processor.LogLevel = "debug" + }) + By("Expecting PrometheusRule to exist") + Eventually(func() interface{} { + return k8sClient.Get(ctx, types.NamespacedName{ + Name: "flowlogs-pipeline-alert", + Namespace: operatorNamespace, + }, &pr) + }, timeout, interval).Should(Succeed()) + }) }) Context("With Kafka", func() { @@ -739,7 +805,7 @@ func UpdateCR(key types.NamespacedName, updater func(*flowslatest.FlowCollector) Eventually(func() error { updater(cr) return k8sClient.Update(ctx, cr) - }).Should(Succeed()) + }, timeout, interval).Should(Succeed()) } func checkDigestUpdate(oldDigest *string, annots map[string]string) error { diff --git a/controllers/flowlogspipeline/flp_ingest_reconciler.go b/controllers/flowlogspipeline/flp_ingest_reconciler.go index 795663021..d807ca2c0 100644 --- a/controllers/flowlogspipeline/flp_ingest_reconciler.go +++ b/controllers/flowlogspipeline/flp_ingest_reconciler.go @@ -12,6 +12,7 @@ import ( flowslatest "github.com/netobserv/network-observability-operator/api/v1beta1" "github.com/netobserv/network-observability-operator/controllers/constants" + "github.com/netobserv/network-observability-operator/controllers/reconcilers" "github.com/netobserv/network-observability-operator/pkg/helper" ) @@ -50,6 +51,8 @@ func newIngesterReconciler(info *reconcilersCommonInfo) *flpIngesterReconciler { info.nobjMngr.AddManagedObject(configMapName(ConfKafkaIngester), owned.configMap) if info.availableAPIs.HasSvcMonitor() { info.nobjMngr.AddManagedObject(serviceMonitorName(ConfKafkaIngester), owned.serviceMonitor) + } + if info.availableAPIs.HasPromRule() { info.nobjMngr.AddManagedObject(prometheusRuleName(ConfKafkaIngester), owned.prometheusRule) } @@ -64,18 +67,9 @@ func (r *flpIngesterReconciler) context(ctx context.Context) context.Context { return log.IntoContext(ctx, l) } -// initStaticResources inits some "static" / one-shot resources, usually not subject to reconciliation -func (r *flpIngesterReconciler) initStaticResources(ctx context.Context) error { - cr := buildClusterRoleIngester(r.useOpenShiftSCC) - return r.ReconcileClusterRole(ctx, cr) -} - -// PrepareNamespaceChange cleans up old namespace and restore the relevant "static" resources -func (r *flpIngesterReconciler) prepareNamespaceChange(ctx context.Context) error { - // Switching namespace => delete everything in the previous namespace +// cleanupNamespace cleans up old namespace +func (r *flpIngesterReconciler) cleanupNamespace(ctx context.Context) { r.nobjMngr.CleanupPreviousNamespace(ctx) - cr := buildClusterRoleIngester(r.useOpenShiftSCC) - return r.ReconcileClusterRole(ctx, cr) } func (r *flpIngesterReconciler) reconcile(ctx context.Context, desired *flowslatest.FlowCollector) error { @@ -126,34 +120,24 @@ func (r *flpIngesterReconciler) reconcilePrometheusService(ctx context.Context, if err := r.CreateOwned(ctx, builder.newPromService()); err != nil { return err } - if r.availableAPIs.HasSvcMonitor() { - if err := r.CreateOwned(ctx, builder.generic.serviceMonitor()); err != nil { - return err - } - if err := r.CreateOwned(ctx, builder.generic.prometheusRule()); err != nil { + } else { + newSVC := builder.fromPromService(r.owned.promService) + if helper.ServiceChanged(r.owned.promService, newSVC, &report) { + if err := r.UpdateOwned(ctx, r.owned.promService, newSVC); err != nil { return err } } - return nil } - newSVC := builder.fromPromService(r.owned.promService) - if helper.ServiceChanged(r.owned.promService, newSVC, &report) { - if err := r.UpdateOwned(ctx, r.owned.promService, newSVC); err != nil { + if r.availableAPIs.HasSvcMonitor() { + serviceMonitor := builder.generic.serviceMonitor() + if err := reconcilers.GenericReconcile(ctx, r.nobjMngr, &r.ClientHelper, r.owned.serviceMonitor, serviceMonitor, &report, helper.ServiceMonitorChanged); err != nil { return err } } - if r.availableAPIs.HasSvcMonitor() { - newMonitorSvc := builder.generic.serviceMonitor() - if helper.ServiceMonitorChanged(r.owned.serviceMonitor, newMonitorSvc) { - if err := r.UpdateOwned(ctx, r.owned.serviceMonitor, newMonitorSvc); err != nil { - return err - } - } - newPromRules := builder.generic.prometheusRule() - if helper.PrometheusRuleChanged(r.owned.prometheusRule, newPromRules) { - if err := r.UpdateOwned(ctx, r.owned.prometheusRule, newPromRules); err != nil { - return err - } + if r.availableAPIs.HasPromRule() { + promRules := builder.generic.prometheusRule() + if err := reconcilers.GenericReconcile(ctx, r.nobjMngr, &r.ClientHelper, r.owned.prometheusRule, promRules, &report, helper.PrometheusRuleChanged); err != nil { + return err } } return nil @@ -183,6 +167,11 @@ func (r *flpIngesterReconciler) reconcilePermissions(ctx context.Context, builde return r.CreateOwned(ctx, builder.serviceAccount()) } // We only configure name, update is not needed for now + cr := buildClusterRoleIngester(r.useOpenShiftSCC) + if err := r.ReconcileClusterRole(ctx, cr); err != nil { + return err + } + desired := builder.clusterRoleBinding() if err := r.ClientHelper.ReconcileClusterRoleBinding(ctx, desired); err != nil { return err diff --git a/controllers/flowlogspipeline/flp_monolith_reconciler.go b/controllers/flowlogspipeline/flp_monolith_reconciler.go index 4539007fe..1ab7d2280 100644 --- a/controllers/flowlogspipeline/flp_monolith_reconciler.go +++ b/controllers/flowlogspipeline/flp_monolith_reconciler.go @@ -12,6 +12,7 @@ import ( flowslatest "github.com/netobserv/network-observability-operator/api/v1beta1" "github.com/netobserv/network-observability-operator/controllers/constants" + "github.com/netobserv/network-observability-operator/controllers/reconcilers" "github.com/netobserv/network-observability-operator/pkg/helper" ) @@ -53,6 +54,8 @@ func newMonolithReconciler(info *reconcilersCommonInfo) *flpMonolithReconciler { info.nobjMngr.AddManagedObject(configMapName(ConfMonolith), owned.configMap) if info.availableAPIs.HasSvcMonitor() { info.nobjMngr.AddManagedObject(serviceMonitorName(ConfMonolith), owned.serviceMonitor) + } + if info.availableAPIs.HasPromRule() { info.nobjMngr.AddManagedObject(prometheusRuleName(ConfMonolith), owned.prometheusRule) } @@ -67,17 +70,9 @@ func (r *flpMonolithReconciler) context(ctx context.Context) context.Context { return log.IntoContext(ctx, l) } -// initStaticResources inits some "static" / one-shot resources, usually not subject to reconciliation -func (r *flpMonolithReconciler) initStaticResources(ctx context.Context) error { - // Nothing to do here: monolith FLP uses cluster roles defined by Transformer and Ingester reconcilers - return nil -} - -// prepareNamespaceChange cleans up old namespace and restore the relevant "static" resources -func (r *flpMonolithReconciler) prepareNamespaceChange(ctx context.Context) error { - // Switching namespace => delete everything in the previous namespace +// cleanupNamespace cleans up old namespace +func (r *flpMonolithReconciler) cleanupNamespace(ctx context.Context) { r.nobjMngr.CleanupPreviousNamespace(ctx) - return nil } func (r *flpMonolithReconciler) reconcile(ctx context.Context, desired *flowslatest.FlowCollector) error { @@ -133,34 +128,24 @@ func (r *flpMonolithReconciler) reconcilePrometheusService(ctx context.Context, if err := r.CreateOwned(ctx, builder.newPromService()); err != nil { return err } - if r.availableAPIs.HasSvcMonitor() { - if err := r.CreateOwned(ctx, builder.generic.serviceMonitor()); err != nil { - return err - } - if err := r.CreateOwned(ctx, builder.generic.prometheusRule()); err != nil { + } else { + newSVC := builder.fromPromService(r.owned.promService) + if helper.ServiceChanged(r.owned.promService, newSVC, &report) { + if err := r.UpdateOwned(ctx, r.owned.promService, newSVC); err != nil { return err } } - return nil } - newSVC := builder.fromPromService(r.owned.promService) - if helper.ServiceChanged(r.owned.promService, newSVC, &report) { - if err := r.UpdateOwned(ctx, r.owned.promService, newSVC); err != nil { + if r.availableAPIs.HasSvcMonitor() { + serviceMonitor := builder.generic.serviceMonitor() + if err := reconcilers.GenericReconcile(ctx, r.nobjMngr, &r.ClientHelper, r.owned.serviceMonitor, serviceMonitor, &report, helper.ServiceMonitorChanged); err != nil { return err } } - if r.availableAPIs.HasSvcMonitor() { - newMonitorSvc := builder.generic.serviceMonitor() - if helper.ServiceMonitorChanged(r.owned.serviceMonitor, newMonitorSvc) { - if err := r.UpdateOwned(ctx, r.owned.serviceMonitor, newMonitorSvc); err != nil { - return err - } - } - newPromRules := builder.generic.prometheusRule() - if helper.PrometheusRuleChanged(r.owned.prometheusRule, newPromRules) { - if err := r.UpdateOwned(ctx, r.owned.prometheusRule, newPromRules); err != nil { - return err - } + if r.availableAPIs.HasPromRule() { + promRules := builder.generic.prometheusRule() + if err := reconcilers.GenericReconcile(ctx, r.nobjMngr, &r.ClientHelper, r.owned.prometheusRule, promRules, &report, helper.PrometheusRuleChanged); err != nil { + return err } } return nil @@ -190,6 +175,14 @@ func (r *flpMonolithReconciler) reconcilePermissions(ctx context.Context, builde return r.CreateOwned(ctx, builder.serviceAccount()) } // We only configure name, update is not needed for now + cr := buildClusterRoleIngester(r.useOpenShiftSCC) + if err := r.ReconcileClusterRole(ctx, cr); err != nil { + return err + } + cr = buildClusterRoleTransformer() + if err := r.ReconcileClusterRole(ctx, cr); err != nil { + return err + } // Monolith uses ingester + transformer cluster roles for _, kind := range []ConfKind{ConfKafkaIngester, ConfKafkaTransformer} { desired := builder.clusterRoleBinding(kind) diff --git a/controllers/flowlogspipeline/flp_reconciler.go b/controllers/flowlogspipeline/flp_reconciler.go index 7395a8f6c..7f51b5047 100644 --- a/controllers/flowlogspipeline/flp_reconciler.go +++ b/controllers/flowlogspipeline/flp_reconciler.go @@ -3,6 +3,7 @@ package flowlogspipeline import ( "context" "fmt" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/errors" @@ -25,8 +26,7 @@ const contextReconcilerName = "FLP kind" type singleReconciler interface { context(ctx context.Context) context.Context - initStaticResources(ctx context.Context) error - prepareNamespaceChange(ctx context.Context) error + cleanupNamespace(ctx context.Context) reconcile(ctx context.Context, desired *flowslatest.FlowCollector) error } @@ -60,24 +60,11 @@ func NewReconciler(ctx context.Context, cl reconcilers.ClientHelper, ns, prevNS, } } -// InitStaticResources inits some "static" / one-shot resources, usually not subject to reconciliation -func (r *FLPReconciler) InitStaticResources(ctx context.Context) error { +// CleanupNamespace cleans up old namespace +func (r *FLPReconciler) CleanupNamespace(ctx context.Context) { for _, sr := range r.reconcilers { - if err := sr.initStaticResources(sr.context(ctx)); err != nil { - return err - } + sr.cleanupNamespace(sr.context(ctx)) } - return nil -} - -// PrepareNamespaceChange cleans up old namespace and restore the relevant "static" resources -func (r *FLPReconciler) PrepareNamespaceChange(ctx context.Context) error { - for _, sr := range r.reconcilers { - if err := sr.prepareNamespaceChange(sr.context(ctx)); err != nil { - return err - } - } - return nil } func validateDesired(desired *flpSpec) error { diff --git a/controllers/flowlogspipeline/flp_test.go b/controllers/flowlogspipeline/flp_test.go index 33ef3ef57..96cc82f05 100644 --- a/controllers/flowlogspipeline/flp_test.go +++ b/controllers/flowlogspipeline/flp_test.go @@ -479,7 +479,9 @@ func TestServiceMonitorNoChange(t *testing.T) { // Check no change newServiceMonitor := first.DeepCopy() - assert.False(helper.ServiceMonitorChanged(first, newServiceMonitor)) + report := helper.NewChangeReport("") + assert.False(helper.ServiceMonitorChanged(first, newServiceMonitor, &report)) + assert.Contains(report.String(), "no change") } func TestServiceMonitorChanged(t *testing.T) { @@ -496,7 +498,9 @@ func TestServiceMonitorChanged(t *testing.T) { b = newMonolithBuilder("namespace2", image, &cfg, true, &certWatcher) second := b.generic.serviceMonitor() - assert.True(helper.ServiceMonitorChanged(first, second)) + report := helper.NewChangeReport("") + assert.True(helper.ServiceMonitorChanged(first, second, &report)) + assert.Contains(report.String(), "ServiceMonitor meta changed") } func TestPrometheusRuleNoChange(t *testing.T) { @@ -511,7 +515,9 @@ func TestPrometheusRuleNoChange(t *testing.T) { // Check no change newServiceMonitor := first.DeepCopy() - assert.False(helper.PrometheusRuleChanged(first, newServiceMonitor)) + report := helper.NewChangeReport("") + assert.False(helper.PrometheusRuleChanged(first, newServiceMonitor, &report)) + assert.Contains(report.String(), "no change") } func TestPrometheusRuleChanged(t *testing.T) { @@ -528,7 +534,9 @@ func TestPrometheusRuleChanged(t *testing.T) { b = newMonolithBuilder("namespace2", image, &cfg, true, &certWatcher) second := b.generic.prometheusRule() - assert.True(helper.PrometheusRuleChanged(first, second)) + report := helper.NewChangeReport("") + assert.True(helper.PrometheusRuleChanged(first, second, &report)) + assert.Contains(report.String(), "PrometheusRule meta changed") } func TestConfigMapShouldDeserializeAsJSON(t *testing.T) { diff --git a/controllers/flowlogspipeline/flp_transfo_reconciler.go b/controllers/flowlogspipeline/flp_transfo_reconciler.go index aa8947eae..c12b9d7e7 100644 --- a/controllers/flowlogspipeline/flp_transfo_reconciler.go +++ b/controllers/flowlogspipeline/flp_transfo_reconciler.go @@ -13,6 +13,7 @@ import ( flowslatest "github.com/netobserv/network-observability-operator/api/v1beta1" "github.com/netobserv/network-observability-operator/controllers/constants" + "github.com/netobserv/network-observability-operator/controllers/reconcilers" "github.com/netobserv/network-observability-operator/pkg/helper" ) @@ -54,6 +55,8 @@ func newTransformerReconciler(info *reconcilersCommonInfo) *flpTransformerReconc info.nobjMngr.AddManagedObject(configMapName(ConfKafkaTransformer), owned.configMap) if info.availableAPIs.HasSvcMonitor() { info.nobjMngr.AddManagedObject(serviceMonitorName(ConfKafkaTransformer), owned.serviceMonitor) + } + if info.availableAPIs.HasPromRule() { info.nobjMngr.AddManagedObject(prometheusRuleName(ConfKafkaTransformer), owned.prometheusRule) } @@ -68,18 +71,9 @@ func (r *flpTransformerReconciler) context(ctx context.Context) context.Context return log.IntoContext(ctx, l) } -// initStaticResources inits some "static" / one-shot resources, usually not subject to reconciliation -func (r *flpTransformerReconciler) initStaticResources(ctx context.Context) error { - cr := buildClusterRoleTransformer() - return r.ReconcileClusterRole(ctx, cr) -} - -// prepareNamespaceChange cleans up old namespace and restore the relevant "static" resources -func (r *flpTransformerReconciler) prepareNamespaceChange(ctx context.Context) error { - // Switching namespace => delete everything in the previous namespace +// cleanupNamespace cleans up old namespace +func (r *flpTransformerReconciler) cleanupNamespace(ctx context.Context) { r.nobjMngr.CleanupPreviousNamespace(ctx) - cr := buildClusterRoleTransformer() - return r.ReconcileClusterRole(ctx, cr) } func (r *flpTransformerReconciler) reconcile(ctx context.Context, desired *flowslatest.FlowCollector) error { @@ -176,34 +170,24 @@ func (r *flpTransformerReconciler) reconcilePrometheusService(ctx context.Contex if err := r.CreateOwned(ctx, builder.newPromService()); err != nil { return err } - if r.availableAPIs.HasSvcMonitor() { - if err := r.CreateOwned(ctx, builder.generic.serviceMonitor()); err != nil { - return err - } - if err := r.CreateOwned(ctx, builder.generic.prometheusRule()); err != nil { + } else { + newSVC := builder.fromPromService(r.owned.promService) + if helper.ServiceChanged(r.owned.promService, newSVC, &report) { + if err := r.UpdateOwned(ctx, r.owned.promService, newSVC); err != nil { return err } } - return nil } - newSVC := builder.fromPromService(r.owned.promService) - if helper.ServiceChanged(r.owned.promService, newSVC, &report) { - if err := r.UpdateOwned(ctx, r.owned.promService, newSVC); err != nil { + if r.availableAPIs.HasSvcMonitor() { + serviceMonitor := builder.generic.serviceMonitor() + if err := reconcilers.GenericReconcile(ctx, r.nobjMngr, &r.ClientHelper, r.owned.serviceMonitor, serviceMonitor, &report, helper.ServiceMonitorChanged); err != nil { return err } } - if r.availableAPIs.HasSvcMonitor() { - newMonitorSvc := builder.generic.serviceMonitor() - if helper.ServiceMonitorChanged(r.owned.serviceMonitor, newMonitorSvc) { - if err := r.UpdateOwned(ctx, r.owned.serviceMonitor, newMonitorSvc); err != nil { - return err - } - } - newPromRules := builder.generic.prometheusRule() - if helper.PrometheusRuleChanged(r.owned.prometheusRule, newPromRules) { - if err := r.UpdateOwned(ctx, r.owned.prometheusRule, newPromRules); err != nil { - return err - } + if r.availableAPIs.HasPromRule() { + promRules := builder.generic.prometheusRule() + if err := reconcilers.GenericReconcile(ctx, r.nobjMngr, &r.ClientHelper, r.owned.prometheusRule, promRules, &report, helper.PrometheusRuleChanged); err != nil { + return err } } return nil @@ -214,6 +198,11 @@ func (r *flpTransformerReconciler) reconcilePermissions(ctx context.Context, bui return r.CreateOwned(ctx, builder.serviceAccount()) } // We only configure name, update is not needed for now + cr := buildClusterRoleTransformer() + if err := r.ReconcileClusterRole(ctx, cr); err != nil { + return err + } + desired := builder.clusterRoleBinding() if err := r.ReconcileClusterRoleBinding(ctx, desired); err != nil { return err diff --git a/controllers/reconcilers/namespaced_objects_manager.go b/controllers/reconcilers/namespaced_objects_manager.go index 26206c94a..cb6fa0206 100644 --- a/controllers/reconcilers/namespaced_objects_manager.go +++ b/controllers/reconcilers/namespaced_objects_manager.go @@ -9,6 +9,8 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/netobserv/network-observability-operator/pkg/helper" ) // NamespacedObjectManager provides some helpers to manage (fetch, delete) namespace-scoped objects @@ -123,3 +125,13 @@ func (m *NamespacedObjectManager) Exists(obj client.Object) bool { } return false } + +func GenericReconcile[K client.Object](ctx context.Context, m *NamespacedObjectManager, cl *ClientHelper, old, new K, report *helper.ChangeReport, changeFunc func(old, new K, report *helper.ChangeReport) bool) error { + if !m.Exists(old) { + return cl.CreateOwned(ctx, new) + } + if changeFunc(old, new, report) { + return cl.UpdateOwned(ctx, old, new) + } + return nil +} diff --git a/controllers/suite_test.go b/controllers/suite_test.go index f6e328d16..3de1394ea 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -26,6 +26,7 @@ import ( . "github.com/onsi/gomega" osv1alpha1 "github.com/openshift/api/console/v1alpha1" operatorsv1 "github.com/openshift/api/operator/v1" + monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" "github.com/stretchr/testify/mock" ascv2 "k8s.io/api/autoscaling/v2" corev1 "k8s.io/api/core/v1" @@ -40,10 +41,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/manager" - "github.com/netobserv/network-observability-operator/controllers/operator" - flowsv1alpha1 "github.com/netobserv/network-observability-operator/api/v1alpha1" flowsv1beta1 "github.com/netobserv/network-observability-operator/api/v1beta1" + "github.com/netobserv/network-observability-operator/controllers/operator" //+kubebuilder:scaffold:imports ) @@ -86,6 +86,7 @@ var _ = BeforeSuite(func() { // We need to install the ConsolePlugin CRD to test setup of our Network Console Plugin filepath.Join("..", "vendor", "github.com", "openshift", "api", "console", "v1alpha1"), filepath.Join("..", "vendor", "github.com", "openshift", "api", "operator", "v1"), + filepath.Join("..", "test-assets"), }, ErrorIfCRDPathMissing: true, } @@ -115,7 +116,7 @@ var _ = BeforeSuite(func() { err = operatorsv1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) - err = flowsv1beta1.AddToScheme(scheme.Scheme) + err = monitoringv1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme diff --git a/pkg/discover/apis.go b/pkg/discover/apis.go index 997b812b7..012bd3171 100644 --- a/pkg/discover/apis.go +++ b/pkg/discover/apis.go @@ -15,6 +15,7 @@ var ( consoleConfig = "consoles." + configv1.GroupName cno = "networks." + operatorv1.GroupName svcMonitor = "servicemonitors." + monitoring.GroupName + promRule = "prometheusrules." + monitoring.GroupName ) // AvailableAPIs discovers the available APIs in the running cluster @@ -28,6 +29,7 @@ func NewAvailableAPIs(client *discovery.DiscoveryClient) (*AvailableAPIs, error) consoleConfig: false, cno: false, svcMonitor: false, + promRule: false, } _, resources, err := client.ServerGroupsAndResources() if err != nil { @@ -67,3 +69,8 @@ func (c *AvailableAPIs) HasCNO() bool { func (c *AvailableAPIs) HasSvcMonitor() bool { return c.apisMap[svcMonitor] } + +// HasPromRule returns true if "prometheusrules.monitoring.coreos.com" API was found +func (c *AvailableAPIs) HasPromRule() bool { + return c.apisMap[promRule] +} diff --git a/pkg/helper/comparators.go b/pkg/helper/comparators.go index 0ba23b6f2..a85e64180 100644 --- a/pkg/helper/comparators.go +++ b/pkg/helper/comparators.go @@ -79,14 +79,14 @@ func ServiceChanged(old, new *corev1.Service, report *ChangeReport) bool { report.Check("Service spec changed", !equality.Semantic.DeepDerivative(new.Spec, old.Spec)) } -func ServiceMonitorChanged(old, new *monitoringv1.ServiceMonitor) bool { - return !equality.Semantic.DeepDerivative(new.ObjectMeta, old.ObjectMeta) || - !equality.Semantic.DeepDerivative(new.Spec, old.Spec) +func ServiceMonitorChanged(old, new *monitoringv1.ServiceMonitor, report *ChangeReport) bool { + return report.Check("ServiceMonitor meta changed", !equality.Semantic.DeepDerivative(new.ObjectMeta, old.ObjectMeta)) || + report.Check("ServiceMonitor spec changed", !equality.Semantic.DeepDerivative(new.Spec, old.Spec)) } -func PrometheusRuleChanged(old, new *monitoringv1.PrometheusRule) bool { - return !equality.Semantic.DeepDerivative(new.ObjectMeta, old.ObjectMeta) || - !equality.Semantic.DeepDerivative(new.Spec, old.Spec) +func PrometheusRuleChanged(old, new *monitoringv1.PrometheusRule, report *ChangeReport) bool { + return report.Check("PrometheusRule meta changed", !equality.Semantic.DeepDerivative(new.ObjectMeta, old.ObjectMeta)) || + report.Check("PrometheusRule spec changed", !equality.Semantic.DeepDerivative(new.Spec, old.Spec)) } // FindContainer searches in pod containers one that matches the provided name diff --git a/test-assets/prometheusrule.yaml b/test-assets/prometheusrule.yaml new file mode 100644 index 000000000..6f1654789 --- /dev/null +++ b/test-assets/prometheusrule.yaml @@ -0,0 +1,121 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + include.release.openshift.io/ibm-cloud-managed: "true" + include.release.openshift.io/self-managed-high-availability: "true" + include.release.openshift.io/single-node-developer: "true" + creationTimestamp: null + name: prometheusrules.monitoring.coreos.com +spec: + group: monitoring.coreos.com + names: + categories: + - prometheus-operator + kind: PrometheusRule + listKind: PrometheusRuleList + plural: prometheusrules + shortNames: + - promrule + singular: prometheusrule + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: PrometheusRule defines recording and alerting rules for a Prometheus + instance + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Specification of desired alerting rule definitions for Prometheus. + properties: + groups: + description: Content of Prometheus rule file + items: + description: RuleGroup is a list of sequentially evaluated recording + and alerting rules. + properties: + interval: + description: Interval determines how often rules in the group + are evaluated. + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + name: + description: Name of the rule group. + minLength: 1 + type: string + partial_response_strategy: + description: 'PartialResponseStrategy is only used by ThanosRuler + and will be ignored by Prometheus instances. More info: https://github.com/thanos-io/thanos/blob/main/docs/components/rule.md#partial-response' + pattern: ^(?i)(abort|warn)?$ + type: string + rules: + description: List of alerting and recording rules. + items: + description: 'Rule describes an alerting or recording rule + See Prometheus documentation: [alerting](https://www.prometheus.io/docs/prometheus/latest/configuration/alerting_rules/) + or [recording](https://www.prometheus.io/docs/prometheus/latest/configuration/recording_rules/#recording-rules) + rule' + properties: + alert: + description: Name of the alert. Must be a valid label + value. Only one of `record` and `alert` must be set. + type: string + annotations: + additionalProperties: + type: string + description: Annotations to add to each alert. Only valid + for alerting rules. + type: object + expr: + anyOf: + - type: integer + - type: string + description: PromQL expression to evaluate. + x-kubernetes-int-or-string: true + for: + description: Alerts are considered firing once they have + been returned for this long. + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + labels: + additionalProperties: + type: string + description: Labels to add or overwrite. + type: object + record: + description: Name of the time series to output to. Must + be a valid metric name. Only one of `record` and `alert` + must be set. + type: string + required: + - expr + type: object + type: array + required: + - name + - rules + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true \ No newline at end of file diff --git a/test-assets/servicemonitors.yaml b/test-assets/servicemonitors.yaml new file mode 100644 index 000000000..16548a396 --- /dev/null +++ b/test-assets/servicemonitors.yaml @@ -0,0 +1,705 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + include.release.openshift.io/ibm-cloud-managed: "true" + include.release.openshift.io/self-managed-high-availability: "true" + include.release.openshift.io/single-node-developer: "true" + creationTimestamp: null + name: servicemonitors.monitoring.coreos.com +spec: + group: monitoring.coreos.com + names: + categories: + - prometheus-operator + kind: ServiceMonitor + listKind: ServiceMonitorList + plural: servicemonitors + shortNames: + - smon + singular: servicemonitor + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: ServiceMonitor defines monitoring for a set of services. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Specification of desired Service selection for target discovery + by Prometheus. + properties: + attachMetadata: + description: Attaches node metadata to discovered targets. Requires + Prometheus v2.37.0 and above. + properties: + node: + description: When set to true, Prometheus must have permissions + to get Nodes. + type: boolean + type: object + endpoints: + description: A list of endpoints allowed as part of this ServiceMonitor. + items: + description: Endpoint defines a scrapeable endpoint serving Prometheus + metrics. + properties: + authorization: + description: Authorization section for this endpoint + properties: + credentials: + description: The secret's key that contains the credentials + of the request + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: + description: Set the authentication type. Defaults to Bearer, + Basic will cause an error + type: string + type: object + basicAuth: + description: 'BasicAuth allow an endpoint to authenticate over + basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints' + properties: + password: + description: The secret in the service monitor namespace + that contains the password for authentication. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + username: + description: The secret in the service monitor namespace + that contains the username for authentication. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + bearerTokenFile: + description: File to read bearer token for scraping targets. + type: string + bearerTokenSecret: + description: Secret to mount to read bearer token for scraping + targets. The secret needs to be in the same namespace as the + service monitor and accessible by the Prometheus Operator. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + enableHttp2: + description: Whether to enable HTTP2. + type: boolean + filterRunning: + description: 'Drop pods that are not running. (Failed, Succeeded). + Enabled by default. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase' + type: boolean + followRedirects: + description: FollowRedirects configures whether scrape requests + follow HTTP 3xx redirects. + type: boolean + honorLabels: + description: HonorLabels chooses the metric's labels on collisions + with target labels. + type: boolean + honorTimestamps: + description: HonorTimestamps controls whether Prometheus respects + the timestamps present in scraped data. + type: boolean + interval: + description: Interval at which metrics should be scraped If + not specified Prometheus' global scrape interval is used. + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + metricRelabelings: + description: MetricRelabelConfigs to apply to samples before + ingestion. + items: + description: 'RelabelConfig allows dynamic rewriting of the + label set, being applied to samples before ingestion. It + defines ``-section of Prometheus + configuration. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' + properties: + action: + default: replace + description: Action to perform based on regex matching. + Default is 'replace'. uppercase and lowercase actions + require Prometheus >= 2.36. + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual + type: string + modulus: + description: Modulus to take of the hash of the source + label values. + format: int64 + type: integer + regex: + description: Regular expression against which the extracted + value is matched. Default is '(.*)' + type: string + replacement: + description: Replacement value against which a regex replace + is performed if the regular expression matches. Regex + capture groups are available. Default is '$1' + type: string + separator: + description: Separator placed between concatenated source + label values. default is ';'. + type: string + sourceLabels: + description: The source labels select values from existing + labels. Their content is concatenated using the configured + separator and matched against the configured regular + expression for the replace, keep, and drop actions. + items: + description: LabelName is a valid Prometheus label name + which may only contain ASCII letters, numbers, as + well as underscores. + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: + description: Label to which the resulting value is written + in a replace action. It is mandatory for replace actions. + Regex capture groups are available. + type: string + type: object + type: array + oauth2: + description: OAuth2 for the URL. Only valid in Prometheus versions + 2.27.0 and newer. + properties: + clientId: + description: The secret or configmap containing the OAuth2 + client id + properties: + configMap: + description: ConfigMap containing data to use for the + targets. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + description: Secret containing data to use for the targets. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + clientSecret: + description: The secret containing the OAuth2 client secret + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + endpointParams: + additionalProperties: + type: string + description: Parameters to append to the token URL + type: object + scopes: + description: OAuth2 scopes used for the token request + items: + type: string + type: array + tokenUrl: + description: The URL to fetch the token from + minLength: 1 + type: string + required: + - clientId + - clientSecret + - tokenUrl + type: object + params: + additionalProperties: + items: + type: string + type: array + description: Optional HTTP URL parameters + type: object + path: + description: HTTP path to scrape for metrics. If empty, Prometheus + uses the default value (e.g. `/metrics`). + type: string + port: + description: Name of the service port this endpoint refers to. + Mutually exclusive with targetPort. + type: string + proxyUrl: + description: ProxyURL eg http://proxyserver:2195 Directs scrapes + to proxy through this endpoint. + type: string + relabelings: + description: 'RelabelConfigs to apply to samples before scraping. + Prometheus Operator automatically adds relabelings for a few + standard Kubernetes fields. The original scrape job''s name + is available via the `__tmp_prometheus_job_name` label. More + info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config' + items: + description: 'RelabelConfig allows dynamic rewriting of the + label set, being applied to samples before ingestion. It + defines ``-section of Prometheus + configuration. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' + properties: + action: + default: replace + description: Action to perform based on regex matching. + Default is 'replace'. uppercase and lowercase actions + require Prometheus >= 2.36. + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual + type: string + modulus: + description: Modulus to take of the hash of the source + label values. + format: int64 + type: integer + regex: + description: Regular expression against which the extracted + value is matched. Default is '(.*)' + type: string + replacement: + description: Replacement value against which a regex replace + is performed if the regular expression matches. Regex + capture groups are available. Default is '$1' + type: string + separator: + description: Separator placed between concatenated source + label values. default is ';'. + type: string + sourceLabels: + description: The source labels select values from existing + labels. Their content is concatenated using the configured + separator and matched against the configured regular + expression for the replace, keep, and drop actions. + items: + description: LabelName is a valid Prometheus label name + which may only contain ASCII letters, numbers, as + well as underscores. + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: + description: Label to which the resulting value is written + in a replace action. It is mandatory for replace actions. + Regex capture groups are available. + type: string + type: object + type: array + scheme: + description: HTTP scheme to use for scraping. + type: string + scrapeTimeout: + description: Timeout after which the scrape is ended If not + specified, the Prometheus global scrape timeout is used unless + it is less than `Interval` in which the latter is used. + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: Name or number of the target port of the Pod behind + the Service, the port must be specified with container port + property. Mutually exclusive with port. + x-kubernetes-int-or-string: true + tlsConfig: + description: TLS configuration to use when scraping the endpoint + properties: + ca: + description: Certificate authority used when verifying server + certificates. + properties: + configMap: + description: ConfigMap containing data to use for the + targets. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + description: Secret containing data to use for the targets. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + caFile: + description: Path to the CA cert in the Prometheus container + to use for the targets. + type: string + cert: + description: Client certificate to present when doing client-authentication. + properties: + configMap: + description: ConfigMap containing data to use for the + targets. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + description: Secret containing data to use for the targets. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + certFile: + description: Path to the client cert file in the Prometheus + container for the targets. + type: string + insecureSkipVerify: + description: Disable target certificate validation. + type: boolean + keyFile: + description: Path to the client key file in the Prometheus + container for the targets. + type: string + keySecret: + description: Secret containing the client key file for the + targets. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + serverName: + description: Used to verify the hostname for the targets. + type: string + type: object + type: object + type: array + jobLabel: + description: "JobLabel selects the label from the associated Kubernetes + service which will be used as the `job` label for all metrics. \n + For example: If in `ServiceMonitor.spec.jobLabel: foo` and in `Service.metadata.labels.foo: + bar`, then the `job=\"bar\"` label is added to all metrics. \n If + the value of this field is empty or if the label doesn't exist for + the given Service, the `job` label of the metrics defaults to the + name of the Kubernetes Service." + type: string + labelLimit: + description: Per-scrape limit on number of labels that will be accepted + for a sample. Only valid in Prometheus versions 2.27.0 and newer. + format: int64 + type: integer + labelNameLengthLimit: + description: Per-scrape limit on length of labels name that will be + accepted for a sample. Only valid in Prometheus versions 2.27.0 + and newer. + format: int64 + type: integer + labelValueLengthLimit: + description: Per-scrape limit on length of labels value that will + be accepted for a sample. Only valid in Prometheus versions 2.27.0 + and newer. + format: int64 + type: integer + namespaceSelector: + description: Selector to select which namespaces the Kubernetes Endpoints + objects are discovered from. + properties: + any: + description: Boolean describing whether all namespaces are selected + in contrast to a list restricting them. + type: boolean + matchNames: + description: List of namespace names to select from. + items: + type: string + type: array + type: object + podTargetLabels: + description: PodTargetLabels transfers labels on the Kubernetes `Pod` + onto the created metrics. + items: + type: string + type: array + sampleLimit: + description: SampleLimit defines per-scrape limit on number of scraped + samples that will be accepted. + format: int64 + type: integer + selector: + description: Selector to select Endpoints objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + targetLabels: + description: TargetLabels transfers labels from the Kubernetes `Service` + onto the created metrics. + items: + type: string + type: array + targetLimit: + description: TargetLimit defines a limit on the number of scraped + targets that will be accepted. + format: int64 + type: integer + required: + - endpoints + - selector + type: object + required: + - spec + type: object + served: true + storage: true