Skip to content

Commit b16d3ea

Browse files
committed
feat: introduced liveness and readiness probes
Signed-off-by: RealAnna <anna.reale@dynatrace.com>
1 parent 5b663ea commit b16d3ea

9 files changed

Lines changed: 261 additions & 18 deletions

File tree

cmd/helmify/flags.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func ReadFlags() config.Config {
3737
flag.BoolVar(&result.VeryVerbose, "vv", false, "Enable very verbose output. Same as verbose but with DEBUG. Example: helmify -vv")
3838
flag.BoolVar(&crd, "crd-dir", false, "Enable crd install into 'crds' directory.\nWarning: CRDs placed in 'crds' directory will not be templated by Helm.\nSee https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#some-caveats-and-explanations\nExample: helmify -crd-dir")
3939
flag.BoolVar(&result.ImagePullSecrets, "image-pull-secrets", false, "Allows the user to use existing secrets as imagePullSecrets in values.yaml")
40+
flag.BoolVar(&result.Probes, "probes", false, "Allows the user to customize liveness and readiness probes")
4041

4142
flag.Parse()
4243
if h || help {

examples/operator/templates/_helpers.tpl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,17 @@ Create the name of the service account to use
6060
{{- default "default" .Values.serviceAccount.name }}
6161
{{- end }}
6262
{{- end }}
63+
64+
{{/*
65+
Renders a value that contains template.
66+
Usage:
67+
{{ include "tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }}
68+
*/}}
69+
{{- define "tplvalues.render" -}}
70+
{{- if typeIs "string" .value }}
71+
{{- tpl .value .context }}
72+
{{- else }}
73+
{{- tpl (.value | toYaml) .context }}
74+
{{- end }}
75+
{{- end -}}
76+

examples/operator/templates/deployment.yaml

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -60,21 +60,8 @@ spec:
6060
value: {{ .Values.kubernetesClusterDomain }}
6161
image: {{ .Values.controllerManager.manager.image.repository }}:{{ .Values.controllerManager.manager.image.tag
6262
| default .Chart.AppVersion }}
63-
livenessProbe:
64-
httpGet:
65-
path: /healthz
66-
port: 8081
67-
initialDelaySeconds: 15
68-
periodSeconds: 20
6963
name: manager
70-
readinessProbe:
71-
httpGet:
72-
path: /readyz
73-
port: 8081
74-
initialDelaySeconds: 5
75-
periodSeconds: 10
76-
resources: {{- toYaml .Values.controllerManager.manager.resources | nindent 10
77-
}}
64+
resources: {{- toYaml .Values.controllerManager.manager.resources | nindent 10 }}
7865
securityContext:
7966
allowPrivilegeEscalation: false
8067
volumeMounts:
@@ -83,6 +70,26 @@ spec:
8370
subPath: controller_manager_config.yaml
8471
- mountPath: /my.ca
8572
name: secret-volume
73+
{{- if .Values.controllerManager.manager.livenessProbe }}
74+
livenessProbe: {{- include "tplvalues.render" (dict "value" .Values.controllerManager.manager.livenessProbe "context" $) | nindent 10 }}
75+
{{- else }}
76+
livenessProbe:
77+
httpGet:
78+
path: /healthz
79+
port: 8081
80+
initialDelaySeconds: 15
81+
periodSeconds: 20
82+
{{- end }}
83+
{{- if .Values.controllerManager.manager.readinessProbe }}
84+
readinessProbe: {{- include "tplvalues.render" (dict "value" .Values.controllerManager.manager.readinessProbe "context" $) | nindent 10 }}
85+
{{- else }}
86+
readinessProbe:
87+
httpGet:
88+
path: /readyz
89+
port: 8081
90+
initialDelaySeconds: 5
91+
periodSeconds: 10
92+
{{- end }}
8693
imagePullSecrets:
8794
- name: {{ include "operator.fullname" . }}-secret-registry-credentials
8895
securityContext:

examples/operator/values.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@ controllerManager:
1010
image:
1111
repository: controller
1212
tag: latest
13+
livenessProbe:
14+
httpGet:
15+
path: /healthz
16+
port: 8081
17+
initialDelaySeconds: 15
18+
periodSeconds: 20
19+
readinessProbe:
20+
httpGet:
21+
path: /readyz
22+
port: 8081
23+
initialDelaySeconds: 5
24+
periodSeconds: 10
1325
resources:
1426
limits:
1527
cpu: 100m

pkg/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ type Config struct {
2323
Crd bool
2424
// ImagePullSecrets flag
2525
ImagePullSecrets bool
26+
// Probes flag if true the probes will be parametrised
27+
Probes bool
2628
}
2729

2830
func (c *Config) Validate() error {

pkg/helm/init.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,20 @@ Create the name of the service account to use
9999
{{- default "default" .Values.serviceAccount.name }}
100100
{{- end }}
101101
{{- end }}
102+
103+
{{/*
104+
Renders a value that contains template.
105+
Usage:
106+
{{ include "tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }}
107+
*/}}
108+
{{- define "tplvalues.render" -}}
109+
{{- if typeIs "string" .value }}
110+
{{- tpl .value .context }}
111+
{{- else }}
112+
{{- tpl (.value | toYaml) .context }}
113+
{{- end }}
114+
{{- end -}}
115+
102116
`
103117

104118
const defaultChartfile = `apiVersion: v2

pkg/processor/deployment/deployment.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import (
77
"text/template"
88

99
"github.com/arttor/helmify/pkg/cluster"
10+
"github.com/arttor/helmify/pkg/helmify"
1011
"github.com/arttor/helmify/pkg/processor"
1112
"github.com/arttor/helmify/pkg/processor/imagePullSecrets"
12-
13-
"github.com/arttor/helmify/pkg/helmify"
13+
"github.com/arttor/helmify/pkg/processor/probes"
1414
yamlformat "github.com/arttor/helmify/pkg/yaml"
1515
"github.com/iancoleman/strcase"
1616
"github.com/pkg/errors"
@@ -47,7 +47,7 @@ const selectorTempl = `%[1]s
4747
{{- include "%[2]s.selectorLabels" . | nindent 6 }}
4848
%[3]s`
4949

50-
const envValue = "{{ .Values.%[1]s.%[2]s.%[3]s }}"
50+
const envValue = "{{ .Values.%[1]s.%[2]s.%[3]s.%[4]s }}"
5151

5252
// New creates processor for k8s Deployment resource.
5353
func New() helmify.Processor {
@@ -162,12 +162,21 @@ func (d deployment) Process(appMeta helmify.AppMetadata, obj *unstructured.Unstr
162162
imagePullSecrets.ProcessSpecMap(specMap, &values)
163163
}
164164

165+
if appMeta.Config().Probes {
166+
err = probes.ProcessSpecMap(nameCamel, specMap, &values)
167+
if err != nil {
168+
return true, nil, err
169+
}
170+
}
171+
165172
spec, err := yamlformat.Marshal(specMap, 6)
166173
if err != nil {
167174
return true, nil, err
168175
}
169-
spec = strings.ReplaceAll(spec, "'", "")
170176

177+
spec = strings.ReplaceAll(spec, "'", "")
178+
spec = strings.ReplaceAll(spec, "|\n ", "")
179+
spec = strings.ReplaceAll(spec, "|-\n ", "")
171180
return true, &result{
172181
values: values,
173182
data: struct {

pkg/processor/probes/probes.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package probes
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/arttor/helmify/pkg/helmify"
7+
yamlformat "github.com/arttor/helmify/pkg/yaml"
8+
"github.com/iancoleman/strcase"
9+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
10+
"sigs.k8s.io/yaml"
11+
)
12+
13+
const livenessProbe = "livenessProbe"
14+
const readinessProbe = "readinessProbe"
15+
16+
const livenessProbeTemplate = "{{- if .Values.%[1]s.%[2]s.livenessProbe }}\n" +
17+
"livenessProbe: {{- include \"tplvalues.render\" (dict \"value\" .Values.%[1]s.%[2]s.livenessProbe \"context\" $) | nindent 10 }}\n" +
18+
" {{- else }}\n" +
19+
"livenessProbe:\n%[3]s" +
20+
"\n{{- end }}"
21+
22+
const readinessProbeTemplate = "\n{{- if .Values.%[1]s.%[2]s.readinessProbe }}\n" +
23+
"readinessProbe: {{- include \"tplvalues.render\" (dict \"value\" .Values.%[1]s.%[2]s.readinessProbe \"context\" $) | nindent 10 }}\n" +
24+
" {{- else }}\n" +
25+
"readinessProbe:\n%[3]s" +
26+
"\n{{- end }}"
27+
28+
// ProcessSpecMap adds 'probes' to the Containers in specMap, if they are defined
29+
func ProcessSpecMap(name string, specMap map[string]interface{}, values *helmify.Values) error {
30+
31+
cs, _, err := unstructured.NestedSlice(specMap, "containers")
32+
if err != nil {
33+
return err
34+
}
35+
36+
strContainers := make([]interface{}, len(cs))
37+
for i, c := range cs {
38+
castedContainer := c.(map[string]interface{})
39+
strContainers[i], err = setProbesTemplates(name, castedContainer, values)
40+
if err != nil {
41+
return err
42+
}
43+
}
44+
45+
return unstructured.SetNestedSlice(specMap, strContainers, "containers")
46+
47+
}
48+
49+
func setProbesTemplates(name string, castedContainer map[string]interface{}, values *helmify.Values) (string, error) {
50+
51+
var ready, live string
52+
var err error
53+
if _, defined := castedContainer[livenessProbe]; defined {
54+
live, err = setProbe(name, castedContainer, values, livenessProbe)
55+
if err != nil {
56+
return "", err
57+
}
58+
delete(castedContainer, livenessProbe)
59+
}
60+
if _, defined := castedContainer[readinessProbe]; defined {
61+
ready, err = setProbe(name, castedContainer, values, readinessProbe)
62+
if err != nil {
63+
return "", err
64+
}
65+
delete(castedContainer, readinessProbe)
66+
}
67+
return setMap(name, castedContainer, live, ready)
68+
69+
}
70+
71+
func setMap(name string, castedContainer map[string]interface{}, live string, ready string) (string, error) {
72+
containerName := strcase.ToLowerCamel(castedContainer["name"].(string))
73+
content, err := yaml.Marshal(castedContainer)
74+
75+
if err != nil {
76+
return "", err
77+
}
78+
strContainer := string(content)
79+
if live != "" {
80+
strContainer = strContainer + fmt.Sprintf(livenessProbeTemplate, name, containerName, live)
81+
}
82+
if ready != "" {
83+
strContainer = strContainer + fmt.Sprintf(readinessProbeTemplate, name, containerName, ready)
84+
}
85+
return strContainer, nil
86+
}
87+
88+
func setProbe(name string, castedContainer map[string]interface{}, values *helmify.Values, probe string) (string, error) {
89+
containerName := strcase.ToLowerCamel(castedContainer["name"].(string))
90+
live, err := yamlformat.Marshal(castedContainer[probe], 1)
91+
if err != nil {
92+
return "", err
93+
}
94+
95+
return live, unstructured.SetNestedField(*values, castedContainer[probe], name, containerName, probe)
96+
97+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package probes
2+
3+
import (
4+
"testing"
5+
6+
"github.com/arttor/helmify/pkg/helmify"
7+
"github.com/stretchr/testify/require"
8+
"sigs.k8s.io/yaml"
9+
)
10+
11+
func Test_setProbesTemplates(t *testing.T) {
12+
13+
tests := []struct {
14+
name string
15+
deploymentName string
16+
container map[string]interface{}
17+
wantMap string
18+
wantValue string
19+
wantErr bool
20+
}{
21+
{
22+
name: "no probe no data generated",
23+
deploymentName: "test",
24+
container: map[string]interface{}{
25+
"name": "mycontainer",
26+
},
27+
wantMap: "",
28+
wantErr: false,
29+
},
30+
{
31+
name: "readinessProbe probe",
32+
deploymentName: "test",
33+
container: map[string]interface{}{
34+
"name": "mycontainer",
35+
readinessProbe: map[string]interface{}{
36+
"timeoutSeconds": "1",
37+
"periodSeconds": "20",
38+
},
39+
},
40+
wantMap: "\n{{- if .Values.test.mycontainer.readinessProbe }}\n" +
41+
"readinessProbe: {{- include \"tplvalues.render\" (dict \"value\" .Values.test.mycontainer.readinessProbe \"context\" $) | nindent 10 }}\n {{- else }}\n" +
42+
"readinessProbe:\n" +
43+
" periodSeconds: \"20\"\n" +
44+
" timeoutSeconds: \"1\"\n" +
45+
"{{- end }}",
46+
wantValue: "readinessProbe:\n periodSeconds: \"20\"\n timeoutSeconds: \"1\"\n",
47+
wantErr: false,
48+
},
49+
{
50+
name: "add livenessProbe probe",
51+
deploymentName: "test",
52+
container: map[string]interface{}{
53+
"name": "mycontainer",
54+
livenessProbe: map[string]interface{}{
55+
"timeoutSeconds": "14",
56+
"periodSeconds": "2",
57+
},
58+
},
59+
wantMap: "{{- if .Values.test.mycontainer.livenessProbe }}\n" +
60+
"livenessProbe: {{- include \"tplvalues.render\" (dict \"value\" .Values.test.mycontainer.livenessProbe \"context\" $) | nindent 10 }}\n" +
61+
" {{- else }}\nlivenessProbe:\n" +
62+
" periodSeconds: \"2\"\n" +
63+
" timeoutSeconds: \"14\"\n" +
64+
"{{- end }}",
65+
wantValue: "livenessProbe:\n periodSeconds: \"2\"\n timeoutSeconds: \"14\"\n",
66+
wantErr: false,
67+
},
68+
}
69+
for _, tt := range tests {
70+
t.Run(tt.name, func(t *testing.T) {
71+
v := make(helmify.Values)
72+
res, err := setProbesTemplates(tt.deploymentName, tt.container, &v)
73+
require.True(t, (err != nil) == tt.wantErr)
74+
75+
require.Contains(t, res, tt.wantMap)
76+
if tt.wantValue != "" {
77+
val := (v)["test"].(map[string]interface{})["mycontainer"]
78+
t.Log("VAL", val)
79+
b, err := yaml.Marshal(val)
80+
require.Nil(t, err)
81+
require.Contains(t, string(b), tt.wantValue)
82+
} else {
83+
require.Empty(t, v)
84+
}
85+
})
86+
}
87+
}

0 commit comments

Comments
 (0)