clusterexternalsecret_controller.go 13 KB

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