clusterexternalsecret_controller.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. /*
  2. Copyright © 2025 ESO Maintainer Team
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package clusterexternalsecret
  14. import (
  15. "context"
  16. "errors"
  17. "fmt"
  18. "slices"
  19. "sort"
  20. "time"
  21. "github.com/go-logr/logr"
  22. v1 "k8s.io/api/core/v1"
  23. apierrors "k8s.io/apimachinery/pkg/api/errors"
  24. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  25. "k8s.io/apimachinery/pkg/labels"
  26. "k8s.io/apimachinery/pkg/runtime"
  27. "k8s.io/apimachinery/pkg/runtime/schema"
  28. "k8s.io/apimachinery/pkg/types"
  29. ctrl "sigs.k8s.io/controller-runtime"
  30. "sigs.k8s.io/controller-runtime/pkg/builder"
  31. "sigs.k8s.io/controller-runtime/pkg/client"
  32. "sigs.k8s.io/controller-runtime/pkg/controller"
  33. "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
  34. "sigs.k8s.io/controller-runtime/pkg/handler"
  35. "sigs.k8s.io/controller-runtime/pkg/reconcile"
  36. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  37. "github.com/external-secrets/external-secrets/pkg/controllers/clusterexternalsecret/cesmetrics"
  38. ctrlmetrics "github.com/external-secrets/external-secrets/pkg/controllers/metrics"
  39. "github.com/external-secrets/external-secrets/pkg/utils"
  40. )
  41. // Reconciler reconciles a ClusterExternalSecret object.
  42. type Reconciler struct {
  43. client.Client
  44. Log logr.Logger
  45. Scheme *runtime.Scheme
  46. RequeueInterval time.Duration
  47. }
  48. const (
  49. errGetCES = "could not get ClusterExternalSecret"
  50. errPatchStatus = "unable to patch status"
  51. errConvertLabelSelector = "unable to convert labelselector"
  52. errGetExistingES = "could not get existing ExternalSecret"
  53. errNamespacesFailed = "one or more namespaces failed"
  54. // ClusterExternalSecretFinalizer is the finalizer for ClusterExternalSecret resources.
  55. // This finalizer ensures that all ExternalSecrets created by the ClusterExternalSecret
  56. // are properly cleaned up before the ClusterExternalSecret is deleted, preventing orphaned resources.
  57. ClusterExternalSecretFinalizer = "externalsecrets.external-secrets.io/clusterexternalsecret-cleanup"
  58. )
  59. func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
  60. log := r.Log.WithValues("ClusterExternalSecret", req.NamespacedName)
  61. resourceLabels := ctrlmetrics.RefineNonConditionMetricLabels(map[string]string{"name": req.Name, "namespace": req.Namespace})
  62. start := time.Now()
  63. externalSecretReconcileDuration := cesmetrics.GetGaugeVec(cesmetrics.ClusterExternalSecretReconcileDurationKey)
  64. defer func() { externalSecretReconcileDuration.With(resourceLabels).Set(float64(time.Since(start))) }()
  65. var clusterExternalSecret esv1.ClusterExternalSecret
  66. err := r.Get(ctx, req.NamespacedName, &clusterExternalSecret)
  67. if err != nil {
  68. if apierrors.IsNotFound(err) {
  69. cesmetrics.RemoveMetrics(req.Namespace, req.Name)
  70. return ctrl.Result{}, nil
  71. }
  72. log.Error(err, errGetCES)
  73. return ctrl.Result{}, err
  74. }
  75. // Handle deletion with finalizer
  76. // When a ClusterExternalSecret is being deleted, we need to ensure all created ExternalSecrets
  77. // and namespace finalizers are cleaned up to prevent resource leaks and namespace deletion blocking.
  78. if clusterExternalSecret.DeletionTimestamp != nil {
  79. // Always attempt cleanup to handle edge case where finalizer might be removed externally
  80. if err := r.cleanupExternalSecrets(ctx, log, &clusterExternalSecret); err != nil {
  81. log.Error(err, "failed to cleanup ExternalSecrets")
  82. return ctrl.Result{}, err
  83. }
  84. // Remove finalizer from ClusterExternalSecret if it exists
  85. if updated := controllerutil.RemoveFinalizer(&clusterExternalSecret, ClusterExternalSecretFinalizer); updated {
  86. if err := r.Update(ctx, &clusterExternalSecret); err != nil {
  87. return ctrl.Result{}, err
  88. }
  89. }
  90. return ctrl.Result{}, nil
  91. }
  92. // Add finalizer if it doesn't exist
  93. // This ensures the ClusterExternalSecret cannot be deleted until we've cleaned up all
  94. // ExternalSecrets it created and removed our finalizers from namespaces.
  95. if updated := controllerutil.AddFinalizer(&clusterExternalSecret, ClusterExternalSecretFinalizer); updated {
  96. if err := r.Update(ctx, &clusterExternalSecret); err != nil {
  97. return ctrl.Result{}, err
  98. }
  99. // Return immediately after update to let the change propagate
  100. return ctrl.Result{}, nil
  101. }
  102. p := client.MergeFrom(clusterExternalSecret.DeepCopy())
  103. defer r.deferPatch(ctx, log, &clusterExternalSecret, p)
  104. return r.reconcile(ctx, log, &clusterExternalSecret)
  105. }
  106. func (r *Reconciler) reconcile(ctx context.Context, log logr.Logger, clusterExternalSecret *esv1.ClusterExternalSecret) (ctrl.Result, error) {
  107. refreshInt := r.RequeueInterval
  108. if clusterExternalSecret.Spec.RefreshInterval != nil {
  109. refreshInt = clusterExternalSecret.Spec.RefreshInterval.Duration
  110. }
  111. esName := clusterExternalSecret.Spec.ExternalSecretName
  112. if esName == "" {
  113. esName = clusterExternalSecret.ObjectMeta.Name
  114. }
  115. if prevName := clusterExternalSecret.Status.ExternalSecretName; prevName != "" && prevName != esName {
  116. // ExternalSecretName has changed, so remove the old ones
  117. if err := r.removeOldSecrets(ctx, log, clusterExternalSecret, prevName); err != nil {
  118. return ctrl.Result{}, err
  119. }
  120. }
  121. clusterExternalSecret.Status.ExternalSecretName = esName
  122. selectors := []*metav1.LabelSelector{}
  123. if s := clusterExternalSecret.Spec.NamespaceSelector; s != nil {
  124. selectors = append(selectors, s)
  125. }
  126. selectors = append(selectors, clusterExternalSecret.Spec.NamespaceSelectors...)
  127. namespaces, err := utils.GetTargetNamespaces(ctx, r.Client, clusterExternalSecret.Spec.Namespaces, selectors)
  128. if err != nil {
  129. log.Error(err, "failed to get target Namespaces")
  130. failedNamespaces := map[string]error{
  131. "unknown": err,
  132. }
  133. condition := NewClusterExternalSecretCondition(failedNamespaces)
  134. SetClusterExternalSecretCondition(clusterExternalSecret, *condition)
  135. clusterExternalSecret.Status.FailedNamespaces = toNamespaceFailures(failedNamespaces)
  136. return ctrl.Result{}, err
  137. }
  138. failedNamespaces := r.deleteOutdatedExternalSecrets(ctx, namespaces, esName, clusterExternalSecret.Name, clusterExternalSecret.Status.ProvisionedNamespaces)
  139. provisionedNamespaces := r.gatherProvisionedNamespaces(ctx, log, clusterExternalSecret, namespaces, esName, failedNamespaces)
  140. condition := NewClusterExternalSecretCondition(failedNamespaces)
  141. SetClusterExternalSecretCondition(clusterExternalSecret, *condition)
  142. clusterExternalSecret.Status.FailedNamespaces = toNamespaceFailures(failedNamespaces)
  143. sort.Strings(provisionedNamespaces)
  144. clusterExternalSecret.Status.ProvisionedNamespaces = provisionedNamespaces
  145. // Check if any failures are due to conflicts - if so, requeue immediately
  146. for _, err := range failedNamespaces {
  147. if apierrors.IsConflict(err) {
  148. log.V(1).Info("conflict detected, requeuing immediately")
  149. return ctrl.Result{}, fmt.Errorf("conflict detected, will retry: %w", err)
  150. }
  151. }
  152. return ctrl.Result{RequeueAfter: refreshInt}, nil
  153. }
  154. func (r *Reconciler) gatherProvisionedNamespaces(
  155. ctx context.Context,
  156. log logr.Logger,
  157. clusterExternalSecret *esv1.ClusterExternalSecret,
  158. namespaces []v1.Namespace,
  159. esName string,
  160. failedNamespaces map[string]error,
  161. ) []string {
  162. var provisionedNamespaces []string //nolint:prealloc // we don't know the size
  163. for _, namespace := range namespaces {
  164. // Skip namespace if it's being deleted
  165. if namespace.DeletionTimestamp != nil {
  166. log.Info("skipping namespace as it is being deleted", "namespace", namespace.Name)
  167. continue
  168. }
  169. var existingES esv1.ExternalSecret
  170. err := r.Get(ctx, types.NamespacedName{
  171. Name: esName,
  172. Namespace: namespace.Name,
  173. }, &existingES)
  174. if err != nil && !apierrors.IsNotFound(err) {
  175. log.Error(err, errGetExistingES)
  176. failedNamespaces[namespace.Name] = err
  177. continue
  178. }
  179. if err == nil && !isExternalSecretOwnedBy(&existingES, clusterExternalSecret.Name) {
  180. failedNamespaces[namespace.Name] = errors.New("external secret already exists in namespace")
  181. continue
  182. }
  183. if err := r.createOrUpdateExternalSecret(ctx, clusterExternalSecret, namespace, esName, clusterExternalSecret.Spec.ExternalSecretMetadata); err != nil {
  184. // If conflict, don't log as error - just add to failed namespaces for retry
  185. if apierrors.IsConflict(err) {
  186. log.V(1).Info("conflict while updating namespace, will retry", "namespace", namespace.Name)
  187. failedNamespaces[namespace.Name] = err
  188. continue
  189. }
  190. log.Error(err, "failed to create or update external secret")
  191. failedNamespaces[namespace.Name] = err
  192. continue
  193. }
  194. provisionedNamespaces = append(provisionedNamespaces, namespace.Name)
  195. }
  196. return provisionedNamespaces
  197. }
  198. func (r *Reconciler) removeOldSecrets(ctx context.Context, log logr.Logger, clusterExternalSecret *esv1.ClusterExternalSecret, prevName string) error {
  199. var (
  200. failedNamespaces = map[string]error{}
  201. lastErr error
  202. )
  203. for _, ns := range clusterExternalSecret.Status.ProvisionedNamespaces {
  204. if err := r.deleteExternalSecret(ctx, prevName, clusterExternalSecret.Name, ns); err != nil {
  205. log.Error(err, "could not delete ExternalSecret")
  206. failedNamespaces[ns] = err
  207. lastErr = err
  208. }
  209. }
  210. if len(failedNamespaces) > 0 {
  211. condition := NewClusterExternalSecretCondition(failedNamespaces)
  212. SetClusterExternalSecretCondition(clusterExternalSecret, *condition)
  213. clusterExternalSecret.Status.FailedNamespaces = toNamespaceFailures(failedNamespaces)
  214. return lastErr
  215. }
  216. return nil
  217. }
  218. // cleanupExternalSecrets removes all ExternalSecrets created by this ClusterExternalSecret
  219. // and removes the namespace finalizers we added. This uses a dual-finalizer strategy:
  220. // 1. ClusterExternalSecret has a finalizer to ensure cleanup happens
  221. // 2. Each namespace gets a CES-specific finalizer to prevent deletion race conditions
  222. // The namespace finalizer is named with the CES name to allow multiple CES resources
  223. // to provision into the same namespace independently.
  224. func (r *Reconciler) cleanupExternalSecrets(ctx context.Context, log logr.Logger, clusterExternalSecret *esv1.ClusterExternalSecret) error {
  225. esName := r.getExternalSecretName(clusterExternalSecret)
  226. var err error
  227. for _, ns := range clusterExternalSecret.Status.ProvisionedNamespaces {
  228. if cleanupErr := r.cleanupNamespaceResources(ctx, log, clusterExternalSecret, ns, esName); cleanupErr != nil {
  229. err = errors.Join(err, cleanupErr)
  230. }
  231. }
  232. return err
  233. }
  234. // getExternalSecretName returns the name to use for ExternalSecrets.
  235. func (r *Reconciler) getExternalSecretName(clusterExternalSecret *esv1.ClusterExternalSecret) string {
  236. if clusterExternalSecret.Status.ExternalSecretName != "" {
  237. return clusterExternalSecret.Status.ExternalSecretName
  238. }
  239. return clusterExternalSecret.ObjectMeta.Name
  240. }
  241. // cleanupNamespaceResources handles cleanup for a single namespace.
  242. func (r *Reconciler) cleanupNamespaceResources(ctx context.Context, log logr.Logger, clusterExternalSecret *esv1.ClusterExternalSecret, namespaceName, esName string) error {
  243. // Get the namespace
  244. namespace, err := r.getNamespace(ctx, namespaceName)
  245. if err != nil {
  246. if apierrors.IsNotFound(err) {
  247. // Namespace already deleted, that's ok
  248. return nil
  249. }
  250. return fmt.Errorf("failed to get namespace %s: %w", namespaceName, err)
  251. }
  252. // Remove namespace finalizer
  253. if err := r.removeNamespaceFinalizer(ctx, log, namespace, clusterExternalSecret.Name); err != nil {
  254. return err
  255. }
  256. // Delete ExternalSecret
  257. if err := r.deleteExternalSecret(ctx, esName, clusterExternalSecret.Name, namespaceName); err != nil {
  258. return fmt.Errorf("failed to delete ExternalSecret in namespace %s: %w", namespaceName, err)
  259. }
  260. return nil
  261. }
  262. // getNamespace fetches a namespace by name.
  263. func (r *Reconciler) getNamespace(ctx context.Context, name string) (*v1.Namespace, error) {
  264. var namespace v1.Namespace
  265. err := r.Get(ctx, types.NamespacedName{Name: name}, &namespace)
  266. return &namespace, err
  267. }
  268. // removeNamespaceFinalizer removes the CES-specific finalizer from a namespace.
  269. func (r *Reconciler) removeNamespaceFinalizer(ctx context.Context, log logr.Logger, namespace *v1.Namespace, cesName string) error {
  270. finalizer := r.buildCESFinalizer(cesName)
  271. if !controllerutil.ContainsFinalizer(namespace, finalizer) {
  272. return nil // Finalizer doesn't exist, nothing to do
  273. }
  274. return r.updateNamespaceRemoveFinalizer(ctx, log, namespace.Name, finalizer)
  275. }
  276. // buildCESFinalizer creates the finalizer name for a CES.
  277. func (r *Reconciler) buildCESFinalizer(cesName string) string {
  278. return "externalsecrets.external-secrets.io/ces-" + cesName
  279. }
  280. // updateNamespaceRemoveFinalizer removes a finalizer from a namespace with conflict handling.
  281. func (r *Reconciler) updateNamespaceRemoveFinalizer(ctx context.Context, log logr.Logger, namespaceName, finalizer string) error {
  282. // Fetch the latest namespace to avoid conflicts
  283. namespace, err := r.getNamespace(ctx, namespaceName)
  284. if err != nil {
  285. if apierrors.IsNotFound(err) {
  286. return nil // Namespace deleted, that's OK
  287. }
  288. return fmt.Errorf("failed to get latest namespace %s: %w", namespaceName, err)
  289. }
  290. // Only update if the finalizer was actually removed
  291. if updated := controllerutil.RemoveFinalizer(namespace, finalizer); updated {
  292. if err := r.Update(ctx, namespace); err != nil {
  293. // Ignore NotFound (namespace deleted) and Conflict (will retry)
  294. if apierrors.IsNotFound(err) || apierrors.IsConflict(err) {
  295. log.V(1).Info("ignoring expected error during finalizer removal",
  296. "namespace", namespaceName,
  297. "error", err.Error())
  298. return nil
  299. }
  300. return fmt.Errorf("failed to remove finalizer from namespace %s: %w", namespaceName, err)
  301. }
  302. }
  303. return nil
  304. }
  305. func (r *Reconciler) createOrUpdateExternalSecret(ctx context.Context, clusterExternalSecret *esv1.ClusterExternalSecret, namespace v1.Namespace, esName string, esMetadata esv1.ExternalSecretMetadata) error {
  306. // Add namespace finalizer first to prevent deletion race conditions
  307. if err := r.ensureNamespaceFinalizer(ctx, &namespace, clusterExternalSecret.Name); err != nil {
  308. return err
  309. }
  310. // Create or update the ExternalSecret
  311. externalSecret := &esv1.ExternalSecret{
  312. ObjectMeta: metav1.ObjectMeta{
  313. Namespace: namespace.Name,
  314. Name: esName,
  315. },
  316. }
  317. mutateFunc := func() error {
  318. externalSecret.Labels = esMetadata.Labels
  319. externalSecret.Annotations = esMetadata.Annotations
  320. if value, ok := clusterExternalSecret.Annotations[esv1.AnnotationForceSync]; ok {
  321. if externalSecret.Annotations == nil {
  322. externalSecret.Annotations = map[string]string{}
  323. }
  324. externalSecret.Annotations[esv1.AnnotationForceSync] = value
  325. } else {
  326. delete(externalSecret.Annotations, esv1.AnnotationForceSync)
  327. }
  328. externalSecret.Spec = clusterExternalSecret.Spec.ExternalSecretSpec
  329. if err := controllerutil.SetControllerReference(clusterExternalSecret, externalSecret, r.Scheme); err != nil {
  330. return fmt.Errorf("could not set the controller owner reference %w", err)
  331. }
  332. return nil
  333. }
  334. if _, err := ctrl.CreateOrUpdate(ctx, r.Client, externalSecret, mutateFunc); err != nil {
  335. return fmt.Errorf("could not create or update ExternalSecret: %w", err)
  336. }
  337. return nil
  338. }
  339. // ensureNamespaceFinalizer adds a CES-specific finalizer to the namespace if it doesn't exist.
  340. // This prevents the namespace from being deleted while we're managing ExternalSecrets in it.
  341. func (r *Reconciler) ensureNamespaceFinalizer(ctx context.Context, namespace *v1.Namespace, cesName string) error {
  342. finalizer := r.buildCESFinalizer(cesName)
  343. if controllerutil.ContainsFinalizer(namespace, finalizer) {
  344. return nil // Already has finalizer
  345. }
  346. return r.addNamespaceFinalizer(ctx, namespace.Name, finalizer)
  347. }
  348. // addNamespaceFinalizer adds a finalizer to a namespace with conflict handling.
  349. func (r *Reconciler) addNamespaceFinalizer(ctx context.Context, namespaceName, finalizer string) error {
  350. // Fetch the latest namespace to avoid conflicts
  351. namespace, err := r.getNamespace(ctx, namespaceName)
  352. if err != nil {
  353. return fmt.Errorf("could not get latest namespace: %w", err)
  354. }
  355. // Only update if the finalizer was actually added
  356. if updated := controllerutil.AddFinalizer(namespace, finalizer); updated {
  357. if err := r.Update(ctx, namespace); err != nil {
  358. // If conflict, return error to trigger requeue
  359. if apierrors.IsConflict(err) {
  360. return err
  361. }
  362. return fmt.Errorf("could not add namespace finalizer: %w", err)
  363. }
  364. }
  365. return nil
  366. }
  367. func (r *Reconciler) deleteExternalSecret(ctx context.Context, esName, cesName, namespace string) error {
  368. var existingES esv1.ExternalSecret
  369. err := r.Get(ctx, types.NamespacedName{
  370. Name: esName,
  371. Namespace: namespace,
  372. }, &existingES)
  373. if err != nil {
  374. // If we can't find it then just leave
  375. if apierrors.IsNotFound(err) {
  376. return nil
  377. }
  378. return err
  379. }
  380. if !isExternalSecretOwnedBy(&existingES, cesName) {
  381. return nil
  382. }
  383. err = r.Delete(ctx, &existingES, &client.DeleteOptions{})
  384. if err != nil {
  385. return fmt.Errorf("external secret in non matching namespace could not be deleted: %w", err)
  386. }
  387. return nil
  388. }
  389. func (r *Reconciler) deferPatch(ctx context.Context, log logr.Logger, clusterExternalSecret *esv1.ClusterExternalSecret, p client.Patch) {
  390. if err := r.Status().Patch(ctx, clusterExternalSecret, p); err != nil {
  391. log.Error(err, errPatchStatus)
  392. }
  393. }
  394. func (r *Reconciler) deleteOutdatedExternalSecrets(ctx context.Context, namespaces []v1.Namespace, esName, cesName string, provisionedNamespaces []string) map[string]error {
  395. failedNamespaces := map[string]error{}
  396. // Loop through existing namespaces first to make sure they still have our labels
  397. for _, namespace := range getRemovedNamespaces(namespaces, provisionedNamespaces) {
  398. err := r.deleteExternalSecret(ctx, esName, cesName, namespace)
  399. if err != nil {
  400. r.Log.Error(err, "unable to delete external secret")
  401. failedNamespaces[namespace] = err
  402. }
  403. }
  404. return failedNamespaces
  405. }
  406. func isExternalSecretOwnedBy(es *esv1.ExternalSecret, cesName string) bool {
  407. owner := metav1.GetControllerOf(es)
  408. return owner != nil && schema.FromAPIVersionAndKind(owner.APIVersion, owner.Kind).GroupKind().String() == esv1.ClusterExtSecretGroupKind && owner.Name == cesName
  409. }
  410. func getRemovedNamespaces(currentNSs []v1.Namespace, provisionedNSs []string) []string {
  411. currentNSSet := map[string]struct{}{}
  412. for _, currentNs := range currentNSs {
  413. currentNSSet[currentNs.Name] = struct{}{}
  414. }
  415. var removedNSs []string
  416. for _, ns := range provisionedNSs {
  417. if _, ok := currentNSSet[ns]; !ok {
  418. removedNSs = append(removedNSs, ns)
  419. }
  420. }
  421. return removedNSs
  422. }
  423. func toNamespaceFailures(failedNamespaces map[string]error) []esv1.ClusterExternalSecretNamespaceFailure {
  424. namespaceFailures := make([]esv1.ClusterExternalSecretNamespaceFailure, len(failedNamespaces))
  425. i := 0
  426. for namespace, err := range failedNamespaces {
  427. namespaceFailures[i] = esv1.ClusterExternalSecretNamespaceFailure{
  428. Namespace: namespace,
  429. Reason: err.Error(),
  430. }
  431. i++
  432. }
  433. sort.Slice(namespaceFailures, func(i, j int) bool { return namespaceFailures[i].Namespace < namespaceFailures[j].Namespace })
  434. return namespaceFailures
  435. }
  436. // SetupWithManager sets up the controller with the Manager.
  437. func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
  438. return ctrl.NewControllerManagedBy(mgr).
  439. WithOptions(opts).
  440. For(&esv1.ClusterExternalSecret{}).
  441. Owns(&esv1.ExternalSecret{}).
  442. Watches(
  443. &v1.Namespace{},
  444. handler.EnqueueRequestsFromMapFunc(r.findObjectsForNamespace),
  445. builder.WithPredicates(utils.NamespacePredicate()),
  446. ).
  447. Complete(r)
  448. }
  449. func (r *Reconciler) findObjectsForNamespace(ctx context.Context, namespace client.Object) []reconcile.Request {
  450. var clusterExternalSecrets esv1.ClusterExternalSecretList
  451. if err := r.List(ctx, &clusterExternalSecrets); err != nil {
  452. r.Log.Error(err, errGetCES)
  453. return []reconcile.Request{}
  454. }
  455. return r.queueRequestsForItem(&clusterExternalSecrets, namespace)
  456. }
  457. func (r *Reconciler) queueRequestsForItem(clusterExternalSecrets *esv1.ClusterExternalSecretList, namespace client.Object) []reconcile.Request {
  458. var requests []reconcile.Request
  459. for i := range clusterExternalSecrets.Items {
  460. clusterExternalSecret := clusterExternalSecrets.Items[i]
  461. var selectors []*metav1.LabelSelector
  462. if s := clusterExternalSecret.Spec.NamespaceSelector; s != nil {
  463. selectors = append(selectors, s)
  464. }
  465. selectors = append(selectors, clusterExternalSecret.Spec.NamespaceSelectors...)
  466. var selected bool
  467. for _, selector := range selectors {
  468. labelSelector, err := metav1.LabelSelectorAsSelector(selector)
  469. if err != nil {
  470. r.Log.Error(err, errConvertLabelSelector)
  471. continue
  472. }
  473. if labelSelector.Matches(labels.Set(namespace.GetLabels())) {
  474. requests = append(requests, reconcile.Request{
  475. NamespacedName: types.NamespacedName{
  476. Name: clusterExternalSecret.GetName(),
  477. Namespace: clusterExternalSecret.GetNamespace(),
  478. },
  479. })
  480. selected = true
  481. break
  482. }
  483. }
  484. // Prevent the object from being added twice if it happens to be listed
  485. // by Namespaces selector as well.
  486. if selected {
  487. continue
  488. }
  489. if slices.Contains(clusterExternalSecret.Spec.Namespaces, namespace.GetName()) {
  490. requests = append(requests, reconcile.Request{
  491. NamespacedName: types.NamespacedName{
  492. Name: clusterExternalSecret.GetName(),
  493. Namespace: clusterExternalSecret.GetNamespace(),
  494. },
  495. })
  496. }
  497. }
  498. return requests
  499. }