Skip to content

Commit 360c6d5

Browse files
committed
various enhancements to HelmGenerator
- add .Capabilities.KubeVersion.GitVersion to be backwards compatible - add .Chart.Type field (application/library) - add .Release.IsInstall (always true) and .Release.IsUpgrade (always false) - lookup manifests in ./templates directory recursively - support (one level of) library subcharts
1 parent 941ecda commit 360c6d5

File tree

6 files changed

+212
-71
lines changed

6 files changed

+212
-71
lines changed

internal/helm/capabilities.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@ func GetCapabilities(client discovery.DiscoveryInterface) (*CapabilitiesData, er
4040
}
4141
capabilities := &CapabilitiesData{
4242
KubeVersion: KubeVersionData{
43-
Version: kubeVersion.GitVersion,
44-
Major: kubeVersion.Major,
45-
Minor: kubeVersion.Minor,
43+
Version: kubeVersion.GitVersion,
44+
Major: kubeVersion.Major,
45+
Minor: kubeVersion.Minor,
46+
GitVersion: kubeVersion.GitVersion,
4647
},
4748
APIVersions: slices.Uniq(apiVersions),
4849
}

internal/helm/types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,15 @@ package helm
1919
type ChartData struct {
2020
Name string `json:"name,omitempty"`
2121
Version string `json:"version,omitempty"`
22+
Type string `json:"type,omitempty"`
2223
AppVersion string `json:"appVersion,omitempty"`
2324
}
2425

