clusterexternalsecret_controller.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  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. "fmt"
  16. "reflect"
  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/types"
  27. ctrl "sigs.k8s.io/controller-runtime"
  28. "sigs.k8s.io/controller-runtime/pkg/builder"
  29. "sigs.k8s.io/controller-runtime/pkg/client"
  30. "sigs.k8s.io/controller-runtime/pkg/controller"
  31. "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
  32. "sigs.k8s.io/controller-runtime/pkg/event"
  33. "sigs.k8s.io/controller-runtime/pkg/handler"
  34. "sigs.k8s.io/controller-runtime/pkg/predicate"
  35. "sigs.k8s.io/controller-runtime/pkg/reconcile"
  36. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  37. "github.com/external-secrets/external-secrets/pkg/controllers/clusterexternalsecret/cesmetrics"
  38. ctrlmetrics "github.com/external-secrets/external-secrets/pkg/controllers/metrics"
  39. )
  40. // ClusterExternalSecretReconciler 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. )
  54. func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
  55. log := r.Log.WithValues("ClusterExternalSecret", req.NamespacedName)
  56. resourceLabels := ctrlmetrics.RefineNonConditionMetricLabels(map[string]string{"name": req.Name, "namespace": req.Namespace})
  57. start := time.Now()
  58. externalSecretReconcileDuration := cesmetrics.GetGaugeVec(cesmetrics.ClusterExternalSecretReconcileDurationKey)
  59. defer func() { externalSecretReconcileDuration.With(resourceLabels).Set(float64(time.Since(start))) }()
  60. var clusterExternalSecret esv1beta1.ClusterExternalSecret
  61. err := r.Get(ctx, req.NamespacedName, &clusterExternalSecret)
  62. if err != nil {
  63. if apierrors.IsNotFound(err) {
  64. cesmetrics.RemoveMetrics(req.Namespace, req.Name)
  65. return ctrl.Result{}, nil
  66. }
  67. log.Error(err, errGetCES)
  68. return ctrl.Result{}, err
  69. }
  70. // skip reconciliation if deletion timestamp is set on cluster external secret
  71. if clusterExternalSecret.DeletionTimestamp != nil {
  72. log.Info("skipping as it is in deletion")
  73. return ctrl.Result{}, nil
  74. }
  75. p := client.MergeFrom(clusterExternalSecret.DeepCopy())
  76. defer r.deferPatch(ctx, log, &clusterExternalSecret, p)
  77. refreshInt := r.RequeueInterval
  78. if clusterExternalSecret.Spec.RefreshInterval != nil {
  79. refreshInt = clusterExternalSecret.Spec.RefreshInterval.Duration
  80. }
  81. esName := clusterExternalSecret.Spec.ExternalSecretName
  82. if esName == "" {
  83. esName = clusterExternalSecret.ObjectMeta.Name
  84. }
  85. if prevName := clusterExternalSecret.Status.ExternalSecretName; prevName != esName {
  86. // ExternalSecretName has changed, so remove the old ones
  87. for _, ns := range clusterExternalSecret.Status.ProvisionedNamespaces {
  88. if err := r.deleteExternalSecret(ctx, prevName, clusterExternalSecret.Name, ns); err != nil {
  89. log.Error(err, "could not delete ExternalSecret")
  90. return ctrl.Result{}, err
  91. }
  92. }
  93. }
  94. clusterExternalSecret.Status.ExternalSecretName = esName
  95. namespaces, err := r.getTargetNamespaces(ctx, &clusterExternalSecret)
  96. if err != nil {
  97. log.Error(err, "failed to get target Namespaces")
  98. return ctrl.Result{}, err
  99. }
  100. failedNamespaces := r.deleteOutdatedExternalSecrets(ctx, namespaces, esName, clusterExternalSecret.Name, clusterExternalSecret.Status.ProvisionedNamespaces)
  101. provisionedNamespaces := []string{}
  102. for _, namespace := range namespaces {
  103. var existingES esv1beta1.ExternalSecret
  104. err = r.Get(ctx, types.NamespacedName{
  105. Name: esName,
  106. Namespace: namespace.Name,
  107. }, &existingES)
  108. if err != nil && !apierrors.IsNotFound(err) {
  109. log.Error(err, errGetExistingES)
  110. failedNamespaces[namespace.Name] = err
  111. continue
  112. }
  113. if err == nil && !isExternalSecretOwnedBy(&existingES, clusterExternalSecret.Name) {
  114. failedNamespaces[namespace.Name] = fmt.Errorf("external secret already exists in namespace")
  115. continue
  116. }
  117. if err := r.createOrUpdateExternalSecret(ctx, &clusterExternalSecret, namespace, esName, clusterExternalSecret.Spec.ExternalSecretMetadata); err != nil {
  118. log.Error(err, "failed to create or update external secret")
  119. failedNamespaces[namespace.Name] = err
  120. continue
  121. }
  122. provisionedNamespaces = append(provisionedNamespaces, namespace.Name)
  123. }
  124. condition := NewClusterExternalSecretCondition(failedNamespaces)
  125. SetClusterExternalSecretCondition(&clusterExternalSecret, *condition)
  126. clusterExternalSecret.Status.FailedNamespaces = toNamespaceFailures(failedNamespaces)
  127. sort.Strings(provisionedNamespaces)
  128. clusterExternalSecret.Status.ProvisionedNamespaces = provisionedNamespaces
  129. return ctrl.Result{RequeueAfter: refreshInt}, nil
  130. }
  131. func (r *Reconciler) getTargetNamespaces(ctx context.Context, ces *esv1beta1.ClusterExternalSecret) ([]v1.Namespace, error) {
  132. selectors := []*metav1.LabelSelector{}
  133. if s := ces.Spec.NamespaceSelector; s != nil {
  134. selectors = append(selectors, s)
  135. }
  136. for _, ns := range ces.Spec.Namespaces {
  137. selectors = append(selectors, &metav1.LabelSelector{
  138. MatchLabels: map[string]string{
  139. "kubernetes.io/metadata.name": ns,
  140. },
  141. })
  142. }
  143. selectors = append(selectors, ces.Spec.NamespaceSelectors...)
  144. var namespaces []v1.Namespace
  145. namespaceSet := make(map[string]struct{})
  146. for _, selector := range selectors {
  147. labelSelector, err := metav1.LabelSelectorAsSelector(selector)
  148. if err != nil {
  149. return nil, fmt.Errorf("failed to convert label selector %s: %w", selector, err)
  150. }
  151. var nl v1.NamespaceList
  152. err = r.List(ctx, &nl, &client.ListOptions{LabelSelector: labelSelector})
  153. if err != nil {
  154. return nil, fmt.Errorf("failed to list namespaces by label selector %s: %w", selector, err)
  155. }
  156. for _, n := range nl.Items {
  157. if _, exist := namespaceSet[n.Name]; exist {
  158. continue
  159. }
  160. namespaceSet[n.Name] = struct{}{}
  161. namespaces = append(namespaces, n)
  162. }
  163. }
  164. return namespaces, nil
  165. }
  166. func (r *Reconciler) createOrUpdateExternalSecret(ctx context.Context, clusterExternalSecret *esv1beta1.ClusterExternalSecret, namespace v1.Namespace, esName string, esMetadata esv1beta1.ExternalSecretMetadata) error {
  167. externalSecret := &esv1beta1.ExternalSecret{
  168. ObjectMeta: metav1.ObjectMeta{
  169. Namespace: namespace.Name,
  170. Name: esName,
  171. },
  172. }
  173. mutateFunc := func() error {
  174. externalSecret.Labels = esMetadata.Labels
  175. externalSecret.Annotations = esMetadata.Annotations
  176. externalSecret.Spec = clusterExternalSecret.Spec.ExternalSecretSpec
  177. if err := controllerutil.SetControllerReference(clusterExternalSecret, externalSecret, r.Scheme); err != nil {
  178. return fmt.Errorf("could not set the controller owner reference %w", err)
  179. }
  180. return nil
  181. }
  182. if _, err := ctrl.CreateOrUpdate(ctx, r.Client, externalSecret, mutateFunc); err != nil {
  183. return fmt.Errorf("could not create or update ExternalSecret: %w", err)
  184. }
  185. return nil
  186. }
  187. func (r *Reconciler) deleteExternalSecret(ctx context.Context, esName, cesName, namespace string) error {
  188. var existingES esv1beta1.ExternalSecret
  189. err := r.Get(ctx, types.NamespacedName{
  190. Name: esName,
  191. Namespace: namespace,
  192. }, &existingES)
  193. if err != nil {
  194. // If we can't find it then just leave
  195. if apierrors.IsNotFound(err) {
  196. return nil
  197. }
  198. return err
  199. }
  200. if !isExternalSecretOwnedBy(&existingES, cesName) {
  201. return nil
  202. }
  203. err = r.Delete(ctx, &existingES, &client.DeleteOptions{})
  204. if err != nil {
  205. return fmt.Errorf("external secret in non matching namespace could not be deleted: %w", err)
  206. }
  207. return nil
  208. }
  209. func (r *Reconciler) deferPatch(ctx context.Context, log logr.Logger, clusterExternalSecret *esv1beta1.ClusterExternalSecret, p client.Patch) {
  210. if err := r.Status().Patch(ctx, clusterExternalSecret, p); err != nil {
  211. log.Error(err, errPatchStatus)
  212. }
  213. }
  214. func (r *Reconciler) deleteOutdatedExternalSecrets(ctx context.Context, namespaces []v1.Namespace, esName, cesName string, provisionedNamespaces []string) map[string]error {
  215. failedNamespaces := map[string]error{}
  216. // Loop through existing namespaces first to make sure they still have our labels
  217. for _, namespace := range getRemovedNamespaces(namespaces, provisionedNamespaces) {
  218. err := r.deleteExternalSecret(ctx, esName, cesName, namespace)
  219. if err != nil {
  220. r.Log.Error(err, "unable to delete external secret")
  221. failedNamespaces[namespace] = err
  222. }
  223. }
  224. return failedNamespaces
  225. }
  226. func isExternalSecretOwnedBy(es *esv1beta1.ExternalSecret, cesName string) bool {
  227. owner := metav1.GetControllerOf(es)
  228. return owner != nil && owner.APIVersion == esv1beta1.SchemeGroupVersion.String() && owner.Kind == esv1beta1.ClusterExtSecretKind && owner.Name == cesName
  229. }
  230. func getRemovedNamespaces(currentNSs []v1.Namespace, provisionedNSs []string) []string {
  231. currentNSSet := map[string]struct{}{}
  232. for _, currentNs := range currentNSs {
  233. currentNSSet[currentNs.Name] = struct{}{}
  234. }
  235. var removedNSs []string
  236. for _, ns := range provisionedNSs {
  237. if _, ok := currentNSSet[ns]; !ok {
  238. removedNSs = append(removedNSs, ns)
  239. }
  240. }
  241. return removedNSs
  242. }
  243. func toNamespaceFailures(failedNamespaces map[string]error) []esv1beta1.ClusterExternalSecretNamespaceFailure {
  244. namespaceFailures := make([]esv1beta1.ClusterExternalSecretNamespaceFailure, len(failedNamespaces))
  245. i := 0
  246. for namespace, err := range failedNamespaces {
  247. namespaceFailures[i] = esv1beta1.ClusterExternalSecretNamespaceFailure{
  248. Namespace: namespace,
  249. Reason: err.Error(),
  250. }
  251. i++
  252. }
  253. sort.Slice(namespaceFailures, func(i, j int) bool { return namespaceFailures[i].Namespace < namespaceFailures[j].Namespace })
  254. return namespaceFailures
  255. }
  256. // SetupWithManager sets up the controller with the Manager.
  257. func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
  258. return ctrl.NewControllerManagedBy(mgr).
  259. WithOptions(opts).
  260. For(&esv1beta1.ClusterExternalSecret{}).
  261. Owns(&esv1beta1.ExternalSecret{}).
  262. Watches(
  263. &v1.Namespace{},
  264. handler.EnqueueRequestsFromMapFunc(r.findObjectsForNamespace),
  265. builder.WithPredicates(namespacePredicate()),
  266. ).
  267. Complete(r)
  268. }
  269. func (r *Reconciler) findObjectsForNamespace(ctx context.Context, namespace client.Object) []reconcile.Request {
  270. var clusterExternalSecrets esv1beta1.ClusterExternalSecretList
  271. if err := r.List(ctx, &clusterExternalSecrets); err != nil {
  272. r.Log.Error(err, errGetCES)
  273. return []reconcile.Request{}
  274. }
  275. var requests []reconcile.Request
  276. for i := range clusterExternalSecrets.Items {
  277. clusterExternalSecret := &clusterExternalSecrets.Items[i]
  278. var selectors []*metav1.LabelSelector
  279. if s := clusterExternalSecret.Spec.NamespaceSelector; s != nil {
  280. selectors = append(selectors, s)
  281. }
  282. selectors = append(selectors, clusterExternalSecret.Spec.NamespaceSelectors...)
  283. var selected bool
  284. for _, selector := range selectors {
  285. labelSelector, err := metav1.LabelSelectorAsSelector(selector)
  286. if err != nil {
  287. r.Log.Error(err, errConvertLabelSelector)
  288. continue
  289. }
  290. if labelSelector.Matches(labels.Set(namespace.GetLabels())) {
  291. requests = append(requests, reconcile.Request{
  292. NamespacedName: types.NamespacedName{
  293. Name: clusterExternalSecret.GetName(),
  294. Namespace: clusterExternalSecret.GetNamespace(),
  295. },
  296. })
  297. selected = true
  298. break
  299. }
  300. }
  301. // Prevent the object from being added twice if it happens to be listed
  302. // by Namespaces selector as well.
  303. if selected {
  304. continue
  305. }
  306. if slices.Contains(clusterExternalSecret.Spec.Namespaces, namespace.GetName()) {
  307. requests = append(requests, reconcile.Request{
  308. NamespacedName: types.NamespacedName{
  309. Name: clusterExternalSecret.GetName(),
  310. Namespace: clusterExternalSecret.GetNamespace(),
  311. },
  312. })
  313. }
  314. }
  315. return requests
  316. }
  317. func namespacePredicate() predicate.Predicate {
  318. return predicate.Funcs{
  319. CreateFunc: func(e event.CreateEvent) bool {
  320. return true
  321. },
  322. UpdateFunc: func(e event.UpdateEvent) bool {
  323. if e.ObjectOld == nil || e.ObjectNew == nil {
  324. return false
  325. }
  326. return !reflect.DeepEqual(e.ObjectOld.GetLabels(), e.ObjectNew.GetLabels())
  327. },
  328. DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
  329. return true
  330. },
  331. }
  332. }