sigs.k8s.io/kubebuilder/v3@v3.14.0/hack/docs/internal/cronjob-tutorial/generate_cronjob.go (about) 1 /* 2 Copyright 2023 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 cronjob 18 19 import ( 20 "os" 21 "os/exec" 22 "path/filepath" 23 24 log "github.com/sirupsen/logrus" 25 "github.com/spf13/afero" 26 27 pluginutil "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util" 28 "sigs.k8s.io/kubebuilder/v3/test/e2e/utils" 29 ) 30 31 type Sample struct { 32 ctx *utils.TestContext 33 } 34 35 func NewSample(binaryPath, samplePath string) Sample { 36 log.Infof("Generating the sample context of Cronjob...") 37 38 ctx := newSampleContext(binaryPath, samplePath, "GO111MODULE=on") 39 40 return Sample{&ctx} 41 } 42 43 func newSampleContext(binaryPath string, samplePath string, env ...string) utils.TestContext { 44 cmdContext := &utils.CmdContext{ 45 Env: env, 46 Dir: samplePath, 47 } 48 49 testContext := utils.TestContext{ 50 CmdContext: cmdContext, 51 BinaryName: binaryPath, 52 } 53 54 return testContext 55 } 56 57 // Prepare the Context for the sample project 58 func (sp *Sample) Prepare() { 59 log.Infof("destroying directory for cronjob sample project") 60 sp.ctx.Destroy() 61 62 log.Infof("refreshing tools and creating directory...") 63 err := sp.ctx.Prepare() 64 65 CheckError("creating directory for sample project", err) 66 } 67 68 func (sp *Sample) GenerateSampleProject() { 69 log.Infof("Initializing the cronjob project") 70 71 err := sp.ctx.Init( 72 "--plugins", "go/v4", 73 "--domain", "tutorial.kubebuilder.io", 74 "--repo", "tutorial.kubebuilder.io/project", 75 "--license", "apache2", 76 "--owner", "The Kubernetes authors", 77 ) 78 CheckError("Initializing the cronjob project", err) 79 80 log.Infof("Adding a new config type") 81 err = sp.ctx.CreateAPI( 82 "--group", "batch", 83 "--version", "v1", 84 "--kind", "CronJob", 85 "--resource", "--controller", 86 ) 87 CheckError("Creating the API", err) 88 89 log.Infof("Implementing admission webhook") 90 err = sp.ctx.CreateWebhook( 91 "--group", "batch", 92 "--version", "v1", 93 "--kind", "CronJob", 94 "--defaulting", "--programmatic-validation", 95 ) 96 CheckError("Implementing admission webhook", err) 97 } 98 99 func (sp *Sample) UpdateTutorial() { 100 log.Println("TODO: update tutorial") 101 // 1. update specs 102 updateSpec(sp) 103 // 2. update webhook 104 updateWebhook(sp) 105 // 3. generate extra files 106 codeGen(sp) 107 // 4. compensate other intro in API 108 updateAPIStuff(sp) 109 // 5. update reconciliation and main.go 110 // 5.1 update controller 111 updateController(sp) 112 // 5.2 update main.go 113 updateMain(sp) 114 // 6. generate extra files 115 codeGen(sp) 116 // 7. update suite_test explanation 117 updateSuiteTest(sp) 118 // 8. uncomment kustomization 119 updateKustomization(sp) 120 // 9. add example 121 updateExample(sp) 122 // 10. add test 123 addControllerTest(sp) 124 } 125 126 func codeGen(sp *Sample) { 127 cmd := exec.Command("go", "get", "github.com/robfig/cron") 128 _, err := sp.ctx.Run(cmd) 129 CheckError("Failed to get package robfig/cron", err) 130 131 cmd = exec.Command("make", "manifests") 132 _, err = sp.ctx.Run(cmd) 133 CheckError("Failed to run make manifests for cronjob tutorial", err) 134 135 cmd = exec.Command("make", "all") 136 _, err = sp.ctx.Run(cmd) 137 CheckError("Failed to run make all for cronjob tutorial", err) 138 139 cmd = exec.Command("go", "mod", "tidy") 140 _, err = sp.ctx.Run(cmd) 141 CheckError("Failed to run go mod tidy for cronjob tutorial", err) 142 } 143 144 // insert code to fix docs 145 func updateSpec(sp *Sample) { 146 var err error 147 err = pluginutil.InsertCode( 148 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_types.go"), 149 `limitations under the License. 150 */`, 151 ` 152 // +kubebuilder:docs-gen:collapse=Apache License 153 154 /* 155 */`) 156 CheckError("fixing cronjob_types.go", err) 157 158 err = pluginutil.InsertCode( 159 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_types.go"), 160 `package v1`, 161 ` 162 /* 163 */`) 164 CheckError("fixing cronjob_types.go", err) 165 166 err = pluginutil.InsertCode( 167 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_types.go"), 168 `import (`, 169 ` 170 batchv1 "k8s.io/api/batch/v1" 171 corev1 "k8s.io/api/core/v1"`) 172 CheckError("fixing cronjob_types.go", err) 173 174 err = pluginutil.InsertCode( 175 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_types.go"), 176 `to be serialized.`, CronjobSpecExplaination) 177 CheckError("fixing cronjob_types.go", err) 178 179 err = pluginutil.InsertCode( 180 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_types.go"), 181 `type CronJobSpec struct {`, CronjobSpecStruct) 182 CheckError("fixing cronjob_types.go", err) 183 184 err = pluginutil.ReplaceInFile( 185 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_types.go"), 186 `// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 187 // Important: Run "make" to regenerate code after modifying this file 188 189 // Foo is an example field of CronJob. Edit cronjob_types.go to remove/update 190 Foo string`+" `"+`json:"foo,omitempty"`+"`", "") 191 CheckError("fixing cronjob_types.go", err) 192 193 err = pluginutil.InsertCode( 194 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_types.go"), 195 `// Important: Run "make" to regenerate code after modifying this file`, CronjobList) 196 CheckError("fixing cronjob_types.go", err) 197 198 err = pluginutil.InsertCode( 199 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_types.go"), 200 `SchemeBuilder.Register(&CronJob{}, &CronJobList{}) 201 }`, ` 202 //+kubebuilder:docs-gen:collapse=Root Object Definitions`) 203 CheckError("fixing cronjob_types.go", err) 204 205 err = pluginutil.ReplaceInFile( 206 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_types.go"), 207 `// CronJob is the Schema for the cronjobs API 208 type CronJob struct {`, `// CronJob is the Schema for the cronjobs API 209 type CronJob struct {`+` 210 /* 211 */`) 212 CheckError("fixing cronjob_types.go", err) 213 214 // fix lint 215 err = pluginutil.ReplaceInFile( 216 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_types.go"), 217 ` 218 219 }`, "") 220 CheckError("fixing cronjob_types.go", err) 221 222 err = pluginutil.ReplaceInFile( 223 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_types.go"), 224 ` 225 226 227 }`, "") 228 CheckError("fixing cronjob_types.go", err) 229 } 230 231 func updateAPIStuff(sp *Sample) { 232 var err error 233 // fix groupversion_info 234 err = pluginutil.InsertCode( 235 filepath.Join(sp.ctx.Dir, "api/v1/groupversion_info.go"), 236 `limitations under the License. 237 */`, GroupversionIntro) 238 CheckError("fixing groupversion_info.go", err) 239 240 err = pluginutil.InsertCode( 241 filepath.Join(sp.ctx.Dir, "api/v1/groupversion_info.go"), 242 ` "sigs.k8s.io/controller-runtime/pkg/scheme" 243 )`, GroupversionSchema) 244 CheckError("fixing groupversion_info.go", err) 245 } 246 247 func updateController(sp *Sample) { 248 var err error 249 err = pluginutil.InsertCode( 250 filepath.Join(sp.ctx.Dir, "internal/controller/cronjob_controller.go"), 251 `limitations under the License. 252 */`, ControllerIntro) 253 CheckError("fixing cronjob_controller.go", err) 254 255 err = pluginutil.ReplaceInFile( 256 filepath.Join(sp.ctx.Dir, "internal/controller/cronjob_controller.go"), 257 `import ( 258 "context" 259 260 "k8s.io/apimachinery/pkg/runtime" 261 ctrl "sigs.k8s.io/controller-runtime" 262 "sigs.k8s.io/controller-runtime/pkg/client" 263 "sigs.k8s.io/controller-runtime/pkg/log" 264 265 batchv1 "tutorial.kubebuilder.io/project/api/v1" 266 )`, ControllerImport) 267 CheckError("fixing cronjob_controller.go", err) 268 269 err = pluginutil.InsertCode( 270 filepath.Join(sp.ctx.Dir, "internal/controller/cronjob_controller.go"), 271 `Scheme *runtime.Scheme`, ` 272 Clock`) 273 CheckError("fixing cronjob_controller.go", err) 274 275 err = pluginutil.InsertCode( 276 filepath.Join(sp.ctx.Dir, "internal/controller/cronjob_controller.go"), 277 ` Clock 278 }`, ControllerMockClock) 279 CheckError("fixing cronjob_controller.go", err) 280 281 err = pluginutil.InsertCode( 282 filepath.Join(sp.ctx.Dir, "internal/controller/cronjob_controller.go"), 283 `//+kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs/finalizers,verbs=update`, ControllerReconcile) 284 CheckError("fixing cronjob_controller.go", err) 285 286 err = pluginutil.ReplaceInFile( 287 filepath.Join(sp.ctx.Dir, "internal/controller/cronjob_controller.go"), 288 ` _ = log.FromContext(ctx) 289 290 // TODO(user): your logic here 291 292 return ctrl.Result{}, nil 293 }`, ControllerReconcileLogic) 294 CheckError("fixing cronjob_controller.go", err) 295 296 err = pluginutil.InsertCode( 297 filepath.Join(sp.ctx.Dir, "internal/controller/cronjob_controller.go"), 298 `SetupWithManager(mgr ctrl.Manager) error {`, ControllerSetupWithManager) 299 CheckError("fixing cronjob_controller.go", err) 300 301 err = pluginutil.InsertCode( 302 filepath.Join(sp.ctx.Dir, "internal/controller/cronjob_controller.go"), 303 `For(&batchv1.CronJob{}).`, ` 304 Owns(&kbatch.Job{}).`) 305 CheckError("fixing cronjob_controller.go", err) 306 } 307 308 func updateMain(sp *Sample) { 309 var err error 310 err = pluginutil.InsertCode( 311 filepath.Join(sp.ctx.Dir, "cmd/main.go"), 312 `limitations under the License. 313 */`, 314 ` 315 // +kubebuilder:docs-gen:collapse=Apache License`) 316 CheckError("fixing main.go", err) 317 318 err = pluginutil.InsertCode( 319 filepath.Join(sp.ctx.Dir, "cmd/main.go"), 320 `//+kubebuilder:scaffold:imports 321 )`, MainBatch) 322 CheckError("fixing main.go", err) 323 324 err = pluginutil.InsertCode( 325 filepath.Join(sp.ctx.Dir, "cmd/main.go"), 326 `//+kubebuilder:scaffold:scheme 327 }`, ` 328 /* 329 The other thing that's changed is that kubebuilder has added a block calling our 330 CronJob controller's`+" `"+`SetupWithManager`+"`"+` method. 331 */`) 332 CheckError("fixing main.go", err) 333 334 err = pluginutil.InsertCode( 335 filepath.Join(sp.ctx.Dir, "cmd/main.go"), 336 `func main() {`, ` 337 /* 338 */`) 339 CheckError("fixing main.go", err) 340 341 err = pluginutil.InsertCode( 342 filepath.Join(sp.ctx.Dir, "cmd/main.go"), 343 `if err != nil { 344 setupLog.Error(err, "unable to start manager") 345 os.Exit(1) 346 }`, ` 347 348 // +kubebuilder:docs-gen:collapse=old stuff`) 349 CheckError("fixing main.go", err) 350 351 err = pluginutil.InsertCode( 352 filepath.Join(sp.ctx.Dir, "cmd/main.go"), 353 `setupLog.Error(err, "unable to create controller", "controller", "CronJob") 354 os.Exit(1) 355 }`, MainEnableWebhook) 356 CheckError("fixing main.go", err) 357 358 err = pluginutil.InsertCode( 359 filepath.Join(sp.ctx.Dir, "cmd/main.go"), 360 `setupLog.Error(err, "problem running manager") 361 os.Exit(1) 362 }`, ` 363 // +kubebuilder:docs-gen:collapse=old stuff`) 364 CheckError("fixing main.go", err) 365 } 366 367 func updateWebhook(sp *Sample) { 368 var err error 369 err = pluginutil.InsertCode( 370 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), 371 `limitations under the License. 372 */`, 373 ` 374 // +kubebuilder:docs-gen:collapse=Apache License`) 375 CheckError("fixing cronjob_webhook.go by adding collapse", err) 376 377 err = pluginutil.ReplaceInFile( 378 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), 379 `import ( 380 "k8s.io/apimachinery/pkg/runtime" 381 ctrl "sigs.k8s.io/controller-runtime" 382 logf "sigs.k8s.io/controller-runtime/pkg/log" 383 "sigs.k8s.io/controller-runtime/pkg/webhook" 384 "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 385 ) 386 387 // log is for logging in this package. 388 `, WebhookIntro) 389 CheckError("fixing cronjob_webhook.go", err) 390 391 err = pluginutil.InsertCode( 392 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), 393 `var cronjoblog = logf.Log.WithName("cronjob-resource")`, 394 ` 395 /* 396 Then, we set up the webhook with the manager. 397 */`) 398 CheckError("fixing cronjob_webhook.go by setting webhook with manager comment", err) 399 400 err = pluginutil.ReplaceInFile( 401 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), 402 `// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!`, WebhookMarker) 403 CheckError("fixing cronjob_webhook.go by replacing TODO", err) 404 405 err = pluginutil.ReplaceInFile( 406 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), 407 `// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.`, "") 408 CheckError("fixing cronjob_webhook.go by replace TODO to change verbs", err) 409 410 err = pluginutil.ReplaceInFile( 411 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), 412 `//+kubebuilder:webhook:path=/mutate-batch-tutorial-kubebuilder-io-v1-cronjob,mutating=true,failurePolicy=fail,sideEffects=None,groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=create;update,versions=v1,name=mcronjob.kb.io,admissionReviewVersions=v1`, "") 413 CheckError("fixing cronjob_webhook.go by replacing marker", err) 414 415 err = pluginutil.ReplaceInFile( 416 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), 417 `//+kubebuilder:webhook:path=/validate-batch-tutorial-kubebuilder-io-v1-cronjob,mutating=false,failurePolicy=fail,sideEffects=None,groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=create;update,versions=v1,name=vcronjob.kb.io,admissionReviewVersions=v1`, "") 418 CheckError("fixing cronjob_webhook.go validate batch marker", err) 419 420 err = pluginutil.ReplaceInFile( 421 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), 422 `cronjoblog.Info("default", "name", r.Name) 423 424 // TODO(user): fill in your defaulting logic. 425 `, WebhookValidate) 426 CheckError("fixing cronjob_webhook.go by adding logic", err) 427 428 err = pluginutil.ReplaceInFile( 429 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), 430 `// TODO(user): fill in your validation logic upon object creation. 431 return nil, nil`, 432 ` 433 return nil, r.validateCronJob()`) 434 CheckError("fixing cronjob_webhook.go by fill in your validation", err) 435 436 err = pluginutil.ReplaceInFile( 437 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), 438 `// TODO(user): fill in your validation logic upon object update. 439 return nil, nil`, 440 ` 441 return nil, r.validateCronJob()`) 442 CheckError("fixing cronjob_webhook.go by adding validation logic upon object update", err) 443 444 err = pluginutil.InsertCode( 445 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), 446 `func (r *CronJob) ValidateDelete() (admission.Warnings, error) { 447 cronjoblog.Info("validate delete", "name", r.Name) 448 449 // TODO(user): fill in your validation logic upon object deletion. 450 return nil, nil 451 }`, WebhookValidateSpec) 452 CheckError("fixing cronjob_webhook.go", err) 453 454 err = pluginutil.ReplaceInFile( 455 filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), 456 `validate anything on deletion. 457 */ 458 }`, `validate anything on deletion. 459 */`) 460 CheckError("fixing cronjob_webhook.go by adding comments to validate on deletion", err) 461 } 462 463 func updateSuiteTest(sp *Sample) { 464 var err error 465 err = pluginutil.InsertCode( 466 filepath.Join(sp.ctx.Dir, "internal/controller/suite_test.go"), 467 `limitations under the License. 468 */`, SuiteTestIntro) 469 CheckError("updating suite_test.go to add license intro", err) 470 471 err = pluginutil.InsertCode( 472 filepath.Join(sp.ctx.Dir, "internal/controller/suite_test.go"), 473 `import (`, ` 474 "context"`) 475 CheckError("updating suite_test.go to add context", err) 476 477 err = pluginutil.InsertCode( 478 filepath.Join(sp.ctx.Dir, "internal/controller/suite_test.go"), 479 ` 480 "testing" 481 `, ` 482 ctrl "sigs.k8s.io/controller-runtime" 483 `) 484 CheckError("updating suite_test.go to add ctrl import", err) 485 486 err = pluginutil.ReplaceInFile( 487 filepath.Join(sp.ctx.Dir, "internal/controller/suite_test.go"), 488 ` 489 var cfg *rest.Config 490 var k8sClient client.Client 491 var testEnv *envtest.Environment 492 `, SuiteTestEnv) 493 CheckError("updating suite_test.go to add more variables", err) 494 495 err = pluginutil.InsertCode( 496 filepath.Join(sp.ctx.Dir, "internal/controller/suite_test.go"), 497 ` 498 logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 499 `, SuiteTestReadCRD) 500 CheckError("updating suite_test.go to add text about CRD", err) 501 502 err = pluginutil.InsertCode( 503 filepath.Join(sp.ctx.Dir, "internal/controller/suite_test.go"), 504 `, runtime.GOOS, runtime.GOARCH)), 505 } 506 `, ` 507 /* 508 Then, we start the envtest cluster. 509 */`) 510 CheckError("updating suite_test.go to add text to show where envtest cluster start", err) 511 512 err = pluginutil.ReplaceInFile( 513 filepath.Join(sp.ctx.Dir, "internal/controller/suite_test.go"), 514 ` 515 err = batchv1.AddToScheme(scheme.Scheme) 516 Expect(err).NotTo(HaveOccurred()) 517 518 //+kubebuilder:scaffold:scheme 519 `, SuiteTestAddSchema) 520 CheckError("updating suite_test.go to add schema", err) 521 522 err = pluginutil.InsertCode( 523 filepath.Join(sp.ctx.Dir, "internal/controller/suite_test.go"), 524 ` 525 k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 526 Expect(err).NotTo(HaveOccurred()) 527 Expect(k8sClient).NotTo(BeNil()) 528 `, SuiteTestDescription) 529 CheckError("updating suite_test.go for test description", err) 530 531 err = pluginutil.ReplaceInFile( 532 filepath.Join(sp.ctx.Dir, "internal/controller/suite_test.go"), 533 ` 534 var _ = AfterSuite(func() { 535 By("tearing down the test environment") 536 err := testEnv.Stop() 537 Expect(err).NotTo(HaveOccurred()) 538 }) 539 `, SuiteTestCleanup) 540 CheckError("updating suite_test.go to cleanup tests", err) 541 } 542 543 func updateKustomization(sp *Sample) { 544 var err error 545 err = pluginutil.UncommentCode( 546 filepath.Join(sp.ctx.Dir, "config/default/kustomization.yaml"), 547 `#- ../certmanager`, `#`) 548 CheckError("fixing default/kustomization", err) 549 550 err = pluginutil.UncommentCode( 551 filepath.Join(sp.ctx.Dir, "config/default/kustomization.yaml"), 552 `#- path: webhookcainjection`, `#`) 553 CheckError("fixing default/kustomization", err) 554 555 err = pluginutil.UncommentCode( 556 filepath.Join(sp.ctx.Dir, "config/default/kustomization.yaml"), 557 `#- ../prometheus`, `#`) 558 CheckError("fixing default/kustomization", err) 559 560 err = pluginutil.UncommentCode( 561 filepath.Join(sp.ctx.Dir, "config/default/kustomization.yaml"), 562 DefaultKustomization, `#`) 563 CheckError("fixing default/kustomization", err) 564 565 err = pluginutil.UncommentCode( 566 filepath.Join(sp.ctx.Dir, "config/crd/kustomization.yaml"), 567 `#- path: patches/cainjection_in_cronjobs.yaml`, `#`) 568 CheckError("fixing crd/kustomization", err) 569 } 570 571 func updateExample(sp *Sample) { 572 var err error 573 574 // samples/batch_v1_cronjob 575 err = pluginutil.InsertCode( 576 filepath.Join(sp.ctx.Dir, "config/samples/batch_v1_cronjob.yaml"), 577 `spec:`, CronjobSample) 578 CheckError("fixing samples/batch_v1_cronjob.yaml", err) 579 580 err = pluginutil.ReplaceInFile( 581 filepath.Join(sp.ctx.Dir, "config/samples/batch_v1_cronjob.yaml"), 582 `# TODO(user): Add fields here`, "") 583 CheckError("fixing samples/batch_v1_cronjob.yaml", err) 584 585 // update default/manager_auth_proxy_patch.yaml 586 err = pluginutil.InsertCode( 587 filepath.Join(sp.ctx.Dir, "config/default/manager_auth_proxy_patch.yaml"), 588 ` template: 589 spec:`, ManagerAuthProxySample) 590 CheckError("fixing default/manager_auth_proxy_patch.yaml", err) 591 } 592 593 func addControllerTest(sp *Sample) { 594 var fs = afero.NewOsFs() 595 err := afero.WriteFile(fs, filepath.Join(sp.ctx.Dir, "internal/controller/cronjob_controller_test.go"), []byte(ControllerTest), 0600) 596 CheckError("adding cronjob_controller_test", err) 597 } 598 599 // CheckError will exit with exit code 1 when err is not nil. 600 func CheckError(msg string, err error) { 601 if err != nil { 602 log.Errorf("error %s: %s", msg, err) 603 os.Exit(1) 604 } 605 }