clusterexternalsecret_controller.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  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. "sort"
  16. "time"
  17. "github.com/go-logr/logr"
  18. v1 "k8s.io/api/core/v1"
  19. apierrors "k8s.io/apimachinery/pkg/api/errors"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/apimachinery/pkg/runtime"
  22. "k8s.io/apimachinery/pkg/types"
  23. ctrl "sigs.k8s.io/controller-runtime"
  24. "sigs.k8s.io/controller-runtime/pkg/builder"
  25. "sigs.k8s.io/controller-runtime/pkg/client"
  26. "sigs.k8s.io/controller-runtime/pkg/controller"
  27. "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
  28. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  29. "github.com/external-secrets/external-secrets/pkg/controllers/clusterexternalsecret/cesmetrics"
  30. ctrlmetrics "github.com/external-secrets/external-secrets/pkg/controllers/metrics"
  31. )
  32. // ClusterExternalSecretReconciler reconciles a ClusterExternalSecret object.
  33. type Reconciler struct {
  34. client.Client
  35. Log logr.Logger
  36. Scheme *runtime.Scheme
  37. RequeueInterval time.Duration
  38. }
  39. const (
  40. errGetCES = "could not get ClusterExternalSecret"
  41. errPatchStatus = "unable to patch status"
  42. errConvertLabelSelector = "unable to convert labelselector"
  43. errNamespaces = "could not get namespaces from selector"
  44. errGetExistingES = "could not get existing ExternalSecret"
  45. errCreatingOrUpdating = "could not create or update ExternalSecret"
  46. errSetCtrlReference = "could not set the controller owner reference"
  47. errSecretAlreadyExists = "external secret already exists in namespace"
  48. errNamespacesFailed = "one or more namespaces failed"
  49. errFailedToDelete = "external secret in non matching namespace could not be deleted"
  50. )
  51. func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
  52. log := r.Log.WithValues("ClusterExternalSecret", req.NamespacedName)
  53. resourceLabels := ctrlmetrics.RefineNonConditionMetricLabels(map[string]string{"name": req.Name, "namespace": req.Namespace})
  54. start := time.Now()
  55. externalSecretReconcileDuration := cesmetrics.GetGaugeVec(cesmetrics.ClusterExternalSecretReconcileDurationKey)
  56. defer func() { externalSecretReconcileDuration.With(resourceLabels).Set(float64(time.Since(start))) }()
  57. var clusterExternalSecret esv1beta1.ClusterExternalSecret
  58. err := r.Get(ctx, req.NamespacedName, &clusterExternalSecret)
  59. if apierrors.IsNotFound(err) {
  60. return ctrl.Result{}, nil
  61. } else if err != nil {
  62. log.Error(err, errGetCES)
  63. return ctrl.Result{}, nil
  64. }
  65. p := client.MergeFrom(clusterExternalSecret.DeepCopy())
  66. defer r.deferPatch(ctx, log, &clusterExternalSecret, p)
  67. refreshInt := r.RequeueInterval
  68. if clusterExternalSecret.Spec.RefreshInterval != nil {
  69. refreshInt = clusterExternalSecret.Spec.RefreshInterval.Duration
  70. }
  71. labelSelector, err := metav1.LabelSelectorAsSelector(&clusterExternalSecret.Spec.NamespaceSelector)
  72. if err != nil {
  73. log.Error(err, errConvertLabelSelector)
  74. return ctrl.Result{RequeueAfter: refreshInt}, err
  75. }
  76. namespaceList := v1.NamespaceList{}
  77. err = r.List(ctx, &namespaceList, &client.ListOptions{LabelSelector: labelSelector})
  78. if err != nil {
  79. log.Error(err, errNamespaces)
  80. return ctrl.Result{RequeueAfter: refreshInt}, err
  81. }
  82. esName := clusterExternalSecret.Spec.ExternalSecretName
  83. if esName == "" {
  84. esName = clusterExternalSecret.ObjectMeta.Name
  85. }
  86. failedNamespaces := r.removeOldNamespaces(ctx, namespaceList, esName, clusterExternalSecret.Status.ProvisionedNamespaces)
  87. provisionedNamespaces := []string{}
  88. for _, namespace := range namespaceList.Items {
  89. var existingES esv1beta1.ExternalSecret
  90. err = r.Get(ctx, types.NamespacedName{
  91. Name: esName,
  92. Namespace: namespace.Name,
  93. }, &existingES)
  94. if result := checkForError(err, &existingES); result != "" {
  95. log.Error(err, result)
  96. failedNamespaces[namespace.Name] = result
  97. continue
  98. }
  99. if result, err := r.resolveExternalSecret(ctx, &clusterExternalSecret, &existingES, namespace, esName); err != nil {
  100. log.Error(err, result)
  101. failedNamespaces[namespace.Name] = result
  102. continue
  103. }
  104. provisionedNamespaces = append(provisionedNamespaces, namespace.ObjectMeta.Name)
  105. }
  106. conditionType := getCondition(failedNamespaces, &namespaceList)
  107. condition := NewClusterExternalSecretCondition(conditionType, v1.ConditionTrue)
  108. if conditionType != esv1beta1.ClusterExternalSecretReady {
  109. condition.Message = errNamespacesFailed
  110. }
  111. SetClusterExternalSecretCondition(&clusterExternalSecret, *condition)
  112. setFailedNamespaces(&clusterExternalSecret, failedNamespaces)
  113. if len(provisionedNamespaces) > 0 {
  114. sort.Strings(provisionedNamespaces)
  115. clusterExternalSecret.Status.ProvisionedNamespaces = provisionedNamespaces
  116. }
  117. return ctrl.Result{RequeueAfter: refreshInt}, nil
  118. }
  119. func (r *Reconciler) resolveExternalSecret(ctx context.Context, clusterExternalSecret *esv1beta1.ClusterExternalSecret, existingES *esv1beta1.ExternalSecret, namespace v1.Namespace, esName string) (string, error) {
  120. // this means the existing ES does not belong to us
  121. if err := controllerutil.SetControllerReference(clusterExternalSecret, existingES, r.Scheme); err != nil {
  122. return errSetCtrlReference, err
  123. }
  124. externalSecret := esv1beta1.ExternalSecret{
  125. ObjectMeta: metav1.ObjectMeta{
  126. Name: esName,
  127. Namespace: namespace.Name,
  128. },
  129. Spec: clusterExternalSecret.Spec.ExternalSecretSpec,
  130. }
  131. if err := controllerutil.SetControllerReference(clusterExternalSecret, &externalSecret, r.Scheme); err != nil {
  132. return errSetCtrlReference, err
  133. }
  134. mutateFunc := func() error {
  135. externalSecret.Spec = clusterExternalSecret.Spec.ExternalSecretSpec
  136. return nil
  137. }
  138. // An empty mutate func as nothing needs to happen currently
  139. if _, err := ctrl.CreateOrUpdate(ctx, r.Client, &externalSecret, mutateFunc); err != nil {
  140. return errCreatingOrUpdating, err
  141. }
  142. return "", nil
  143. }
  144. func (r *Reconciler) removeExternalSecret(ctx context.Context, esName, namespace string) (string, error) {
  145. var existingES esv1beta1.ExternalSecret
  146. err := r.Get(ctx, types.NamespacedName{
  147. Name: esName,
  148. Namespace: namespace,
  149. }, &existingES)
  150. // If we can't find it then just leave
  151. if err != nil && apierrors.IsNotFound(err) {
  152. return "", nil
  153. }
  154. if result := checkForError(err, &existingES); result != "" {
  155. return result, err
  156. }
  157. err = r.Delete(ctx, &existingES, &client.DeleteOptions{})
  158. if err != nil {
  159. return errFailedToDelete, err
  160. }
  161. return "", nil
  162. }
  163. func (r *Reconciler) deferPatch(ctx context.Context, log logr.Logger, clusterExternalSecret *esv1beta1.ClusterExternalSecret, p client.Patch) {
  164. if err := r.Status().Patch(ctx, clusterExternalSecret, p); err != nil {
  165. log.Error(err, errPatchStatus)
  166. }
  167. }
  168. func (r *Reconciler) removeOldNamespaces(ctx context.Context, namespaceList v1.NamespaceList, esName string, provisionedNamespaces []string) map[string]string {
  169. failedNamespaces := map[string]string{}
  170. // Loop through existing namespaces first to make sure they still have our labels
  171. for _, namespace := range getRemovedNamespaces(namespaceList, provisionedNamespaces) {
  172. result, err := r.removeExternalSecret(ctx, esName, namespace)
  173. if err != nil {
  174. r.Log.Error(err, "unable to delete external-secret")
  175. }
  176. if result != "" {
  177. failedNamespaces[namespace] = result
  178. }
  179. }
  180. return failedNamespaces
  181. }
  182. func checkForError(getError error, existingES *esv1beta1.ExternalSecret) string {
  183. if getError != nil && !apierrors.IsNotFound(getError) {
  184. return errGetExistingES
  185. }
  186. // No one owns this resource so error out
  187. if !apierrors.IsNotFound(getError) && len(existingES.ObjectMeta.OwnerReferences) == 0 {
  188. return errSecretAlreadyExists
  189. }
  190. return ""
  191. }
  192. func getCondition(namespaces map[string]string, namespaceList *v1.NamespaceList) esv1beta1.ClusterExternalSecretConditionType {
  193. if len(namespaces) == 0 {
  194. return esv1beta1.ClusterExternalSecretReady
  195. }
  196. if len(namespaces) < len(namespaceList.Items) {
  197. return esv1beta1.ClusterExternalSecretPartiallyReady
  198. }
  199. return esv1beta1.ClusterExternalSecretNotReady
  200. }
  201. func getRemovedNamespaces(nsList v1.NamespaceList, provisionedNs []string) []string {
  202. result := []string{}
  203. for _, ns := range provisionedNs {
  204. if !ContainsNamespace(nsList, ns) {
  205. result = append(result, ns)
  206. }
  207. }
  208. return result
  209. }
  210. func setFailedNamespaces(ces *esv1beta1.ClusterExternalSecret, failedNamespaces map[string]string) {
  211. if len(failedNamespaces) == 0 {
  212. return
  213. }
  214. ces.Status.FailedNamespaces = []esv1beta1.ClusterExternalSecretNamespaceFailure{}
  215. for namespace, message := range failedNamespaces {
  216. ces.Status.FailedNamespaces = append(ces.Status.FailedNamespaces, esv1beta1.ClusterExternalSecretNamespaceFailure{
  217. Namespace: namespace,
  218. Reason: message,
  219. })
  220. }
  221. }
  222. // SetupWithManager sets up the controller with the Manager.
  223. func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
  224. return ctrl.NewControllerManagedBy(mgr).
  225. WithOptions(opts).
  226. For(&esv1beta1.ClusterExternalSecret{}).
  227. Owns(&esv1beta1.ExternalSecret{}, builder.OnlyMetadata).
  228. Complete(r)
  229. }