| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- /*
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package clusterexternalsecret
- import (
- "context"
- "time"
- "github.com/go-logr/logr"
- v1 "k8s.io/api/core/v1"
- apierrors "k8s.io/apimachinery/pkg/api/errors"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/labels"
- "k8s.io/apimachinery/pkg/runtime"
- "k8s.io/apimachinery/pkg/types"
- ctrl "sigs.k8s.io/controller-runtime"
- "sigs.k8s.io/controller-runtime/pkg/builder"
- "sigs.k8s.io/controller-runtime/pkg/client"
- "sigs.k8s.io/controller-runtime/pkg/controller"
- "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
- esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
- )
- // ClusterExternalSecretReconciler reconciles a ClusterExternalSecret object.
- type Reconciler struct {
- client.Client
- Log logr.Logger
- Scheme *runtime.Scheme
- RequeueInterval time.Duration
- }
- const (
- errGetCES = "could not get ClusterExternalSecret"
- errPatchStatus = "unable to patch status"
- errLabelMap = "unable to get map from labels"
- errNamespaces = "could not get namespaces from selector"
- errGetExistingES = "could not get existing ExternalSecret"
- errCreatingOrUpdating = "could not create or update ExternalSecret"
- errSetCtrlReference = "could not set the controller owner reference"
- errSecretAlreadyExists = "external secret already exists in namespace"
- errNamespacesFailed = "one or more namespaces failed"
- errFailedToDelete = "external secret in non matching namespace could not be deleted"
- )
- func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
- log := r.Log.WithValues("ClusterExternalSecret", req.NamespacedName)
- var clusterExternalSecret esv1beta1.ClusterExternalSecret
- err := r.Get(ctx, req.NamespacedName, &clusterExternalSecret)
- if apierrors.IsNotFound(err) {
- return ctrl.Result{}, nil
- } else if err != nil {
- log.Error(err, errGetCES)
- return ctrl.Result{}, nil
- }
- p := client.MergeFrom(clusterExternalSecret.DeepCopy())
- defer r.deferPatch(ctx, log, &clusterExternalSecret, p)
- refreshInt := r.RequeueInterval
- if clusterExternalSecret.Spec.RefreshInterval != nil {
- refreshInt = clusterExternalSecret.Spec.RefreshInterval.Duration
- }
- labelMap, err := metav1.LabelSelectorAsMap(&clusterExternalSecret.Spec.NamespaceSelector)
- if err != nil {
- log.Error(err, errLabelMap)
- return ctrl.Result{RequeueAfter: refreshInt}, err
- }
- namespaceList := v1.NamespaceList{}
- err = r.List(ctx, &namespaceList, &client.ListOptions{LabelSelector: labels.SelectorFromSet(labelMap)})
- if err != nil {
- log.Error(err, errNamespaces)
- return ctrl.Result{RequeueAfter: refreshInt}, err
- }
- esName := clusterExternalSecret.Spec.ExternalSecretName
- if esName == "" {
- esName = clusterExternalSecret.ObjectMeta.Name
- }
- failedNamespaces := r.removeOldNamespaces(ctx, namespaceList, esName, clusterExternalSecret.Status.ProvisionedNamespaces)
- provisionedNamespaces := []string{}
- for _, namespace := range namespaceList.Items {
- var existingES esv1beta1.ExternalSecret
- err = r.Get(ctx, types.NamespacedName{
- Name: esName,
- Namespace: namespace.Name,
- }, &existingES)
- if result := checkForError(err, &existingES); result != "" {
- log.Error(err, result)
- failedNamespaces[namespace.Name] = result
- continue
- }
- if result, err := r.resolveExternalSecret(ctx, &clusterExternalSecret, &existingES, namespace, esName); err != nil {
- log.Error(err, result)
- failedNamespaces[namespace.Name] = result
- continue
- }
- provisionedNamespaces = append(provisionedNamespaces, namespace.ObjectMeta.Name)
- }
- conditionType := getCondition(failedNamespaces, &namespaceList)
- condition := NewClusterExternalSecretCondition(conditionType, v1.ConditionTrue)
- if conditionType != esv1beta1.ClusterExternalSecretReady {
- condition.Message = errNamespacesFailed
- }
- SetClusterExternalSecretCondition(&clusterExternalSecret, *condition)
- setFailedNamespaces(&clusterExternalSecret, failedNamespaces)
- if len(provisionedNamespaces) > 0 {
- clusterExternalSecret.Status.ProvisionedNamespaces = provisionedNamespaces
- }
- return ctrl.Result{RequeueAfter: refreshInt}, nil
- }
- func (r *Reconciler) resolveExternalSecret(ctx context.Context, clusterExternalSecret *esv1beta1.ClusterExternalSecret, existingES *esv1beta1.ExternalSecret, namespace v1.Namespace, esName string) (string, error) {
- // this means the existing ES does not belong to us
- if err := controllerutil.SetControllerReference(clusterExternalSecret, existingES, r.Scheme); err != nil {
- return errSetCtrlReference, err
- }
- externalSecret := esv1beta1.ExternalSecret{
- ObjectMeta: metav1.ObjectMeta{
- Name: esName,
- Namespace: namespace.Name,
- },
- Spec: clusterExternalSecret.Spec.ExternalSecretSpec,
- }
- if err := controllerutil.SetControllerReference(clusterExternalSecret, &externalSecret, r.Scheme); err != nil {
- return errSetCtrlReference, err
- }
- mutateFunc := func() error {
- externalSecret.Spec = clusterExternalSecret.Spec.ExternalSecretSpec
- return nil
- }
- // An empty mutate func as nothing needs to happen currently
- if _, err := ctrl.CreateOrUpdate(ctx, r.Client, &externalSecret, mutateFunc); err != nil {
- return errCreatingOrUpdating, err
- }
- return "", nil
- }
- func (r *Reconciler) removeExternalSecret(ctx context.Context, esName, namespace string) (string, error) {
- //
- var existingES esv1beta1.ExternalSecret
- err := r.Get(ctx, types.NamespacedName{
- Name: esName,
- Namespace: namespace,
- }, &existingES)
- // If we can't find it then just leave
- if err != nil && apierrors.IsNotFound(err) {
- return "", nil
- }
- if result := checkForError(err, &existingES); result != "" {
- return result, err
- }
- err = r.Delete(ctx, &existingES, &client.DeleteOptions{})
- if err != nil {
- return errFailedToDelete, err
- }
- return "", nil
- }
- func (r *Reconciler) deferPatch(ctx context.Context, log logr.Logger, clusterExternalSecret *esv1beta1.ClusterExternalSecret, p client.Patch) {
- if err := r.Status().Patch(ctx, clusterExternalSecret, p); err != nil {
- log.Error(err, errPatchStatus)
- }
- }
- func (r *Reconciler) removeOldNamespaces(ctx context.Context, namespaceList v1.NamespaceList, esName string, provisionedNamespaces []string) map[string]string {
- failedNamespaces := map[string]string{}
- // Loop through existing namespaces first to make sure they still have our labels
- for _, namespace := range getRemovedNamespaces(namespaceList, provisionedNamespaces) {
- if result, _ := r.removeExternalSecret(ctx, esName, namespace); result != "" {
- failedNamespaces[namespace] = result
- }
- }
- return failedNamespaces
- }
- func checkForError(getError error, existingES *esv1beta1.ExternalSecret) string {
- if getError != nil && !apierrors.IsNotFound(getError) {
- return errGetExistingES
- }
- // No one owns this resource so error out
- if !apierrors.IsNotFound(getError) && len(existingES.ObjectMeta.OwnerReferences) == 0 {
- return errSecretAlreadyExists
- }
- return ""
- }
- func getCondition(namespaces map[string]string, namespaceList *v1.NamespaceList) esv1beta1.ClusterExternalSecretConditionType {
- if len(namespaces) == 0 {
- return esv1beta1.ClusterExternalSecretReady
- }
- if len(namespaces) < len(namespaceList.Items) {
- return esv1beta1.ClusterExternalSecretPartiallyReady
- }
- return esv1beta1.ClusterExternalSecretNotReady
- }
- func getRemovedNamespaces(nsList v1.NamespaceList, provisionedNs []string) []string {
- result := []string{}
- for _, ns := range provisionedNs {
- if !ContainsNamespace(nsList, ns) {
- result = append(result, ns)
- }
- }
- return result
- }
- func setFailedNamespaces(ces *esv1beta1.ClusterExternalSecret, failedNamespaces map[string]string) {
- if len(failedNamespaces) == 0 {
- return
- }
- ces.Status.FailedNamespaces = []esv1beta1.ClusterExternalSecretNamespaceFailure{}
- for namespace, message := range failedNamespaces {
- ces.Status.FailedNamespaces = append(ces.Status.FailedNamespaces, esv1beta1.ClusterExternalSecretNamespaceFailure{
- Namespace: namespace,
- Reason: message,
- })
- }
- }
- // SetupWithManager sets up the controller with the Manager.
- func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
- return ctrl.NewControllerManagedBy(mgr).
- WithOptions(opts).
- For(&esv1beta1.ClusterExternalSecret{}).
- Owns(&esv1beta1.ExternalSecret{}, builder.OnlyMetadata).
- Complete(r)
- }
|