• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubernetes-sigs / kubebuilder / 16231804143

11 Jul 2025 11:47PM UTC coverage: 58.994%. Remained the same
16231804143

Pull #4922

github

web-flow
:seedling: Bump golang.org/x/tools from 0.34.0 to 0.35.0

Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.34.0 to 0.35.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.34.0...v0.35.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-version: 0.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #4922: :seedling: Bump golang.org/x/tools from 0.34.0 to 0.35.0

2404 of 4075 relevant lines covered (58.99%)

13.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/pkg/cli/alpha/internal/generate.go
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 internal
18

19
import (
20
        "errors"
21
        "fmt"
22
        "os"
23
        "os/exec"
24
        "strings"
25

26
        "sigs.k8s.io/kubebuilder/v4/pkg/cli/alpha/internal/common"
27

28
        log "github.com/sirupsen/logrus"
29

30
        "sigs.k8s.io/kubebuilder/v4/pkg/config"
31
        "sigs.k8s.io/kubebuilder/v4/pkg/config/store"
32
        "sigs.k8s.io/kubebuilder/v4/pkg/model/resource"
33
        "sigs.k8s.io/kubebuilder/v4/pkg/plugin"
34
        "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util"
35
        "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/deploy-image/v1alpha1"
36
        "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/grafana/v1alpha"
37
        hemlv1alpha "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha"
38
)
39

40
// Generate store the required info for the command
41
type Generate struct {
42
        InputDir  string
43
        OutputDir string
44
}
45

46
// Generate handles the migration and scaffolding process.
47
func (opts *Generate) Generate() error {
×
48
        projectConfig, err := common.LoadProjectConfig(opts.InputDir)
×
49
        if err != nil {
×
50
                return fmt.Errorf("error loading project config: %v", err)
×
51
        }
×
52

53
        if opts.OutputDir == "" {
×
54
                cwd, getWdErr := os.Getwd()
×
55
                if getWdErr != nil {
×
56
                        return fmt.Errorf("failed to get working directory: %w", getWdErr)
×
57
                }
×
58
                opts.OutputDir = cwd
×
59
                if _, err = os.Stat(opts.OutputDir); err == nil {
×
60
                        log.Warn("Using current working directory to re-scaffold the project")
×
61
                        log.Warn("This directory will be cleaned up and all files removed before the re-generation")
×
62

×
63
                        // Ensure we clean the correct directory
×
64
                        log.Info("Cleaning directory:", opts.OutputDir)
×
65

×
66
                        // Use an absolute path to target files directly
×
67
                        cleanupCmd := fmt.Sprintf("rm -rf %s/*", opts.OutputDir)
×
68
                        err = util.RunCmd("Running cleanup", "sh", "-c", cleanupCmd)
×
69
                        if err != nil {
×
70
                                log.Error("Cleanup failed:", err)
×
71
                                return fmt.Errorf("cleanup failed: %w", err)
×
72
                        }
×
73

74
                        // Note that we should remove ALL files except the PROJECT file and .git directory
75
                        cleanupCmd = fmt.Sprintf(
×
76
                                `find %q -mindepth 1 -maxdepth 1 ! -name '.git' ! -name 'PROJECT' -exec rm -rf {} +`,
×
77
                                opts.OutputDir,
×
78
                        )
×
79
                        err = util.RunCmd("Running cleanup", "sh", "-c", cleanupCmd)
×
80
                        if err != nil {
×
81
                                log.Error("Cleanup failed:", err)
×
82
                                return fmt.Errorf("cleanup failed: %w", err)
×
83
                        }
×
84
                }
85
        }
86

87
        if err = createDirectory(opts.OutputDir); err != nil {
×
88
                return fmt.Errorf("error creating output directory %q: %w", opts.OutputDir, err)
×
89
        }
×
90

91
        if err = changeWorkingDirectory(opts.OutputDir); err != nil {
×
92
                return fmt.Errorf("error changing working directory %q: %w", opts.OutputDir, err)
×
93
        }
×
94

95
        if err = kubebuilderInit(projectConfig); err != nil {
×
96
                return fmt.Errorf("error initializing project config: %w", err)
×
97
        }
×
98

99
        if err = kubebuilderEdit(projectConfig); err != nil {
×
100
                return fmt.Errorf("error editing project config: %w", err)
×
101
        }
×
102

103
        if err = kubebuilderCreate(projectConfig); err != nil {
×
104
                return fmt.Errorf("error creating project config: %w", err)
×
105
        }
×
106

107
        if err = migrateGrafanaPlugin(projectConfig, opts.InputDir, opts.OutputDir); err != nil {
×
108
                return fmt.Errorf("error migrating Grafana plugin: %w", err)
×
109
        }
×
110

111
        if hasHelmPlugin(projectConfig) {
×
112
                if err = kubebuilderHelmEdit(); err != nil {
×
113
                        return fmt.Errorf("error editing Helm plugin: %w", err)
×
114
                }
×
115
        }
116

117
        if err = migrateDeployImagePlugin(projectConfig); err != nil {
×
118
                return fmt.Errorf("error migrating deploy-image plugin: %w", err)
×
119
        }
×
120

121
        // Run make targets to ensure the project is properly set up.
122
        // These steps are performed on a best-effort basis: if any of the targets fail,
123
        // we log a warning to inform the user, but we do not stop the process or return an error.
124
        // This is to avoid blocking the migration flow due to non-critical issues during setup.
125
        targets := []string{"manifests", "generate", "fmt", "vet", "lint-fix"}
×
126
        for _, target := range targets {
×
127
                log.Infof("Running: make %s", target)
×
128
                err := util.RunCmd(fmt.Sprintf("Running make %s", target), "make", target)
×
129
                if err != nil {
×
130
                        log.Warnf("make %s failed: %v", target, err)
×
131
                }
×
132
        }
133

134
        return nil
×
135
}
136

