clusterexternalsecret_controller.go 21 KB

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