clusterexternalsecret_controller.go 8.7 KB

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