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

kubernetes-sigs / kubebuilder / 24534582207

16 Apr 2026 09:19PM UTC coverage: 82.422%. Remained the same
24534582207

Pull #5637

github

camilamacedo86
refactor(helm/v2-alpha): Remove redundant code comments
Pull Request #5637: 🌱 refactor(helm/v2-alpha): Remove redundant code comments

7685 of 9324 relevant lines covered (82.42%)

73.81 hits per line

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

40.68
/pkg/plugins/optional/helm/v2alpha/edit.go
1
/*
2
Copyright 2025 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 v2alpha
18

19
import (
20
        "errors"
21
        "fmt"
22
        "io"
23
        "log/slog"
24
        "os"
25
        "path/filepath"
26
        "strings"
27

28
        "github.com/spf13/pflag"
29
        "go.yaml.in/yaml/v3"
30

31
        "sigs.k8s.io/kubebuilder/v4/pkg/config"
32
        cfgv3 "sigs.k8s.io/kubebuilder/v4/pkg/config/v3"
33
        "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
34
        "sigs.k8s.io/kubebuilder/v4/pkg/plugin"
35
        "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util"
36
        "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v2alpha/internal/common"
37
        "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v2alpha/scaffolds"
38
)
39

40
const (
41
        // DefaultManifestsFile is the default path for kustomize output manifests
42
        DefaultManifestsFile = "dist/install.yaml"
43
        // v1AlphaPluginKey is the deprecated v1-alpha plugin key
44
        v1AlphaPluginKey = "helm.kubebuilder.io/v1-alpha"
45
)
46

47
var _ plugin.EditSubcommand = &editSubcommand{}
48

49
type editSubcommand struct {
50
        config        config.Config
51
        force         bool
52
        manifestsFile string
53
        outputDir     string
54
}
55

56
//nolint:lll
57
func (p *editSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
1✔
58
        subcmdMeta.Description = `Generate a Helm chart from your project's kustomize output.
1✔
59

1✔
60
Parses 'make build-installer' output (dist/install.yaml) and generates chart to allow easy
1✔
61
distribution of your project. When enabled, adds Helm helpers targets to Makefile`
1✔
62

1✔
63
        subcmdMeta.Examples = fmt.Sprintf(`# Generate Helm chart from default manifests (dist/install.yaml) to default output (dist/)
1✔
64
  %[1]s edit --plugins=%[2]s
1✔
65

1✔
66
# Generate Helm chart and overwrite existing files (useful for updates)
1✔
67
  %[1]s edit --plugins=%[2]s --force
1✔
68

1✔
69
# Generate Helm chart from a custom manifests file
1✔
70
  %[1]s edit --plugins=%[2]s --manifests=path/to/custom-install.yaml
1✔
71

1✔
72
# Generate Helm chart to a custom output directory
1✔
73
  %[1]s edit --plugins=%[2]s --output-dir=charts
1✔
74

1✔
75
# Generate from custom manifests to custom output directory
1✔
76
  %[1]s edit --plugins=%[2]s --manifests=manifests/install.yaml --output-dir=helm-charts
1✔
77

1✔
78
# Typical workflow:
1✔
79
  make build-installer  # Generate dist/install.yaml with latest changes
1✔
80
  %[1]s edit --plugins=%[2]s  # Generate/update Helm chart in dist/chart/
1✔
81

1✔
82
**NOTE**: Chart.yaml is never overwritten (contains user-managed version info).
1✔
83
Without --force, the plugin also preserves values.yaml, NOTES.txt, _helpers.tpl, .helmignore,
1✔
84
and .github/workflows/test-chart.yml.
1✔
85
All other template files in templates/ are always regenerated to match your current
1✔
86
kustomize output. Use --force to regenerate all files except Chart.yaml.
1✔
87

1✔
88
The generated chart structure mirrors your config/ directory:
1✔
89
<output>/chart/
1✔
90
├── Chart.yaml
1✔
91
├── values.yaml
1✔
92
├── .helmignore
1✔
93
└── templates/
1✔
94
    ├── NOTES.txt
1✔
95
    ├── _helpers.tpl
1✔
96
    ├── rbac/
1✔
97
    ├── manager/
1✔
98
    ├── webhook/
1✔
99
    └── ...
1✔
100
`, cliMeta.CommandName, plugin.KeyFor(Plugin{}))
1✔
101
}
1✔
102

103
func (p *editSubcommand) BindFlags(fs *pflag.FlagSet) {
1✔
104
        fs.BoolVar(&p.force, "force", false, "if true, regenerates all the files")
1✔
105
        fs.StringVar(&p.manifestsFile, "manifests", DefaultManifestsFile,
1✔
106
                "path to the YAML file containing Kubernetes manifests from kustomize output")
1✔
107
        fs.StringVar(&p.outputDir, "output-dir", common.DefaultOutputDir, "output directory for the generated Helm chart")
1✔
108
}
1✔
109

110
func (p *editSubcommand) InjectConfig(c config.Config) error {
1✔
111
        p.config = c
1✔
112
        return nil
1✔
113
}
1✔
114

115
func (p *editSubcommand) Scaffold(fs machinery.Filesystem) error {
×
116
        // If using default manifests file, ensure it exists by running make build-installer
×
117
        if p.manifestsFile == DefaultManifestsFile {
×
118
                if err := p.ensureManifestsExist(); err != nil {
×
119
                        slog.Warn("Failed to generate default manifests file", "error", err, "file", p.manifestsFile)
×
120
                }
×
121
        }
122

123
        scaffolder := scaffolds.NewChartScaffolder(p.config, p.force, p.manifestsFile, p.outputDir)
×
124
        scaffolder.InjectFS(fs)
×
125
        err := scaffolder.Scaffold()
×
126
        if err != nil {
×
127
                return fmt.Errorf("error scaffolding Helm chart: %w", err)
×
128
        }
×
129

130
        // Remove deprecated v1-alpha plugin entry from PROJECT file
131
        // This must happen in Scaffold (before config is saved) to be persisted
132
        p.removeV1AlphaPluginEntry()
×
133

×
134
        // Save plugin config to PROJECT file
×
135
        key := plugin.GetPluginKeyForConfig(p.config.GetPluginChain(), Plugin{})
×
136
        canonicalKey := plugin.KeyFor(Plugin{})
×
137
        cfg := pluginConfig{}
×
138
        isFirstRun := false
×
139
        if err = p.config.DecodePluginConfig(key, &cfg); err != nil {
×
140
                switch {
×
141
                case errors.As(err, &config.UnsupportedFieldError{}):
×
142
                        // Config version doesn't support plugin metadata
×
143
                        return nil
×
144
                case errors.As(err, &config.PluginKeyNotFoundError{}):
×
145
                        // This is the first time the plugin is run
×
146
                        isFirstRun = true
×
147
                        if key != canonicalKey {
×
148
                                if err2 := p.config.DecodePluginConfig(canonicalKey, &cfg); err2 != nil {
×
149
                                        if errors.As(err2, &config.UnsupportedFieldError{}) {
×
150
                                                return nil
×
151
                                        }
×
152
                                        if !errors.As(err2, &config.PluginKeyNotFoundError{}) {
×
153
                                                return fmt.Errorf("error decoding plugin configuration: %w", err2)
×
154
                                        }
×
155
                                } else {
×
156
                                        // Found config under canonical key, not first run
×
157
                                        isFirstRun = false
×
158
                                }
×
159
                        }
160
                default:
×
161
                        return fmt.Errorf("error decoding plugin configuration: %w", err)
×
162
                }
163
        }
164

165
        // Update configuration with current parameters
166
        cfg.ManifestsFile = p.manifestsFile
×
167
        cfg.OutputDir = p.outputDir
×
168

×
169
        if err = p.config.EncodePluginConfig(key, cfg); err != nil {
×
170
                return fmt.Errorf("error encoding plugin configuration: %w", err)
×
171
        }
×
172

173
        // Add Helm deployment targets to Makefile only on first run
174
        if isFirstRun {
×
175
                slog.Info("adding Helm deployment targets to Makefile...")
×
176
                // Extract namespace from manifests for accurate Makefile generation
×
177
                namespace := p.extractNamespaceFromManifests()
×
178
                if err := p.addHelmMakefileTargets(namespace); err != nil {
×
179
                        slog.Warn("failed to add Helm targets to Makefile", "error", err)
×
180
                }
×
181
        }
182

183
        return nil
×
184
}
185

186
func (p *editSubcommand) ensureManifestsExist() error {
×
187
        slog.Info("Generating default manifests file", "file", p.manifestsFile)
×
188

×
189
        // Run the required make targets to generate the manifests file
×
190
        targets := []string{"manifests", "generate", "build-installer"}
×
191
        for _, target := range targets {
×
192
                if err := util.RunCmd(fmt.Sprintf("Running make %s", target), "make", target); err != nil {
×
193
                        return fmt.Errorf("make %s failed: %w", target, err)
×
194
                }
×
195
        }
196

197
        // Verify the file was created
198
        if _, err := os.Stat(p.manifestsFile); err != nil {
×
199
                return fmt.Errorf("manifests file %s was not created: %w", p.manifestsFile, err)
×
200
        }
×
201

202
        slog.Info("Successfully generated manifests file", "file", p.manifestsFile)
×
203
        return nil
×
204
}
205

206
func (p *editSubcommand) PostScaffold() error {
2✔
207
        hasWebhooks := hasWebhooksWith(p.config)
2✔
208

2✔
209
        if hasWebhooks {
2✔
210
                workflowFile := filepath.Join(".github", "workflows", "test-chart.yml")
×
211
                if _, err := os.Stat(workflowFile); err != nil {
×
212
                        slog.Info(
×
213
                                "Workflow file not found, unable to uncomment cert-manager installation",
×
214
                                "error", err,
×
215
                                "file", workflowFile,
×
216
                        )
×
217
                        return nil
×
218
                }
×
219
                target := `
×
220
#      - name: Install cert-manager via Helm (wait for readiness)
×
221
#        run: |
×
222
#          helm repo add jetstack https://charts.jetstack.io
×
223
#          helm repo update
×
224
#          helm install cert-manager jetstack/cert-manager \
×
225
#            --namespace cert-manager \
×
226
#            --create-namespace \
×
227
#            --set crds.enabled=true \
×
228
#            --wait \
×
229
#            --timeout 300s`
×
230
                if err := util.UncommentCode(workflowFile, target, "#"); err != nil {
×
231
                        hasUncommented, errCheck := util.HasFileContentWith(workflowFile, "- name: Install cert-manager via Helm")
×
232
                        if !hasUncommented || errCheck != nil {
×
233
                                slog.Warn("Failed to uncomment cert-manager installation in workflow file", "error", err, "file", workflowFile)
×
234
                        }
×
235
                } else {
×
236
                        target = `# TODO: Uncomment if cert-manager is enabled`
×
237
                        _ = util.ReplaceInFile(workflowFile, target, "")
×
238
                }
×
239
        }
240
        return nil
2✔
241
}
242

243
func (p *editSubcommand) addHelmMakefileTargets(namespace string) error {
3✔
244
        makefilePath := "Makefile"
3✔
245
        if _, err := os.Stat(makefilePath); os.IsNotExist(err) {
4✔
246
                return fmt.Errorf("makefile not found")
1✔
247
        }
1✔
248

249
        // Get the Helm Makefile targets
250
        helmTargets := getHelmMakefileTargets(p.config.GetProjectName(), namespace, p.outputDir)
2✔
251

2✔
252
        // Append the targets if they don't already exist
2✔
253
        if err := util.AppendCodeIfNotExist(makefilePath, helmTargets); err != nil {
2✔
254
                return fmt.Errorf("failed to append Helm targets to Makefile: %w", err)
×
255
        }
×
256

257
        slog.Info("added Helm deployment targets to Makefile",
2✔
258
                "targets", "helm-deploy, helm-uninstall, helm-status, helm-history, helm-rollback")
2✔
259
        return nil
2✔
260
}
261

262
// extractNamespaceFromManifests parses the manifests file to extract the manager namespace.
263
// Returns projectName-system if manifests don't exist or namespace not found.
264
func (p *editSubcommand) extractNamespaceFromManifests() string {
×
265
        // Default to project-name-system pattern
×
266
        defaultNamespace := p.config.GetProjectName() + "-system"
×
267

×
268
        // If manifests file doesn't exist, use default
×
269
        if _, err := os.Stat(p.manifestsFile); os.IsNotExist(err) {
×
270
                return defaultNamespace
×
271
        }
×
272

273
        // Parse the manifests to get the namespace
274
        file, err := os.Open(p.manifestsFile)
×
275
        if err != nil {
×
276
                return defaultNamespace
×
277
        }
×
278
        defer func() {
×
279
                _ = file.Close()
×
280
        }()
×
281

282
        // Parse YAML documents looking for the manager Deployment
283
        decoder := yaml.NewDecoder(file)
×
284
        for {
×
285
                var doc map[string]any
×
286
                if err := decoder.Decode(&doc); err != nil {
×
287
                        if err == io.EOF {
×
288
                                break
×
289
                        }
290
                        continue
×
291
                }
292

293
                // Check if this is a Deployment (manager)
294
                if kind, ok := doc["kind"].(string); ok && kind == "Deployment" {
×
295
                        if metadata, ok := doc["metadata"].(map[string]any); ok {
×
296
                                // Check if it's the manager deployment
×
297
                                if name, ok := metadata["name"].(string); ok && strings.Contains(name, "controller-manager") {
×
298
                                        // Extract namespace from the manager Deployment
×
299
                                        if namespace, ok := metadata["namespace"].(string); ok && namespace != "" {
×
300
                                                return namespace
×
301
                                        }
×
302
                                }
303
                        }
304
                }
305
        }
306

307
        // Fallback to default if manager Deployment not found
308
        return defaultNamespace
×
309
}
310

311
// getHelmMakefileTargets returns the Helm Makefile targets as a string
312
// following the same patterns as the existing Makefile deployment section
313
func getHelmMakefileTargets(projectName, namespace, outputDir string) string {
5✔
314
        if outputDir == "" {
5✔
315
                outputDir = "dist"
×
316
        }
×
317

318
        // Use the project name as default for release name
319
        release := projectName
5✔
320

5✔
321
        return helmMakefileTemplate(namespace, release, outputDir)
5✔
322
}
323

324
// helmMakefileTemplate returns the Helm deployment section template
325
// This follows the same pattern as the Kustomize deployment section in the Go plugin
326
const helmMakefileTemplateFormat = `
327
##@ Helm Deployment
328

329
## Helm binary to use for deploying the chart
330
HELM ?= helm
331
## Namespace to deploy the Helm release
332
HELM_NAMESPACE ?= %s
333
## Name of the Helm release
334
HELM_RELEASE ?= %s
335
## Path to the Helm chart directory
336
HELM_CHART_DIR ?= %s/chart
337
## Additional arguments to pass to helm commands
338
HELM_EXTRA_ARGS ?=
339

340
.PHONY: install-helm
341
install-helm: ## Install the latest version of Helm.
342
        @command -v $(HELM) >/dev/null 2>&1 || { \
343
                echo "Installing Helm..." && \
344
                curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4 | bash; \
345
        }
346

347
.PHONY: helm-deploy
348
helm-deploy: install-helm ## Deploy manager to the K8s cluster via Helm. Specify an image with IMG.
349
        $(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART_DIR) \
350
                --namespace $(HELM_NAMESPACE) \
351
                --create-namespace \
352
                --set manager.image.repository=$${IMG%%:*} \
353
                --set manager.image.tag=$${IMG##*:} \
354
                --wait \
355
                --timeout 5m \
356
                $(HELM_EXTRA_ARGS)
357

358
.PHONY: helm-uninstall
359
helm-uninstall: ## Uninstall the Helm release from the K8s cluster.
360
        $(HELM) uninstall $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)
361

362
.PHONY: helm-status
363
helm-status: ## Show Helm release status.
364
        $(HELM) status $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)
365

366
.PHONY: helm-history
367
helm-history: ## Show Helm release history.
368
        $(HELM) history $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)
369

370
.PHONY: helm-rollback
371
helm-rollback: ## Rollback to previous Helm release.
372
        $(HELM) rollback $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)
373
`
374

375
func helmMakefileTemplate(namespace, release, outputDir string) string {
5✔
376
        return fmt.Sprintf(helmMakefileTemplateFormat, namespace, release, outputDir)
5✔
377
}
5✔
378

379
func hasWebhooksWith(c config.Config) bool {
3✔
380
        resources, err := c.GetResources()
3✔
381
        if err != nil {
3✔
382
                return false
×
383
        }
×
384

385
        for _, res := range resources {
3✔
386
                if res.HasDefaultingWebhook() || res.HasValidationWebhook() || res.HasConversionWebhook() {
×
387
                        return true
×
388
                }
×
389
        }
390

391
        return false
3✔
392
}
393

394
// removeV1AlphaPluginEntry removes the deprecated helm.kubebuilder.io/v1-alpha plugin entry.
395
// This must be called from Scaffold (before config is saved) for changes to be persisted.
396
func (p *editSubcommand) removeV1AlphaPluginEntry() {
4✔
397
        // Only attempt to remove if using v3 config (which supports plugin configs)
4✔
398
        cfg, ok := p.config.(*cfgv3.Cfg)
4✔
399
        if !ok {
4✔
400
                return
×
401
        }
×
402

403
        // Check if v1-alpha plugin entry exists
404
        if cfg.Plugins == nil {
6✔
405
                return
2✔
406
        }
2✔
407

408
        if _, exists := cfg.Plugins[v1AlphaPluginKey]; exists {
4✔
409
                delete(cfg.Plugins, v1AlphaPluginKey)
2✔
410
                slog.Info("removed deprecated v1-alpha plugin entry")
2✔
411
        }
2✔
412
}
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

© 2026 Coveralls, Inc