137
// Validate ensures the options are valid and kubebuilder is installed.
138
func (opts *Generate) Validate() error {
×
139
        var err error
×
140
        opts.InputDir, err = common.GetInputPath(opts.InputDir)
×
141
        if err != nil {
×
142
                return fmt.Errorf("error getting input path %q: %w", opts.InputDir, err)
×
143
        }
×
144

145
        _, err = exec.LookPath("kubebuilder")
×
146
        if err != nil {
×
147
                return fmt.Errorf("kubebuilder not found in the path: %w", err)
×
148
        }
×
149

150
        return nil
×
151
}
152

153
// Helper function to create the output directory.
154
func createDirectory(outputDir string) error {
×
155
        if err := os.MkdirAll(outputDir, 0o755); err != nil {
×
156
                return fmt.Errorf("failed to create output directory %q: %w", outputDir, err)
×
157
        }
×
158
        return nil
×
159
}
160

161
// Helper function to change the current working directory.
162
func changeWorkingDirectory(outputDir string) error {
×
163
        if err := os.Chdir(outputDir); err != nil {
×
164
                return fmt.Errorf("failed to change the working directory to %q: %w", outputDir, err)
×
165
        }
×
166
        return nil
×
167
}
168

169
// Initializes the project with Kubebuilder.
170
func kubebuilderInit(s store.Store) error {
×
171
        args := append([]string{"init"}, getInitArgs(s)...)
×
172
        if err := util.RunCmd("kubebuilder init", "kubebuilder", args...); err != nil {
×
173
                return fmt.Errorf("failed to run kubebuilder init command: %w", err)
×
174
        }
×
175

176
        return nil
×
177
}
178

179
// Edits the project to enable or disable multigroup layout.
180
func kubebuilderEdit(s store.Store) error {
×
181
        if s.Config().IsMultiGroup() {
×
182
                args := []string{"edit", "--multigroup"}
×
183
                if err := util.RunCmd("kubebuilder edit", "kubebuilder", args...); err != nil {
×
184
                        return fmt.Errorf("failed to run kubebuilder edit command: %w", err)
×
185
                }
×
186
        }
187

188
        return nil
×
189
}
190

191
// Creates APIs and Webhooks for the project.
192
func kubebuilderCreate(s store.Store) error {
×
193
        resources, err := s.Config().GetResources()
×
194
        if err != nil {
×
195
                return fmt.Errorf("failed to get resources: %w", err)
×
196
        }
×
197

198
        // First, scaffold all APIs
199
        for _, r := range resources {
×
200
                if err = createAPI(r); err != nil {
×
201
                        return fmt.Errorf("failed to create API for %s/%s/%s: %w", r.Group, r.Version, r.Kind, err)
×
202
                }
×
203
        }
204

205
        // Then, scaffold all webhooks
206
        // We cannot create a webhook for an API that does not exist
207
        for _, r := range resources {
×
208
                if err = createWebhook(r); err != nil {
×
209
                        return fmt.Errorf("failed to create webhook for %s/%s/%s: %w", r.Group, r.Version, r.Kind, err)
×
210
                }
×
211
        }
212

213
        return nil
×
214
}
215

