clusterexternalsecret_controller.go 11 KB

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