DEV Community

Tim Downey
Tim Downey

Posted on

2 2

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode
#! values.yaml


#@data/values

---
myPrefix: null
myKey: "app"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Now it will template out the following:

labelSelector: app
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
#! values.yaml


#@data/values
---
labelPrefix: k8s.downey.dev/
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

#! values.yaml


#@data/values
---
labelPrefix: ~
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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! 😌

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more →

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay