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  }