Skip to content

Commit

Permalink
feat: introduced liveness and readiness probes
Browse files Browse the repository at this point in the history
Signed-off-by: RealAnna <[email protected]>
  • Loading branch information
RealAnna committed Feb 20, 2023
1 parent 5b663ea commit b16d3ea
Show file tree
Hide file tree
Showing 9 changed files with 261 additions and 18 deletions.
1 change: 1 addition & 0 deletions cmd/helmify/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func ReadFlags() config.Config {
flag.BoolVar(&result.VeryVerbose, "vv", false, "Enable very verbose output. Same as verbose but with DEBUG. Example: helmify -vv")
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")
flag.BoolVar(&result.ImagePullSecrets, "image-pull-secrets", false, "Allows the user to use existing secrets as imagePullSecrets in values.yaml")
flag.BoolVar(&result.Probes, "probes", false, "Allows the user to customize liveness and readiness probes")

flag.Parse()
if h || help {
Expand Down
14 changes: 14 additions & 0 deletions examples/operator/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,17 @@ Create the name of the service account to use
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

{{/*
Renders a value that contains template.
Usage:
{{ include "tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }}
*/}}
{{- define "tplvalues.render" -}}
{{- if typeIs "string" .value }}
{{- tpl .value .context }}
{{- else }}
{{- tpl (.value | toYaml) .context }}
{{- end }}
{{- end -}}

35 changes: 21 additions & 14 deletions examples/operator/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,8 @@ spec:
value: {{ .Values.kubernetesClusterDomain }}
image: {{ .Values.controllerManager.manager.image.repository }}:{{ .Values.controllerManager.manager.image.tag
| default .Chart.AppVersion }}
livenessProbe:
httpGet:
path: /healthz
port: 8081
initialDelaySeconds: 15
periodSeconds: 20
name: manager
readinessProbe:
httpGet:
path: /readyz
port: 8081
initialDelaySeconds: 5
periodSeconds: 10
resources: {{- toYaml .Values.controllerManager.manager.resources | nindent 10
}}
resources: {{- toYaml .Values.controllerManager.manager.resources | nindent 10 }}
securityContext:
allowPrivilegeEscalation: false
volumeMounts:
Expand All @@ -83,6 +70,26 @@ spec:
subPath: controller_manager_config.yaml
- mountPath: /my.ca
name: secret-volume
{{- if .Values.controllerManager.manager.livenessProbe }}
livenessProbe: {{- include "tplvalues.render" (dict "value" .Values.controllerManager.manager.livenessProbe "context" $) | nindent 10 }}
{{- else }}
livenessProbe:
httpGet:
path: /healthz
port: 8081
initialDelaySeconds: 15
periodSeconds: 20
{{- end }}
{{- if .Values.controllerManager.manager.readinessProbe }}
readinessProbe: {{- include "tplvalues.render" (dict "value" .Values.controllerManager.manager.readinessProbe "context" $) | nindent 10 }}
{{- else }}
readinessProbe:
httpGet:
path: /readyz
port: 8081
initialDelaySeconds: 5
periodSeconds: 10
{{- end }}
imagePullSecrets:
- name: {{ include "operator.fullname" . }}-secret-registry-credentials
securityContext:
Expand Down
12 changes: 12 additions & 0 deletions examples/operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ controllerManager:
image:
repository: controller
tag: latest
livenessProbe:
httpGet:
path: /healthz
port: 8081
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /readyz
port: 8081
initialDelaySeconds: 5
periodSeconds: 10
resources:
limits:
cpu: 100m
Expand Down
2 changes: 2 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ type Config struct {
Crd bool
// ImagePullSecrets flag
ImagePullSecrets bool
// Probes flag if true the probes will be parametrised
Probes bool
}

func (c *Config) Validate() error {
Expand Down
14 changes: 14 additions & 0 deletions pkg/helm/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,20 @@ Create the name of the service account to use
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
Renders a value that contains template.
Usage:
{{ include "tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }}
*/}}
{{- define "tplvalues.render" -}}
{{- if typeIs "string" .value }}
{{- tpl .value .context }}
{{- else }}
{{- tpl (.value | toYaml) .context }}
{{- end }}
{{- end -}}
`

const defaultChartfile = `apiVersion: v2
Expand Down
17 changes: 13 additions & 4 deletions pkg/processor/deployment/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import (
"text/template"

"github.com/arttor/helmify/pkg/cluster"
"github.com/arttor/helmify/pkg/helmify"
"github.com/arttor/helmify/pkg/processor"
"github.com/arttor/helmify/pkg/processor/imagePullSecrets"

"github.com/arttor/helmify/pkg/helmify"
"github.com/arttor/helmify/pkg/processor/probes"
yamlformat "github.com/arttor/helmify/pkg/yaml"
"github.com/iancoleman/strcase"
"github.com/pkg/errors"
Expand Down Expand Up @@ -47,7 +47,7 @@ const selectorTempl = `%[1]s
{{- include "%[2]s.selectorLabels" . | nindent 6 }}
%[3]s`

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

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

if appMeta.Config().Probes {
err = probes.ProcessSpecMap(nameCamel, specMap, &values)
if err != nil {
return true, nil, err
}
}

spec, err := yamlformat.Marshal(specMap, 6)
if err != nil {
return true, nil, err
}
spec = strings.ReplaceAll(spec, "'", "")

spec = strings.ReplaceAll(spec, "'", "")
spec = strings.ReplaceAll(spec, "|\n ", "")
spec = strings.ReplaceAll(spec, "|-\n ", "")
return true, &result{
values: values,
data: struct {
Expand Down
97 changes: 97 additions & 0 deletions pkg/processor/probes/probes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package probes

import (
"fmt"

"github.com/arttor/helmify/pkg/helmify"
yamlformat "github.com/arttor/helmify/pkg/yaml"
"github.com/iancoleman/strcase"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/yaml"
)

const livenessProbe = "livenessProbe"
const readinessProbe = "readinessProbe"

const livenessProbeTemplate = "{{- if .Values.%[1]s.%[2]s.livenessProbe }}\n" +
"livenessProbe: {{- include \"tplvalues.render\" (dict \"value\" .Values.%[1]s.%[2]s.livenessProbe \"context\" $) | nindent 10 }}\n" +
" {{- else }}\n" +
"livenessProbe:\n%[3]s" +
"\n{{- end }}"

const readinessProbeTemplate = "\n{{- if .Values.%[1]s.%[2]s.readinessProbe }}\n" +
"readinessProbe: {{- include \"tplvalues.render\" (dict \"value\" .Values.%[1]s.%[2]s.readinessProbe \"context\" $) | nindent 10 }}\n" +
" {{- else }}\n" +
"readinessProbe:\n%[3]s" +
"\n{{- end }}"

// ProcessSpecMap adds 'probes' to the Containers in specMap, if they are defined
func ProcessSpecMap(name string, specMap map[string]interface{}, values *helmify.Values) error {

cs, _, err := unstructured.NestedSlice(specMap, "containers")
if err != nil {
return err
}

strContainers := make([]interface{}, len(cs))
for i, c := range cs {
castedContainer := c.(map[string]interface{})
strContainers[i], err = setProbesTemplates(name, castedContainer, values)
if err != nil {
return err
}
}

return unstructured.SetNestedSlice(specMap, strContainers, "containers")

}

func setProbesTemplates(name string, castedContainer map[string]interface{}, values *helmify.Values) (string, error) {

var ready, live string
var err error
if _, defined := castedContainer[livenessProbe]; defined {
live, err = setProbe(name, castedContainer, values, livenessProbe)
if err != nil {
return "", err
}
delete(castedContainer, livenessProbe)
}
if _, defined := castedContainer[readinessProbe]; defined {
ready, err = setProbe(name, castedContainer, values, readinessProbe)
if err != nil {
return "", err
}
delete(castedContainer, readinessProbe)
}
return setMap(name, castedContainer, live, ready)

}

func setMap(name string, castedContainer map[string]interface{}, live string, ready string) (string, error) {
containerName := strcase.ToLowerCamel(castedContainer["name"].(string))
content, err := yaml.Marshal(castedContainer)

if err != nil {
return "", err
}
strContainer := string(content)
if live != "" {
strContainer = strContainer + fmt.Sprintf(livenessProbeTemplate, name, containerName, live)
}
if ready != "" {
strContainer = strContainer + fmt.Sprintf(readinessProbeTemplate, name, containerName, ready)
}
return strContainer, nil
}

func setProbe(name string, castedContainer map[string]interface{}, values *helmify.Values, probe string) (string, error) {
containerName := strcase.ToLowerCamel(castedContainer["name"].(string))
live, err := yamlformat.Marshal(castedContainer[probe], 1)
if err != nil {
return "", err
}

return live, unstructured.SetNestedField(*values, castedContainer[probe], name, containerName, probe)

}
87 changes: 87 additions & 0 deletions pkg/processor/probes/probes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package probes

import (
"testing"

"github.com/arttor/helmify/pkg/helmify"
"github.com/stretchr/testify/require"
"sigs.k8s.io/yaml"
)

func Test_setProbesTemplates(t *testing.T) {

tests := []struct {
name string
deploymentName string
container map[string]interface{}
wantMap string
wantValue string
wantErr bool
}{
{
name: "no probe no data generated",
deploymentName: "test",
container: map[string]interface{}{
"name": "mycontainer",
},
wantMap: "",
wantErr: false,
},
{
name: "readinessProbe probe",
deploymentName: "test",
container: map[string]interface{}{
"name": "mycontainer",
readinessProbe: map[string]interface{}{
"timeoutSeconds": "1",
"periodSeconds": "20",
},
},
wantMap: "\n{{- if .Values.test.mycontainer.readinessProbe }}\n" +
"readinessProbe: {{- include \"tplvalues.render\" (dict \"value\" .Values.test.mycontainer.readinessProbe \"context\" $) | nindent 10 }}\n {{- else }}\n" +
"readinessProbe:\n" +
" periodSeconds: \"20\"\n" +
" timeoutSeconds: \"1\"\n" +
"{{- end }}",
wantValue: "readinessProbe:\n periodSeconds: \"20\"\n timeoutSeconds: \"1\"\n",
wantErr: false,
},
{
name: "add livenessProbe probe",
deploymentName: "test",
container: map[string]interface{}{
"name": "mycontainer",
livenessProbe: map[string]interface{}{
"timeoutSeconds": "14",
"periodSeconds": "2",
},
},
wantMap: "{{- if .Values.test.mycontainer.livenessProbe }}\n" +
"livenessProbe: {{- include \"tplvalues.render\" (dict \"value\" .Values.test.mycontainer.livenessProbe \"context\" $) | nindent 10 }}\n" +
" {{- else }}\nlivenessProbe:\n" +
" periodSeconds: \"2\"\n" +
" timeoutSeconds: \"14\"\n" +
"{{- end }}",
wantValue: "livenessProbe:\n periodSeconds: \"2\"\n timeoutSeconds: \"14\"\n",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
v := make(helmify.Values)
res, err := setProbesTemplates(tt.deploymentName, tt.container, &v)
require.True(t, (err != nil) == tt.wantErr)

require.Contains(t, res, tt.wantMap)
if tt.wantValue != "" {
val := (v)["test"].(map[string]interface{})["mycontainer"]
t.Log("VAL", val)
b, err := yaml.Marshal(val)
require.Nil(t, err)
require.Contains(t, string(b), tt.wantValue)
} else {
require.Empty(t, v)
}
})
}
}

0 comments on commit b16d3ea

Please sign in to comment.