clusterexternalsecret_controller.go 12 KB

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