How to default null YAML values to empty strings when using ytt

An example of converting null YAML values to empty strings when using ytt for Kubernetes config templating. This approach uses Python’s boolean short-circuiting behavior to concisely substitute empty strings for None values. It was inspired by this Stack Overflow post.

If you stumbled upon this page and have no clue what ytt is, it’s basically yet another way to template out YAML for Kubernetes. Think helm template but where you get to use a Python-like programming language and manipulate the YAML structures directly instead of just manipulating text.

tl;dr

Use Python’s boolean short-circuiting.

#@ prefix = data.values.optionalPrefix or ""
labelSelector: #@ prefix + str(data.values.myKey)

long form

Consider a scenario where you have a config-template.yaml file that generates the configuration YAML for your app and a values.yaml that operators can provide to supply their own values. You can do this with ytt via the following command:

ytt -f config-template.yaml -f values.yaml

Let’s say you want to get fancy and allow installers of your software to be able to supply their own label selectors with an optional label prefix.

#! config-template.yaml


#@ load("@ytt:data", "data")

---
labelSelector: #@ str(data.values.myPrefix) + str(data.values.myKey)
#! values.yaml


#@data/values

---
myPrefix: null
myKey: "app"

If you allow them to simply supply null for the myPrefix value from their values.yaml file you’ll end up generating something like this:

labelSelector: Noneapp

Probably not what they intended. Instead, you can do something like this in your template:

#! config-template.yaml


#@ load("@ytt:data", "data")

---
#@ prefix = data.values.myPrefix or ""
labelSelector: #@ prefix + str(data.values.myKey)

Now it will template out the following:

labelSelector: app

Much better! This approach uses Python’s (really Starlark in the case of ytt) boolean short-circuiting to skip past the falsey NoneType value. Warning this also means that other falsey values become empty strings as well. I’d recommend that you add stricter assertions if that distinction is important to you.

Here’s a more realistic example where you might want to let someone configure the label prefix on a Kubernetes Service’s selector, but not let them override the selectors completely.

#! service-template.yaml

#! This is the way:
#@ xstr = lambda s: s or ""

#@ labelPrefix = xstr(data.values.labelPrefix)
#@ labelSelectors = {}
#@ labelSelectors[labelPrefix+ "process"] = "web"

---
apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector: #@ labelSelectors    
  ports:
    - protocol: TCP
      name: http
      port: 80
      targetPort: 9001
#! values.yaml


#@data/values
---
labelPrefix: k8s.downey.dev/

This assigns the short-circuiting logic to a lambda names xstr which lets us reuse it. This template + values files produces the following:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    k8s.downey.dev/process: web
  ports:
  - protocol: TCP
    name: http
    port: 80
    targetPort: 9001

If we were to set ~ or null in the values file like this:

#! values.yaml


#@data/values
---
labelPrefix: ~

It would template out the selector without the prefix!

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    process: web
  ports:
  - protocol: TCP
    name: http
    port: 80
    targetPort: 9001

Pretty powerful stuff. To play around with ytt on your own, head over to https://get-ytt.io/. There is a section at the bottom containing sandbox examples where you can try it out for yourself. Good luck! 😌