Skip to content

Commit

Permalink
helm-convert: Enable capturegroup default substitution
Browse files Browse the repository at this point in the history
Signed-off-by: Jim Ramsay <[email protected]>
  • Loading branch information
lack committed Jan 30, 2025
1 parent e043724 commit 84de6f4
Show file tree
Hide file tree
Showing 12 changed files with 194 additions and 9 deletions.
40 changes: 40 additions & 0 deletions addon-tools/helm-convert/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,46 @@ service:
k8s-app: {}
```

### Capturegroup default substitution

The helm-convert tool supports a mechanism to substitute capturegroup default
values if required. If the defaults.yaml contains a section called
`captureGroup_defaults`, and the YAML in question contains one or more
captureGroups using either the `regex` or `capturegroup` inlineDiff mechanism,
all capturegroups with a default in the `captureGroup_defaults` section will be
replaced by the default value when converting the reference template to helm
chart template.

For example, if you have a CR like this:

```yaml
apiVersion: v1
Kind: Foo
spec:
value: |-
Something with (?<blee>.*) in it,
And another capturegroup (?<bar>.*) with no default.
```

With this in the values.yaml:

```yaml
Foo:
- captureGroup_defaults:
blee: 42
```

The resulting Helm template will look like this:

```yaml
apiVersion: v1
Kind: Foo
spec:
value: |-
Something with 42 in it,
And another capturegroup (?<bar>.*) with no default.
```

## Auto Extracting of default values from Existing CRs

another feature that can help in initial building of values.yaml files is extracting default values from existing CRs,
Expand Down
72 changes: 63 additions & 9 deletions addon-tools/helm-convert/convert/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type Options struct {
func convertToHelm(o *Options) error {
helmTemplates := make(map[string]string)
helmValues := make(map[string]any)
var preValues map[string]any
crsWithDefaults := make(map[string]map[string]interface{})

cfs, err := compare.GetRefFS(o.refPath)
Expand All @@ -79,12 +80,19 @@ func convertToHelm(o *Options) error {
}
}

if o.valuesPath != "" {
preValues, err = loadValues(o.valuesPath)
if err != nil {
return err
}
}

for _, t := range templates {

visitor := ExpectedValuesFinder{}
Inspect(t.GetTemplateTree().Root, visitor.Visit())

helmTemplate, err := convertToHelmTemplate(cfs, t)
helmTemplate, err := convertToHelmTemplate(cfs, t, preValues)
if err != nil {
return err
}
Expand All @@ -107,11 +115,7 @@ func convertToHelm(o *Options) error {
}
}

if o.valuesPath != "" {
preValues, err := loadValues(o.valuesPath)
if err != nil {
return err
}
if preValues != nil {
merged, err := compare.MergeManifests(&unstructured.Unstructured{Object: preValues}, &unstructured.Unstructured{Object: helmValues})
if err != nil {
return fmt.Errorf("failed to merge given values with generated values %w", err)
Expand Down Expand Up @@ -177,7 +181,26 @@ func loadYAMLFiles(root string) (map[string]map[string]interface{}, error) {
return filesMapping, nil
}

func convertToHelmTemplate(cfs fs.FS, t compare.ReferenceTemplate) (string, error) {
func cgDefaultsFor(compName string, helmValues map[string]any) (map[string]any, error) {
if values, ok := helmValues[compName].([]any); ok && len(values) > 0 {
if section, ok := values[0].(map[string]any); ok && len(section) > 0 {
if dflts, ok := section["captureGroup_defaults"].(map[string]any); ok && len(dflts) > 0 {
return dflts, nil
}
}
}
return nil, fmt.Errorf("no captureGroup_defaults found for %s", compName)
}

func removeCgDefaults(compName string, helmValues map[string]any) {
if values, ok := helmValues[compName].([]any); ok && len(values) > 0 {
if section, ok := values[0].(map[string]any); ok && len(section) > 0 {
delete(section, "captureGroup_defaults")
}
}
}

func convertToHelmTemplate(cfs fs.FS, t compare.ReferenceTemplate, helmValues map[string]any) (string, error) {
var templateStructure = `{{- $values := list (dict)}}
{{- if .Values.%v}}
{{- $values = .Values.%v }}
Expand All @@ -187,12 +210,43 @@ func convertToHelmTemplate(cfs fs.FS, t compare.ReferenceTemplate) (string, erro
%v
{{ end -}}
`
content, err := fs.ReadFile(cfs, t.GetIdentifier())
data, err := fs.ReadFile(cfs, t.GetIdentifier())
if err != nil {
return "", fmt.Errorf("failed to read template named: %s %w", t.GetIdentifier(), err)
}

compName := getCompName(t.GetIdentifier())
helmTemplate := fmt.Sprintf(templateStructure, compName, compName, string(content))

content := string(data)

if len(t.GetConfig().GetInlineDiffFuncs()) > 0 {
if dflts, err := cgDefaultsFor(compName, helmValues); err == nil {
cgs := compare.CapturegroupIndex(content)
contentBuilder := strings.Builder{}
idx := 0
for _, group := range cgs {
if idx < group.Start {
contentBuilder.WriteString(content[idx:group.Start])
}
if dflt, ok := dflts[group.Name]; ok {
fmt.Fprintf(os.Stderr, " %s replacing CaptureGroup (?<%s>...) at [%d:%d] with default: %v\n", compName, group.Name, group.Start, group.End, dflt)
contentBuilder.WriteString(fmt.Sprintf("%v", dflt))
} else {
contentBuilder.WriteString(content[group.Start:group.End])
}
idx = group.End
}
if idx < len(content) {
contentBuilder.WriteString(content[idx:])
}
content = contentBuilder.String()
// Now that we've fully consumed the defaults, strip them so they don't appear in the Helm chart...
removeCgDefaults(compName, helmValues)
}
}

helmTemplate := fmt.Sprintf(templateStructure, compName, compName, content)

return helmTemplate, nil
}

Expand Down
4 changes: 4 additions & 0 deletions addon-tools/helm-convert/convert/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ func TestConvert(t *testing.T) {
name: "Values Contain Keys With Dots",
passDefaultDir: true,
},
{
name: "Capturegroup Defaults",
passValuesFile: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: v2
parts:
- name: ExamplePart
components:
- name: DemonSets
allOf:
- path: sa.yaml
config:
perField:
- pathToKey: spec.value
inlineDiffFunc: capturegroups
- path: secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: {{ .apiVersion }}
kind: ServiceAccount
metadata:
labels:
k8s-app: kubernetes-dashboard
name: {{ .metadata.name }}
namespace: kubernetes-dashboard
spec:
value: |-
Long string with (?<one>[a-z0-9]*) capturegroup
And another (?<two>[a-z0-9]*) capturegorup
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: v1
kind: Secret
metadata:
labels:
k8s-app: kubernetes-dashboard
name: {{ .metadata.name }}
namespace: kubernetes-dashboard
type: Opaque
{{ if .data }}data:
{{ .data | toYaml }}{{ end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
description: This Helm Chart was generated from a kube-compare reference
name: Capturegroup Defaults
version: "1"
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{{- $values := list (dict)}}
{{- if .Values.sa}}
{{- $values = .Values.sa }}
{{- end }}
{{- range $values -}}
---
apiVersion: {{ .apiVersion }}
kind: ServiceAccount
metadata:
labels:
k8s-app: kubernetes-dashboard
name: {{ .metadata.name }}
namespace: kubernetes-dashboard
spec:
value: |-
Long string with 100 capturegroup
And another (?<two>[a-z0-9]*) capturegorup
{{ end -}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{{- $values := list (dict)}}
{{- if .Values.secret}}
{{- $values = .Values.secret }}
{{- end }}
{{- range $values -}}
---
apiVersion: v1
kind: Secret
metadata:
labels:
k8s-app: kubernetes-dashboard
name: {{ .metadata.name }}
namespace: kubernetes-dashboard
type: Opaque
{{ if .data }}data:
{{ .data | toYaml }}{{ end }}

{{ end -}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
sa:
- apiVersion: v1
metadata:
name: kubernetes-dashboard
secret:
- data: {}
metadata:
name: {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
sa:
- apiVersion: v1
metadata:
name: kubernetes-dashboard
captureGroup_defaults:
one: 100

0 comments on commit 84de6f4

Please sign in to comment.