sigs.k8s.io/kubebuilder/v3@v3.14.0/pkg/plugins/golang/v4/scaffolds/internal/templates/main.go (about)

     1  /*
     2  Copyright 2022 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 templates
    18  
    19  import (
    20  	"fmt"
    21  	"path/filepath"
    22  
    23  	"sigs.k8s.io/kubebuilder/v3/pkg/machinery"
    24  )
    25  
    26  const defaultMainPath = "cmd/main.go"
    27  
    28  var _ machinery.Template = &Main{}
    29  
    30  // Main scaffolds a file that defines the controller manager entry point
    31  type Main struct {
    32  	machinery.TemplateMixin
    33  	machinery.BoilerplateMixin
    34  	machinery.DomainMixin
    35  	machinery.RepositoryMixin
    36  	machinery.ComponentConfigMixin
    37  }
    38  
    39  // SetTemplateDefaults implements file.Template
    40  func (f *Main) SetTemplateDefaults() error {
    41  	if f.Path == "" {
    42  		f.Path = filepath.Join(defaultMainPath)
    43  	}
    44  
    45  	f.TemplateBody = fmt.Sprintf(mainTemplate,
    46  		machinery.NewMarkerFor(f.Path, importMarker),
    47  		machinery.NewMarkerFor(f.Path, addSchemeMarker),
    48  		machinery.NewMarkerFor(f.Path, setupMarker),
    49  	)
    50  
    51  	return nil
    52  }
    53  
    54  var _ machinery.Inserter = &MainUpdater{}
    55  
    56  // MainUpdater updates cmd/main.go to run Controllers
    57  type MainUpdater struct { //nolint:maligned
    58  	machinery.RepositoryMixin
    59  	machinery.MultiGroupMixin
    60  	machinery.ResourceMixin
    61  
    62  	// Flags to indicate which parts need to be included when updating the file
    63  	WireResource, WireController, WireWebhook bool
    64  }
    65  
    66  // GetPath implements file.Builder
    67  func (*MainUpdater) GetPath() string {
    68  	return defaultMainPath
    69  }
    70  
    71  // GetIfExistsAction implements file.Builder
    72  func (*MainUpdater) GetIfExistsAction() machinery.IfExistsAction {
    73  	return machinery.OverwriteFile
    74  }
    75  
    76  const (
    77  	importMarker    = "imports"
    78  	addSchemeMarker = "scheme"
    79  	setupMarker     = "builder"
    80  )
    81  
    82  // GetMarkers implements file.Inserter
    83  func (f *MainUpdater) GetMarkers() []machinery.Marker {
    84  	return []machinery.Marker{
    85  		machinery.NewMarkerFor(defaultMainPath, importMarker),
    86  		machinery.NewMarkerFor(defaultMainPath, addSchemeMarker),
    87  		machinery.NewMarkerFor(defaultMainPath, setupMarker),
    88  	}
    89  }
    90  
    91  const (
    92  	apiImportCodeFragment = `%s "%s"
    93  `
    94  	controllerImportCodeFragment = `"%s/internal/controller"
    95  `
    96  	multiGroupControllerImportCodeFragment = `%scontroller "%s/internal/controller/%s"
    97  `
    98  	addschemeCodeFragment = `utilruntime.Must(%s.AddToScheme(scheme))
    99  `
   100  	reconcilerSetupCodeFragment = `if err = (&controller.%sReconciler{
   101  		Client: mgr.GetClient(),
   102  		Scheme: mgr.GetScheme(),
   103  	}).SetupWithManager(mgr); err != nil {
   104  		setupLog.Error(err, "unable to create controller", "controller", "%s")
   105  		os.Exit(1)
   106  	}
   107  `
   108  	multiGroupReconcilerSetupCodeFragment = `if err = (&%scontroller.%sReconciler{
   109  		Client: mgr.GetClient(),
   110  		Scheme: mgr.GetScheme(),
   111  	}).SetupWithManager(mgr); err != nil {
   112  		setupLog.Error(err, "unable to create controller", "controller", "%s")
   113  		os.Exit(1)
   114  	}
   115  `
   116  	webhookSetupCodeFragment = `if os.Getenv("ENABLE_WEBHOOKS") != "false" {
   117  		if err = (&%s.%s{}).SetupWebhookWithManager(mgr); err != nil {
   118  			setupLog.Error(err, "unable to create webhook", "webhook", "%s")
   119  			os.Exit(1)
   120  		}
   121  	}
   122  `
   123  )
   124  
   125  // GetCodeFragments implements file.Inserter
   126  func (f *MainUpdater) GetCodeFragments() machinery.CodeFragmentsMap {
   127  	fragments := make(machinery.CodeFragmentsMap, 3)
   128  
   129  	// If resource is not being provided we are creating the file, not updating it
   130  	if f.Resource == nil {
   131  		return fragments
   132  	}
   133  
   134  	// Generate import code fragments
   135  	imports := make([]string, 0)
   136  	if f.WireResource {
   137  		imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path))
   138  	}
   139  
   140  	if f.WireController {
   141  		if !f.MultiGroup || f.Resource.Group == "" {
   142  			imports = append(imports, fmt.Sprintf(controllerImportCodeFragment, f.Repo))
   143  		} else {
   144  			imports = append(imports, fmt.Sprintf(multiGroupControllerImportCodeFragment,
   145  				f.Resource.PackageName(), f.Repo, f.Resource.Group))
   146  		}
   147  	}
   148  
   149  	// Generate add scheme code fragments
   150  	addScheme := make([]string, 0)
   151  	if f.WireResource {
   152  		addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias()))
   153  	}
   154  
   155  	// Generate setup code fragments
   156  	setup := make([]string, 0)
   157  	if f.WireController {
   158  		if !f.MultiGroup || f.Resource.Group == "" {
   159  			setup = append(setup, fmt.Sprintf(reconcilerSetupCodeFragment,
   160  				f.Resource.Kind, f.Resource.Kind))
   161  		} else {
   162  			setup = append(setup, fmt.Sprintf(multiGroupReconcilerSetupCodeFragment,
   163  				f.Resource.PackageName(), f.Resource.Kind, f.Resource.Kind))
   164  		}
   165  	}
   166  	if f.WireWebhook {
   167  		setup = append(setup, fmt.Sprintf(webhookSetupCodeFragment,
   168  			f.Resource.ImportAlias(), f.Resource.Kind, f.Resource.Kind))
   169  	}
   170  
   171  	// Only store code fragments in the map if the slices are non-empty
   172  	if len(imports) != 0 {
   173  		fragments[machinery.NewMarkerFor(defaultMainPath, importMarker)] = imports
   174  	}
   175  	if len(addScheme) != 0 {
   176  		fragments[machinery.NewMarkerFor(defaultMainPath, addSchemeMarker)] = addScheme
   177  	}
   178  	if len(setup) != 0 {
   179  		fragments[machinery.NewMarkerFor(defaultMainPath, setupMarker)] = setup
   180  	}
   181  
   182  	return fragments
   183  }
   184  
   185  var mainTemplate = `{{ .Boilerplate }}
   186  
   187  package main
   188  
   189  import (
   190  	"crypto/tls"
   191  	"flag"
   192  	"os"
   193  
   194  	// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
   195  	// to ensure that exec-entrypoint and run can make use of them.
   196  	_ "k8s.io/client-go/plugin/pkg/client/auth"
   197  
   198  	"k8s.io/apimachinery/pkg/runtime"
   199  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
   200  	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
   201  	ctrl "sigs.k8s.io/controller-runtime"
   202  	"sigs.k8s.io/controller-runtime/pkg/log/zap"
   203  	"sigs.k8s.io/controller-runtime/pkg/healthz"
   204  	"sigs.k8s.io/controller-runtime/pkg/webhook"
   205  	metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
   206  	%s
   207  )
   208  
   209  var (
   210  	scheme = runtime.NewScheme()
   211  	setupLog = ctrl.Log.WithName("setup")
   212  )
   213  
   214  func init() {
   215  	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
   216  
   217  	%s
   218  }
   219  
   220  func main() {
   221  {{- if not .ComponentConfig }}
   222  	var metricsAddr string
   223  	var enableLeaderElection bool
   224  	var probeAddr string
   225  	var secureMetrics bool
   226  	var enableHTTP2 bool
   227  	flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
   228  	flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
   229  	flag.BoolVar(&enableLeaderElection, "leader-elect", false,
   230  		"Enable leader election for controller manager. " +
   231  		"Enabling this will ensure there is only one active controller manager.")
   232  	flag.BoolVar(&secureMetrics, "metrics-secure", false, 
   233  		"If set the metrics endpoint is served securely")
   234  	flag.BoolVar(&enableHTTP2, "enable-http2", false, 
   235  		"If set, HTTP/2 will be enabled for the metrics and webhook servers")
   236  {{- else }}
   237    var configFile string
   238  	flag.StringVar(&configFile, "config", "", 
   239  		"The controller will load its initial configuration from this file. " +
   240  		"Omit this flag to use the default configuration values. " +
   241  		"Command-line flags override configuration from this file.")
   242  {{- end }}
   243  	opts := zap.Options{
   244  		Development: true,
   245  	}
   246  	opts.BindFlags(flag.CommandLine)
   247  	flag.Parse()
   248  
   249  	ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
   250  
   251  {{ if not .ComponentConfig }}
   252  	// if the enable-http2 flag is false (the default), http/2 should be disabled
   253  	// due to its vulnerabilities. More specifically, disabling http/2 will
   254  	// prevent from being vulnerable to the HTTP/2 Stream Cancelation and 
   255  	// Rapid Reset CVEs. For more information see:
   256  	// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
   257  	// - https://github.com/advisories/GHSA-4374-p667-p6c8
   258  	disableHTTP2 := func(c *tls.Config) {
   259  		setupLog.Info("disabling http/2")
   260  		c.NextProtos = []string{"http/1.1"}
   261  	}
   262  
   263  	tlsOpts := []func(*tls.Config){}
   264  	if !enableHTTP2 {
   265  		tlsOpts = append(tlsOpts, disableHTTP2)
   266  	}
   267  
   268  	webhookServer := webhook.NewServer(webhook.Options{
   269  		TLSOpts: tlsOpts,
   270  	})
   271  
   272  	mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
   273  		Scheme:                 scheme,
   274  		Metrics:                metricsserver.Options{
   275  			BindAddress: metricsAddr,
   276  			SecureServing: secureMetrics,
   277  			TLSOpts: tlsOpts,
   278  		},
   279  		WebhookServer: webhookServer,
   280  		HealthProbeBindAddress: probeAddr,
   281  		LeaderElection:         enableLeaderElection,
   282  		LeaderElectionID:       "{{ hashFNV .Repo }}.{{ .Domain }}",
   283  		// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
   284  		// when the Manager ends. This requires the binary to immediately end when the
   285  		// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
   286  		// speeds up voluntary leader transitions as the new leader don't have to wait
   287  		// LeaseDuration time first.
   288  		//
   289  		// In the default scaffold provided, the program ends immediately after 
   290  		// the manager stops, so would be fine to enable this option. However, 
   291  		// if you are doing or is intended to do any operation such as perform cleanups 
   292  		// after the manager stops then its usage might be unsafe.
   293  		// LeaderElectionReleaseOnCancel: true,
   294  	})
   295  {{- else }}
   296  	var err error
   297  	options := ctrl.Options{Scheme: scheme}
   298  	if configFile != "" {
   299  		options, err = options.AndFrom(ctrl.ConfigFile().AtPath(configFile))
   300  		if err != nil {
   301  			setupLog.Error(err, "unable to load the config file")
   302  			os.Exit(1)
   303  		}
   304  	}
   305  
   306  	mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), options)
   307  {{- end }}
   308  	if err != nil {
   309  		setupLog.Error(err, "unable to start manager")
   310  		os.Exit(1)
   311  	}
   312  
   313  	%s
   314  
   315  	if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
   316  		setupLog.Error(err, "unable to set up health check")
   317  		os.Exit(1)
   318  	}
   319  	if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
   320  		setupLog.Error(err, "unable to set up ready check")
   321  		os.Exit(1)
   322  	}
   323  
   324  	setupLog.Info("starting manager")
   325  	if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
   326  		setupLog.Error(err, "problem running manager")
   327  		os.Exit(1)
   328  	}
   329  }
   330  `