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

     1  /*
     2  Copyright 2020 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 = "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 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/controllers"
    95  `
    96  	multiGroupControllerImportCodeFragment = `%scontrollers "%s/controllers/%s"
    97  `
    98  	addschemeCodeFragment = `utilruntime.Must(%s.AddToScheme(scheme))
    99  `
   100  	reconcilerSetupCodeFragment = `if err = (&controllers.%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 = (&%scontrollers.%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 err = (&%s.%s{}).SetupWebhookWithManager(mgr); err != nil {
   117  		setupLog.Error(err, "unable to create webhook", "webhook", "%s")
   118  		os.Exit(1)
   119  	}
   120  `
   121  )
   122  
   123  // GetCodeFragments implements file.Inserter
   124  func (f *MainUpdater) GetCodeFragments() machinery.CodeFragmentsMap {
   125  	fragments := make(machinery.CodeFragmentsMap, 3)
   126  
   127  	// If resource is not being provided we are creating the file, not updating it
   128  	if f.Resource == nil {
   129  		return fragments
   130  	}
   131  
   132  	// Generate import code fragments
   133  	imports := make([]string, 0)
   134  	if f.WireResource {
   135  		imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path))
   136  	}
   137  
   138  	if f.WireController {
   139  		if !f.MultiGroup || f.Resource.Group == "" {
   140  			imports = append(imports, fmt.Sprintf(controllerImportCodeFragment, f.Repo))
   141  		} else {
   142  			imports = append(imports, fmt.Sprintf(multiGroupControllerImportCodeFragment,
   143  				f.Resource.PackageName(), f.Repo, f.Resource.Group))
   144  		}
   145  	}
   146  
   147  	// Generate add scheme code fragments
   148  	addScheme := make([]string, 0)
   149  	if f.WireResource {
   150  		addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias()))
   151  	}
   152  
   153  	// Generate setup code fragments
   154  	setup := make([]string, 0)
   155  	if f.WireController {
   156  		if !f.MultiGroup || f.Resource.Group == "" {
   157  			setup = append(setup, fmt.Sprintf(reconcilerSetupCodeFragment,
   158  				f.Resource.Kind, f.Resource.Kind))
   159  		} else {
   160  			setup = append(setup, fmt.Sprintf(multiGroupReconcilerSetupCodeFragment,
   161  				f.Resource.PackageName(), f.Resource.Kind, f.Resource.Kind))
   162  		}
   163  	}
   164  	if f.WireWebhook {
   165  		setup = append(setup, fmt.Sprintf(webhookSetupCodeFragment,
   166  			f.Resource.ImportAlias(), f.Resource.Kind, f.Resource.Kind))
   167  	}
   168  
   169  	// Only store code fragments in the map if the slices are non-empty
   170  	if len(imports) != 0 {
   171  		fragments[machinery.NewMarkerFor(defaultMainPath, importMarker)] = imports
   172  	}
   173  	if len(addScheme) != 0 {
   174  		fragments[machinery.NewMarkerFor(defaultMainPath, addSchemeMarker)] = addScheme
   175  	}
   176  	if len(setup) != 0 {
   177  		fragments[machinery.NewMarkerFor(defaultMainPath, setupMarker)] = setup
   178  	}
   179  
   180  	return fragments
   181  }
   182  
   183  var mainTemplate = `{{ .Boilerplate }}
   184  
   185  package main
   186  
   187  import (
   188  	"crypto/tls"
   189  	"flag"
   190  	"os"
   191  
   192  	// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
   193  	// to ensure that exec-entrypoint and run can make use of them.
   194  	_ "k8s.io/client-go/plugin/pkg/client/auth"
   195  
   196  	"k8s.io/apimachinery/pkg/runtime"
   197  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
   198  	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
   199  	ctrl "sigs.k8s.io/controller-runtime"
   200  	"sigs.k8s.io/controller-runtime/pkg/log/zap"
   201  	"sigs.k8s.io/controller-runtime/pkg/healthz"
   202  	"sigs.k8s.io/controller-runtime/pkg/webhook"
   203  	%s
   204  )
   205  
   206  var (
   207  	scheme = runtime.NewScheme()
   208  	setupLog = ctrl.Log.WithName("setup")
   209  )
   210  
   211  func init() {
   212  	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
   213  
   214  	%s
   215  }
   216  
   217  func main() {
   218  {{- if not .ComponentConfig }}
   219  	var metricsAddr string
   220  	var enableLeaderElection bool
   221  	var probeAddr string
   222  	var enableHTTP2 bool
   223  	flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
   224  	flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
   225  	flag.BoolVar(&enableLeaderElection, "leader-elect", false,
   226  		"Enable leader election for controller manager. " +
   227  		"Enabling this will ensure there is only one active controller manager.")
   228  	flag.BoolVar(&enableHTTP2, "enable-http2", false, 
   229  		"If set, HTTP/2 will be enabled for the metrics and webhook servers")
   230  {{- else }}
   231    var configFile string
   232  	flag.StringVar(&configFile, "config", "", 
   233  		"The controller will load its initial configuration from this file. " +
   234  		"Omit this flag to use the default configuration values. " +
   235  		"Command-line flags override configuration from this file.")
   236  {{- end }}
   237  	opts := zap.Options{
   238  		Development: true,
   239  	}
   240  	opts.BindFlags(flag.CommandLine)
   241  	flag.Parse()
   242  
   243  	ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
   244  
   245  {{ if not .ComponentConfig }}
   246  	// if the enable-http2 flag is false (the default), http/2 should be disabled
   247  	// due to its vulnerabilities. More specifically, disabling http/2 will
   248  	// prevent from being vulnerable to the HTTP/2 Stream Cancelation and 
   249  	// Rapid Reset CVEs. For more information see:
   250  	// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
   251  	// - https://github.com/advisories/GHSA-4374-p667-p6c8
   252  	disableHTTP2 := func(c *tls.Config) {
   253  		setupLog.Info("disabling http/2")
   254  		c.NextProtos = []string{"http/1.1"}
   255  	}
   256  
   257  	tlsOpts := []func(*tls.Config){}
   258  	if !enableHTTP2 {
   259  		tlsOpts = append(tlsOpts, disableHTTP2)
   260  	}
   261  
   262  	mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
   263  		Scheme:                 scheme,
   264  		MetricsBindAddress:     metricsAddr,
   265  		WebhookServer:          &webhook.Server{
   266  			TLSOpts: tlsOpts,
   267  		},
   268  		Port:                   9443,
   269  		HealthProbeBindAddress: probeAddr,
   270  		LeaderElection:         enableLeaderElection,
   271  		LeaderElectionID:       "{{ hashFNV .Repo }}.{{ .Domain }}",
   272  		// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
   273  		// when the Manager ends. This requires the binary to immediately end when the
   274  		// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
   275  		// speeds up voluntary leader transitions as the new leader don't have to wait
   276  		// LeaseDuration time first.
   277  		//
   278  		// In the default scaffold provided, the program ends immediately after 
   279  		// the manager stops, so would be fine to enable this option. However, 
   280  		// if you are doing or is intended to do any operation such as perform cleanups 
   281  		// after the manager stops then its usage might be unsafe.
   282  		// LeaderElectionReleaseOnCancel: true,
   283  	})
   284  {{- else }}
   285  	var err error
   286  	options := ctrl.Options{Scheme: scheme}
   287  	if configFile != "" {
   288  		options, err = options.AndFrom(ctrl.ConfigFile().AtPath(configFile))
   289  		if err != nil {
   290  			setupLog.Error(err, "unable to load the config file")
   291  			os.Exit(1)
   292  		}
   293  	}
   294  
   295  	mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), options)
   296  {{- end }}
   297  	if err != nil {
   298  		setupLog.Error(err, "unable to start manager")
   299  		os.Exit(1)
   300  	}
   301  
   302  	%s
   303  
   304  	if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
   305  		setupLog.Error(err, "unable to set up health check")
   306  		os.Exit(1)
   307  	}
   308  	if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
   309  		setupLog.Error(err, "unable to set up ready check")
   310  		os.Exit(1)
   311  	}
   312  
   313  	setupLog.Info("starting manager")
   314  	if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
   315  		setupLog.Error(err, "problem running manager")
   316  		os.Exit(1)
   317  	}
   318  }
   319  `