26+
const (
27+
ChartTypeApplication = "application"
28+
ChartTypeLibrary = "library"
29+
)
30+
2531
type TemplateData struct {
2632
Name string `json:"name,omitempty"`
2733
BasePath string `json:"basePath,omitempty"`
@@ -31,6 +37,8 @@ type ReleaseData struct {
3137
Namespace string `json:"namespace,omitempty"`
3238
Name string `json:"name,omitempty"`
3339
Service string `json:"service,omitempty"`
40+
IsInstall bool `json:"isInstall,omitempty"`
41+
IsUpgrade bool `json:"isUpgrade,omitempty"`
3442
}
3543

3644
type CapabilitiesData struct {
@@ -53,6 +61,8 @@ type KubeVersionData struct {
5361
Version string `json:"version,omitempty"`
5462
Major string `json:"major,omitempty"`
5563
Minor string `json:"minor,omitempty"`
64+
// GitVersion is actually deprecated, but some charts still use it
65+
GitVersion string `json:"gitVersion,omitempty"`
5666
}
5767

5868
func (kubeVersion *KubeVersionData) String() string {

pkg/manifests/helm.go

Lines changed: 70 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import (
4848
type HelmGenerator struct {
4949
name string
5050
discoveryClient discovery.DiscoveryInterface
51-
files [][]byte
51+
crds [][]byte
5252
templates []*template.Template
5353
data map[string]any
5454
}
@@ -73,12 +73,17 @@ func NewHelmGenerator(name string, fsys fs.FS, chartPath string, client client.C
7373
if err != nil {
7474
return nil, err
7575
}
76-
g.data["Chart"] = &helm.ChartData{}
77-
if err := kyaml.Unmarshal(chartRaw, g.data["Chart"]); err != nil {
76+
chartData := &helm.ChartData{}
77+
if err := kyaml.Unmarshal(chartRaw, chartData); err != nil {
7878
return nil, err
7979
}
80-
// TODO: validate that dependencies is not set in Chart.yaml
81-
// TODO: validate that ./charts does not exist
80+
if chartData.Type == "" {
81+
chartData.Type = helm.ChartTypeApplication
82+
}
83+
if chartData.Type != helm.ChartTypeApplication {
84+
return nil, fmt.Errorf("only application charts are supported")
85+
}
86+
g.data["Chart"] = chartData
8287

8388
valuesRaw, err := fs.ReadFile(fsys, chartPath+"/values.yaml")
8489
if err == nil {
@@ -92,43 +97,68 @@ func NewHelmGenerator(name string, fsys fs.FS, chartPath string, client client.C
9297
return nil, err
9398
}
9499

95-
files, err := fs.Glob(fsys, chartPath+"/crds/*.yaml")
100+
crds, err := find(fsys, chartPath+"/crds", "*.yaml", fileTypeRegular, 0)
96101
if err != nil {
97-
panic("this cannot happen")
98-
// because an error would occur only if the pattern is malformed, which is not the case
102+
return nil, err
99103
}
100-
for _, file := range files {
101-
raw, err := fs.ReadFile(fsys, file)
104+
for _, crd := range crds {
105+
raw, err := fs.ReadFile(fsys, crd)
102106
if err != nil {
103107
return nil, err
104108
}
105-
g.files = append(g.files, raw)
109+
g.crds = append(g.crds, raw)
106110
}
107111

108-
includes, err := fs.Glob(fsys, chartPath+"/templates/_*")
112+
includes, err := find(fsys, chartPath+"/templates", "_*", fileTypeRegular, 0)
109113
if err != nil {
110-
panic("this cannot happen")
111-
// because an error would occur only if the pattern is malformed, which is not the case
114+
return nil, err
112115
}
113-
sources, err := fs.Glob(fsys, chartPath+"/templates/[^_]*.yaml")
116+
117+
manifests, err := find(fsys, chartPath+"/templates", "[^_]*.yaml", fileTypeRegular, 0)
114118
if err != nil {
115-
panic("this cannot happen")
116-
// because an error would occur only if the pattern is malformed, which is not the case
119+
return nil, err
117120
}
118-
if len(sources) == 0 {
121+
if len(manifests) == 0 {
119122
return &g, nil
120123
}
121124

125+
// TODO: for now, one level of library subcharts is supported
126+
// we should enhance the support of subcharts (nested charts, application charts)
127+
subChartPaths, err := find(fsys, chartPath+"/charts", "*", fileTypeDir, 1)
128+
if err != nil {
129+
return nil, err
130+
}
131+
for _, subChartPath := range subChartPaths {
132+
subChartRaw, err := fs.ReadFile(fsys, subChartPath+"/Chart.yaml")
133+
if err != nil {
134+
return nil, err
135+
}
136+
var subChartData helm.ChartData
137+
if err := kyaml.Unmarshal(subChartRaw, &subChartData); err != nil {
138+
return nil, err
139+
}
140+
if subChartData.Type != helm.ChartTypeLibrary {
141+
return nil, fmt.Errorf("only library subcharts are supported (path: %s)", subChartPath)
142+
}
143+
subIncludes, err := find(fsys, subChartPath+"/templates", "_*", fileTypeRegular, 0)
144+
if err != nil {
145+
return nil, err
146+
}
147+
includes = append(includes, subIncludes...)
148+
}
149+
122150
var t *template.Template
123-
for _, source := range sources {
124-
raw, err := fs.ReadFile(fsys, source)
151+
for _, manifest := range manifests {
152+
raw, err := fs.ReadFile(fsys, manifest)
125153
if err != nil {
126154
return nil, err
127155
}
156+
// Note: we use absolute paths (instead of relative ones) as template names
157+
// because the 'Template' builtin needs that to work properly
128158
if t == nil {
129-
t = template.New(source)
159+
t = template.New(manifest)
130160
} else {
131-
t = t.New(source)
161+
t = t.New(manifest)
132162
}
133163
t.Option("missingkey=zero").
134164
Funcs(sprig.TxtFuncMap()).
@@ -145,6 +175,8 @@ func NewHelmGenerator(name string, fsys fs.FS, chartPath string, client client.C
145175
if err != nil {
146176
return nil, err
147177
}
178+
// Note: we use absolute paths (instead of relative ones) as template names
179+
// because the 'Template' builtin needs that to work properly
148180
t = t.New(include)
149181
t.Option("missingkey=zero").
150182
Funcs(sprig.TxtFuncMap()).
@@ -202,11 +234,15 @@ func (g *HelmGenerator) Generate(namespace string, name string, parameters types
202234
Namespace: namespace,
203235
Name: name,
204236
Service: g.name,
237+
// TODO: probably IsInstall and IsUpgrade should be set in a more differentiated way;
238+
// but we don't know how, since this framework does not really distinguish between installation and upgrade ...
239+
IsInstall: true,
240+
IsUpgrade: false,
205241
}
206242

207243
data["Values"] = MergeMaps(*data["Values"].(*map[string]any), parameters.ToUnstructured())
208244

209-
for _, f := range g.files {
245+
for _, f := range g.crds {
210246
decoder := utilyaml.NewYAMLToJSONDecoder(bytes.NewBuffer(f))
211247
for {
212248
object := &unstructured.Unstructured{}
@@ -225,8 +261,17 @@ func (g *HelmGenerator) Generate(namespace string, name string, parameters types
225261

226262
for _, t := range g.templates {
227263
data["Template"] = &helm.TemplateData{
228-
Name: t.Name(),
229-
BasePath: filepath.Dir(t.Name()),
264+
Name: t.Name(),
265+
BasePath: func(path string) string {
266+
for path != "." && path != "/" {
267+
path = filepath.Dir(path)
268+
if filepath.Base(path) == "templates" {
269+
return path
270+
}
271+
}
272+
panic("this cannot happen")
273+
// because templates were selected such that reside under 'templates' directory
274+
}(t.Name()),
230275
}
231276
var buf bytes.Buffer
232277
if err := t.Execute(&buf, data); err != nil {

pkg/manifests/kustomize.go

Lines changed: 29 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"io/fs"
2323
"os"
2424
"path/filepath"
25-
"strings"
2625
"text/template"
2726

2827
"github.com/Masterminds/sprig/v3"
@@ -66,47 +65,37 @@ func NewKustomizeGenerator(fsys fs.FS, kustomizationPath string, templateSuffix
6665
g.kustomizer = krusty.MakeKustomizer(options)
6766

6867
var t *template.Template
69-
if err := fs.WalkDir(
70-
fsys,
71-
kustomizationPath,
72-
func(path string, dirEntry fs.DirEntry, err error) error {
73-
if err != nil {
74-
return err
75-
}
76-
if !dirEntry.Type().IsRegular() {
77-
return nil
78-
}
79-
if !strings.HasSuffix(path, templateSuffix) {
80-
return nil
81-
}
82-
raw, err := fs.ReadFile(fsys, path)
83-
if err != nil {
84-
return err
85-
}
86-
name, err := filepath.Rel(kustomizationPath, path)
87-
if err != nil {
88-
// TODO: is it ok to panic here in case of error ?
89-
panic(err)
90-
}
91-
if t == nil {
92-
t = template.New(name)
93-
} else {
94-
t = t.New(name)
95-
}
96-
t.Option("missingkey=zero").
97-
Funcs(sprig.TxtFuncMap()).
98-
Funcs(templatex.FuncMap()).
99-
Funcs(templatex.FuncMapForTemplate(t)).
100-
Funcs(templatex.FuncMapForClient(client))
101-
if _, err := t.Parse(string(raw)); err != nil {
102-
return err
103-
}
104-
g.templates = append(g.templates, t)
105-
return nil
106-
},
107-
); err != nil {
68+
files, err := find(fsys, kustomizationPath, "*"+templateSuffix, fileTypeRegular, 0)
69+
if err != nil {
10870
return nil, err
10971
}
72+
for _, file := range files {
73+
raw, err := fs.ReadFile(fsys, file)
74+
if err != nil {
75+
return nil, err
76+
}
77+
// Note: we use relative paths as templates names to make it easier to copy the kustomization
78+
// content into the ephemeral in-memory filesystem used by krusty in Generate()
79+
name, err := filepath.Rel(kustomizationPath, file)
80+
if err != nil {
81+
// TODO: is it ok to panic here in case of error ?
82+
panic(err)
83+
}
84+
if t == nil {
85+
t = template.New(name)
86+
} else {
87+
t = t.New(name)
88+
}
89+
t.Option("missingkey=zero").
90+
Funcs(sprig.TxtFuncMap()).
91+
Funcs(templatex.FuncMap()).
92+
Funcs(templatex.FuncMapForTemplate(t)).
93+
Funcs(templatex.FuncMapForClient(client))
94+
if _, err := t.Parse(string(raw)); err != nil {
95+
return nil, err
96+
}
97+
g.templates = append(g.templates, t)
98+
}
11099

111100
return &g, nil
112101
}

0 commit comments

Comments
 (0)