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

kubernetes-sigs / kubebuilder / 20691086904

04 Jan 2026 09:48AM UTC coverage: 71.685% (+0.3%) from 71.354%
20691086904

Pull #5339

github

camilamacedo86
(helm/v2-alpha): add custom resources to templates/extras

Manual resources not matching the standard layout now go to
templates/extras/ with proper templating applied.

Assisted-by: Cursor
Pull Request #5339: (Blocked by #5294) ✨ (helm/v2-alpha): add custom resources to templates/extras

103 of 107 new or added lines in 7 files covered. (96.26%)

183 existing lines in 5 files now uncovered.

6266 of 8741 relevant lines covered (71.69%)

30.51 hits per line

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

43.42
/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
        "log/slog"
23
        "os"
24
        "path/filepath"
25

26
        "github.com/spf13/pflag"
27

28
        "sigs.k8s.io/kubebuilder/v4/pkg/config"
29
        "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
30
        "sigs.k8s.io/kubebuilder/v4/pkg/plugin"
31
        "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util"
32
        "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v2alpha/scaffolds"
33
)
34

35
const (
36
        // DefaultManifestsFile is the default path for kustomize output manifests
37
        DefaultManifestsFile = "dist/install.yaml"
38
        // DefaultOutputDir is the default output directory for Helm charts
39
        DefaultOutputDir = "dist"
40
)
41

42
var _ plugin.EditSubcommand = &editSubcommand{}
43

44
type editSubcommand struct {
45
        config        config.Config
46
        force         bool
47
        manifestsFile string
48
        outputDir     string
49
}
50

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

1✔
55
This plugin dynamically generates Helm chart templates by parsing the output of 'make build-installer' 
1✔
56
(dist/install.yaml by default). The generated chart preserves all customizations made to your kustomize 
1✔
57
configuration including environment variables, labels, and annotations.
1✔
58

