clusterexternalsecret_controller.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  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/runtime/schema"
  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. )
  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. return ctrl.Result{}, nil
  66. }
  67. log.Error(err, errGetCES)
  68. return ctrl.Result{}, err
  69. }
  70. p := client.MergeFrom(clusterExternalSecret.DeepCopy())
  71. defer r.deferPatch(ctx, log, &clusterExternalSecret, p)
  72. refreshInt := r.RequeueInterval
  73. if clusterExternalSecret.Spec.RefreshInterval != nil {
  74. refreshInt = clusterExternalSecret.Spec.RefreshInterval.Duration
  75. }
  76. labelSelector, err := metav1.LabelSelectorAsSelector(&clusterExternalSecret.Spec.NamespaceSelector)
  77. if err != nil {
  78. log.Error(err, errConvertLabelSelector)
  79. return ctrl.Result{RequeueAfter: refreshInt}, err
  80. }
  81. namespaceList := v1.NamespaceList{}
  82. err = r.List(ctx, &namespaceList, &client.ListOptions{LabelSelector: labelSelector})
  83. if err != nil {
  84. log.Error(err, errNamespaces)
  85. return ctrl.Result{RequeueAfter: refreshInt}, err
  86. }
  87. esName := clusterExternalSecret.Spec.ExternalSecretName
  88. if esName == "" {
  89. esName = clusterExternalSecret.ObjectMeta.Name
  90. }
  91. failedNamespaces := r.deleteOutdatedExternalSecrets(ctx, namespaceList, esName, clusterExternalSecret.Name, clusterExternalSecret.Status.ProvisionedNamespaces)
  92. provisionedNamespaces := []string{}
  93. for _, namespace := range namespaceList.Items {
  94. existingES, err := r.getExternalSecret(ctx, namespace.Name, esName)
  95. if err != nil && !apierrors.IsNotFound(err) {
  96. log.Error(err, errGetExistingES)
  97. failedNamespaces[namespace.Name] = err
  98. continue
  99. }
  100. if err == nil && !isExternalSecretOwnedBy(existingES, clusterExternalSecret.Name) {
  101. failedNamespaces[namespace.Name] = fmt.Errorf("external secret already exists in namespace")
  102. continue
  103. }
  104. if err := r.createOrUpdateExternalSecret(ctx, &clusterExternalSecret, namespace, esName, clusterExternalSecret.Spec.ExternalSecretMetadata); err != nil {
  105. log.Error(err, "failed to create or update external secret")
  106. failedNamespaces[namespace.Name] = err
  107. continue
  108. }
  109. provisionedNamespaces = append(provisionedNamespaces, namespace.Name)
  110. }
  111. condition := NewClusterExternalSecretCondition(failedNamespaces, &namespaceList)
  112. SetClusterExternalSecretCondition(&clusterExternalSecret, *condition)
  113. clusterExternalSecret.Status.FailedNamespaces = toNamespaceFailures(failedNamespaces)
  114. sort.Strings(provisionedNamespaces)
  115. clusterExternalSecret.Status.ProvisionedNamespaces = provisionedNamespaces
  116. return ctrl.Result{RequeueAfter: refreshInt}, nil
  117. }
  118. func (r *Reconciler) createOrUpdateExternalSecret(ctx context.Context, clusterExternalSecret *esv1beta1.ClusterExternalSecret, namespace v1.Namespace, esName string, esMetadata esv1beta1.ExternalSecretMetadata) error {
  119. externalSecret := &esv1beta1.ExternalSecret{
  120. ObjectMeta: metav1.ObjectMeta{
  121. Namespace: namespace.Name,
  122. Name: esName,
  123. },
  124. }
  125. mutateFunc := func() error {
  126. externalSecret.Labels = esMetadata.Labels
  127. externalSecret.Annotations = esMetadata.Annotations
  128. externalSecret.Spec = clusterExternalSecret.Spec.ExternalSecretSpec
  129. if err := controllerutil.SetControllerReference(clusterExternalSecret, externalSecret, r.Scheme); err != nil {
  130. return fmt.Errorf("could not set the controller owner reference %w", err)
  131. }
  132. return nil
  133. }
  134. if _, err := ctrl.CreateOrUpdate(ctx, r.Client, externalSecret, mutateFunc); err != nil {
  135. return fmt.Errorf("could not create or update ExternalSecret: %w", err)
  136. }
  137. return nil
  138. }
  139. func (r *Reconciler) deleteExternalSecret(ctx context.Context, esName, cesName, namespace string) error {
  140. existingES, err := r.getExternalSecret(ctx, namespace, esName)
  141. if err != nil {
  142. // If we can't find it then just leave
  143. if apierrors.IsNotFound(err) {
  144. return nil
  145. }
  146. return err
  147. }
  148. if !isExternalSecretOwnedBy(existingES, cesName) {
  149. return nil
  150. }
  151. err = r.Delete(ctx, existingES, &client.DeleteOptions{})
  152. if err != nil {
  153. return fmt.Errorf("external secret in non matching namespace could not be deleted: %w", err)
  154. }
  155. return nil
  156. }
  157. func (r *Reconciler) deferPatch(ctx context.Context, log logr.Logger, clusterExternalSecret *esv1beta1.ClusterExternalSecret, p client.Patch) {
  158. if err := r.Status().Patch(ctx, clusterExternalSecret, p); err != nil {
  159. log.Error(err, errPatchStatus)
  160. }
  161. }
  162. func (r *Reconciler) deleteOutdatedExternalSecrets(ctx context.Context, namespaceList v1.NamespaceList, esName, cesName string, provisionedNamespaces []string) map[string]error {
  163. failedNamespaces := map[string]error{}
  164. // Loop through existing namespaces first to make sure they still have our labels
  165. for _, namespace := range getRemovedNamespaces(namespaceList, provisionedNamespaces) {
  166. err := r.deleteExternalSecret(ctx, esName, cesName, namespace)
  167. if err != nil {
  168. r.Log.Error(err, "unable to delete external secret")
  169. failedNamespaces[namespace] = err
  170. }
  171. }
  172. return failedNamespaces
  173. }
  174. func (r *Reconciler) getExternalSecret(ctx context.Context, namespace, name string) (*metav1.PartialObjectMetadata, error) {
  175. // Should not use esv1beta1.ExternalSecret since we specify builder.OnlyMetadata and cache only metadata
  176. metadata := metav1.PartialObjectMetadata{}
  177. metadata.SetGroupVersionKind(schema.GroupVersionKind{
  178. Group: esv1beta1.Group,
  179. Version: esv1beta1.Version,
  180. Kind: esv1beta1.ExtSecretKind,
  181. })
  182. err := r.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, &metadata)
  183. return &metadata, err
  184. }
  185. func isExternalSecretOwnedBy(es *metav1.PartialObjectMetadata, cesName string) bool {
  186. owner := metav1.GetControllerOf(es)
  187. return owner != nil && owner.APIVersion == esv1beta1.SchemeGroupVersion.String() && owner.Kind == esv1beta1.ClusterExtSecretKind && owner.Name == cesName
  188. }
  189. func getRemovedNamespaces(currentNSs v1.NamespaceList, provisionedNSs []string) []string {
  190. currentNSSet := map[string]struct{}{}
  191. for i := range currentNSs.Items {
  192. currentNSSet[currentNSs.Items[i].Name] = struct{}{}
  193. }
  194. var removedNSs []string
  195. for _, ns := range provisionedNSs {
  196. if _, ok := currentNSSet[ns]; !ok {
  197. removedNSs = append(removedNSs, ns)
  198. }
  199. }
  200. return removedNSs
  201. }
  202. func toNamespaceFailures(failedNamespaces map[string]error) []esv1beta1.ClusterExternalSecretNamespaceFailure {
  203. namespaceFailures := make([]esv1beta1.ClusterExternalSecretNamespaceFailure, len(failedNamespaces))
  204. i := 0
  205. for namespace, err := range failedNamespaces {
  206. namespaceFailures[i] = esv1beta1.ClusterExternalSecretNamespaceFailure{
  207. Namespace: namespace,
  208. Reason: err.Error(),
  209. }
  210. i++
  211. }
  212. sort.Slice(namespaceFailures, func(i, j int) bool { return namespaceFailures[i].Namespace < namespaceFailures[j].Namespace })
  213. return namespaceFailures
  214. }
  215. // SetupWithManager sets up the controller with the Manager.
  216. func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
  217. return ctrl.NewControllerManagedBy(mgr).
  218. WithOptions(opts).
  219. For(&esv1beta1.ClusterExternalSecret{}).
  220. Owns(&esv1beta1.ExternalSecret{}, builder.OnlyMetadata).
  221. Watches(
  222. &v1.Namespace{},
  223. handler.EnqueueRequestsFromMapFunc(r.findObjectsForNamespace),
  224. builder.WithPredicates(namespacePredicate()),
  225. ).
  226. Complete(r)
  227. }
  228. func (r *Reconciler) findObjectsForNamespace(ctx context.Context, namespace client.Object) []reconcile.Request {
  229. namespaceLabels := labels.Set(namespace.GetLabels())
  230. // Avoid consuming too much memory
  231. const limit = 100
  232. var requests []reconcile.Request
  233. options := &client.ListOptions{Limit: limit}
  234. for {
  235. var clusterExternalSecrets esv1beta1.ClusterExternalSecretList
  236. if err := r.List(ctx, &clusterExternalSecrets, options); err != nil {
  237. r.Log.Error(err, errGetCES)
  238. return []reconcile.Request{}
  239. }
  240. for i := range clusterExternalSecrets.Items {
  241. clusterExternalSecret := &clusterExternalSecrets.Items[i]
  242. labelSelector, err := metav1.LabelSelectorAsSelector(&clusterExternalSecret.Spec.NamespaceSelector)
  243. if err != nil {
  244. r.Log.Error(err, errConvertLabelSelector)
  245. return []reconcile.Request{}
  246. }
  247. if labelSelector.Matches(namespaceLabels) {
  248. requests = append(requests, reconcile.Request{
  249. NamespacedName: types.NamespacedName{
  250. Name: clusterExternalSecret.GetName(),
  251. Namespace: clusterExternalSecret.GetNamespace(),
  252. },
  253. })
  254. }
  255. }
  256. if clusterExternalSecrets.Continue == "" {
  257. break
  258. }
  259. options = &client.ListOptions{
  260. Limit: limit,
  261. Continue: clusterExternalSecrets.Continue,
  262. }
  263. }
  264. return requests
  265. }
  266. func namespacePredicate() predicate.Predicate {
  267. return predicate.Funcs{
  268. CreateFunc: func(e event.CreateEvent) bool {
  269. return true
  270. },
  271. UpdateFunc: func(e event.UpdateEvent) bool {
  272. if e.ObjectOld == nil || e.ObjectNew == nil {
  273. return false
  274. }
  275. return !reflect.DeepEqual(e.ObjectOld.GetLabels(), e.ObjectNew.GetLabels())
  276. },
  277. DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
  278. return true
  279. },
  280. }
  281. }