blob: afe777894457867fe975babdbf94d1a17169c05e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package controllers
import (
"context"
"crypto/md5"
"fmt"
"github.com/apache/solr-operator/controllers/util"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
solrv1beta1 "github.com/apache/solr-operator/api/v1beta1"
)
// SolrPrometheusExporterReconciler reconciles a SolrPrometheusExporter object
type SolrPrometheusExporterReconciler struct {
client.Client
Scheme *runtime.Scheme
}
//+kubebuilder:rbac:groups=,resources=configmaps,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=,resources=configmaps/status,verbs=get
//+kubebuilder:rbac:groups=,resources=services,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=,resources=services/status,verbs=get
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=apps,resources=deployments/status,verbs=get
//+kubebuilder:rbac:groups=solr.apache.org,resources=solrclouds,verbs=get;list;watch
//+kubebuilder:rbac:groups=solr.apache.org,resources=solrclouds/status,verbs=get
//+kubebuilder:rbac:groups=solr.apache.org,resources=solrprometheusexporters,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=solr.apache.org,resources=solrprometheusexporters/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=solr.apache.org,resources=solrprometheusexporters/finalizers,verbs=update
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.8.3/pkg/reconcile
func (r *SolrPrometheusExporterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
// Fetch the SolrPrometheusExporter instance
prometheusExporter := &solrv1beta1.SolrPrometheusExporter{}
err := r.Get(ctx, req.NamespacedName, prometheusExporter)
if err != nil {
if errors.IsNotFound(err) {
// Object not found, return. Created objects are automatically garbage collected.
// For additional cleanup logic use finalizers.
return ctrl.Result{}, nil
}
// Error reading the object - requeue the req.
return ctrl.Result{}, err
}
changed := prometheusExporter.WithDefaults()
if changed {
logger.Info("Setting default settings for Solr PrometheusExporter")
if err := r.Update(ctx, prometheusExporter); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{Requeue: true}, nil
}
requeueOrNot := ctrl.Result{}
configMapKey := util.PrometheusExporterConfigMapKey
configXmlMd5 := ""
if prometheusExporter.Spec.Config == "" && prometheusExporter.Spec.CustomKubeOptions.ConfigMapOptions != nil && prometheusExporter.Spec.CustomKubeOptions.ConfigMapOptions.ProvidedConfigMap != "" {
foundConfigMap := &corev1.ConfigMap{}
err = r.Get(ctx, types.NamespacedName{Name: prometheusExporter.Spec.CustomKubeOptions.ConfigMapOptions.ProvidedConfigMap, Namespace: prometheusExporter.Namespace}, foundConfigMap)
if err != nil {
return requeueOrNot, err
}
if foundConfigMap.Data != nil {
configXml, ok := foundConfigMap.Data[configMapKey]
if ok {
configXmlMd5 = fmt.Sprintf("%x", md5.Sum([]byte(configXml)))
} else {
return requeueOrNot, fmt.Errorf("required '%s' key not found in provided ConfigMap %s",
configMapKey, prometheusExporter.Spec.CustomKubeOptions.ConfigMapOptions.ProvidedConfigMap)
}
} else {
return requeueOrNot, fmt.Errorf("provided ConfigMap %s has no data",
prometheusExporter.Spec.CustomKubeOptions.ConfigMapOptions.ProvidedConfigMap)
}
}
if prometheusExporter.Spec.Config != "" {
// Generate ConfigMap
configMap := util.GenerateMetricsConfigMap(prometheusExporter)
// capture the MD5 for the default config XML, otherwise we already computed it above
if configXmlMd5 == "" {
configXmlMd5 = fmt.Sprintf("%x", md5.Sum([]byte(configMap.Data[configMapKey])))
}
// Check if the ConfigMap already exists
configMapLogger := logger.WithValues("configMap", configMap.Name)
foundConfigMap := &corev1.ConfigMap{}
err = r.Get(ctx, types.NamespacedName{Name: configMap.Name, Namespace: configMap.Namespace}, foundConfigMap)
if err != nil && errors.IsNotFound(err) {
configMapLogger.Info("Creating ConfigMap")
if err = controllerutil.SetControllerReference(prometheusExporter, configMap, r.Scheme); err == nil {
err = r.Create(ctx, configMap)
}
} else if err == nil {
var needsUpdate bool
needsUpdate, err = util.OvertakeControllerRef(prometheusExporter, foundConfigMap, r.Scheme)
needsUpdate = util.CopyConfigMapFields(configMap, foundConfigMap, configMapLogger) || needsUpdate
// Update the found ConfigMap and write the result back if there are any changes
if needsUpdate && err == nil {
configMapLogger.Info("Updating ConfigMap")
err = r.Update(ctx, foundConfigMap)
}
}
if err != nil {
return requeueOrNot, err
}
}
// Generate Metrics Service
metricsService := util.GenerateSolrMetricsService(prometheusExporter)
// Check if the Metrics Service already exists
serviceLogger := logger.WithValues("service", metricsService.Name)
foundMetricsService := &corev1.Service{}
err = r.Get(ctx, types.NamespacedName{Name: metricsService.Name, Namespace: metricsService.Namespace}, foundMetricsService)
if err != nil && errors.IsNotFound(err) {
serviceLogger.Info("Creating Service")
if err = controllerutil.SetControllerReference(prometheusExporter, metricsService, r.Scheme); err == nil {
err = r.Create(ctx, metricsService)
}
} else if err == nil {
var needsUpdate bool
needsUpdate, err = util.OvertakeControllerRef(prometheusExporter, foundMetricsService, r.Scheme)
needsUpdate = util.CopyServiceFields(metricsService, foundMetricsService, serviceLogger) || needsUpdate
// Update the found Metrics Service and write the result back if there are any changes
if needsUpdate && err == nil {
serviceLogger.Info("Updating Service")
err = r.Update(ctx, foundMetricsService)
}
}
if err != nil {
return requeueOrNot, err
}
// Get the ZkConnectionString to connect to
solrConnectionInfo := util.SolrConnectionInfo{}
var solrCloudImage *solrv1beta1.ContainerImage
if solrConnectionInfo, solrCloudImage, err = getSolrConnectionInfo(ctx, r, prometheusExporter); err != nil {
return requeueOrNot, err
}
// Make sure the TLS config is in order
var tls *util.TLSCerts = nil
if prometheusExporter.Spec.SolrReference.SolrTLS != nil {
tls, err = r.reconcileTLSConfig(prometheusExporter)
if err != nil {
return requeueOrNot, err
}
}
basicAuthMd5 := ""
if prometheusExporter.Spec.SolrReference.BasicAuthSecret != "" {
basicAuthSecret := &corev1.Secret{}
err := r.Get(ctx, types.NamespacedName{Name: prometheusExporter.Spec.SolrReference.BasicAuthSecret, Namespace: prometheusExporter.Namespace}, basicAuthSecret)
if err != nil {
return reconcile.Result{}, err
}
err = util.ValidateBasicAuthSecret(basicAuthSecret)
if err != nil {
return reconcile.Result{}, err
}
creds := fmt.Sprintf("%s:%s", basicAuthSecret.Data[corev1.BasicAuthUsernameKey], basicAuthSecret.Data[corev1.BasicAuthPasswordKey])
basicAuthMd5 = fmt.Sprintf("%x", md5.Sum([]byte(creds)))
}
deploy := util.GenerateSolrPrometheusExporterDeployment(prometheusExporter, solrConnectionInfo, solrCloudImage, configXmlMd5, tls, basicAuthMd5)
ready := false
// Check if the Metrics Deployment already exists
deploymentLogger := logger.WithValues("deployment", deploy.Name)
foundDeploy := &appsv1.Deployment{}
err = r.Get(ctx, types.NamespacedName{Name: deploy.Name, Namespace: deploy.Namespace}, foundDeploy)
// Set the annotation for a scheduled restart, if necessary.
if nextRestartAnnotation, reconcileWaitDuration, err := util.ScheduleNextRestart(prometheusExporter.Spec.RestartSchedule, foundDeploy.Spec.Template.Annotations); err != nil {
logger.Error(err, "Cannot parse restartSchedule cron", "cron", prometheusExporter.Spec.RestartSchedule)
} else {
if nextRestartAnnotation != "" {
if deploy.Spec.Template.Annotations == nil {
deploy.Spec.Template.Annotations = make(map[string]string, 1)
}
// Set the new restart time annotation
deploy.Spec.Template.Annotations[util.SolrScheduledRestartAnnotation] = nextRestartAnnotation
// TODO: Create event for the CRD.
} else if existingRestartAnnotation, exists := foundDeploy.Spec.Template.Annotations[util.SolrScheduledRestartAnnotation]; exists {
if deploy.Spec.Template.Annotations == nil {
deploy.Spec.Template.Annotations = make(map[string]string, 1)
}
// Keep the existing nextRestart annotation if it exists and we aren't setting a new one.
deploy.Spec.Template.Annotations[util.SolrScheduledRestartAnnotation] = existingRestartAnnotation
}
if reconcileWaitDuration != nil {
// Set the requeueAfter if it has not been set, or is greater than the time we need to wait to restart again
updateRequeueAfter(&requeueOrNot, *reconcileWaitDuration)
}
}
if err != nil && errors.IsNotFound(err) {
deploymentLogger.Info("Creating Deployment")
if err = controllerutil.SetControllerReference(prometheusExporter, deploy, r.Scheme); err == nil {
err = r.Create(ctx, deploy)
}
} else if err == nil {
var needsUpdate bool
needsUpdate, err = util.OvertakeControllerRef(prometheusExporter, foundDeploy, r.Scheme)
needsUpdate = util.CopyDeploymentFields(deploy, foundDeploy, deploymentLogger) || needsUpdate
// Update the found Metrics Service and write the result back if there are any changes
if needsUpdate && err == nil {
deploymentLogger.Info("Updating Deployment")
err = r.Update(ctx, foundDeploy)
}
ready = foundDeploy.Status.ReadyReplicas > 0
}
if err != nil {
return requeueOrNot, err
}
if ready != prometheusExporter.Status.Ready {
originalPrometheusExporter := prometheusExporter.DeepCopy()
prometheusExporter.Status.Ready = ready
logger.Info("Updating status for solr-prometheus-exporter")
err = r.Status().Patch(ctx, prometheusExporter, client.MergeFrom(originalPrometheusExporter))
}
return requeueOrNot, err
}
func getSolrConnectionInfo(ctx context.Context, r *SolrPrometheusExporterReconciler, prometheusExporter *solrv1beta1.SolrPrometheusExporter) (solrConnectionInfo util.SolrConnectionInfo, solrCloudImage *solrv1beta1.ContainerImage, err error) {
solrConnectionInfo = util.SolrConnectionInfo{}
if prometheusExporter.Spec.SolrReference.Standalone != nil {
solrConnectionInfo.StandaloneAddress = prometheusExporter.Spec.SolrReference.Standalone.Address
}
if prometheusExporter.Spec.SolrReference.Cloud != nil {
cloudRef := prometheusExporter.Spec.SolrReference.Cloud
if cloudRef.ZookeeperConnectionInfo != nil {
solrConnectionInfo.CloudZkConnnectionInfo = cloudRef.ZookeeperConnectionInfo
} else if cloudRef.Name != "" {
solrCloud := &solrv1beta1.SolrCloud{}
solrNamespace := prometheusExporter.Spec.SolrReference.Cloud.Namespace
if solrNamespace == "" {
solrNamespace = prometheusExporter.Namespace
}
err = r.Get(ctx, types.NamespacedName{Name: prometheusExporter.Spec.SolrReference.Cloud.Name, Namespace: solrNamespace}, solrCloud)
if err == nil {
solrConnectionInfo.CloudZkConnnectionInfo = &solrCloud.Status.ZookeeperConnectionInfo
solrCloudImage = solrCloud.Spec.SolrImage
}
}
}
return
}
// reconcileTLSConfig Reconciles the various options for configuring TLS for the exporter
// The exporter is a client to Solr pods, so can either just have a truststore so it trusts Solr certs
// Or it can have its own client auth cert when Solr mTLS is required
func (r *SolrPrometheusExporterReconciler) reconcileTLSConfig(prometheusExporter *solrv1beta1.SolrPrometheusExporter) (*util.TLSCerts, error) {
tls := util.TLSCertsForExporter(prometheusExporter)
opts := tls.ClientConfig.Options
if opts.PKCS12Secret != nil {
// Ensure one or the other have been configured, but not both
if opts.MountedTLSDir != nil {
return nil, fmt.Errorf("invalid TLS config, either supply `solrTLS.pkcs12Secret` or `solrTLS.mountedTLSDir` but not both")
}
// make sure the PKCS12Secret and corresponding keystore password exist and agree with the supplied config
_, err := tls.ClientConfig.VerifyKeystoreAndTruststoreSecretConfig(&r.Client)
if err != nil {
return nil, err
}
} else if opts.TrustStoreSecret != nil {
// no client cert, but we have truststore for the exporter, configure it ...
// Ensure one or the other have been configured, but not both
if opts.MountedTLSDir != nil {
return nil, fmt.Errorf("invalid TLS config, either supply `solrTLS.trustStoreSecret` or `solrTLS.mountedTLSDir` but not both")
}
// make sure the TrustStoreSecret and corresponding password exist and agree with the supplied config
err := tls.ClientConfig.VerifyTruststoreOnly(&r.Client)
if err != nil {
return nil, err
}
} else {
// per-pod TLS files get mounted into a dir on the pod dynamically using some external agent / CSI driver type mechanism
if opts.MountedTLSDir == nil {
return nil, fmt.Errorf("invalid TLS config, the 'solrTLS.mountedTLSDir' option is required unless you specify a keystore and/or truststore secret")
}
if opts.MountedTLSDir.KeystoreFile == "" && opts.MountedTLSDir.TruststoreFile == "" {
return nil, fmt.Errorf("invalid TLS config, the 'solrTLS.mountedTLSDir' option must specify a keystoreFile and/or truststoreFile")
}
}
return tls, nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *SolrPrometheusExporterReconciler) SetupWithManager(mgr ctrl.Manager) error {
ctrlBuilder := ctrl.NewControllerManagedBy(mgr).
For(&solrv1beta1.SolrPrometheusExporter{}).
Owns(&corev1.ConfigMap{}).
Owns(&corev1.Service{}).
Owns(&appsv1.Deployment{})
var err error
ctrlBuilder, err = r.indexAndWatchForSolrClouds(mgr, ctrlBuilder)
if err != nil {
return err
}
ctrlBuilder, err = r.indexAndWatchForProvidedConfigMaps(mgr, ctrlBuilder)
if err != nil {
return err
}
// Get notified when the TLS secret updates (such as when the cert gets renewed)
ctrlBuilder, err = r.indexAndWatchForKeystoreSecret(mgr, ctrlBuilder)
if err != nil {
return err
}
// Exporter may only have a truststore w/o a keystore
ctrlBuilder, err = r.indexAndWatchForTruststoreSecret(mgr, ctrlBuilder)
if err != nil {
return err
}
// Get notified when the basic auth secret updates; exporter pods must be restarted if the basic auth password
// changes b/c the credentials are loaded from a Java system property at startup and not watched for changes.
ctrlBuilder, err = r.indexAndWatchForBasicAuthSecret(mgr, ctrlBuilder)
if err != nil {
return err
}
return ctrlBuilder.Complete(r)
}
func (r *SolrPrometheusExporterReconciler) indexAndWatchForSolrClouds(mgr ctrl.Manager, ctrlBuilder *builder.Builder) (*builder.Builder, error) {
solrCloudField := ".spec.solrReference.cloud.name"
if err := mgr.GetFieldIndexer().IndexField(context.Background(), &solrv1beta1.SolrPrometheusExporter{}, solrCloudField, func(rawObj client.Object) []string {
// grab the SolrPrometheusExporter object, extract the used SolrCloud...
exporter := rawObj.(*solrv1beta1.SolrPrometheusExporter)
if exporter.Spec.SolrReference.Cloud == nil {
return nil
}
if exporter.Spec.SolrReference.Cloud.Name == "" {
return nil
}
// ...and if so, return it
return []string{exporter.Spec.SolrReference.Cloud.Name}
}); err != nil {
return ctrlBuilder, err
}
return ctrlBuilder.Watches(
&source.Kind{Type: &solrv1beta1.SolrCloud{}},
handler.EnqueueRequestsFromMapFunc(func(obj client.Object) []reconcile.Request {
foundExporters := &solrv1beta1.SolrPrometheusExporterList{}
listOps := &client.ListOptions{
FieldSelector: fields.OneTermEqualSelector(solrCloudField, obj.GetName()),
Namespace: obj.GetNamespace(),
}
err := r.List(context.Background(), foundExporters, listOps)
if err != nil {
// if no exporters found, just no-op this
return []reconcile.Request{}
}
requests := make([]reconcile.Request, len(foundExporters.Items))
for i, item := range foundExporters.Items {
requests[i] = reconcile.Request{
NamespacedName: types.NamespacedName{
Name: item.GetName(),
Namespace: item.GetNamespace(),
},
}
}
return requests
}),
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{})), nil
}
func (r *SolrPrometheusExporterReconciler) indexAndWatchForProvidedConfigMaps(mgr ctrl.Manager, ctrlBuilder *builder.Builder) (*builder.Builder, error) {
providedConfigMapField := ".spec.customKubeOptions.configMapOptions.providedConfigMap"
if err := mgr.GetFieldIndexer().IndexField(context.Background(), &solrv1beta1.SolrPrometheusExporter{}, providedConfigMapField, func(rawObj client.Object) []string {
// grab the SolrPrometheusExporter object, extract the used configMap...
exporter := rawObj.(*solrv1beta1.SolrPrometheusExporter)
if exporter.Spec.CustomKubeOptions.ConfigMapOptions == nil {
return nil
}
if exporter.Spec.CustomKubeOptions.ConfigMapOptions.ProvidedConfigMap == "" {
return nil
}
// ...and if so, return it
return []string{exporter.Spec.CustomKubeOptions.ConfigMapOptions.ProvidedConfigMap}
}); err != nil {
return ctrlBuilder, err
}
return ctrlBuilder.Watches(
&source.Kind{Type: &corev1.ConfigMap{}},
handler.EnqueueRequestsFromMapFunc(func(obj client.Object) []reconcile.Request {
foundExporters := &solrv1beta1.SolrPrometheusExporterList{}
listOps := &client.ListOptions{
FieldSelector: fields.OneTermEqualSelector(providedConfigMapField, obj.GetName()),
Namespace: obj.GetNamespace(),
}
err := r.List(context.Background(), foundExporters, listOps)
if err != nil {
// if no exporters found, just no-op this
return []reconcile.Request{}
}
requests := make([]reconcile.Request, len(foundExporters.Items))
for i, item := range foundExporters.Items {
requests[i] = reconcile.Request{
NamespacedName: types.NamespacedName{
Name: item.GetName(),
Namespace: item.GetNamespace(),
},
}
}
return requests
}),
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{})), nil
}
func (r *SolrPrometheusExporterReconciler) indexAndWatchForKeystoreSecret(mgr ctrl.Manager, ctrlBuilder *builder.Builder) (*builder.Builder, error) {
tlsSecretField := ".spec.solrReference.solrTLS.pkcs12Secret"
if err := mgr.GetFieldIndexer().IndexField(context.Background(), &solrv1beta1.SolrPrometheusExporter{}, tlsSecretField, func(rawObj client.Object) []string {
// grab the SolrPrometheusExporter object, extract the referenced TLS secret...
exporter := rawObj.(*solrv1beta1.SolrPrometheusExporter)
if exporter.Spec.SolrReference.SolrTLS == nil || exporter.Spec.SolrReference.SolrTLS.PKCS12Secret == nil {
return nil
}
// ...and if so, return it
return []string{exporter.Spec.SolrReference.SolrTLS.PKCS12Secret.Name}
}); err != nil {
return ctrlBuilder, err
}
return r.buildSecretWatch(tlsSecretField, ctrlBuilder)
}
func (r *SolrPrometheusExporterReconciler) indexAndWatchForTruststoreSecret(mgr ctrl.Manager, ctrlBuilder *builder.Builder) (*builder.Builder, error) {
tlsSecretField := ".spec.solrReference.solrTLS.trustStoreSecret"
if err := mgr.GetFieldIndexer().IndexField(context.Background(), &solrv1beta1.SolrPrometheusExporter{}, tlsSecretField, func(rawObj client.Object) []string {
// grab the SolrPrometheusExporter object, extract the referenced truststore secret...
exporter := rawObj.(*solrv1beta1.SolrPrometheusExporter)
if exporter.Spec.SolrReference.SolrTLS == nil || exporter.Spec.SolrReference.SolrTLS.TrustStoreSecret == nil {
return nil
}
// ...and if so, return it
return []string{exporter.Spec.SolrReference.SolrTLS.TrustStoreSecret.Name}
}); err != nil {
return ctrlBuilder, err
}
return r.buildSecretWatch(tlsSecretField, ctrlBuilder)
}
func (r *SolrPrometheusExporterReconciler) indexAndWatchForBasicAuthSecret(mgr ctrl.Manager, ctrlBuilder *builder.Builder) (*builder.Builder, error) {
secretField := ".spec.solrReference.basicAuthSecret"
if err := mgr.GetFieldIndexer().IndexField(context.Background(), &solrv1beta1.SolrPrometheusExporter{}, secretField, func(rawObj client.Object) []string {
// grab the SolrPrometheusExporter object, extract the referenced BasicAuth secret...
exporter := rawObj.(*solrv1beta1.SolrPrometheusExporter)
if exporter.Spec.SolrReference.BasicAuthSecret == "" {
return nil
}
// ...and if so, return it
return []string{exporter.Spec.SolrReference.BasicAuthSecret}
}); err != nil {
return ctrlBuilder, err
}
return r.buildSecretWatch(secretField, ctrlBuilder)
}
func (r *SolrPrometheusExporterReconciler) buildSecretWatch(secretField string, ctrlBuilder *builder.Builder) (*builder.Builder, error) {
return ctrlBuilder.Watches(
&source.Kind{Type: &corev1.Secret{}},
handler.EnqueueRequestsFromMapFunc(func(obj client.Object) []reconcile.Request {
foundExporters := &solrv1beta1.SolrPrometheusExporterList{}
listOps := &client.ListOptions{
FieldSelector: fields.OneTermEqualSelector(secretField, obj.GetName()),
Namespace: obj.GetNamespace(),
}
err := r.List(context.Background(), foundExporters, listOps)
if err != nil {
// if no exporters found, just no-op this
return []reconcile.Request{}
}
requests := make([]reconcile.Request, len(foundExporters.Items))
for i, item := range foundExporters.Items {
requests[i] = reconcile.Request{
NamespacedName: types.NamespacedName{
Name: item.GetName(),
Namespace: item.GetNamespace(),
},
}
}
return requests
}),
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{})), nil
}