216
// Migrates the Grafana plugin.
217
func migrateGrafanaPlugin(s store.Store, src, des string) error {
×
218
        var grafanaPlugin struct{}
×
219
        err := s.Config().DecodePluginConfig(plugin.KeyFor(v1alpha.Plugin{}), grafanaPlugin)
×
220
        if errors.As(err, &config.PluginKeyNotFoundError{}) {
×
221
                log.Info("Grafana plugin not found, skipping migration")
×
222
                return nil
×
223
        } else if err != nil {
×
224
                return fmt.Errorf("failed to decode grafana plugin config: %w", err)
×
225
        }
×
226

227
        if err = kubebuilderGrafanaEdit(); err != nil {
×
228
                return fmt.Errorf("error editing Grafana plugin: %w", err)
×
229
        }
×
230

231
        if err = grafanaConfigMigrate(src, des); err != nil {
×
232
                return fmt.Errorf("error migrating Grafana config: %w", err)
×
233
        }
×
234

235
        return kubebuilderGrafanaEdit()
×
236
}
237

238
// Migrates the Deploy Image plugin.
239
func migrateDeployImagePlugin(s store.Store) error {
×
240
        var deployImagePlugin v1alpha1.PluginConfig
×
241
        err := s.Config().DecodePluginConfig(plugin.KeyFor(v1alpha1.Plugin{}), &deployImagePlugin)
×
242
        if errors.As(err, &config.PluginKeyNotFoundError{}) {
×
243
                log.Info("Deploy-image plugin not found, skipping migration")
×
244
                return nil
×
245
        } else if err != nil {
×
246
                return fmt.Errorf("failed to decode deploy-image plugin config: %w", err)
×
247
        }
×
248

249
        for _, r := range deployImagePlugin.Resources {
×
250
                if err := createAPIWithDeployImage(r); err != nil {
×
251
                        return fmt.Errorf("failed to create API with deploy-image: %w", err)
×
252
                }
×
253
        }
254

255
        return nil
×
256
}
257

258
// Creates an API with Deploy Image plugin.
259
func createAPIWithDeployImage(resourceData v1alpha1.ResourceData) error {
×
260
        args := append([]string{"create", "api"}, getGVKFlagsFromDeployImage(resourceData)...)
×
261
        args = append(args, getDeployImageOptions(resourceData)...)
×
262
        if err := util.RunCmd("kubebuilder create api", "kubebuilder", args...); err != nil {
×
263
                return fmt.Errorf("failed to run kubebuilder create api command: %w", err)
×
264
        }
×
265

266
        return nil
×
267
}
268

269
// Helper function to get Init arguments for Kubebuilder.
270
func getInitArgs(s store.Store) []string {
×
271
        var args []string
×
272
        plugins := s.Config().GetPluginChain()
×
273

×
274
        // Define outdated plugin versions that need replacement
×
275
        outdatedPlugins := map[string]string{
×
276
                "go.kubebuilder.io/v3":       "go.kubebuilder.io/v4",
×
277
                "go.kubebuilder.io/v3-alpha": "go.kubebuilder.io/v4",
×
278
                "go.kubebuilder.io/v2":       "go.kubebuilder.io/v4",
×
279
        }
×
280

×
281
        // Replace outdated plugins and exit after the first replacement
×
282
        for i, plg := range plugins {
×
283
                if newPlugin, exists := outdatedPlugins[plg]; exists {
×
284
                        log.Warnf("We checked that your PROJECT file is configured with the layout '%s', which is no longer supported.\n"+
×
285
                                "However, we will try our best to re-generate the project using '%s'.", plg, newPlugin)
×
286
                        plugins[i] = newPlugin
×
287
                        break
×
288
                }
289
        }
290

291
        if len(plugins) > 0 {
×
292
                args = append(args, "--plugins", strings.Join(plugins, ","))
×
293
        }
×
294
        if domain := s.Config().GetDomain(); domain != "" {
×
295
                args = append(args, "--domain", domain)
×
296
        }
×
297
        if repo := s.Config().GetRepository(); repo != "" {
×
298
                args = append(args, "--repo", repo)
×
299
        }
×
300
        return args
×
301
}
302

