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

kubernetes-sigs / kubebuilder / 20493728758

24 Dec 2025 10:46AM UTC coverage: 60.912% (-0.03%) from 60.942%
20493728758

Pull #5256

github

UJESH2K
helm/v2-alpha: preserve unhandled resources under templates/extras

The helm/v2-alpha plugin previously dropped Kubernetes resources that were
present in install.yaml but not explicitly handled by the default Helm
scaffolds.

This change ensures that:
- All resources produced by kustomize are tracked during parsing
- Known resources are scaffolded as before
- Any unhandled resources are preserved under templates/extras
- A warning is emitted to inform users when extras are generated
- Unit tests validate that multiple unhandled resources are not dropped

This prevents silent data loss in generated Helm charts.
Pull Request #5256: ✨ (helm/v2-alpha): Add support for exporting unknown resources from install YAML via extra directory

27 of 49 new or added lines in 3 files covered. (55.1%)

130 existing lines in 4 files now uncovered.

5800 of 9522 relevant lines covered (60.91%)

25.97 hits per line

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

90.37
/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/values_basic.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 templates
18

19
import (
20
        "bytes"
21
        "fmt"
22
        "path/filepath"
23
        "strings"
24

25
        "go.yaml.in/yaml/v3"
26

27
        "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
28
)
29

30
var _ machinery.Template = &HelmValuesBasic{}
31

32
// HelmValuesBasic scaffolds a basic values.yaml based on detected features
33
type HelmValuesBasic struct {
34
        machinery.TemplateMixin
35
        machinery.ProjectNameMixin
36

37
        // DeploymentConfig stores extracted deployment configuration (env, resources, security contexts)
38
        DeploymentConfig map[string]any
39
        // OutputDir specifies the output directory for the chart
40
        OutputDir string
41
        // Force if true allows overwriting the scaffolded file
42
        Force bool
43
        // HasWebhooks is true when webhooks were found in the config
44
        HasWebhooks bool
45
        // HasMetrics is true when metrics service/monitor were found in the config
46
        HasMetrics bool
47
}
48

49
// SetTemplateDefaults implements machinery.Template
50
func (f *HelmValuesBasic) SetTemplateDefaults() error {
24✔
51
        if f.Path == "" {
48✔
52
                outputDir := f.OutputDir
24✔
53
                if outputDir == "" {
45✔
54
                        outputDir = "dist"
21✔
55
                }
21✔
56
                f.Path = filepath.Join(outputDir, "chart", "values.yaml")
24✔
57
        }
58

59
        f.TemplateBody = f.generateBasicValues()
24✔
60

24✔
61
        if f.Force {
24✔
62
                f.IfExistsAction = machinery.OverwriteFile
×
63
        } else {
24✔
64
                f.IfExistsAction = machinery.SkipFile
24✔
65
        }
24✔
66

67
        return nil
24✔
68
}
69

