sigs.k8s.io/cluster-api-provider-aws@v1.5.5/main.go (about)

     1  /*
     2  Copyright 2018 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 main
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"flag"
    23  	"fmt"
    24  	"math/rand"
    25  	"net/http"
    26  	_ "net/http/pprof"
    27  	"os"
    28  	"time"
    29  
    30  	"github.com/spf13/pflag"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	cgscheme "k8s.io/client-go/kubernetes/scheme"
    33  	"k8s.io/client-go/tools/leaderelection/resourcelock"
    34  	cgrecord "k8s.io/client-go/tools/record"
    35  	"k8s.io/klog/v2"
    36  	"k8s.io/klog/v2/klogr"
    37  	ctrl "sigs.k8s.io/controller-runtime"
    38  	"sigs.k8s.io/controller-runtime/pkg/controller"
    39  
    40  	// +kubebuilder:scaffold:imports
    41  	infrav1alpha3 "sigs.k8s.io/cluster-api-provider-aws/api/v1alpha3"
    42  	infrav1alpha4 "sigs.k8s.io/cluster-api-provider-aws/api/v1alpha4"
    43  	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
    44  	eksbootstrapv1alpha3 "sigs.k8s.io/cluster-api-provider-aws/bootstrap/eks/api/v1alpha3"
    45  	eksbootstrapv1alpha4 "sigs.k8s.io/cluster-api-provider-aws/bootstrap/eks/api/v1alpha4"
    46  	eksbootstrapv1 "sigs.k8s.io/cluster-api-provider-aws/bootstrap/eks/api/v1beta1"
    47  	eksbootstrapcontrollers "sigs.k8s.io/cluster-api-provider-aws/bootstrap/eks/controllers"
    48  	"sigs.k8s.io/cluster-api-provider-aws/controllers"
    49  	ekscontrolplanev1alpha3 "sigs.k8s.io/cluster-api-provider-aws/controlplane/eks/api/v1alpha3"
    50  	ekscontrolplanev1alpha4 "sigs.k8s.io/cluster-api-provider-aws/controlplane/eks/api/v1alpha4"
    51  	ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/controlplane/eks/api/v1beta1"
    52  	ekscontrolplanecontrollers "sigs.k8s.io/cluster-api-provider-aws/controlplane/eks/controllers"
    53  	expinfrav1alpha3 "sigs.k8s.io/cluster-api-provider-aws/exp/api/v1alpha3"
    54  	expinfrav1alpha4 "sigs.k8s.io/cluster-api-provider-aws/exp/api/v1alpha4"
    55  	expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/exp/api/v1beta1"
    56  	"sigs.k8s.io/cluster-api-provider-aws/exp/controlleridentitycreator"
    57  	expcontrollers "sigs.k8s.io/cluster-api-provider-aws/exp/controllers"
    58  	"sigs.k8s.io/cluster-api-provider-aws/exp/instancestate"
    59  	"sigs.k8s.io/cluster-api-provider-aws/feature"
    60  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/endpoints"
    61  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope"
    62  	"sigs.k8s.io/cluster-api-provider-aws/pkg/record"
    63  	"sigs.k8s.io/cluster-api-provider-aws/version"
    64  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    65  	expclusterv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    66  )
    67  
    68  var (
    69  	scheme   = runtime.NewScheme()
    70  	setupLog = ctrl.Log.WithName("setup")
    71  )
    72  
    73  func init() {
    74  	_ = eksbootstrapv1.AddToScheme(scheme)
    75  	_ = eksbootstrapv1alpha3.AddToScheme(scheme)
    76  	_ = eksbootstrapv1alpha4.AddToScheme(scheme)
    77  	_ = cgscheme.AddToScheme(scheme)
    78  	_ = clusterv1.AddToScheme(scheme)
    79  	_ = expclusterv1.AddToScheme(scheme)
    80  	_ = ekscontrolplanev1.AddToScheme(scheme)
    81  	_ = ekscontrolplanev1alpha3.AddToScheme(scheme)
    82  	_ = ekscontrolplanev1alpha4.AddToScheme(scheme)
    83  	_ = infrav1.AddToScheme(scheme)
    84  	_ = infrav1alpha3.AddToScheme(scheme)
    85  	_ = expinfrav1alpha3.AddToScheme(scheme)
    86  	_ = infrav1alpha4.AddToScheme(scheme)
    87  	_ = expinfrav1alpha4.AddToScheme(scheme)
    88  	_ = expinfrav1.AddToScheme(scheme)
    89  	// +kubebuilder:scaffold:scheme
    90  }
    91  
    92  var (
    93  	metricsBindAddr          string
    94  	enableLeaderElection     bool
    95  	leaderElectionNamespace  string
    96  	watchNamespace           string
    97  	watchFilterValue         string
    98  	profilerAddress          string
    99  	awsClusterConcurrency    int
   100  	instanceStateConcurrency int
   101  	awsMachineConcurrency    int
   102  	syncPeriod               time.Duration
   103  	webhookPort              int
   104  	webhookCertDir           string
   105  	healthAddr               string
   106  	serviceEndpoints         string
   107  
   108  	// maxEKSSyncPeriod is the maximum allowed duration for the sync-period flag when using EKS. It is set to 10 minutes
   109  	// because during resync it will create a new AWS auth token which can a maximum life of 15 minutes and this ensures
   110  	// the token (and kubeconfig secret) is refreshed before token expiration.
   111  	maxEKSSyncPeriod         = time.Minute * 10
   112  	errMaxSyncPeriodExceeded = errors.New("sync period greater than maximum allowed")
   113  	errEKSInvalidFlags       = errors.New("invalid EKS flag combination")
   114  )
   115  
   116  func main() {
   117  	klog.InitFlags(nil)
   118  
   119  	rand.Seed(time.Now().UnixNano())
   120  	initFlags(pflag.CommandLine)
   121  	pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
   122  	pflag.Parse()
   123  
   124  	ctrl.SetLogger(klogr.New())
   125  
   126  	if watchNamespace != "" {
   127  		setupLog.Info("Watching cluster-api objects only in namespace for reconciliation", "namespace", watchNamespace)
   128  	}
   129  
   130  	if profilerAddress != "" {
   131  		setupLog.Info("Profiler listening for requests", "profiler-address", profilerAddress)
   132  		go func() {
   133  			setupLog.Error(http.ListenAndServe(profilerAddress, nil), "listen and serve error")
   134  		}()
   135  	}
   136  
   137  	// Machine and cluster operations can create enough events to trigger the event recorder spam filter
   138  	// Setting the burst size higher ensures all events will be recorded and submitted to the API
   139  	broadcaster := cgrecord.NewBroadcasterWithCorrelatorOptions(cgrecord.CorrelatorOptions{
   140  		BurstSize: 100,
   141  	})
   142  
   143  	ctx := ctrl.SetupSignalHandler()
   144  
   145  	restConfig := ctrl.GetConfigOrDie()
   146  	restConfig.UserAgent = "cluster-api-provider-aws-controller"
   147  	mgr, err := ctrl.NewManager(restConfig, ctrl.Options{
   148  		Scheme:                     scheme,
   149  		MetricsBindAddress:         metricsBindAddr,
   150  		LeaderElection:             enableLeaderElection,
   151  		LeaderElectionResourceLock: resourcelock.LeasesResourceLock,
   152  		LeaderElectionID:           "controller-leader-elect-capa",
   153  		LeaderElectionNamespace:    leaderElectionNamespace,
   154  		SyncPeriod:                 &syncPeriod,
   155  		Namespace:                  watchNamespace,
   156  		EventBroadcaster:           broadcaster,
   157  		Port:                       webhookPort,
   158  		CertDir:                    webhookCertDir,
   159  		HealthProbeBindAddress:     healthAddr,
   160  	})
   161  	if err != nil {
   162  		setupLog.Error(err, "unable to start manager")
   163  		os.Exit(1)
   164  	}
   165  
   166  	// Initialize event recorder.
   167  	record.InitFromRecorder(mgr.GetEventRecorderFor("aws-controller"))
   168  
   169  	setupLog.V(1).Info(fmt.Sprintf("feature gates: %+v\n", feature.Gates))
   170  
   171  	externalResourceGC := false
   172  	if feature.Gates.Enabled(feature.ExternalResourceGC) {
   173  		setupLog.Info("enabling external resource garbage collection")
   174  		externalResourceGC = true
   175  	}
   176  
   177  	// Parse service endpoints.
   178  	AWSServiceEndpoints, err := endpoints.ParseFlag(serviceEndpoints)
   179  	if err != nil {
   180  		setupLog.Error(err, "unable to parse service endpoints", "controller", "AWSCluster")
   181  		os.Exit(1)
   182  	}
   183  
   184  	if err = (&controllers.AWSMachineReconciler{
   185  		Client:           mgr.GetClient(),
   186  		Log:              ctrl.Log.WithName("controllers").WithName("AWSMachine"),
   187  		Recorder:         mgr.GetEventRecorderFor("awsmachine-controller"),
   188  		Endpoints:        AWSServiceEndpoints,
   189  		WatchFilterValue: watchFilterValue,
   190  	}).SetupWithManager(ctx, mgr, controller.Options{MaxConcurrentReconciles: awsMachineConcurrency, RecoverPanic: true}); err != nil {
   191  		setupLog.Error(err, "unable to create controller", "controller", "AWSMachine")
   192  		os.Exit(1)
   193  	}
   194  	if err = (&controllers.AWSClusterReconciler{
   195  		Client:             mgr.GetClient(),
   196  		Recorder:           mgr.GetEventRecorderFor("awscluster-controller"),
   197  		Endpoints:          AWSServiceEndpoints,
   198  		WatchFilterValue:   watchFilterValue,
   199  		ExternalResourceGC: externalResourceGC,
   200  	}).SetupWithManager(ctx, mgr, controller.Options{MaxConcurrentReconciles: awsClusterConcurrency, RecoverPanic: true}); err != nil {
   201  		setupLog.Error(err, "unable to create controller", "controller", "AWSCluster")
   202  		os.Exit(1)
   203  	}
   204  	enableGates(ctx, mgr, AWSServiceEndpoints, externalResourceGC)
   205  
   206  	if err = (&infrav1.AWSMachineTemplate{}).SetupWebhookWithManager(mgr); err != nil {
   207  		setupLog.Error(err, "unable to create webhook", "webhook", "AWSMachineTemplate")
   208  		os.Exit(1)
   209  	}
   210  	if err = (&infrav1.AWSCluster{}).SetupWebhookWithManager(mgr); err != nil {
   211  		setupLog.Error(err, "unable to create webhook", "webhook", "AWSCluster")
   212  		os.Exit(1)
   213  	}
   214  	if err = (&infrav1.AWSClusterTemplate{}).SetupWebhookWithManager(mgr); err != nil {
   215  		setupLog.Error(err, "unable to create webhook", "webhook", "AWSClusterTemplate")
   216  		os.Exit(1)
   217  	}
   218  	if err = (&infrav1.AWSClusterControllerIdentity{}).SetupWebhookWithManager(mgr); err != nil {
   219  		setupLog.Error(err, "unable to create webhook", "webhook", "AWSClusterControllerIdentity")
   220  		os.Exit(1)
   221  	}
   222  	if err = (&infrav1.AWSClusterRoleIdentity{}).SetupWebhookWithManager(mgr); err != nil {
   223  		setupLog.Error(err, "unable to create webhook", "webhook", "AWSClusterRoleIdentity")
   224  		os.Exit(1)
   225  	}
   226  	if err = (&infrav1.AWSClusterStaticIdentity{}).SetupWebhookWithManager(mgr); err != nil {
   227  		setupLog.Error(err, "unable to create webhook", "webhook", "AWSClusterStaticIdentity")
   228  		os.Exit(1)
   229  	}
   230  	if err = (&infrav1.AWSMachine{}).SetupWebhookWithManager(mgr); err != nil {
   231  		setupLog.Error(err, "unable to create webhook", "webhook", "AWSMachine")
   232  		os.Exit(1)
   233  	}
   234  	if feature.Gates.Enabled(feature.EKS) {
   235  		setupLog.Info("enabling EKS webhooks")
   236  		if err := (&ekscontrolplanev1.AWSManagedControlPlane{}).SetupWebhookWithManager(mgr); err != nil {
   237  			setupLog.Error(err, "unable to create webhook", "webhook", "AWSManagedControlPlane")
   238  			os.Exit(1)
   239  		}
   240  		if feature.Gates.Enabled(feature.EKSFargate) {
   241  			if err = (&expinfrav1.AWSFargateProfile{}).SetupWebhookWithManager(mgr); err != nil {
   242  				setupLog.Error(err, "unable to create webhook", "webhook", "AWSFargateProfile")
   243  				os.Exit(1)
   244  			}
   245  		}
   246  		if feature.Gates.Enabled(feature.MachinePool) {
   247  			if err = (&expinfrav1.AWSManagedMachinePool{}).SetupWebhookWithManager(mgr); err != nil {
   248  				setupLog.Error(err, "unable to create webhook", "webhook", "AWSManagedMachinePool")
   249  				os.Exit(1)
   250  			}
   251  		}
   252  	}
   253  	if feature.Gates.Enabled(feature.MachinePool) {
   254  		setupLog.Info("enabling webhook for AWSMachinePool")
   255  		if err = (&expinfrav1.AWSMachinePool{}).SetupWebhookWithManager(mgr); err != nil {
   256  			setupLog.Error(err, "unable to create webhook", "webhook", "AWSMachinePool")
   257  			os.Exit(1)
   258  		}
   259  	}
   260  
   261  	// +kubebuilder:scaffold:builder
   262  
   263  	if err := mgr.AddReadyzCheck("webhook", mgr.GetWebhookServer().StartedChecker()); err != nil {
   264  		setupLog.Error(err, "unable to create ready check")
   265  		os.Exit(1)
   266  	}
   267  
   268  	if err := mgr.AddHealthzCheck("webhook", mgr.GetWebhookServer().StartedChecker()); err != nil {
   269  		setupLog.Error(err, "unable to create health check")
   270  		os.Exit(1)
   271  	}
   272  
   273  	setupLog.Info("starting manager", "version", version.Get().String())
   274  	if err := mgr.Start(ctx); err != nil {
   275  		setupLog.Error(err, "problem running manager")
   276  		os.Exit(1)
   277  	}
   278  }
   279  
   280  func enableGates(ctx context.Context, mgr ctrl.Manager, awsServiceEndpoints []scope.ServiceEndpoint, externalResourceGC bool) {
   281  	if feature.Gates.Enabled(feature.EKS) {
   282  		setupLog.Info("enabling EKS controllers")
   283  
   284  		if syncPeriod > maxEKSSyncPeriod {
   285  			setupLog.Error(errMaxSyncPeriodExceeded, "failed to enable EKS", "max-sync-period", maxEKSSyncPeriod, "syn-period", syncPeriod)
   286  			os.Exit(1)
   287  		}
   288  
   289  		enableIAM := feature.Gates.Enabled(feature.EKSEnableIAM)
   290  		allowAddRoles := feature.Gates.Enabled(feature.EKSAllowAddRoles)
   291  		setupLog.V(2).Info("EKS IAM role creation", "enabled", enableIAM)
   292  		setupLog.V(2).Info("EKS IAM additional roles", "enabled", allowAddRoles)
   293  		if allowAddRoles && !enableIAM {
   294  			setupLog.Error(errEKSInvalidFlags, "cannot use EKSAllowAddRoles flag without EKSEnableIAM")
   295  			os.Exit(1)
   296  		}
   297  
   298  		setupLog.V(2).Info("enabling EKS control plane controller")
   299  		if err := (&ekscontrolplanecontrollers.AWSManagedControlPlaneReconciler{
   300  			Client:               mgr.GetClient(),
   301  			EnableIAM:            enableIAM,
   302  			AllowAdditionalRoles: allowAddRoles,
   303  			Endpoints:            awsServiceEndpoints,
   304  			WatchFilterValue:     watchFilterValue,
   305  			ExternalResourceGC:   externalResourceGC,
   306  		}).SetupWithManager(ctx, mgr, controller.Options{MaxConcurrentReconciles: awsClusterConcurrency, RecoverPanic: true}); err != nil {
   307  			setupLog.Error(err, "unable to create controller", "controller", "AWSManagedControlPlane")
   308  			os.Exit(1)
   309  		}
   310  
   311  		setupLog.V(2).Info("enabling EKS bootstrap controller")
   312  		if err := (&eksbootstrapcontrollers.EKSConfigReconciler{
   313  			Client:           mgr.GetClient(),
   314  			WatchFilterValue: watchFilterValue,
   315  		}).SetupWithManager(ctx, mgr, controller.Options{MaxConcurrentReconciles: awsClusterConcurrency, RecoverPanic: true}); err != nil {
   316  			setupLog.Error(err, "unable to create controller", "controller", "EKSConfig")
   317  			os.Exit(1)
   318  		}
   319  
   320  		if feature.Gates.Enabled(feature.EKSFargate) {
   321  			setupLog.V(2).Info("enabling EKS fargate profile controller")
   322  			if err := (&expcontrollers.AWSFargateProfileReconciler{
   323  				Client:           mgr.GetClient(),
   324  				Recorder:         mgr.GetEventRecorderFor("awsfargateprofile-reconciler"),
   325  				EnableIAM:        enableIAM,
   326  				Endpoints:        awsServiceEndpoints,
   327  				WatchFilterValue: watchFilterValue,
   328  			}).SetupWithManager(ctx, mgr, controller.Options{MaxConcurrentReconciles: awsClusterConcurrency, RecoverPanic: true}); err != nil {
   329  				setupLog.Error(err, "unable to create controller", "controller", "AWSFargateProfile")
   330  			}
   331  		}
   332  
   333  		if feature.Gates.Enabled(feature.MachinePool) {
   334  			setupLog.V(2).Info("enabling EKS managed machine pool controller")
   335  			if err := (&expcontrollers.AWSManagedMachinePoolReconciler{
   336  				AllowAdditionalRoles: allowAddRoles,
   337  				Client:               mgr.GetClient(),
   338  				EnableIAM:            enableIAM,
   339  				Endpoints:            awsServiceEndpoints,
   340  				Recorder:             mgr.GetEventRecorderFor("awsmanagedmachinepool-reconciler"),
   341  				WatchFilterValue:     watchFilterValue,
   342  			}).SetupWithManager(ctx, mgr, controller.Options{MaxConcurrentReconciles: instanceStateConcurrency, RecoverPanic: true}); err != nil {
   343  				setupLog.Error(err, "unable to create controller", "controller", "AWSManagedMachinePool")
   344  				os.Exit(1)
   345  			}
   346  		}
   347  	}
   348  	if feature.Gates.Enabled(feature.MachinePool) {
   349  		setupLog.V(2).Info("enabling machine pool controller")
   350  		if err := (&expcontrollers.AWSMachinePoolReconciler{
   351  			Client:           mgr.GetClient(),
   352  			Recorder:         mgr.GetEventRecorderFor("awsmachinepool-controller"),
   353  			WatchFilterValue: watchFilterValue,
   354  		}).SetupWithManager(ctx, mgr, controller.Options{MaxConcurrentReconciles: instanceStateConcurrency, RecoverPanic: true}); err != nil {
   355  			setupLog.Error(err, "unable to create controller", "controller", "AWSMachinePool")
   356  			os.Exit(1)
   357  		}
   358  	}
   359  	if feature.Gates.Enabled(feature.EventBridgeInstanceState) {
   360  		setupLog.Info("EventBridge notifications enabled. enabling AWSInstanceStateController")
   361  		if err := (&instancestate.AwsInstanceStateReconciler{
   362  			Client:           mgr.GetClient(),
   363  			Log:              ctrl.Log.WithName("controllers").WithName("AWSInstanceStateController"),
   364  			Endpoints:        awsServiceEndpoints,
   365  			WatchFilterValue: watchFilterValue,
   366  		}).SetupWithManager(ctx, mgr, controller.Options{MaxConcurrentReconciles: instanceStateConcurrency, RecoverPanic: true}); err != nil {
   367  			setupLog.Error(err, "unable to create controller", "controller", "AWSInstanceStateController")
   368  			os.Exit(1)
   369  		}
   370  	}
   371  	if feature.Gates.Enabled(feature.AutoControllerIdentityCreator) {
   372  		setupLog.Info("AutoControllerIdentityCreator enabled")
   373  		if err := (&controlleridentitycreator.AWSControllerIdentityReconciler{
   374  			Client:           mgr.GetClient(),
   375  			Log:              ctrl.Log.WithName("controllers").WithName("AWSControllerIdentity"),
   376  			Endpoints:        awsServiceEndpoints,
   377  			WatchFilterValue: watchFilterValue,
   378  		}).SetupWithManager(ctx, mgr, controller.Options{MaxConcurrentReconciles: awsClusterConcurrency, RecoverPanic: true}); err != nil {
   379  			setupLog.Error(err, "unable to create controller", "controller", "AWSControllerIdentity")
   380  			os.Exit(1)
   381  		}
   382  	}
   383  
   384  	if feature.Gates.Enabled(feature.BootstrapFormatIgnition) {
   385  		setupLog.Info("Enabling Ignition support for machine bootstrap data")
   386  	}
   387  }
   388  func initFlags(fs *pflag.FlagSet) {
   389  	fs.StringVar(
   390  		&metricsBindAddr,
   391  		"metrics-bind-addr",
   392  		"localhost:8080",
   393  		"The address the metric endpoint binds to.",
   394  	)
   395  
   396  	fs.BoolVar(
   397  		&enableLeaderElection,
   398  		"leader-elect",
   399  		false,
   400  		"Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.",
   401  	)
   402  
   403  	fs.StringVar(
   404  		&watchNamespace,
   405  		"namespace",
   406  		"",
   407  		"Namespace that the controller watches to reconcile cluster-api objects. If unspecified, the controller watches for cluster-api objects across all namespaces.",
   408  	)
   409  
   410  	fs.StringVar(
   411  		&leaderElectionNamespace,
   412  		"leader-elect-namespace",
   413  		"",
   414  		"Namespace that the controller performs leader election in. If unspecified, the controller will discover which namespace it is running in.",
   415  	)
   416  
   417  	fs.StringVar(
   418  		&profilerAddress,
   419  		"profiler-address",
   420  		"",
   421  		"Bind address to expose the pprof profiler (e.g. localhost:6060)",
   422  	)
   423  
   424  	fs.IntVar(&awsClusterConcurrency,
   425  		"awscluster-concurrency",
   426  		5,
   427  		"Number of AWSClusters to process simultaneously",
   428  	)
   429  
   430  	fs.IntVar(&instanceStateConcurrency,
   431  		"instance-state-concurrency",
   432  		5,
   433  		"Number of concurrent watches for instance state changes",
   434  	)
   435  
   436  	fs.IntVar(&awsMachineConcurrency,
   437  		"awsmachine-concurrency",
   438  		10,
   439  		"Number of AWSMachines to process simultaneously",
   440  	)
   441  
   442  	fs.DurationVar(&syncPeriod,
   443  		"sync-period",
   444  		10*time.Minute,
   445  		fmt.Sprintf("The minimum interval at which watched resources are reconciled. If EKS is enabled the maximum allowed is %s", maxEKSSyncPeriod),
   446  	)
   447  
   448  	fs.IntVar(&webhookPort,
   449  		"webhook-port",
   450  		9443,
   451  		"Webhook Server port.",
   452  	)
   453  
   454  	fs.StringVar(&webhookCertDir, "webhook-cert-dir", "/tmp/k8s-webhook-server/serving-certs/",
   455  		"Webhook cert dir, only used when webhook-port is specified.")
   456  
   457  	fs.StringVar(&healthAddr,
   458  		"health-addr",
   459  		":9440",
   460  		"The address the health endpoint binds to.",
   461  	)
   462  
   463  	fs.StringVar(&serviceEndpoints,
   464  		"service-endpoints",
   465  		"",
   466  		"Set custom AWS service endpoins in semi-colon separated format: ${SigningRegion1}:${ServiceID1}=${URL},${ServiceID2}=${URL};${SigningRegion2}...",
   467  	)
   468  
   469  	fs.StringVar(
   470  		&watchFilterValue,
   471  		"watch-filter",
   472  		"",
   473  		fmt.Sprintf("Label value that the controller watches to reconcile cluster-api objects. Label key is always %s. If unspecified, the controller watches for all cluster-api objects.", clusterv1.WatchLabel),
   474  	)
   475  
   476  	feature.MutableGates.AddFlag(fs)
   477  }