303
// Gets the GVK flags for a resource.
304
func getGVKFlags(res resource.Resource) []string {
×
305
        var args []string
×
306
        if res.Plural != "" {
×
307
                args = append(args, "--plural", res.Plural)
×
308
        }
×
309
        if res.Group != "" {
×
310
                args = append(args, "--group", res.Group)
×
311
        }
×
312
        if res.Version != "" {
×
313
                args = append(args, "--version", res.Version)
×
314
        }
×
315
        if res.Kind != "" {
×
316
                args = append(args, "--kind", res.Kind)
×
317
        }
×
318
        return args
×
319
}
320

321
// Gets the GVK flags for a Deploy Image resource.
322
func getGVKFlagsFromDeployImage(resourceData v1alpha1.ResourceData) []string {
×
323
        var args []string
×
324
        if resourceData.Group != "" {
×
325
                args = append(args, "--group", resourceData.Group)
×
326
        }
×
327
        if resourceData.Version != "" {
×
328
                args = append(args, "--version", resourceData.Version)
×
329
        }
×
330
        if resourceData.Kind != "" {
×
331
                args = append(args, "--kind", resourceData.Kind)
×
332
        }
×
333
        return args
×
334
}
335

336
// Gets the options for a Deploy Image resource.
337
func getDeployImageOptions(resourceData v1alpha1.ResourceData) []string {
×
338
        var args []string
×
339
        if resourceData.Options.Image != "" {
×
340
                args = append(args, fmt.Sprintf("--image=%s", resourceData.Options.Image))
×
341
        }
×
342
        if resourceData.Options.ContainerCommand != "" {
×
343
                args = append(args, fmt.Sprintf("--image-container-command=%s", resourceData.Options.ContainerCommand))
×
344
        }
×
345
        if resourceData.Options.ContainerPort != "" {
×
346
                args = append(args, fmt.Sprintf("--image-container-port=%s", resourceData.Options.ContainerPort))
×
347
        }
×
348
        if resourceData.Options.RunAsUser != "" {
×
349
                args = append(args, fmt.Sprintf("--run-as-user=%s", resourceData.Options.RunAsUser))
×
350
        }
×
351
        args = append(args, fmt.Sprintf("--plugins=%s", plugin.KeyFor(v1alpha1.Plugin{})))
×
352
        return args
×
353
}
354

355
// Creates an API resource.
356
func createAPI(res resource.Resource) error {
×
357
        args := append([]string{"create", "api"}, getGVKFlags(res)...)
×
358
        args = append(args, getAPIResourceFlags(res)...)
×
359

×
360
        // Add the external API path flag if the resource is external
×
361
        if res.IsExternal() {
×
362
                args = append(args, "--external-api-path", res.Path)
×
363
                args = append(args, "--external-api-domain", res.Domain)
×
364
        }
×
365

366
        if err := util.RunCmd("kubebuilder create api", "kubebuilder", args...); err != nil {
×
367
                return fmt.Errorf("failed to run kubebuilder create api command: %w", err)
×
368
        }
×
369

370
        return nil
×
371
}
372

373
// Gets flags for API resource creation.
374
func getAPIResourceFlags(res resource.Resource) []string {
×
375
        var args []string
×
376

×
377
        if res.API == nil || res.API.IsEmpty() {
×
378
                args = append(args, "--resource=false")
×
379
        } else {
×
380
                args = append(args, "--resource")
×
381
                if res.API.Namespaced {
×
382
                        args = append(args, "--namespaced")
×
383
                } else {
×
384
                        args = append(args, "--namespaced=false")
×
385
                }
×
386
        }
387
        if res.Controller {
×
388
                args = append(args, "--controller")
×
389
        } else {
×
390
                args = append(args, "--controller=false")
×
391
        }
×
392
        return args
×
393
}
394

