knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/injection/sharedmain/main.go (about)

     1  /*
     2  Copyright 2019 The Knative 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 sharedmain
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"flag"
    23  	"fmt"
    24  	"log"
    25  	"net/http"
    26  	"os"
    27  	"strconv"
    28  	"strings"
    29  	"time"
    30  
    31  	"go.opentelemetry.io/contrib/instrumentation/runtime"
    32  	"go.opentelemetry.io/otel"
    33  	"go.opentelemetry.io/otel/sdk/metric"
    34  	"go.opentelemetry.io/otel/sdk/trace"
    35  
    36  	"github.com/go-logr/zapr"
    37  	"go.uber.org/automaxprocs/maxprocs" // automatically set GOMAXPROCS based on cgroups
    38  	"go.uber.org/zap"
    39  	"golang.org/x/sync/errgroup"
    40  
    41  	corev1 "k8s.io/api/core/v1"
    42  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    43  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    44  	"k8s.io/apimachinery/pkg/labels"
    45  	"k8s.io/apimachinery/pkg/util/sets"
    46  	"k8s.io/apimachinery/pkg/util/wait"
    47  	"k8s.io/client-go/rest"
    48  	k8stoolmetrics "k8s.io/client-go/tools/metrics"
    49  	"k8s.io/client-go/util/workqueue"
    50  	"k8s.io/klog/v2"
    51  
    52  	kubeclient "knative.dev/pkg/client/injection/kube/client"
    53  	"knative.dev/pkg/configmap"
    54  	cminformer "knative.dev/pkg/configmap/informer"
    55  	"knative.dev/pkg/controller"
    56  	"knative.dev/pkg/injection"
    57  	"knative.dev/pkg/leaderelection"
    58  	"knative.dev/pkg/logging"
    59  	"knative.dev/pkg/logging/logkey"
    60  	"knative.dev/pkg/observability"
    61  	o11yconfigmap "knative.dev/pkg/observability/configmap"
    62  	"knative.dev/pkg/observability/metrics"
    63  	k8smetrics "knative.dev/pkg/observability/metrics/k8s"
    64  	"knative.dev/pkg/observability/resource"
    65  	k8sruntime "knative.dev/pkg/observability/runtime/k8s"
    66  	"knative.dev/pkg/observability/tracing"
    67  	"knative.dev/pkg/reconciler"
    68  	"knative.dev/pkg/signals"
    69  	"knative.dev/pkg/system"
    70  	"knative.dev/pkg/version"
    71  	"knative.dev/pkg/webhook"
    72  )
    73  
    74  func init() {
    75  	maxprocs.Set()
    76  }
    77  
    78  // GetLoggingConfig gets the logging config from the (in order):
    79  // 1. provided context,
    80  // 2. reading from the API server,
    81  // 3. defaults (if not found).
    82  // The context is expected to be initialized with injection.
    83  func GetLoggingConfig(ctx context.Context) (*logging.Config, error) {
    84  	if cfg := logging.GetConfig(ctx); cfg != nil {
    85  		return cfg, nil
    86  	}
    87  
    88  	var loggingConfigMap *corev1.ConfigMap
    89  	// These timeout and retry interval are set by heuristics.
    90  	// e.g. istio sidecar needs a few seconds to configure the pod network.
    91  	var lastErr error
    92  	if err := wait.PollUntilContextTimeout(ctx, 1*time.Second, 5*time.Second, true, func(ctx context.Context) (bool, error) {
    93  		loggingConfigMap, lastErr = kubeclient.Get(ctx).CoreV1().ConfigMaps(system.Namespace()).Get(ctx, logging.ConfigMapName(), metav1.GetOptions{})
    94  		return lastErr == nil || apierrors.IsNotFound(lastErr), nil
    95  	}); err != nil {
    96  		return nil, fmt.Errorf("timed out waiting for the condition: %w", lastErr)
    97  	}
    98  	if loggingConfigMap == nil {
    99  		return logging.NewConfigFromMap(nil)
   100  	}
   101  	return logging.NewConfigFromConfigMap(loggingConfigMap)
   102  }
   103  
   104  // GetLeaderElectionConfig gets the leader election config from the (in order):
   105  // 1. provided context,
   106  // 2. reading from the API server,
   107  // 3. defaults (if not found).
   108  func GetLeaderElectionConfig(ctx context.Context) (*leaderelection.Config, error) {
   109  	if cfg := leaderelection.GetConfig(ctx); cfg != nil {
   110  		return cfg, nil
   111  	}
   112  
   113  	leaderElectionConfigMap, err := kubeclient.Get(ctx).CoreV1().ConfigMaps(system.Namespace()).Get(ctx, leaderelection.ConfigMapName(), metav1.GetOptions{})
   114  	if apierrors.IsNotFound(err) {
   115  		return leaderelection.NewConfigFromConfigMap(nil)
   116  	} else if err != nil {
   117  		return nil, err
   118  	}
   119  	return leaderelection.NewConfigFromConfigMap(leaderElectionConfigMap)
   120  }
   121  
   122  // GetObservabilityConfig gets the observability config from the (in order):
   123  // 1. provided context,
   124  // 2. reading from the API server,
   125  // 3. defaults (if not found).
   126  func GetObservabilityConfig(ctx context.Context) (*observability.Config, error) {
   127  	if cfg := observability.GetConfig(ctx); cfg != nil {
   128  		return cfg, nil
   129  	}
   130  
   131  	client := kubeclient.Get(ctx).CoreV1().ConfigMaps(system.Namespace())
   132  	cm, err := client.Get(ctx, o11yconfigmap.Name(), metav1.GetOptions{})
   133  
   134  	if apierrors.IsNotFound(err) {
   135  		return observability.DefaultConfig(), nil
   136  	} else if err != nil {
   137  		return nil, err
   138  	}
   139  
   140  	return o11yconfigmap.Parse(cm)
   141  }
   142  
   143  // EnableInjectionOrDie enables Knative Injection and starts the informers.
   144  // Both Context and Config are optional.
   145  // Deprecated: use injection.EnableInjectionOrDie
   146  func EnableInjectionOrDie(ctx context.Context, cfg *rest.Config) context.Context {
   147  	ctx, startInformers := injection.EnableInjectionOrDie(ctx, cfg)
   148  	go startInformers()
   149  	return ctx
   150  }
   151  
   152  // Main runs the generic main flow with a new context.
   153  // If any of the constructed controllers are AdmissionControllers or Conversion
   154  // webhooks, then a webhook is started to serve them.
   155  func Main(component string, ctors ...injection.ControllerConstructor) {
   156  	// Set up signals so we handle the first shutdown signal gracefully.
   157  	MainWithContext(signals.NewContext(), component, ctors...)
   158  }
   159  
   160  // Legacy aliases for back-compat.
   161  var (
   162  	WebhookMainWithContext = MainWithContext
   163  	WebhookMainWithConfig  = MainWithConfig
   164  )
   165  
   166  // MainNamed runs the generic main flow for controllers and webhooks.
   167  //
   168  // In addition to the MainWithConfig flow, it defines a `disabled-controllers` flag that allows disabling controllers
   169  // by name.
   170  func MainNamed(ctx context.Context, component string, ctors ...injection.NamedControllerConstructor) {
   171  	disabledControllers := flag.String("disable-controllers", "", "Comma-separated list of disabled controllers.")
   172  
   173  	// HACK: This parses flags, so the above should be set once this runs.
   174  	cfg := injection.ParseAndGetRESTConfigOrDie()
   175  
   176  	enabledCtors := enabledControllers(strings.Split(*disabledControllers, ","), ctors)
   177  	MainWithConfig(ctx, component, cfg, toControllerConstructors(enabledCtors)...)
   178  }
   179  
   180  func enabledControllers(disabledControllers []string, ctors []injection.NamedControllerConstructor) []injection.NamedControllerConstructor {
   181  	disabledControllersSet := sets.NewString(disabledControllers...)
   182  	activeCtors := make([]injection.NamedControllerConstructor, 0, len(ctors))
   183  	for _, ctor := range ctors {
   184  		if disabledControllersSet.Has(ctor.Name) {
   185  			log.Printf("Disabling controller %s", ctor.Name)
   186  			continue
   187  		}
   188  		activeCtors = append(activeCtors, ctor)
   189  	}
   190  	return activeCtors
   191  }
   192  
   193  func toControllerConstructors(namedCtors []injection.NamedControllerConstructor) []injection.ControllerConstructor {
   194  	ctors := make([]injection.ControllerConstructor, 0, len(namedCtors))
   195  	for _, ctor := range namedCtors {
   196  		ctors = append(ctors, ctor.ControllerConstructor)
   197  	}
   198  	return ctors
   199  }
   200  
   201  // MainWithContext runs the generic main flow for controllers and
   202  // webhooks. Use MainWithContext if you do not need to serve webhooks.
   203  func MainWithContext(ctx context.Context, component string, ctors ...injection.ControllerConstructor) {
   204  	// Allow configuration of threads per controller
   205  	if val, ok := os.LookupEnv("K_THREADS_PER_CONTROLLER"); ok {
   206  		threadsPerController, err := strconv.Atoi(val)
   207  		if err != nil {
   208  			log.Fatalf("failed to parse value %q of K_THREADS_PER_CONTROLLER: %v\n", val, err)
   209  		}
   210  		controller.DefaultThreadsPerController = threadsPerController
   211  	}
   212  
   213  	// TODO(mattmoor): Remove this once HA is stable.
   214  	disableHighAvailability := flag.Bool("disable-ha", false,
   215  		"Whether to disable high-availability functionality for this component.  This flag will be deprecated "+
   216  			"and removed when we have promoted this feature to stable, so do not pass it without filing an "+
   217  			"issue upstream!")
   218  
   219  	// HACK: This parses flags, so the above should be set once this runs.
   220  	cfg := injection.ParseAndGetRESTConfigOrDie()
   221  
   222  	if *disableHighAvailability {
   223  		ctx = WithHADisabled(ctx)
   224  	}
   225  
   226  	MainWithConfig(ctx, component, cfg, ctors...)
   227  }
   228  
   229  type haDisabledKey struct{}
   230  
   231  // WithHADisabled signals to MainWithConfig that it should not set up an appropriate leader elector for this component.
   232  func WithHADisabled(ctx context.Context) context.Context {
   233  	return context.WithValue(ctx, haDisabledKey{}, struct{}{})
   234  }
   235  
   236  // IsHADisabled checks the context for the desired to disabled leader elector.
   237  func IsHADisabled(ctx context.Context) bool {
   238  	return ctx.Value(haDisabledKey{}) != nil
   239  }
   240  
   241  // MainWithConfig runs the generic main flow for controllers and webhooks
   242  // with the given config.
   243  func MainWithConfig(ctx context.Context, component string, cfg *rest.Config, ctors ...injection.ControllerConstructor) {
   244  	log.Printf("Registering %d clients", len(injection.Default.GetClients()))
   245  	log.Printf("Registering %d informer factories", len(injection.Default.GetInformerFactories()))
   246  	log.Printf("Registering %d informers", len(injection.Default.GetInformers()))
   247  	log.Printf("Registering %d controllers", len(ctors))
   248  
   249  	// Respect user provided settings, but if omitted customize the default behavior.
   250  	if cfg.QPS == 0 {
   251  		// Adjust our client's rate limits based on the number of controllers we are running.
   252  		cfg.QPS = float32(len(ctors)) * rest.DefaultQPS
   253  	}
   254  	if cfg.Burst == 0 {
   255  		cfg.Burst = len(ctors) * rest.DefaultBurst
   256  	}
   257  
   258  	ctx, startInformers := injection.EnableInjectionOrDie(ctx, cfg)
   259  
   260  	logger, atomicLevel := SetupLoggerOrDie(ctx, component)
   261  	defer logger.Sync()
   262  	ctx = logging.WithLogger(ctx, logger)
   263  
   264  	klog.SetLogger(zapr.NewLogger(logger.Desugar()))
   265  
   266  	// Override client-go's warning handler to give us nicely printed warnings.
   267  	rest.SetDefaultWarningHandler(&logging.WarningHandler{Logger: logger})
   268  
   269  	pprof := k8sruntime.NewProfilingServer(logger.Named("pprof"))
   270  
   271  	CheckK8sClientMinimumVersionOrDie(ctx, logger)
   272  	cmw := SetupConfigMapWatchOrDie(ctx, logger)
   273  
   274  	// Set up leader election config
   275  	leaderElectionConfig, err := GetLeaderElectionConfig(ctx)
   276  	if err != nil {
   277  		logger.Fatal("Error loading leader election configuration: ", err)
   278  	}
   279  
   280  	if !IsHADisabled(ctx) {
   281  		// Signal that we are executing in a context with leader election.
   282  		ctx = leaderelection.WithDynamicLeaderElectorBuilder(ctx, kubeclient.Get(ctx),
   283  			leaderElectionConfig.GetComponentConfig(component))
   284  	}
   285  
   286  	mp, tp := SetupObservabilityOrDie(ctx, component, logger, pprof)
   287  	defer func() {
   288  		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   289  		defer cancel()
   290  
   291  		if err := mp.Shutdown(ctx); err != nil {
   292  			logger.Errorw("Error flushing metrics", zap.Error(err))
   293  		}
   294  		if err := tp.Shutdown(ctx); err != nil {
   295  			logger.Errorw("Error flushing traces", zap.Error(err))
   296  		}
   297  	}()
   298  
   299  	controllers, webhooks := ControllersAndWebhooksFromCtors(ctx, cmw, ctors...)
   300  	WatchLoggingConfigOrDie(ctx, cmw, logger, atomicLevel, component)
   301  	WatchObservabilityConfigOrDie(ctx, cmw, pprof, logger, component)
   302  
   303  	eg, egCtx := errgroup.WithContext(ctx)
   304  	eg.Go(pprof.ListenAndServe)
   305  
   306  	// Many of the webhooks rely on configuration, e.g. configurable defaults, feature flags.
   307  	// So make sure that we have synchronized our configuration state before launching the
   308  	// webhooks, so that things are properly initialized.
   309  	logger.Info("Starting configmap watcher...")
   310  	if err := cmw.Start(ctx.Done()); err != nil {
   311  		logger.Fatalw("Failed to start configmap watcher", zap.Error(err))
   312  	}
   313  
   314  	// If we have one or more admission controllers, then start the webhook
   315  	// and pass them in.
   316  	var wh *webhook.Webhook
   317  	if len(webhooks) > 0 {
   318  		wh, err = webhook.New(ctx, webhooks)
   319  		if err != nil {
   320  			logger.Fatalw("Failed to create webhook", zap.Error(err))
   321  		}
   322  		eg.Go(func() error {
   323  			return wh.Run(ctx.Done())
   324  		})
   325  	}
   326  
   327  	// Start the injection clients and informers.
   328  	startInformers()
   329  
   330  	// Wait for webhook informers to sync.
   331  	if wh != nil {
   332  		wh.InformersHaveSynced()
   333  	}
   334  	logger.Info("Starting controllers...")
   335  	eg.Go(func() error {
   336  		return controller.StartAll(ctx, controllers...)
   337  	})
   338  
   339  	// Setup default health checks to catch issues with cache sync etc.
   340  	if !healthProbesDisabled(ctx) {
   341  		eg.Go(func() error {
   342  			return injection.ServeHealthProbes(ctx, injection.HealthCheckDefaultPort)
   343  		})
   344  	}
   345  
   346  	// This will block until either a signal arrives or one of the grouped functions
   347  	// returns an error.
   348  	<-egCtx.Done()
   349  
   350  	pprof.Shutdown(context.Background())
   351  
   352  	// Don't forward ErrServerClosed as that indicates we're already shutting down.
   353  	if err := eg.Wait(); err != nil && !errors.Is(err, http.ErrServerClosed) {
   354  		logger.Errorw("Error while running server", zap.Error(err))
   355  	}
   356  }
   357  
   358  type healthProbesDisabledKey struct{}
   359  
   360  // WithHealthProbesDisabled signals to MainWithContext that it should disable default probes (readiness and liveness).
   361  func WithHealthProbesDisabled(ctx context.Context) context.Context {
   362  	return context.WithValue(ctx, healthProbesDisabledKey{}, struct{}{})
   363  }
   364  
   365  func healthProbesDisabled(ctx context.Context) bool {
   366  	return ctx.Value(healthProbesDisabledKey{}) != nil
   367  }
   368  
   369  // SetupLoggerOrDie sets up the logger using the config from the given context
   370  // and returns a logger and atomic level, or dies by calling log.Fatalf.
   371  func SetupLoggerOrDie(ctx context.Context, component string) (*zap.SugaredLogger, zap.AtomicLevel) {
   372  	loggingConfig, err := GetLoggingConfig(ctx)
   373  	if err != nil {
   374  		log.Fatal("Error reading/parsing logging configuration: ", err)
   375  	}
   376  	l, level := logging.NewLoggerFromConfig(loggingConfig, component)
   377  
   378  	if pn := system.PodName(); pn != "" {
   379  		l = l.With(zap.String(logkey.Pod, pn))
   380  	}
   381  
   382  	return l, level
   383  }
   384  
   385  // SetupObservabilityOrDie sets up the observability using the config from the given context
   386  // or dies by calling log.Fatalf.
   387  func SetupObservabilityOrDie(
   388  	ctx context.Context,
   389  	component string,
   390  	logger *zap.SugaredLogger,
   391  	pprof *k8sruntime.ProfilingServer,
   392  ) (*metrics.MeterProvider, *tracing.TracerProvider) {
   393  	cfg, err := GetObservabilityConfig(ctx)
   394  	if err != nil {
   395  		logger.Fatal("Error loading observability configuration: ", err)
   396  	}
   397  
   398  	pprof.UpdateFromConfig(cfg.Runtime)
   399  
   400  	resource := resource.Default(component)
   401  
   402  	meterProvider, err := metrics.NewMeterProvider(
   403  		ctx,
   404  		cfg.Metrics,
   405  		metric.WithView(OTelViews(ctx)...),
   406  		metric.WithResource(resource),
   407  	)
   408  	if err != nil {
   409  		logger.Fatalw("Failed to setup meter provider", zap.Error(err))
   410  	}
   411  
   412  	otel.SetMeterProvider(meterProvider)
   413  
   414  	workQueueMetrics, err := k8smetrics.NewWorkqueueMetricsProvider(
   415  		k8smetrics.WithMeterProvider(meterProvider),
   416  	)
   417  	if err != nil {
   418  		logger.Fatalw("Failed to setup k8s workqueue metrics", zap.Error(err))
   419  	}
   420  
   421  	workqueue.SetProvider(workQueueMetrics)
   422  	controller.SetMetricsProvider(workQueueMetrics)
   423  
   424  	clientMetrics, err := k8smetrics.NewClientMetricProvider(
   425  		k8smetrics.WithMeterProvider(meterProvider),
   426  	)
   427  	if err != nil {
   428  		logger.Fatalw("Failed to setup k8s client go metrics", zap.Error(err))
   429  	}
   430  
   431  	k8stoolmetrics.Register(k8stoolmetrics.RegisterOpts{
   432  		RequestLatency: clientMetrics.RequestLatencyMetric(),
   433  		RequestResult:  clientMetrics.RequestResultMetric(),
   434  	})
   435  
   436  	err = runtime.Start(
   437  		runtime.WithMinimumReadMemStatsInterval(cfg.Runtime.ExportInterval),
   438  	)
   439  	if err != nil {
   440  		logger.Fatalw("Failed to start runtime metrics", zap.Error(err))
   441  	}
   442  
   443  	tracerProvider, err := tracing.NewTracerProvider(
   444  		ctx,
   445  		cfg.Tracing,
   446  		trace.WithResource(resource),
   447  	)
   448  	if err != nil {
   449  		logger.Fatalw("Failed to setup trace provider", zap.Error(err))
   450  	}
   451  
   452  	otel.SetTextMapPropagator(tracing.DefaultTextMapPropagator())
   453  	otel.SetTracerProvider(tracerProvider)
   454  
   455  	return meterProvider, tracerProvider
   456  }
   457  
   458  // CheckK8sClientMinimumVersionOrDie checks that the hosting Kubernetes cluster
   459  // is at least the minimum allowable version or dies by calling log.Fatalw.
   460  func CheckK8sClientMinimumVersionOrDie(ctx context.Context, logger *zap.SugaredLogger) {
   461  	kc := kubeclient.Get(ctx)
   462  	if err := version.CheckMinimumVersion(kc.Discovery()); err != nil {
   463  		logger.Fatalw("Version check failed", zap.Error(err))
   464  	}
   465  }
   466  
   467  // SetupConfigMapWatchOrDie establishes a watch of the configmaps in the system
   468  // namespace that are labeled to be watched or dies by calling log.Fatalw.
   469  func SetupConfigMapWatchOrDie(ctx context.Context, logger *zap.SugaredLogger) *cminformer.InformedWatcher {
   470  	kc := kubeclient.Get(ctx)
   471  	// Create ConfigMaps watcher with optional label-based filter.
   472  	var cmLabelReqs []labels.Requirement
   473  	if cmLabel := system.ResourceLabel(); cmLabel != "" {
   474  		req, err := cminformer.FilterConfigByLabelExists(cmLabel)
   475  		if err != nil {
   476  			logger.Fatalw("Failed to generate requirement for label "+cmLabel, zap.Error(err))
   477  		}
   478  		logger.Info("Setting up ConfigMap watcher with label selector: ", req)
   479  		cmLabelReqs = append(cmLabelReqs, *req)
   480  	}
   481  	// TODO(mattmoor): This should itself take a context and be injection-based.
   482  	return cminformer.NewInformedWatcher(kc, system.Namespace(), cmLabelReqs...)
   483  }
   484  
   485  // WatchLoggingConfigOrDie establishes a watch of the logging config or dies by
   486  // calling log.Fatalw. Note, if the config does not exist, it will be defaulted
   487  // and this method will not die.
   488  func WatchLoggingConfigOrDie(ctx context.Context, cmw *cminformer.InformedWatcher, logger *zap.SugaredLogger, atomicLevel zap.AtomicLevel, component string) {
   489  	if _, err := kubeclient.Get(ctx).CoreV1().ConfigMaps(system.Namespace()).Get(ctx, logging.ConfigMapName(),
   490  		metav1.GetOptions{}); err == nil {
   491  		cmw.Watch(logging.ConfigMapName(), logging.UpdateLevelFromConfigMap(logger, atomicLevel, component))
   492  	} else if !apierrors.IsNotFound(err) {
   493  		logger.Fatalw("Error reading ConfigMap "+logging.ConfigMapName(), zap.Error(err))
   494  	}
   495  }
   496  
   497  // WatchObservabilityConfigOrDie establishes a watch of the observability config
   498  // or dies by calling log.Fatalw. Note, if the config does not exist, it will be
   499  // defaulted and this method will not die.
   500  func WatchObservabilityConfigOrDie(
   501  	ctx context.Context,
   502  	cmw *cminformer.InformedWatcher,
   503  	pprof *k8sruntime.ProfilingServer,
   504  	logger *zap.SugaredLogger,
   505  	component string,
   506  ) {
   507  	cmName := o11yconfigmap.Name()
   508  	client := kubeclient.Get(ctx).CoreV1().ConfigMaps(system.Namespace())
   509  
   510  	observers := []configmap.Observer{
   511  		pprof.UpdateFromConfigMap,
   512  	}
   513  
   514  	if _, err := client.Get(ctx, cmName, metav1.GetOptions{}); err == nil {
   515  		cmw.Watch(cmName, observers...)
   516  	} else if !apierrors.IsNotFound(err) {
   517  		logger.Fatalw("Error reading ConfigMap "+cmName, zap.Error(err))
   518  	}
   519  }
   520  
   521  // ControllersAndWebhooksFromCtors returns a list of the controllers and a list
   522  // of the webhooks created from the given constructors.
   523  func ControllersAndWebhooksFromCtors(ctx context.Context,
   524  	cmw *cminformer.InformedWatcher,
   525  	ctors ...injection.ControllerConstructor,
   526  ) ([]*controller.Impl, []any) {
   527  	// Check whether the context has been infused with a leader elector builder.
   528  	// If it has, then every reconciler we plan to start MUST implement LeaderAware.
   529  	leEnabled := leaderelection.HasLeaderElection(ctx)
   530  
   531  	controllers := make([]*controller.Impl, 0, len(ctors))
   532  	webhooks := make([]any, 0)
   533  	for _, cf := range ctors {
   534  		ctrl := cf(ctx, cmw)
   535  		controllers = append(controllers, ctrl)
   536  
   537  		// Build a list of any reconcilers that implement webhook.AdmissionController
   538  		switch c := ctrl.Reconciler.(type) {
   539  		case webhook.AdmissionController, webhook.ConversionController:
   540  			webhooks = append(webhooks, c)
   541  		}
   542  
   543  		if leEnabled {
   544  			if _, ok := ctrl.Reconciler.(reconciler.LeaderAware); !ok {
   545  				log.Fatalf("%T is not leader-aware, all reconcilers must be leader-aware to enable fine-grained leader election.", ctrl.Reconciler)
   546  			}
   547  		}
   548  	}
   549  
   550  	return controllers, webhooks
   551  }