clusterexternalsecret_controller.go 12 KB

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