k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controller/serviceaccount/serviceaccounts_controller.go (about) 1 /* 2 Copyright 2014 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 serviceaccount 18 19 import ( 20 "context" 21 "fmt" 22 "time" 23 24 v1 "k8s.io/api/core/v1" 25 apierrors "k8s.io/apimachinery/pkg/api/errors" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 utilerrors "k8s.io/apimachinery/pkg/util/errors" 28 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 29 "k8s.io/apimachinery/pkg/util/wait" 30 coreinformers "k8s.io/client-go/informers/core/v1" 31 clientset "k8s.io/client-go/kubernetes" 32 corelisters "k8s.io/client-go/listers/core/v1" 33 "k8s.io/client-go/tools/cache" 34 "k8s.io/client-go/util/workqueue" 35 "k8s.io/klog/v2" 36 ) 37 38 // ServiceAccountsControllerOptions contains options for running a ServiceAccountsController 39 type ServiceAccountsControllerOptions struct { 40 // ServiceAccounts is the list of service accounts to ensure exist in every namespace 41 ServiceAccounts []v1.ServiceAccount 42 43 // ServiceAccountResync is the interval between full resyncs of ServiceAccounts. 44 // If non-zero, all service accounts will be re-listed this often. 45 // Otherwise, re-list will be delayed as long as possible (until the watch is closed or times out). 46 ServiceAccountResync time.Duration 47 48 // NamespaceResync is the interval between full resyncs of Namespaces. 49 // If non-zero, all namespaces will be re-listed this often. 50 // Otherwise, re-list will be delayed as long as possible (until the watch is closed or times out). 51 NamespaceResync time.Duration 52 } 53 54 // DefaultServiceAccountsControllerOptions returns the default options for creating a ServiceAccountsController. 55 func DefaultServiceAccountsControllerOptions() ServiceAccountsControllerOptions { 56 return ServiceAccountsControllerOptions{ 57 ServiceAccounts: []v1.ServiceAccount{ 58 {ObjectMeta: metav1.ObjectMeta{Name: "default"}}, 59 }, 60 } 61 } 62 63 // NewServiceAccountsController returns a new *ServiceAccountsController. 64 func NewServiceAccountsController(saInformer coreinformers.ServiceAccountInformer, nsInformer coreinformers.NamespaceInformer, cl clientset.Interface, options ServiceAccountsControllerOptions) (*ServiceAccountsController, error) { 65 e := &ServiceAccountsController{ 66 client: cl, 67 serviceAccountsToEnsure: options.ServiceAccounts, 68 queue: workqueue.NewTypedRateLimitingQueueWithConfig( 69 workqueue.DefaultTypedControllerRateLimiter[string](), 70 workqueue.TypedRateLimitingQueueConfig[string]{Name: "serviceaccount"}, 71 ), 72 } 73 74 saHandler, _ := saInformer.Informer().AddEventHandlerWithResyncPeriod(cache.ResourceEventHandlerFuncs{ 75 DeleteFunc: e.serviceAccountDeleted, 76 }, options.ServiceAccountResync) 77 e.saLister = saInformer.Lister() 78 e.saListerSynced = saHandler.HasSynced 79 80 nsHandler, _ := nsInformer.Informer().AddEventHandlerWithResyncPeriod(cache.ResourceEventHandlerFuncs{ 81 AddFunc: e.namespaceAdded, 82 UpdateFunc: e.namespaceUpdated, 83 }, options.NamespaceResync) 84 e.nsLister = nsInformer.Lister() 85 e.nsListerSynced = nsHandler.HasSynced 86 87 e.syncHandler = e.syncNamespace 88 89 return e, nil 90 } 91 92 // ServiceAccountsController manages ServiceAccount objects inside Namespaces 93 type ServiceAccountsController struct { 94 client clientset.Interface 95 serviceAccountsToEnsure []v1.ServiceAccount 96 97 // To allow injection for testing. 98 syncHandler func(ctx context.Context, key string) error 99 100 saLister corelisters.ServiceAccountLister 101 saListerSynced cache.InformerSynced 102 103 nsLister corelisters.NamespaceLister 104 nsListerSynced cache.InformerSynced 105 106 queue workqueue.TypedRateLimitingInterface[string] 107 } 108 109 // Run runs the ServiceAccountsController blocks until receiving signal from stopCh. 110 func (c *ServiceAccountsController) Run(ctx context.Context, workers int) { 111 defer utilruntime.HandleCrash() 112 defer c.queue.ShutDown() 113 114 klog.FromContext(ctx).Info("Starting service account controller") 115 defer klog.FromContext(ctx).Info("Shutting down service account controller") 116 117 if !cache.WaitForNamedCacheSync("service account", ctx.Done(), c.saListerSynced, c.nsListerSynced) { 118 return 119 } 120 121 for i := 0; i < workers; i++ { 122 go wait.UntilWithContext(ctx, c.runWorker, time.Second) 123 } 124 125 <-ctx.Done() 126 } 127 128 // serviceAccountDeleted reacts to a ServiceAccount deletion by recreating a default ServiceAccount in the namespace if needed 129 func (c *ServiceAccountsController) serviceAccountDeleted(obj interface{}) { 130 sa, ok := obj.(*v1.ServiceAccount) 131 if !ok { 132 tombstone, ok := obj.(cache.DeletedFinalStateUnknown) 133 if !ok { 134 utilruntime.HandleError(fmt.Errorf("Couldn't get object from tombstone %#v", obj)) 135 return 136 } 137 sa, ok = tombstone.Obj.(*v1.ServiceAccount) 138 if !ok { 139 utilruntime.HandleError(fmt.Errorf("Tombstone contained object that is not a ServiceAccount %#v", obj)) 140 return 141 } 142 } 143 c.queue.Add(sa.Namespace) 144 } 145 146 // namespaceAdded reacts to a Namespace creation by creating a default ServiceAccount object 147 func (c *ServiceAccountsController) namespaceAdded(obj interface{}) { 148 namespace := obj.(*v1.Namespace) 149 c.queue.Add(namespace.Name) 150 } 151 152 // namespaceUpdated reacts to a Namespace update (or re-list) by creating a default ServiceAccount in the namespace if needed 153 func (c *ServiceAccountsController) namespaceUpdated(oldObj interface{}, newObj interface{}) { 154 newNamespace := newObj.(*v1.Namespace) 155 c.queue.Add(newNamespace.Name) 156 } 157 158 func (c *ServiceAccountsController) runWorker(ctx context.Context) { 159 for c.processNextWorkItem(ctx) { 160 } 161 } 162 163 // processNextWorkItem deals with one key off the queue. It returns false when it's time to quit. 164 func (c *ServiceAccountsController) processNextWorkItem(ctx context.Context) bool { 165 key, quit := c.queue.Get() 166 if quit { 167 return false 168 } 169 defer c.queue.Done(key) 170 171 err := c.syncHandler(ctx, key) 172 if err == nil { 173 c.queue.Forget(key) 174 return true 175 } 176 177 utilruntime.HandleError(fmt.Errorf("%v failed with : %v", key, err)) 178 c.queue.AddRateLimited(key) 179 180 return true 181 } 182 func (c *ServiceAccountsController) syncNamespace(ctx context.Context, key string) error { 183 startTime := time.Now() 184 defer func() { 185 klog.FromContext(ctx).V(4).Info("Finished syncing namespace", "namespace", key, "duration", time.Since(startTime)) 186 }() 187 188 ns, err := c.nsLister.Get(key) 189 if apierrors.IsNotFound(err) { 190 return nil 191 } 192 if err != nil { 193 return err 194 } 195 if ns.Status.Phase != v1.NamespaceActive { 196 // If namespace is not active, we shouldn't try to create anything 197 return nil 198 } 199 200 createFailures := []error{} 201 for _, sa := range c.serviceAccountsToEnsure { 202 switch _, err := c.saLister.ServiceAccounts(ns.Name).Get(sa.Name); { 203 case err == nil: 204 continue 205 case apierrors.IsNotFound(err): 206 case err != nil: 207 return err 208 } 209 // this is only safe because we never read it and we always write it 210 // TODO eliminate this once the fake client can handle creation without NS 211 sa.Namespace = ns.Name 212 213 if _, err := c.client.CoreV1().ServiceAccounts(ns.Name).Create(ctx, &sa, metav1.CreateOptions{}); err != nil && !apierrors.IsAlreadyExists(err) { 214 // we can safely ignore terminating namespace errors 215 if !apierrors.HasStatusCause(err, v1.NamespaceTerminatingCause) { 216 createFailures = append(createFailures, err) 217 } 218 } 219 } 220 221 return utilerrors.Flatten(utilerrors.NewAggregate(createFailures)) 222 }