github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/servicedescriptor_controller.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package apps
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  
    26  	"golang.org/x/exp/slices"
    27  	corev1 "k8s.io/api/core/v1"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/client-go/tools/record"
    30  	ctrl "sigs.k8s.io/controller-runtime"
    31  	"sigs.k8s.io/controller-runtime/pkg/client"
    32  	"sigs.k8s.io/controller-runtime/pkg/log"
    33  
    34  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    35  	"github.com/1aal/kubeblocks/pkg/constant"
    36  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    37  )
    38  
    39  // ServiceDescriptorReconciler reconciles a ServiceDescriptor object
    40  type ServiceDescriptorReconciler struct {
    41  	client.Client
    42  	Scheme   *runtime.Scheme
    43  	Recorder record.EventRecorder
    44  }
    45  
    46  // +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=servicedescriptors,verbs=get;list;watch;create;update;patch;delete
    47  // +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=servicedescriptors/status,verbs=get;update;patch
    48  // +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=servicedescriptors/finalizers,verbs=update
    49  
    50  // Reconcile is part of the main kubernetes reconciliation loop which aims to
    51  // move the current state of the cluster closer to the desired state.
    52  // TODO(user): Modify the Reconcile function to compare the state specified by
    53  // the ServiceDescriptor object against the actual cluster state, and then
    54  // perform operations to make the cluster state reflect the state specified by
    55  // the user.
    56  //
    57  // For more details, check Reconcile and its Result here:
    58  // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile
    59  func (r *ServiceDescriptorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    60  	reqCtx := intctrlutil.RequestCtx{
    61  		Ctx:      ctx,
    62  		Req:      req,
    63  		Log:      log.FromContext(ctx).WithValues("serviceDescriptor", req.NamespacedName),
    64  		Recorder: r.Recorder,
    65  	}
    66  
    67  	serviceDescriptor := &appsv1alpha1.ServiceDescriptor{}
    68  	if err := r.Client.Get(reqCtx.Ctx, reqCtx.Req.NamespacedName, serviceDescriptor); err != nil {
    69  		return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "")
    70  	}
    71  
    72  	res, err := intctrlutil.HandleCRDeletion(reqCtx, r, serviceDescriptor, constant.ServiceDescriptorFinalizerName, func() (*ctrl.Result, error) {
    73  		recordEvent := func() {
    74  			r.Recorder.Event(serviceDescriptor, corev1.EventTypeWarning, constant.ReasonRefCRUnavailable,
    75  				"cannot be deleted because of existing service referencing Cluster.")
    76  		}
    77  		if res, err := intctrlutil.ValidateReferenceCR(reqCtx, r.Client, serviceDescriptor,
    78  			constant.ServiceDescriptorNameLabelKey, recordEvent, &appsv1alpha1.ClusterList{}); res != nil || err != nil {
    79  			return res, err
    80  		}
    81  		return nil, nil
    82  	})
    83  	if res != nil {
    84  		return *res, err
    85  	}
    86  
    87  	if serviceDescriptor.Status.ObservedGeneration == serviceDescriptor.Generation &&
    88  		slices.Contains(serviceDescriptor.Status.GetTerminalPhases(), serviceDescriptor.Status.Phase) {
    89  		return intctrlutil.Reconciled()
    90  	}
    91  
    92  	if err := r.checkServiceDescriptor(reqCtx, serviceDescriptor); err != nil {
    93  		if err := r.updateServiceDescriptorStatus(r.Client, reqCtx, serviceDescriptor, appsv1alpha1.UnavailablePhase); err != nil {
    94  			return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "InvalidServiceDescriptor update unavailable status failed")
    95  		}
    96  		return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "InvalidServiceDescriptor")
    97  	}
    98  
    99  	err = r.updateServiceDescriptorStatus(r.Client, reqCtx, serviceDescriptor, appsv1alpha1.AvailablePhase)
   100  	if err != nil {
   101  		return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "")
   102  	}
   103  
   104  	intctrlutil.RecordCreatedEvent(r.Recorder, serviceDescriptor)
   105  	return ctrl.Result{}, nil
   106  }
   107  
   108  // SetupWithManager sets up the controller with the Manager.
   109  func (r *ServiceDescriptorReconciler) SetupWithManager(mgr ctrl.Manager) error {
   110  	return ctrl.NewControllerManagedBy(mgr).
   111  		For(&appsv1alpha1.ServiceDescriptor{}).
   112  		Complete(r)
   113  }
   114  
   115  // checkServiceDescriptor checks if the service descriptor is valid.
   116  func (r *ServiceDescriptorReconciler) checkServiceDescriptor(reqCtx intctrlutil.RequestCtx, serviceDescriptor *appsv1alpha1.ServiceDescriptor) error {
   117  	secretRefExistFn := func(envFrom *corev1.EnvVarSource) bool {
   118  		if envFrom == nil || envFrom.SecretKeyRef == nil {
   119  			return true
   120  		}
   121  		secret := &corev1.Secret{}
   122  		if err := r.Client.Get(reqCtx.Ctx, client.ObjectKey{Namespace: reqCtx.Req.Namespace, Name: envFrom.SecretKeyRef.Name}, secret); err != nil {
   123  			return false
   124  		}
   125  		// TODO: check secret data key exist
   126  		return true
   127  	}
   128  
   129  	if serviceDescriptor.Spec.ServiceKind == "" {
   130  		return fmt.Errorf("serviceDescriptor %s serviceKind is empty", serviceDescriptor.Name)
   131  	}
   132  
   133  	if serviceDescriptor.Spec.ServiceVersion == "" {
   134  		return fmt.Errorf("serviceDescriptor %s serviceVersion is empty", serviceDescriptor.Name)
   135  	}
   136  
   137  	if serviceDescriptor.Spec.Endpoint != nil && !secretRefExistFn(serviceDescriptor.Spec.Endpoint.ValueFrom) {
   138  		return fmt.Errorf("endpoint.valueFrom.secretRef %s not found", serviceDescriptor.Spec.Endpoint.ValueFrom.SecretKeyRef.Name)
   139  	}
   140  
   141  	if serviceDescriptor.Spec.Auth != nil {
   142  		if serviceDescriptor.Spec.Auth.Username != nil && !secretRefExistFn(serviceDescriptor.Spec.Auth.Username.ValueFrom) {
   143  			return fmt.Errorf("auth.username.valueFrom.secretRef %s not found", serviceDescriptor.Spec.Auth.Username.ValueFrom.SecretKeyRef.Name)
   144  		}
   145  		if serviceDescriptor.Spec.Auth.Password != nil && !secretRefExistFn(serviceDescriptor.Spec.Auth.Password.ValueFrom) {
   146  			return fmt.Errorf("auth.Password.valueFrom.secretRef %s not found", serviceDescriptor.Spec.Auth.Password.ValueFrom.SecretKeyRef.Name)
   147  		}
   148  	}
   149  
   150  	if serviceDescriptor.Spec.Port != nil && !secretRefExistFn(serviceDescriptor.Spec.Port.ValueFrom) {
   151  		return fmt.Errorf("port.valueFrom.secretRef %s not found", serviceDescriptor.Spec.Port.ValueFrom.SecretKeyRef.Name)
   152  	}
   153  	return nil
   154  }
   155  
   156  // updateServiceDescriptorStatus updates the status of the service descriptor.
   157  func (r *ServiceDescriptorReconciler) updateServiceDescriptorStatus(cli client.Client, ctx intctrlutil.RequestCtx, serviceDescriptor *appsv1alpha1.ServiceDescriptor, phase appsv1alpha1.Phase) error {
   158  	patch := client.MergeFrom(serviceDescriptor.DeepCopy())
   159  	serviceDescriptor.Status.Phase = phase
   160  	serviceDescriptor.Status.ObservedGeneration = serviceDescriptor.Generation
   161  	return cli.Status().Patch(ctx.Ctx, serviceDescriptor, patch)
   162  }