k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controller/certificates/certificate_controller.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package certificates implements an abstract controller that is useful for 18 // building controllers that manage CSRs 19 package certificates 20 21 import ( 22 "context" 23 "fmt" 24 "time" 25 26 "golang.org/x/time/rate" 27 28 certificates "k8s.io/api/certificates/v1" 29 "k8s.io/apimachinery/pkg/api/errors" 30 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 31 "k8s.io/apimachinery/pkg/util/wait" 32 certificatesinformers "k8s.io/client-go/informers/certificates/v1" 33 clientset "k8s.io/client-go/kubernetes" 34 certificateslisters "k8s.io/client-go/listers/certificates/v1" 35 "k8s.io/client-go/tools/cache" 36 "k8s.io/client-go/util/workqueue" 37 "k8s.io/klog/v2" 38 "k8s.io/kubernetes/pkg/controller" 39 ) 40 41 type CertificateController struct { 42 // name is an identifier for this particular controller instance. 43 name string 44 45 kubeClient clientset.Interface 46 47 csrLister certificateslisters.CertificateSigningRequestLister 48 csrsSynced cache.InformerSynced 49 50 handler func(context.Context, *certificates.CertificateSigningRequest) error 51 52 queue workqueue.TypedRateLimitingInterface[string] 53 } 54 55 func NewCertificateController( 56 ctx context.Context, 57 name string, 58 kubeClient clientset.Interface, 59 csrInformer certificatesinformers.CertificateSigningRequestInformer, 60 handler func(context.Context, *certificates.CertificateSigningRequest) error, 61 ) *CertificateController { 62 logger := klog.FromContext(ctx) 63 cc := &CertificateController{ 64 name: name, 65 kubeClient: kubeClient, 66 queue: workqueue.NewTypedRateLimitingQueueWithConfig( 67 workqueue.NewTypedMaxOfRateLimiter[string]( 68 workqueue.NewTypedItemExponentialFailureRateLimiter[string](200*time.Millisecond, 1000*time.Second), 69 // 10 qps, 100 bucket size. This is only for retry speed and its only the overall factor (not per item) 70 &workqueue.TypedBucketRateLimiter[string]{Limiter: rate.NewLimiter(rate.Limit(10), 100)}, 71 ), 72 workqueue.TypedRateLimitingQueueConfig[string]{ 73 Name: "certificate", 74 }, 75 ), 76 handler: handler, 77 } 78 79 // Manage the addition/update of certificate requests 80 csrInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ 81 AddFunc: func(obj interface{}) { 82 csr := obj.(*certificates.CertificateSigningRequest) 83 logger.V(4).Info("Adding certificate request", "csr", csr.Name) 84 cc.enqueueCertificateRequest(obj) 85 }, 86 UpdateFunc: func(old, new interface{}) { 87 oldCSR := old.(*certificates.CertificateSigningRequest) 88 logger.V(4).Info("Updating certificate request", "old", oldCSR.Name) 89 cc.enqueueCertificateRequest(new) 90 }, 91 DeleteFunc: func(obj interface{}) { 92 csr, ok := obj.(*certificates.CertificateSigningRequest) 93 if !ok { 94 tombstone, ok := obj.(cache.DeletedFinalStateUnknown) 95 if !ok { 96 logger.V(2).Info("Couldn't get object from tombstone", "object", obj) 97 return 98 } 99 csr, ok = tombstone.Obj.(*certificates.CertificateSigningRequest) 100 if !ok { 101 logger.V(2).Info("Tombstone contained object that is not a CSR", "object", obj) 102 return 103 } 104 } 105 logger.V(4).Info("Deleting certificate request", "csr", csr.Name) 106 cc.enqueueCertificateRequest(obj) 107 }, 108 }) 109 cc.csrLister = csrInformer.Lister() 110 cc.csrsSynced = csrInformer.Informer().HasSynced 111 return cc 112 } 113 114 // Run the main goroutine responsible for watching and syncing jobs. 115 func (cc *CertificateController) Run(ctx context.Context, workers int) { 116 defer utilruntime.HandleCrash() 117 defer cc.queue.ShutDown() 118 119 logger := klog.FromContext(ctx) 120 logger.Info("Starting certificate controller", "name", cc.name) 121 defer logger.Info("Shutting down certificate controller", "name", cc.name) 122 123 if !cache.WaitForNamedCacheSync(fmt.Sprintf("certificate-%s", cc.name), ctx.Done(), cc.csrsSynced) { 124 return 125 } 126 127 for i := 0; i < workers; i++ { 128 go wait.UntilWithContext(ctx, cc.worker, time.Second) 129 } 130 131 <-ctx.Done() 132 } 133 134 // worker runs a thread that dequeues CSRs, handles them, and marks them done. 135 func (cc *CertificateController) worker(ctx context.Context) { 136 for cc.processNextWorkItem(ctx) { 137 } 138 } 139 140 // processNextWorkItem deals with one key off the queue. It returns false when it's time to quit. 141 func (cc *CertificateController) processNextWorkItem(ctx context.Context) bool { 142 cKey, quit := cc.queue.Get() 143 if quit { 144 return false 145 } 146 defer cc.queue.Done(cKey) 147 148 if err := cc.syncFunc(ctx, cKey); err != nil { 149 cc.queue.AddRateLimited(cKey) 150 if _, ignorable := err.(ignorableError); !ignorable { 151 utilruntime.HandleError(fmt.Errorf("Sync %v failed with : %v", cKey, err)) 152 } else { 153 klog.FromContext(ctx).V(4).Info("Sync certificate request failed", "csr", cKey, "err", err) 154 } 155 return true 156 } 157 158 cc.queue.Forget(cKey) 159 return true 160 161 } 162 163 func (cc *CertificateController) enqueueCertificateRequest(obj interface{}) { 164 key, err := controller.KeyFunc(obj) 165 if err != nil { 166 utilruntime.HandleError(fmt.Errorf("Couldn't get key for object %+v: %v", obj, err)) 167 return 168 } 169 cc.queue.Add(key) 170 } 171 172 func (cc *CertificateController) syncFunc(ctx context.Context, key string) error { 173 logger := klog.FromContext(ctx) 174 startTime := time.Now() 175 defer func() { 176 logger.V(4).Info("Finished syncing certificate request", "csr", key, "elapsedTime", time.Since(startTime)) 177 }() 178 csr, err := cc.csrLister.Get(key) 179 if errors.IsNotFound(err) { 180 logger.V(3).Info("csr has been deleted", "csr", key) 181 return nil 182 } 183 if err != nil { 184 return err 185 } 186 187 if len(csr.Status.Certificate) > 0 { 188 // no need to do anything because it already has a cert 189 return nil 190 } 191 192 // need to operate on a copy so we don't mutate the csr in the shared cache 193 csr = csr.DeepCopy() 194 return cc.handler(ctx, csr) 195 } 196 197 // IgnorableError returns an error that we shouldn't handle (i.e. log) because 198 // it's spammy and usually user error. Instead we will log these errors at a 199 // higher log level. We still need to throw these errors to signal that the 200 // sync should be retried. 201 func IgnorableError(s string, args ...interface{}) ignorableError { 202 return ignorableError(fmt.Sprintf(s, args...)) 203 } 204 205 type ignorableError string 206 207 func (e ignorableError) Error() string { 208 return string(e) 209 }