70
// generateBasicValues creates a basic values.yaml based on detected features
71
func (f *HelmValuesBasic) generateBasicValues() string {
24✔
72
        var buf bytes.Buffer
24✔
73

24✔
74
        // Controller Manager configuration
24✔
75
        imageRepo := "controller"
24✔
76
        imageTag := "latest"
24✔
77
        imagePullPolicy := "IfNotPresent"
24✔
78
        if f.DeploymentConfig != nil {
44✔
79
                if imgCfg, ok := f.DeploymentConfig["image"].(map[string]any); ok {
21✔
80
                        if repo, ok := imgCfg["repository"].(string); ok && repo != "" {
2✔
81
                                imageRepo = repo
1✔
82
                        }
1✔
83
                        if tag, ok := imgCfg["tag"].(string); ok && tag != "" {
2✔
84
                                imageTag = tag
1✔
85
                        }
1✔
86
                        if policy, ok := imgCfg["pullPolicy"].(string); ok && policy != "" {
2✔
87
                                imagePullPolicy = policy
1✔
88
                        }
1✔
89
                }
90
        }
91

92
        buf.WriteString(fmt.Sprintf(`# Configure the controller manager deployment
24✔
93
manager:
24✔
94
  replicas: 1
24✔
95
  
24✔
96
  image:
24✔
97
    repository: %s
24✔
98
    tag: %s
24✔
99
    pullPolicy: %s
24✔
100

24✔
101
`, imageRepo, imageTag, imagePullPolicy))
24✔
102

24✔
103
        // Add extracted deployment configuration
24✔
104
        f.addDeploymentConfig(&buf)
24✔
105

24✔
106
        // RBAC configuration
24✔
107
        buf.WriteString(`# Essential RBAC permissions (required for controller operation)
24✔
108
# These include ServiceAccount, controller permissions, leader election, and metrics access
24✔
109
# Note: Essential RBAC is always enabled as it's required for the controller to function
24✔
110

24✔
111
# Helper RBAC roles for managing custom resources
24✔
112
# These provide convenient admin/editor/viewer roles for each CRD type
24✔
113
# Useful for giving users different levels of access to your custom resources
24✔
114
rbacHelpers:
24✔
115
  enable: false  # Install convenience admin/editor/viewer roles for CRDs
24✔
116

24✔
117
`)
24✔
118

24✔
119
        // CRD configuration
24✔
120
        buf.WriteString(`# Custom Resource Definitions
24✔
121
crd:
24✔
122
  enable: true  # Install CRDs with the chart
24✔
123
  keep: true    # Keep CRDs when uninstalling
24✔
124

24✔
125
`)
24✔
126

24✔
127
        // Metrics configuration (enable if metrics artifacts detected in kustomize output)
24✔
128
        metricsPort := 8443
24✔
129
        if f.DeploymentConfig != nil {
44✔
130
                if mp, ok := f.DeploymentConfig["metricsPort"].(int); ok && mp > 0 {
25✔
131
                        metricsPort = mp
5✔
132
                }
5✔
133
        }
134

135
        if f.HasMetrics {
37✔
136
                buf.WriteString(fmt.Sprintf(`# Controller metrics endpoint.
13✔
137
# Enable to expose /metrics endpoint with RBAC protection.
13✔
138
metrics:
13✔
139
  enable: true
13✔
140
  port: %d  # Metrics server port
13✔
141

13✔
142
`, metricsPort))
13✔
143
        } else {
24✔
144
                buf.WriteString(fmt.Sprintf(`# Controller metrics endpoint.
11✔
145
# Enable to expose /metrics endpoint with RBAC protection.
11✔
146
metrics:
11✔
147
  enable: false
11✔
148
  port: %d  # Metrics server port
11✔
149

11✔
150
`, metricsPort))
11✔
151
        }
11✔
152

153
        // Cert-manager configuration (always present, enabled based on webhooks)
154
        if f.HasWebhooks {
37✔
155
                buf.WriteString(`# Cert-manager integration for TLS certificates.
13✔
156
# Required for webhook certificates and metrics endpoint certificates.
13✔
157
certManager:
13✔
158
  enable: true
13✔
159

13✔
160
`)
13✔
161
        } else {
24✔
162
                buf.WriteString(`# Cert-manager integration for TLS certificates.
11✔
163
# Required for webhook certificates and metrics endpoint certificates.
11✔
164
certManager:
11✔
165
  enable: false
11✔
166

11✔
167
`)
11✔
168
        }
11✔
169

170
        // Webhook configuration - only if webhooks are present
171
        if f.HasWebhooks {
37✔
172
                webhookPort := 9443
13✔
173
                if f.DeploymentConfig != nil {
26✔
174
                        if wp, ok := f.DeploymentConfig["webhookPort"].(int); ok && wp > 0 {
16✔
175
                                webhookPort = wp
3✔
176
                        }
3✔
177
                }
178

179
                buf.WriteString(fmt.Sprintf(`# Webhook server configuration
13✔
180
webhook:
13✔
181
  enable: true
13✔
182
  port: %d  # Webhook server port
13✔
183

13✔
184
`, webhookPort))
13✔
185
        }
186

187
        // Prometheus configuration
188
        buf.WriteString(`# Prometheus ServiceMonitor for metrics scraping.
24✔
189
# Requires prometheus-operator to be installed in the cluster.
24✔
190
prometheus:
24✔
191
  enable: false
24✔
192
`)
24✔
193

24✔
194
        buf.WriteString("\n")
24✔
195
        return buf.String()
24✔
196
}
197

