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 `