Bringing the power of Go templates to service descriptions
Tags: go, linux, systemsprogramming, showdev
Published: 2021-01-11 19:07:09

---
title: Bringing the power of Go templates to service descriptions
published: true
description: 
tags: go, linux, systemsprogramming, showdev
series: Writing init with go
---

Hey, on previous articles from this series I mentioned some missing features on the Go-PID1 and its service launcher.

To recap:
* We have no templates - It's a big deal
* Support for reloading services
* Better handling of mounts
* Other things like setting up the keymap for console
* (Added now) Init is very fragile, a single fail on `service-launcher` halts the system.

And I fixed all of them and the fixes have been merged into `master` by this time.

But we're here to talk about templates. Let's see how `systemd` does it:

From `systemd.unit(5)` on section `SPECIFIERS`:
> Many settings resolve specifiers which may be used to write generic unit files referring to runtime or unit parameters that are replaced when the files are loaded. Specifiers must be known and resolvable for the setting to be valid. The following specifiers are understood:

>"%i" Instance name For instantiated units this is the string between the first "@" character and the type suffix. Empty for non-instantiated units.

There are a lot more of specifiers on that list, but let's focus on this one (and it's unescaped variant `%I`).

If you have `systemd` on the PID1 you can do `systemctl cat getty@tty1.service` to see the code under those templates.

`systemd` templates always come with an @ symbol and the text between the '@' and the end of the name (w/o .service or whatever) is the "argument".

Here is the service description for `getty@tty1`, note that is a template linked to `getty@.service`.
```systemd
# /usr/lib/systemd/system/getty@.service
#  SPDX-License-Identifier: LGPL-2.1-or-later
#
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=Getty on %I
Documentation=man:agetty(8) man:systemd-getty-generator(8)
Documentation=http://0pointer.de/blog/projects/serial-console.html
After=systemd-user-sessions.service plymouth-quit-wait.service getty-pre.target

# If additional gettys are spawned during boot then we should make
# sure that this is synchronized before getty.target, even though
# getty.target didn't actually pull it in.
Before=getty.target
IgnoreOnIsolate=yes

# IgnoreOnIsolate causes issues with sulogin, if someone isolates
# rescue.target or starts rescue.service from multi-user.target or
# graphical.target.
Conflicts=rescue.service
Before=rescue.service

# On systems without virtual consoles, don't start any getty. Note
# that serial gettys are covered by serial-getty@.service, not this
# unit.
ConditionPathExists=/dev/tty0

[Service]
# the VT is cleared by TTYVTDisallocate
# The '-o' option value tells agetty to replace 'login' arguments with an
# option to preserve environment (-p), followed by '--' for safety, and then
# the entered username.
ExecStart=-/sbin/agetty -o '-p -- \\u' --noclear %I $TERM
Type=idle
Restart=always
RestartSec=0
UtmpIdentifier=%I
TTYPath=/dev/%I
TTYReset=yes
TTYVHangup=yes
TTYVTDisallocate=yes
IgnoreSIGPIPE=no
SendSIGHUP=yes

# Unset locale for the console getty since the console has problems
# displaying some internationalized messages.
UnsetEnvironment=LANG LANGUAGE LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION

[Install]
WantedBy=getty.target
DefaultInstance=tty1
```

On this unit all occurrences of "%I" get replaced by the argument.

## Ways of implementing this

I initially thought about using Regex replace. And it should work but we have `text/template`, a powerful way of creating data driven templates.

What data? Well, copying the good things from `systemd`, when a service has an @ symbol on it's name, we pass the file to the templates interpreter with the filename and the argument.

### Environment exposed to templates

* Data -> The `Argument` variable based on what follows the '@' (before `.yml` or `.yaml`).
* Functions -> `os.Getenv` is exposed as `Getenv` so you can retrieve environment variables.

Currently there are no plans on adding more things, but the data struct and funcmap are easily extensible. Feel free to add functions adapted to your needs.

## Trying templates
So how do they look.

Let's see the template for getty. We took a look to the `systemd` approach (which includes a lot of things). How was the world before templates on Go-PID1?

```yaml
---
name: agetty@tty1
description: Getty on tty1
exec: setsid
arguments:
  - --wait
  - /sbin/agetty
  - --noclear
  - tty1  # Hardcoded values
  - linux
restart: always
```

Previously each file was only allowed to define a single unit. Now each file can define a slice of services so templates are more powerful yet.

```yaml
---
# vim: filetype=yaml
{{with .Argument}}  # If no argument, no services are defined here
- name: agetty@{{.}}  # Dynamic name!
  description: Getty on {{.}}. We have templates. yay!
  exec: setsid
  arguments:
    - --wait
    - /sbin/agetty
    {{- if eq . "tty1"}}
    - --noclear  # Conditionals inside templates. Take this!
    {{- end}}
    - {{.}}  # This argument takes the value from .Argument
    - {{Getenv "TERM"}}  # We call the Getenv function from template
  restart: always
{{end}}
```

And how do they look on the FS?
![ls of yml-templates](https://dev-to-uploads.s3.amazonaws.com/i/rj7xwuq3kvbniu2sky9z.png)

And another new feature is the hot reload. Add, remove or update services and do `slc reload` to apply changes.
So if you link another service to a template and do a reload, the service will be read and started.

So cool, isn't it?