198
// addDeploymentConfig adds extracted deployment configuration to the values
199
func (f *HelmValuesBasic) addDeploymentConfig(buf *bytes.Buffer) {
24✔
200
        f.addArgsSection(buf)
24✔
201

24✔
202
        if f.DeploymentConfig == nil {
28✔
203
                // Add default sections with examples
4✔
204
                f.addDefaultDeploymentSections(buf)
4✔
205
                return
4✔
206
        }
4✔
207

208
        // Add environment variables if they exist
209
        if env, exists := f.DeploymentConfig["env"]; exists && env != nil {
22✔
210
                buf.WriteString("  # Environment variables\n")
2✔
211
                buf.WriteString("  env:\n")
2✔
212
                if envYaml, err := yaml.Marshal(env); err == nil {
4✔
213
                        // Indent the YAML properly
2✔
214
                        lines := bytes.SplitSeq(envYaml, []byte("\n"))
2✔
215
                        for line := range lines {
10✔
216
                                if len(line) > 0 {
14✔
217
                                        buf.WriteString("    ")
6✔
218
                                        buf.Write(line)
6✔
219
                                        buf.WriteString("\n")
6✔
220
                                }
6✔
221
                        }
UNCOV
222
                } else {
×
UNCOV
223
                        buf.WriteString("    []\n")
×
UNCOV
224
                }
×
225
                buf.WriteString("\n")
2✔
226
        } else {
18✔
227
                buf.WriteString("  # Environment variables\n")
18✔
228
                buf.WriteString("  env: []\n\n")
18✔
229
        }
18✔
230

231
        // Add image pull secrets
232
        if imagePullSecrets, exists := f.DeploymentConfig["imagePullSecrets"]; exists && imagePullSecrets != nil {
21✔
233
                buf.WriteString("  # Image pull secrets\n")
1✔
234
                buf.WriteString("  imagePullSecrets:\n")
1✔
235
                if imagePullSecretsYaml, err := yaml.Marshal(imagePullSecrets); err == nil {
2✔
236
                        lines := bytes.SplitSeq(imagePullSecretsYaml, []byte("\n"))
1✔
237
                        for line := range lines {
4✔
238
                                if len(line) > 0 {
5✔
239
                                        buf.WriteString("    ")
2✔
240
                                        buf.Write(line)
2✔
241
                                        buf.WriteString("\n")
2✔
242
                                }
2✔
243
                        }
244
                }
245
                buf.WriteString("\n")
1✔
246
        } else {
19✔
247
                f.addDefaultImagePullSecrets(buf)
19✔
248
        }
19✔
249

250
        // Add podSecurityContext
251
        if podSecCtx, exists := f.DeploymentConfig["podSecurityContext"]; exists && podSecCtx != nil {
20✔
UNCOV
252
                buf.WriteString("  # Pod-level security settings\n")
×
UNCOV
253
                buf.WriteString("  podSecurityContext:\n")
×
UNCOV
254
                if secYaml, err := yaml.Marshal(podSecCtx); err == nil {
×
255
                        lines := bytes.SplitSeq(secYaml, []byte("\n"))
×
256
                        for line := range lines {
×
257
                                if len(line) > 0 {
×
258
                                        buf.WriteString("    ")
×
259
                                        buf.Write(line)
×
260
                                        buf.WriteString("\n")
×
261
                                }
×
262
                        }
263
                }
UNCOV
264
                buf.WriteString("\n")
×
265
        } else {
20✔
266
                f.addDefaultPodSecurityContext(buf)
20✔
267
        }
20✔
268

269
        // Add securityContext
270
        if secCtx, exists := f.DeploymentConfig["securityContext"]; exists && secCtx != nil {
20✔
271
                buf.WriteString("  # Container-level security settings\n")
×
272
                buf.WriteString("  securityContext:\n")
×
273
                if secYaml, err := yaml.Marshal(secCtx); err == nil {
×
274
                        lines := bytes.SplitSeq(secYaml, []byte("\n"))
×
275
                        for line := range lines {
×
276
                                if len(line) > 0 {
×
277
                                        buf.WriteString("    ")
×
278
                                        buf.Write(line)
×
279
                                        buf.WriteString("\n")
×
280
                                }
×
281
                        }
282
                }
283
                buf.WriteString("\n")
×
284
        } else {
20✔
285
                f.addDefaultSecurityContext(buf)
20✔
286
        }
20✔
287

288
        // Add resources
289
        if resources, exists := f.DeploymentConfig["resources"]; exists && resources != nil {
21✔
290
                buf.WriteString("  # Resource limits and requests\n")
1✔
291
                buf.WriteString("  resources:\n")
1✔
292
                if resYaml, err := yaml.Marshal(resources); err == nil {
2✔
293
                        lines := bytes.SplitSeq(resYaml, []byte("\n"))
1✔
294
                        for line := range lines {
5✔
295
                                if len(line) > 0 {
7✔
296
                                        buf.WriteString("    ")
3✔
297
                                        buf.Write(line)
3✔
298
                                        buf.WriteString("\n")
3✔
299
                                }
3✔
300
                        }
301
                }
302
                buf.WriteString("\n")
1✔
303
        } else {
19✔
304
                f.addDefaultResources(buf)
19✔
305
        }
19✔
306
}
307

