Validating Helm Chart Content
I Am Developing a Chart and I Had an Error in It—Incorrectly Placed imagePullSecrets. When I Tried to Install It via Helm Install. /Mychart the Misplaced...
I am developing a chart and I had an error in it—incorrectly placed imagePullSecrets. When I tried to install it via
helm install ./mychart
the misplaced element was simply ignored and I wondered what is wrong.
When I did
helm template ./mychart | kubectl apply --dry-run -f -
it instead printed:
error: error validating "STDIN": error validating data: ValidationError(Deployment.spec.template.spec.containers[0]): unknown field "imagePullSecrets" in io.k8s.api.core.v1.Container
which clearly shows what is wrong. I am not sure whether it matches what the tiller actually does with the expanded templates.
But if I just do a
helm install --dry-run --debug ./mychart
it just shows the expanded template and looks OK.
So how do I correctly verify all my templates match corresponding schemata with helm?
4 Answers
You can lint the chart by going helm lint ./mychart which should print the following if an issue is found:
$ helm lint ./mychart
==> Linting ./mychart
[ERROR] Chart.yaml: version is required
[INFO] Chart.yaml: icon is recommended
Error: 1 chart(s) linted, 1 chart(s) failed
See helm lint.
Use kubeconform.
helm template ./mychart | kubeconform -strict
If you have CRDs you may need to use kubeconform -ignore-missing-schemas. I would recommend supplying the schema version: kubeconform -kubernetes-version 1.18.
A recommendation: specialise your charts and validate them. Examples follow.
Simple:
helm template --set some.key="val" | kubeconform -strict
Complex:
VALUES_FILE=$(cat << EOF
some:
key: "val"
another:
key:
another: "val"
EOF
)
# It is important to quote "$VALUES_FILE" to ensure line breaks and indentation are preserved
echo "$VALUES_FILE" | helm template -f - | kubeconform -strict
To avoid helm chart installation failure I suggest to follow the following sequence locally or/and in CI pipelines:
- run helm lint. As the docs state:
If the linter encounters things that will cause the chart to fail installation, it will emit [ERROR] messages.
run helm template for testing whether the chart can be rendered locally successfully. While the installation can still fail once helm->kubernetes api server interaction happens, it still gives good quality check.
validate that chart's Kubernetes manifests conform to Kubernetes schema using
kubeval(doesn't support CRDs) orkubeconform(supports CRDs) after rendering the chart:
helm template [chart_name] . | kubeval
- run helm unit tests for validating that the chart values are as expected (e.g. num of replicas is 1, volume is of type
hostPath, etc..):
helm unittest [chart_name] [chart_name]
I summarized how to test helm charts in my blog.
I would high recommend using a combination of the 2 solutions below.
Solution 1
Using a values.schema.json for imposing a structure on the values.yaml file.
Example:
{
"$schema": "",
"properties": {
"image": {
"description": "Container Image",
"properties": {
"repo": {
"type": "string"
},
"tag": {
"type": "string"
}
},
"type": "object"
},
"name": {
"description": "Service name",
"type": "string"
},
"port": {
"description": "Port",
"minimum": 0,
"type": "integer"
},
"protocol": {
"type": "string"
}
},
"required": [
"protocol",
"port"
],
"title": "Values",
"type": "object"
}
(!) This schema will be applied to the values to validate it. Validation occurs when any of the following commands are invoked:
helm install
helm upgrade
helm lint
helm template
In order to add conditions to the validation - read in here.
(*) Further reading: Nice article.
Solution 2
There are cases when the code in values.schema.json might be less readable when using conditions or we want to use a more dynamic logic into our validation.
In this case, we can create an validations.yaml file (some vendors prefer using a .tpl file) and add the validation logic using go templates.
Example:
If a specific feature (when enabled) needs an ip or a dns:
some_feature:
enabled: true
ip:
dns:
The validation logic can be written explicitly:
{{- if .Values.some_feature.enabled -}}
{{- if and (not .Values.some_feature.ip ) (not .Values.some_feature.dns ) -}}
When some_feature is enabled, ip or dns must be given.
{{- end -}}
{{- end -}}
(*) This logic could also be writen using json-schema if-else statement, but it might be less readable.
(**) Consider placing all validations under a /tests or /validations folder where all tests are devided into seperated files (like unit tests).