clusterexternalsecret_controller.go 22 KB

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