308
// addDefaultDeploymentSections adds default sections when no deployment config is available
309
func (f *HelmValuesBasic) addDefaultDeploymentSections(buf *bytes.Buffer) {
4✔
310
        buf.WriteString("  # Environment variables\n")
4✔
311
        buf.WriteString("  env: []\n\n")
4✔
312

4✔
313
        f.addDefaultImagePullSecrets(buf)
4✔
314
        f.addDefaultPodSecurityContext(buf)
4✔
315
        f.addDefaultSecurityContext(buf)
4✔
316
        f.addDefaultResources(buf)
4✔
317
}
4✔
318

319
// addArgsSection adds controller manager args section to the values file
320
func (f *HelmValuesBasic) addArgsSection(buf *bytes.Buffer) {
24✔
321
        buf.WriteString("  # Arguments\n")
24✔
322

24✔
323
        if f.DeploymentConfig != nil {
44✔
324
                if args, exists := f.DeploymentConfig["args"]; exists && args != nil {
21✔
325
                        if argsYaml, err := yaml.Marshal(args); err == nil {
2✔
326
                                if trimmed := strings.TrimSpace(string(argsYaml)); trimmed != "" && trimmed != "[]" {
2✔
327
                                        lines := bytes.Split(argsYaml, []byte("\n"))
1✔
328
                                        buf.WriteString("  args:\n")
1✔
329
                                        for _, line := range lines {
3✔
330
                                                if len(line) > 0 {
3✔
331
                                                        buf.WriteString("    ")
1✔
332
                                                        buf.Write(line)
1✔
333
                                                        buf.WriteString("\n")
1✔
334
                                                }
1✔
335
                                        }
336
                                        buf.WriteString("\n")
1✔
337
                                        return
1✔
338
                                }
339
                        }
340
                }
341
        }
342

343
        buf.WriteString("  args: []\n\n")
23✔
344
}
345

346
// addDefaultImagePullSecrets adds default imagePullSecrets section
347
func (f *HelmValuesBasic) addDefaultImagePullSecrets(buf *bytes.Buffer) {
23✔
348
        buf.WriteString("  # Image pull secrets\n")
23✔
349
        buf.WriteString("  imagePullSecrets: []\n\n")
23✔
350
}
23✔
351

352
// addDefaultPodSecurityContext adds default podSecurityContext section
353
func (f *HelmValuesBasic) addDefaultPodSecurityContext(buf *bytes.Buffer) {
24✔
354
        buf.WriteString("  # Pod-level security settings\n")
24✔
355
        buf.WriteString("  podSecurityContext: {}\n")
24✔
356
        buf.WriteString("    # fsGroup: 2000\n\n")
24✔
357
}
24✔
358

359
// addDefaultSecurityContext adds default securityContext section
360
func (f *HelmValuesBasic) addDefaultSecurityContext(buf *bytes.Buffer) {
24✔
361
        buf.WriteString("  # Container-level security settings\n")
24✔
362
        buf.WriteString("  securityContext: {}\n")
24✔
363
        buf.WriteString("    # capabilities:\n")
24✔
364
        buf.WriteString("    #   drop:\n")
24✔
365
        buf.WriteString("    #   - ALL\n")
24✔
366
        buf.WriteString("    # readOnlyRootFilesystem: true\n")
24✔
367
        buf.WriteString("    # runAsNonRoot: true\n")
24✔
368
        buf.WriteString("    # runAsUser: 1000\n\n")
24✔
369
}
24✔
370

371
// addDefaultResources adds default resources section
372
func (f *HelmValuesBasic) addDefaultResources(buf *bytes.Buffer) {
23✔
373
        buf.WriteString("  # Resource limits and requests\n")
23✔
374
        buf.WriteString("  resources: {}\n")
23✔
375
        buf.WriteString("    # limits:\n")
23✔
376
        buf.WriteString("    #   cpu: 100m\n")
23✔
377
        buf.WriteString("    #   memory: 128Mi\n")
23✔
378
        buf.WriteString("    # requests:\n")
23✔
379
        buf.WriteString("    #   cpu: 100m\n")
23✔
380
        buf.WriteString("    #   memory: 128Mi\n\n")
23✔
381
}
23✔
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