clusterexternalsecret_controller.go 11 KB

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