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 `