395
// Creates a webhook resource.
396
func createWebhook(res resource.Resource) error {
×
397
        if res.Webhooks == nil || res.Webhooks.IsEmpty() {
×
398
                return nil
×
399
        }
×
400
        args := append([]string{"create", "webhook"}, getGVKFlags(res)...)
×
401
        args = append(args, getWebhookResourceFlags(res)...)
×
402

×
403
        if err := util.RunCmd("kubebuilder create webhook", "kubebuilder", args...); err != nil {
×
404
                return fmt.Errorf("failed to run kubebuilder create webhook command: %w", err)
×
405
        }
×
406

407
        return nil
×
408
}
409

410
// Gets flags for webhook creation.
411
func getWebhookResourceFlags(res resource.Resource) []string {
×
412
        var args []string
×
413
        if res.IsExternal() {
×
414
                args = append(args, "--external-api-path", res.Path)
×
415
                args = append(args, "--external-api-domain", res.Domain)
×
416
        }
×
417
        if res.HasValidationWebhook() {
×
418
                args = append(args, "--programmatic-validation")
×
419
        }
×
420
        if res.HasDefaultingWebhook() {
×
421
                args = append(args, "--defaulting")
×
422
        }
×
423
        if res.HasConversionWebhook() {
×
424
                args = append(args, "--conversion")
×
425
                if len(res.Webhooks.Spoke) > 0 {
×
426
                        for _, spoke := range res.Webhooks.Spoke {
×
427
                                args = append(args, "--spoke", spoke)
×
428
                        }
×
429
                }
430
        }
431
        return args
×
432
}
433

434
// Copies files from source to destination.
435
func copyFile(src, des string) error {
×
436
        bytesRead, err := os.ReadFile(src)
×
437
        if err != nil {
×
438
                return fmt.Errorf("source file path %q does not exist: %w", src, err)
×
439
        }
×
440
        if err = os.WriteFile(des, bytesRead, 0o755); err != nil {
×
441
                return fmt.Errorf("failed to write file %q: %w", des, err)
×
442
        }
×
443

444
        return nil
×
445
}
446

447
// Migrates Grafana configuration files.
448
func grafanaConfigMigrate(src, des string) error {
×
449
        grafanaConfig := fmt.Sprintf("%s/grafana/custom-metrics/config.yaml", src)
×
450
        if _, err := os.Stat(grafanaConfig); os.IsNotExist(err) {
×
451
                return fmt.Errorf("grafana config path %s does not exist: %w", grafanaConfig, err)
×
452
        }
×
453
        return copyFile(grafanaConfig, fmt.Sprintf("%s/grafana/custom-metrics/config.yaml", des))
×
454
}
455

456
// Edits the project to include the Grafana plugin.
457
func kubebuilderGrafanaEdit() error {
×
458
        args := []string{"edit", "--plugins", plugin.KeyFor(v1alpha.Plugin{})}
×
459
        if err := util.RunCmd("kubebuilder edit", "kubebuilder", args...); err != nil {
×
460
                return fmt.Errorf("failed to run edit subcommand for Grafana plugin: %w", err)
×
461
        }
×
462
        return nil
×
463
}
464

465
// Edits the project to include the Helm plugin.
466
func kubebuilderHelmEdit() error {
×
467
        args := []string{"edit", "--plugins", plugin.KeyFor(hemlv1alpha.Plugin{})}
×
468
        if err := util.RunCmd("kubebuilder edit", "kubebuilder", args...); err != nil {
×
469
                return fmt.Errorf("failed to run edit subcommand for Helm plugin: %w", err)
×
470
        }
×
471
        return nil
×
472
}
473

474
// hasHelmPlugin checks if the Helm plugin is present by inspecting the plugin chain or configuration.
475
func hasHelmPlugin(cfg store.Store) bool {
×
476
        var pluginConfig map[string]interface{}
×
477

×
478
        // Decode the Helm plugin configuration to check if it's present
×
479
        err := cfg.Config().DecodePluginConfig(plugin.KeyFor(hemlv1alpha.Plugin{}), &pluginConfig)
×
480
        if err != nil {
×
481
                // If the Helm plugin is not found, return false
×
482
                if errors.As(err, &config.PluginKeyNotFoundError{}) {
×
483
                        return false
×
484
                }
×
485
                // Log other errors if needed
486
                log.Errorf("error decoding Helm plugin config: %v", err)
×
487
                return false
×
488
        }
489

490
        // Helm plugin is present
491
        return true
×
492
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc