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 }