1✔
59
The chart structure mirrors your config/ directory organization. Resources not matching the standard 
1✔
60
layout (custom Services, ConfigMaps, etc.) are placed in templates/extras/ for validation.`
1✔
61

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

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

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

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

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

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

1✔
81
**NOTE**: The plugin preserves customizations in values.yaml, Chart.yaml, _helpers.tpl, and .helmignore
1✔
82
unless --force is used. All template files are regenerated to match your current kustomize output.
1✔
83

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

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

106
func (p *editSubcommand) InjectConfig(c config.Config) error {
1✔
107
        p.config = c
1✔
108
        return nil
1✔
109
}
1✔
110

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

119
        scaffolder := scaffolds.NewKustomizeHelmScaffolder(p.config, p.force, p.manifestsFile, p.outputDir)
×
120
        scaffolder.InjectFS(fs)
×
121
        err := scaffolder.Scaffold()
×
122
        if err != nil {
×
UNCOV
123
                return fmt.Errorf("error scaffolding Helm chart: %w", err)
×
UNCOV
124
        }
×
125

126
        // Save plugin config to PROJECT file
127
        key := plugin.GetPluginKeyForConfig(p.config.GetPluginChain(), Plugin{})
×
128
        canonicalKey := plugin.KeyFor(Plugin{})
×
129
        cfg := pluginConfig{}
×
130
        if err = p.config.DecodePluginConfig(key, &cfg); err != nil {
×
131
                switch {
×
132
                case errors.As(err, &config.UnsupportedFieldError{}):
×
133
                        // Config version doesn't support plugin metadata
×
134
                        return nil
×
135
                case errors.As(err, &config.PluginKeyNotFoundError{}):
×
136
                        if key != canonicalKey {
×
137
                                if err2 := p.config.DecodePluginConfig(canonicalKey, &cfg); err2 != nil {
×
138
                                        if errors.As(err2, &config.UnsupportedFieldError{}) {
×
139
                                                return nil
×
140
                                        }
×
141
                                        if !errors.As(err2, &config.PluginKeyNotFoundError{}) {
×
UNCOV
142
                                                return fmt.Errorf("error decoding plugin configuration: %w", err2)
×
UNCOV
143
                                        }
×
144
                                }
145
                        }
UNCOV
146
                default:
×
UNCOV
147
                        return fmt.Errorf("error decoding plugin configuration: %w", err)
×
148
                }
149
        }
150

151
        // Update configuration with current parameters
152
        cfg.ManifestsFile = p.manifestsFile
×
153
        cfg.OutputDir = p.outputDir
×
154

×
155
        if err = p.config.EncodePluginConfig(key, cfg); err != nil {
×
UNCOV
156
                return fmt.Errorf("error encoding plugin configuration: %w", err)
×
157
        }
×
158

UNCOV
159
        return nil
×
160
}
161

162
// ensureManifestsExist runs make build-installer to generate the default manifests file
163
func (p *editSubcommand) ensureManifestsExist() error {
×
164
        slog.Info("Generating default manifests file", "file", p.manifestsFile)
×
165

×
166
        // Run the required make targets to generate the manifests file
×
167
        targets := []string{"manifests", "generate", "build-installer"}
×
168
        for _, target := range targets {
×
169
                if err := util.RunCmd(fmt.Sprintf("Running make %s", target), "make", target); err != nil {
×
UNCOV
170
                        return fmt.Errorf("make %s failed: %w", target, err)
×
UNCOV
171
                }
×
172
        }
173

174
        // Verify the file was created
175
        if _, err := os.Stat(p.manifestsFile); err != nil {
×
UNCOV
176
                return fmt.Errorf("manifests file %s was not created: %w", p.manifestsFile, err)
×
177
        }
×
178

UNCOV
179
        slog.Info("Successfully generated manifests file", "file", p.manifestsFile)
×
UNCOV
180
        return nil
×
181
}
182

183
// PostScaffold automatically uncomments cert-manager installation when webhooks are present
184
func (p *editSubcommand) PostScaffold() error {
2✔
185
        hasWebhooks := hasWebhooksWith(p.config)
2✔
186

2✔
187
        if hasWebhooks {
2✔
188
                workflowFile := filepath.Join(".github", "workflows", "test-chart.yml")
×
189
                if _, err := os.Stat(workflowFile); err != nil {
×
190
                        slog.Info(
×
191
                                "Workflow file not found, unable to uncomment cert-manager installation",
×
192
                                "error", err,
×
193
                                "file", workflowFile,
×
194
                        )
×
195
                        return nil
×
196
                }
×
197
                target := `
×
198
#      - name: Install cert-manager via Helm (wait for readiness)
×
199
#        run: |
×
200
#          helm repo add jetstack https://charts.jetstack.io
×
201
#          helm repo update
×
202
#          helm install cert-manager jetstack/cert-manager \
×
203
#            --namespace cert-manager \
×
204
#            --create-namespace \
×
205
#            --set crds.enabled=true \
×
206
#            --wait \
×
207
#            --timeout 300s`
×
208
                if err := util.UncommentCode(workflowFile, target, "#"); err != nil {
×
209
                        hasUncommented, errCheck := util.HasFileContentWith(workflowFile, "- name: Install cert-manager via Helm")
×
210
                        if !hasUncommented || errCheck != nil {
×
211
                                slog.Warn("Failed to uncomment cert-manager installation in workflow file", "error", err, "file", workflowFile)
×
212
                        }
×
213
                } else {
×
214
                        target = `# TODO: Uncomment if cert-manager is enabled`
×
UNCOV
215
                        _ = util.ReplaceInFile(workflowFile, target, "")
×
UNCOV
216
                }
×
217
        }
218
        return nil
2✔
219
}
220

221
func hasWebhooksWith(c config.Config) bool {
3✔
222
        resources, err := c.GetResources()
3✔
223
        if err != nil {
3✔
UNCOV
224
                return false
×
UNCOV
225
        }
×
226

227
        for _, res := range resources {
3✔
228
                if res.HasDefaultingWebhook() || res.HasValidationWebhook() || res.HasConversionWebhook() {
×
UNCOV
229
                        return true
×
UNCOV
230
                }
×
231
        }
232

233
        return false
3✔
234
}
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