clusterexternalsecret_controller.go 8.7 KB

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