DEV Community

MrViK
MrViK

Posted on

Bringing the power of Go templates to service descriptions

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.

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

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?

---
name: agetty@tty1
description: Getty on tty1
exec: setsid
arguments:
  - --wait
  - /sbin/agetty
  - --noclear
  - tty1  # Hardcoded values
  - linux
restart: always
Enter fullscreen mode Exit fullscreen mode

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.

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

And how do they look on the FS?
ls of yml-templates

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?

Top comments (0)